Browse Source

Rewrite/improve HierarchyUiPlugin.

master
Palash Bansal 1 year ago
parent
commit
cc0cc8a05f
No account linked to committer's email address

+ 2
- 2
plugins/tweakpane-editor/package-lock.json View File

@@ -1,12 +1,12 @@
{
"name": "@threepipe/plugin-tweakpane-editor",
"version": "0.3.4",
"version": "0.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@threepipe/plugin-tweakpane-editor",
"version": "0.3.4",
"version": "0.4.0",
"license": "Apache-2.0",
"dependencies": {
"@threepipe/plugin-tweakpane": "file:./../tweakpane/src/",

+ 3
- 3
plugins/tweakpane-editor/package.json View File

@@ -1,7 +1,7 @@
{
"name": "@threepipe/plugin-tweakpane-editor",
"description": "Tweakpane Editor Plugin for ThreePipe",
"version": "0.3.4",
"version": "0.4.0",
"devDependencies": {
"tippy.js": "^6.3.7",
"treejs": "git://github.com/repalash/treejs.git#d303016bb74e75725d13e97291ac1d4727985918"
@@ -30,8 +30,8 @@
"replace": {
"dependencies": {},
"peerDependencies": {
"threepipe": "^0.0.39",
"@threepipe/plugin-tweakpane": "^0.5.3"
"threepipe": "^0.0.42",
"@threepipe/plugin-tweakpane": "^0.5.4"
}
}
},

+ 174
- 21
plugins/tweakpane-editor/src/HierarchyUiPlugin.ts View File

@@ -1,6 +1,14 @@
import {createDiv, createStyles, css, timeout} from 'ts-browser-helpers'
import {createDiv, createStyles, css} from 'ts-browser-helpers'
import Tree from 'treejs'
import {AViewerPluginSync, IObject3D, Object3D, ThreeViewer} from 'threepipe'
import {
AViewerPluginSync,
IObject3D,
ISceneEventMap,
Object3D,
PickingPlugin,
ThreeViewer,
type UndoManagerPlugin, Event2,
} from 'threepipe'
import {UiObjectConfig} from 'uiconfig.js'

export class HierarchyUiPlugin extends AViewerPluginSync {
@@ -9,7 +17,7 @@ export class HierarchyUiPlugin extends AViewerPluginSync {

toJSON: any = undefined

treeView: any = undefined
treeView?: Tree = undefined

hierarchyDiv = createDiv({
innerHTML: '',
@@ -34,15 +42,62 @@ export class HierarchyUiPlugin extends AViewerPluginSync {
.treejs .treejs-switcher:before {
border-top: 6px solid var(--tp-container-foreground-color, hsl(230, 7%, 75%)) !important;
}
.treejs .treejs-switcher,.treejs-label,.treejs-checkbox {
pointer-events: auto;
}

.treejs .treejs-node {
pointer-events: none;
position: relative;
}

.treejs .treejs-label {
position: absolute;
height: 16px;
line-height: 16px;
text-overflow: ellipsis;
overflow: hidden;
width: calc(100% - 50px);
padding: 1px 4px;
margin: 1px 0px;
border-radius: 2px;
box-sizing: content-box;
}

.treejs .treejs-node-selected .treejs-label {
outline-offset: -1px;
outline: solid 1px #1890ff;
}

.treejs .treejs-node-selected > .treejs-label {
background-color: #067ce9;
color: #eee;
outline: none;
}
`)

}

reset(e?: any) {
reset(e?: Event2<'sceneUpdate', ISceneEventMap, IObject3D>) {
if (e?.source === HierarchyUiPlugin.PluginType) return // for infinite loop

// visible changed from outside
if (e && e.object && e.change === 'visible' && this.treeView) {
const nodeId = e.object.uuid
// @ts-expect-error no type
const node = this.treeView.nodesById[nodeId] as TNode
if (node && !!node.status !== e.object.visible) {
this.treeView.setValue(nodeId)
this.treeView.updateLiElements()
}
// this.treeView.values = obj.children.reduce(this._findVisible, [])
// console.log(this.treeView.values)
}

if (!e?.hierarchyChanged) return
this._needsReset = true
}

protected async _reset() {
this._needsReset = false

@@ -52,15 +107,15 @@ export class HierarchyUiPlugin extends AViewerPluginSync {
if (!obj) return

const data = obj.children.reduce(this._buildData, [])
const visible = obj.children.reduce(this._findVisible, [])
let firstChange = false
return new Promise<void>((resolve) => {

return new Promise<void>((resolve, _reject) => {
this.treeView = new Tree(this.hierarchyDiv, {
closeDepth: 1,
data,
// values: visible, // uuids of visible nodes
loaded: function() {
this.values = visible
this.values = []
resolve()
},
onChange: () => {
@@ -68,10 +123,9 @@ export class HierarchyUiPlugin extends AViewerPluginSync {
firstChange = true
return
}
timeout(200).then(() => { // wait for the UI to update
if (this.treeView)
this._setVisible(this.treeView.values)
})
// timeout(200).then(() => { // wait for the UI to update
this._setVisible()
// })
},
onItemLabelClick: (item: any) => {
const obj1 = this._viewer?.scene.modelRoot.getObjectByProperty('uuid', item)
@@ -79,20 +133,65 @@ export class HierarchyUiPlugin extends AViewerPluginSync {
obj1.dispatchEvent({type: 'select', value: obj1, object: obj1, ui: true})
},
})
}).then(()=>{
this._refreshVisible()
this._selectedObjectChanged()
})
}

private _refreshVisible() {
const obj = this._viewer?.scene.modelRoot as Object3D
if (!obj) return
const visible = obj.children.reduce(this._findVisible, [])
this.treeView?.emptyNodesCheckStatus()
this.treeView?.treeNodes.map(n=>refreshVisible(n, visible, this.treeView!))
this.treeView?.updateLiElements()
}

onAdded(viewer: ThreeViewer) {
super.onAdded(viewer)
this.reset()
viewer.scene.addEventListener('sceneUpdate', this.reset)
viewer.addEventListener('postFrame', this._postFrame)

viewer.forPlugin<PickingPlugin>('PickingPlugin', (pi)=>{
pi.addEventListener('selectedObjectChanged', this._selectedObjectChanged)
}, (pi)=>{
pi.removeEventListener('selectedObjectChanged', this._selectedObjectChanged)
})
viewer.forPlugin<UndoManagerPlugin>('UndoManagerPlugin', (um)=>{
this.undoManager = um.undoManager
}, ()=>{
this.undoManager = undefined
})

this.reset()
}

undoManager?: UndoManagerPlugin['undoManager'] = undefined

private _selectedObjectChanged = ()=>{
const picking = this._viewer?.getPlugin(PickingPlugin)
if (!picking || !this.treeView) return
const liElem = this.treeView.liElementsById as Record<string, HTMLLIElement>
const elems = Object.values(liElem)
for (const li of elems) {
li.classList.remove('treejs-node-selected')
}
const selected = picking.getSelectedObject() as Object3D|undefined
if (selected?.uuid) {
const li = liElem[selected?.uuid]
if (li) {
li.classList.add('treejs-node-selected')
li.scrollIntoView({block: 'nearest', inline: 'nearest'})
}
}
}

onRemove(viewer: ThreeViewer) {
// todo: remove UI element.
viewer.scene.removeEventListener('sceneUpdate', this.reset)
viewer.removeEventListener('postFrame', this._postFrame)
viewer.getPlugin(PickingPlugin)?.removeEventListener('selectedObjectChanged', this._selectedObjectChanged)
return super.onRemove(viewer)
}

@@ -121,22 +220,76 @@ export class HierarchyUiPlugin extends AViewerPluginSync {
}
private _findVisible = (data: any[], obj: Object3D): any[] => { // only leaf.
if (!obj.visible) return data
if (obj.children.length < 1) data.push(obj.uuid)
else data.push(...obj.children.reduce(this._findVisible, []))
data.push(obj.uuid)
data.push(...obj.children.reduce(this._findVisible, []))
return data
}
private _setVisible = (values: string[]): void => {
private _setVisible = (): void => {
this._viewer?.doOnce('postFrame', () => {
const obj = this._viewer?.scene.modelRoot
if (!obj || values === undefined || values === null) return
const ancestorSet: Set<Object3D> = new Set()
if (!obj || !this.treeView) return
const changeMap: Map<Object3D, [boolean, boolean]> = new Map()
function cAdd(o: Object3D, v: boolean) {
const c = changeMap.get(o)?.[0] ?? o.visible
if (v !== c) changeMap.set(o, [v, o.visible])
}
obj.traverse((o)=>{
if (o === obj) return
o.visible = values.includes(o.uuid)
if (o.visible) o.traverseAncestors(p=>ancestorSet.add(p))
// @ts-expect-error no type
const node = this.treeView.nodesById[o.uuid] as TNode|undefined
if (node) {
cAdd(o, !!node.status)
if (o.visible) o.traverseAncestors(p => cAdd(p, true))
}
})
ancestorSet.forEach(o=> o.visible = true)
this._viewer?.scene?.setDirty({refreshScene: true, source: HierarchyUiPlugin.PluginType, updateGround: false})
const cmd = {
redo: (refresh = true)=>{
let changed = false
changeMap.entries().forEach(([o, v])=> {
if (o.visible === v[0]) return
o.visible = v[0]
changed = true
})
if (!changed) return
this._viewer?.scene?.setDirty({refreshScene: true, source: HierarchyUiPlugin.PluginType, updateGround: false})
refresh && this._refreshVisible()
},
undo: ()=>{
let changed = false
changeMap.entries().forEach(([o, v])=> {
if (o.visible === v[1]) return
o.visible = v[1]
changed = true
})
if (!changed) return
this._viewer?.scene?.setDirty({refreshScene: true, source: HierarchyUiPlugin.PluginType, updateGround: false})
this._refreshVisible()
},
}
cmd.redo(false)
this.undoManager?.record(cmd)
})
}
}

interface TNode {id: string, status: 0|1|2, text: string, children: TNode[]}
// this is required because setValues in Treejs toggles it, not sets it
function refreshVisible(node: TNode, visibles: string[], tree: Tree) {
const v = visibles.includes(node.id)
const last = node.status
if (node.children.length) {
let allVisible = true
for (const child of node.children) {
const res = refreshVisible(child, visibles, tree)
if (!res) allVisible = false
}
node.status = v ? allVisible ? 2 : 1 : 0
} else {
node.status = v ? 2 : 0
}
if (last !== node.status) {
// @ts-expect-error no type
tree.willUpdateNodesById[node.id] = node
}
return node.status
}

Loading…
Cancel
Save