From 0f479c6af0f2e10b7d268986d5ae42e75ad0cafe Mon Sep 17 00:00:00 2001 From: Ansgar Richter <28757333+Nsgr@users.noreply.github.com> Date: Mon, 12 Feb 2024 20:08:05 +0100 Subject: [PATCH] Clean up for final submission --- GReaSe-Tools/CMFReactHook.extension.st | 8 + GReaSe-Tools/ComponentBrowser.class.st | 146 +++++++++------- GReaSe-Tools/ComponentBrowserDworph.class.st | 173 ++++++++++--------- squeak/GReaSe-React/GRComponent.class.st | 10 +- 4 files changed, 193 insertions(+), 144 deletions(-) create mode 100644 GReaSe-Tools/CMFReactHook.extension.st diff --git a/GReaSe-Tools/CMFReactHook.extension.st b/GReaSe-Tools/CMFReactHook.extension.st new file mode 100644 index 0000000..53cdfb4 --- /dev/null +++ b/GReaSe-Tools/CMFReactHook.extension.st @@ -0,0 +1,8 @@ +Extension { #name : #CMFReactHook } + +{ #category : #'*GReaSe-Tools' } +CMFReactHook >> hookName: name [ + + "TODO (hack) makes use of an instance variable called hookName. One can probably create the instance variable dynamically using addInstVarName:, however the component Browser could run into issues since the instance variable is not declared at all times and there is no non-error-if-absent getter for it." + hookName := name. +] diff --git a/GReaSe-Tools/ComponentBrowser.class.st b/GReaSe-Tools/ComponentBrowser.class.st index e53f91b..1b56837 100644 --- a/GReaSe-Tools/ComponentBrowser.class.st +++ b/GReaSe-Tools/ComponentBrowser.class.st @@ -3,12 +3,63 @@ Class { #superclass : #Model, #instVars : [ 'currentSelection', - 'rootFiber', - 'currentDataSelection' + 'rootFiber' ], #category : #'GReaSe-Tools' } +{ #category : #'as yet unclassified' } +ComponentBrowser class >> displayHooksForFiber: aCMFFiber [ + + ^ aCMFFiber hooks collect: [:hook | | hookString | + hookString := (hook className) copyReplaceAll: 'CMFReactHook' with: '' asTokens: false. + (hook instVarNamed: 'hookName') ifNotNil: [hookString := hookString , ' ', (hook instVarNamed: 'hookName') asString]. + [(hook instVarNamed: 'state') ifNotNil: [hookString := hookString , ': ', (hook instVarNamed: 'state') asString]] ifError: [hookString := hookString , '']. + ] +] + +{ #category : #'as yet unclassified' } +ComponentBrowser class >> displayPropsForFiber: aCMFFiber [ + + ^ aCMFFiber node isGodotNode + ifTrue: [aCMFFiber node properties associations collect: [:assoc | assoc asString]] + ifFalse: {'<>'} +] + +{ #category : #'as yet unclassified' } +ComponentBrowser class >> displayTempsForFiber: aCMFFiber [ + + ^ (aCMFFiber node isGodotNode + ifTrue: {'' } + ifFalse: [ + (aCMFFiber node isKindOf: CMFRootNode) ifTrue: {''} ifFalse: [ + [ + | parser | + parser := PPParser new + parse: (aCMFFiber node elementClass>>#render:) getSource + class: UndefinedObject + noPattern: false + notifying: nil + ifFail: nil. + parser temporaries collect: [:temp | temp asString] + ] ifError: [{''}] + ]. + ] + ). +] + +{ #category : #'as yet unclassified' } +ComponentBrowser class >> getCurrentRoot [ + + ^ (((GRReact current instVarNamed: #sessions) associations first value) instVarNamed: #react) currentRoot. +] + +{ #category : #'as yet unclassified' } +ComponentBrowser class >> openCurrent [ + + ^ self openForFiber: self getCurrentRoot. +] + { #category : #'as yet unclassified' } ComponentBrowser class >> openForFiber: aFiber [ @@ -28,7 +79,7 @@ ComponentBrowser >> build [ { #category : #'as yet unclassified' } ComponentBrowser >> buildWith: builder [ - | windowSpec treeSpec listSpecLeft listSpecRight listSpecProps | + | windowSpec treeSpec listSpecTemps listSpecHooks listSpecProps | "Base window spec" windowSpec := builder pluggableWindowSpec new. @@ -37,7 +88,7 @@ ComponentBrowser >> buildWith: builder [ label: 'Component Browser'; children: OrderedCollection new. - "Top half tree" + "Top half: React scene tree" treeSpec := builder pluggableTreeSpec new. treeSpec model: self; @@ -49,28 +100,26 @@ ComponentBrowser >> buildWith: builder [ frame: (0@0 corner: 1@0.65). windowSpec children add: treeSpec. - "Bottom half: state" - "Left" - listSpecLeft := builder pluggableListSpec new. - listSpecLeft + "Left list: temps (in Squeak)" + listSpecTemps := builder pluggableListSpec new. + listSpecTemps model: self; frame: (0@0.65 corner: 0.25@1); - list: #getStateOfCurrentSelection. + list: #getTempsOfCurrentSelection. - windowSpec children add: listSpecLeft. + windowSpec children add: listSpecTemps. - "Right" - listSpecRight := builder pluggableListSpec new. - listSpecRight + "Middle list: hooks" + listSpecHooks := builder pluggableListSpec new. + listSpecHooks model: self; - getSelected: #currentDataSelection; - setSelected: #currentDataSelection:; listItem: #getLabelOfState; frame: (0.25@0.65 corner: 0.6@1); - list: #getDataOfCurrentSelection. + list: #getHooksOfCurrentSelection. - windowSpec children add: listSpecRight. + windowSpec children add: listSpecHooks. + "Right list: props" listSpecProps := builder pluggableListSpec new. listSpecProps model: self; @@ -82,20 +131,6 @@ ComponentBrowser >> buildWith: builder [ ^ windowSpec ] -{ #category : #nil } -ComponentBrowser >> currentDataSelection [ - - ^ currentDataSelection -] - -{ #category : #'as yet unclassified' } -ComponentBrowser >> currentDataSelection: anItem [ - - currentDataSelection := anItem. - self changed: #currentDataSelection. - -] - { #category : #'as yet unclassified' } ComponentBrowser >> currentSelection [ @@ -112,17 +147,17 @@ ComponentBrowser >> currentSelection: anItem [ { #category : #'as yet unclassified' } ComponentBrowser >> getChildrenOfFiber: aFiber [ -"Return a collection of children of an object. If empty, there are no children (so there wont be an arrow). We start by asking for a direct child. If child exists, we also need to ask it for its next sibling, then that sibling for it's siblings etc." + "Return a collection of children of an object." ^ aFiber child ifNil: {} ifNotNil: [:child | self getSiblingsOfFiber: child] ] { #category : #'as yet unclassified' } -ComponentBrowser >> getDataOfCurrentSelection [ +ComponentBrowser >> getHooksOfCurrentSelection [ "Return the list of strings that should fill the data section." - ^ self currentSelection ifNil: #() ifNotNil: [:currentSelected | - currentSelected hooks + ^ self currentSelection ifNil: #() ifNotNil: [:currentSelection | + self class displayHooksForFiber: currentSelection ] ] @@ -136,47 +171,36 @@ ComponentBrowser >> getLabelOfFiber: aFiber [ ComponentBrowser >> getPropsOfCurrentSelection [ "Return the list of props that should fill the data section." - ^ self currentSelection ifNil: #() ifNotNil: [:currentSelected | - currentSelected node isGodotNode - ifTrue: [currentSelected node properties associations] - ifFalse: {'<>'} + ^ self currentSelection ifNil: #() ifNotNil: [:currentSelection | + self class displayPropsForFiber: currentSelection ] ] { #category : #'as yet unclassified' } ComponentBrowser >> getSiblingsOfFiber: aFiber [ -"return a collection of aFiber and all of its siblings" + "Return a collection of aFiber and all of its siblings." ^ {aFiber} , (aFiber sibling ifNil: {} ifNotNil: [:sibling | self getSiblingsOfFiber: sibling] ) -"{aFiber} , {} -> {aFiber}" + ] { #category : #'as yet unclassified' } ComponentBrowser >> getStateOfCurrentSelection [ - | parser | + "Return the list of strings that should fill the data section." + ^ self currentSelection ifNil: #() ifNotNil: [:currentSelection | + self class displayStateForFiber: currentSelection + ] +] + +{ #category : #'as yet unclassified' } +ComponentBrowser >> getTempsOfCurrentSelection [ "Return the list of strings that should fill the props section." ^ self currentSelection ifNil: #() ifNotNil: [:currentSelection | - "Find out what currentSelection is. A GreASe-node or a Godot node?" - self currentSelection node isGodotNode - ifTrue: {'' } - ifFalse: [ - (self currentSelection node isKindOf: CMFRootNode) ifTrue: {''} ifFalse: [ - parser := PPParser new - parse: (self currentSelection node elementClass>>#render:) getSource - class: UndefinedObject - noPattern: false - notifying: nil - ifFail: nil. - parser temporaries - ] - ] + self class displayTempsForFiber: currentSelection. ] - "Plan: For Grease-stuff, we put hooks, for Godot-stuff we put subscribed callbacks" - "Issue: State can have info, but no used name?" - "Todo: What are the dependencies for memos about?" ] { #category : #'as yet unclassified' } @@ -197,7 +221,7 @@ ComponentBrowser >> rootFiber: aFiber [ { #category : #'as yet unclassified' } ComponentBrowser >> updateStateList [ - self changed: #getStateOfCurrentSelection. - self changed: #getDataOfCurrentSelection. + self changed: #getTempsOfCurrentSelection. + self changed: #getHooksOfCurrentSelection. self changed: #getPropsOfCurrentSelection. ] diff --git a/GReaSe-Tools/ComponentBrowserDworph.class.st b/GReaSe-Tools/ComponentBrowserDworph.class.st index 9acccc0..8e4f900 100644 --- a/GReaSe-Tools/ComponentBrowserDworph.class.st +++ b/GReaSe-Tools/ComponentBrowserDworph.class.st @@ -11,6 +11,32 @@ ComponentBrowserDworph class >> appDescription [ ^ super appDescription ] +{ #category : #'as yet unclassified' } +ComponentBrowserDworph >> displayStateOfTreeNode: aTreeNode onItemList: anItemList [ + + aTreeNode getSelected ifNotNil: [:treeItem | + anItemList clear. + anItemList addItemText: 'TEMPS'. + (treeItem getMetaName: 'reactTemps') ifNotNil: [:temps | + temps do: [:tempString | + anItemList addItemText: tempString. + ] + ]. + anItemList addItemText: 'HOOKS'. + (treeItem getMetaName: 'reactHooks') ifNotNil: [:hooks | + hooks do: [:hookString | + anItemList addItemText: hookString. + ] + ]. + anItemList addItemText: 'PROPS'. + (treeItem getMetaName: 'reactProps') ifNotNil: [:props | + props do: [:propString | + anItemList addItemText: propString. + ] + ]. + ] +] + { #category : #'as yet unclassified' } ComponentBrowserDworph >> fontSize [ @@ -22,11 +48,15 @@ ComponentBrowserDworph >> generateUIForFiber: aCMFFiber inTree: aGDTree withRoot | currentItem | - aGDTreeItem ifNil: [currentItem := aGDTree createItem] ifNotNil: [currentItem := aGDTree createItemParent: aGDTreeItem]. - [currentItem - setTextColumn: 0 text: (aCMFFiber node asStringOrText)] ifError: - [currentItem - setTextColumn: 0 text: 'Parsing node failed']. + aGDTreeItem + ifNil: [currentItem := aGDTree createItem] + ifNotNil: [currentItem := aGDTree createItemParent: aGDTreeItem]. + [currentItem setTextColumn: 0 text: (aCMFFiber node asStringOrText)] + ifError: [currentItem setTextColumn: 0 text: 'Parsing node failed']. + + currentItem setMetaName: 'reactTemps' value: (ComponentBrowser displayTempsForFiber: aCMFFiber). + currentItem setMetaName: 'reactHooks' value: (ComponentBrowser displayHooksForFiber: aCMFFiber). + currentItem setMetaName: 'reactProps' value: (ComponentBrowser displayPropsForFiber: aCMFFiber). ^ { currentItem. @@ -36,32 +66,75 @@ ComponentBrowserDworph >> generateUIForFiber: aCMFFiber inTree: aGDTree withRoot ] { #category : #'as yet unclassified' } -ComponentBrowserDworph >> hashTree: aCMFFiber [ +ComponentBrowserDworph >> hashFiber: aCMFFiber [ | sum | - sum := 0. + "TODO Make use of Squeak's object ID system." + sum := aCMFFiber index asInteger. + aCMFFiber sibling ifNotNil: [sum := (sum asInteger) + ((self hashFiber: (aCMFFiber sibling)) asInteger)]. + aCMFFiber child ifNotNil: [sum := (sum asInteger) + ((self hashFiber: (aCMFFiber child)) asInteger)]. ^ sum ] +{ #category : #'as yet unclassified' } +ComponentBrowserDworph >> raycastMouseEvent: anEvent onSpatial: spatialRef forViewport: viewportRef withTransform: viewportTransform isMouseDown: down [ + "Raycast a mouse event on a 3D plane to a certain coordinate of a related 2D viewport and input such." + + | raycastResult | + + "Raycast from event-hand transform with distance of hand object to find intersection coordinate" + raycastResult := spatialRef get getWorld directSpaceState + intersectRayFrom: anEvent transform translation + to: (anEvent transform localPointToGlobal: (0 @ 0 @ GRPointer laserLength negated)) + exclude: {} + collisionMask: 2147483647 + collideWithBodies: true + collideWithAreas: true. + + "Convert intersection coordinate to coordinate in viewport" + raycastResult ifNotNil: [ + (raycastResult at: #position ifAbsent: nil) ifNotNil: [ + | viewportCoord3D viewportCoord2D | + + viewportCoord3D := viewportTransform get globalPointToLocal: (raycastResult at: #position). + viewportCoord2D := Point + x: ((viewportCoord3D x / 1.5) + 0.5) * self resolution x + y: ((viewportCoord3D y negated/ 1) + 0.5) * self resolution y. + + "Generate viewport click event" + GDInputEventMouseButton externalNewDuring: [ :mouseEvent | + mouseEvent + buttonIndex: 1; + pressed: down; + position: viewportCoord2D; + globalPosition: viewportCoord2D; + buttonMask: 1. + viewportRef get inputLocalEvent: mouseEvent. + ] . + ]] +] + { #category : #'as yet unclassified' } ComponentBrowserDworph >> render: props [ - | viewportTransform viewport spatial rootFiber treeRef listRef | + | viewportTransform viewport spatial treeRef listRef | viewportTransform := self useState: nil. viewport := self useGodotRef. spatial := self useGodotRef. - rootFiber := (((GRReact current instVarNamed: #sessions) associations first value) instVarNamed: #react) currentRoot. treeRef := self useGodotRef. listRef := self useGodotRef. - self useEffect: [ - treeRef get ifNotNil: [:tree | - tree clear. - self generateUIForFiber: rootFiber inTree: tree withRoot: nil. + + self + useEffect: [ + treeRef get ifNotNil: [:tree | + tree clear. + listRef get ifNotNil: [:listNode | listNode clear]. + self generateUIForFiber: (ComponentBrowser getCurrentRoot) inTree: tree withRoot: nil. ]] - dependencies: {treeRef get ifNotNil: [:node | node objectId]. self hashTree: rootFiber }. + dependencies: {treeRef get ifNotNil: [:node | node objectId]. self hashFiber: ComponentBrowser getCurrentRoot }. ^ GRViewportTexture new getViewport: [:vp | viewport set: vp]; @@ -71,13 +144,10 @@ ComponentBrowserDworph >> render: props [ children: { GDTree new ref: treeRef; - rectSize: (1000@1000); - onItemSelected: [listRef get ifNotNil: [:listNode | + rectSize: (1000@1000); + onItemSelected: [listRef get ifNotNil: [:listNode | treeRef get ifNotNil: [:treeNode | - treeNode getSelected ifNotNil: [:treeItem | - listNode clear. - listNode addItemText: (treeItem getTextColumn: 0). - ] + self displayStateOfTreeNode: treeNode onItemList: listNode. ] ]]. GDItemList new @@ -97,66 +167,11 @@ ComponentBrowserDworph >> render: props [ subscribeTo: #global_transform do: [ :transform | viewportTransform set: transform]. GDCollisionShape new shape: (GDBoxShape new extents: 0.75 @ 0.5 @ 0.001); translation: 0 @ 0 @ 0.001. }; - onButtonPress: [:event || raycastResult | - "Raycast from event-hand transform with distance of hand object to find intersection coordinate" - raycastResult := spatial get getWorld directSpaceState - intersectRayFrom: event transform translation - to: (event transform localPointToGlobal: (0 @ 0 @ GRPointer laserLength negated)) - exclude: {} - collisionMask: 2147483647 - collideWithBodies: true - collideWithAreas: true. - "Convert intersection coordinate to coordinate in viewport" - raycastResult ifNotNil: [ - - (raycastResult at: #position ifAbsent: nil) ifNotNil: [ |viewportCoord3D viewportCoord2D | - viewportCoord3D := viewportTransform get globalPointToLocal: (raycastResult at: #position). - viewportCoord2D := Point - x: ((viewportCoord3D x / 1.5) + 0.5) * self resolution x - y: ((viewportCoord3D y negated/ 1) + 0.5) * self resolution y. - - "Generate viewport click event" - GDInputEventMouseButton externalNewDuring: [ :mouseEvent | - mouseEvent - buttonIndex: 1; - pressed: true; - position: viewportCoord2D; - globalPosition: viewportCoord2D; - buttonMask: 1. - viewport get inputLocalEvent: mouseEvent. - ] . - ]] + onButtonPress: [:event | + self raycastMouseEvent: event onSpatial: spatial forViewport: viewport withTransform: viewportTransform isMouseDown: true. ]; - onButtonRelease: [:event || raycastResult | - "Raycast from event-hand transform with distance of hand object to find intersection coordinate" - raycastResult := spatial get getWorld directSpaceState - intersectRayFrom: event transform translation - to: (event transform localPointToGlobal: (0 @ 0 @ GRPointer laserLength negated)) - exclude: {} - collisionMask: 2147483647 - collideWithBodies: true - collideWithAreas: true. - "Convert intersection coordinate to coordinate in viewport" - raycastResult ifNotNil: [ - - (raycastResult at: #position ifAbsent: nil) ifNotNil: [ |viewportCoord3D viewportCoord2D | - viewportCoord3D := viewportTransform get globalPointToLocal: (raycastResult at: #position). - viewportCoord2D := Point - x: ((viewportCoord3D x / 1.5) + 0.5) * self resolution x - y: ((viewportCoord3D y negated/ 1) + 0.5) * self resolution y. - - "Generate viewport click event" - GDInputEventMouseButton externalNewDuring: [ :mouseEvent | - mouseEvent - buttonIndex: 1; - pressed: false; - position: viewportCoord2D; - globalPosition: viewportCoord2D; - buttonMask: 1. - viewport get inputLocalEvent: mouseEvent. - ] . - - ]] + onButtonRelease: [:event | + self raycastMouseEvent: event onSpatial: spatial forViewport: viewport withTransform: viewportTransform isMouseDown: false. ]. GDMeshInstance new translation: 0@0@0; diff --git a/squeak/GReaSe-React/GRComponent.class.st b/squeak/GReaSe-React/GRComponent.class.st index 75af8e5..fb0288e 100644 --- a/squeak/GReaSe-React/GRComponent.class.st +++ b/squeak/GReaSe-React/GRComponent.class.st @@ -400,13 +400,15 @@ GRComponent >> useSceneInstance: aScenePath [ ^ GDNode new call: 'add_child' arguments: {scene} ] -{ #category : #'as yet unclassified' } +{ #category : #hooks } GRComponent >> useState: default [ - "Check if target is already cached. If not, try to associate the call with a temp name for debugging in the Component Explorer." + | state | + + state := super useState: default. + state hookName: thisContext sender nextStoreTargetName. - "self halt." - ^ super useState: default. + ^ state ] { #category : #hooks }