From 28b163cdd5609158d33fa9ba554ba630e08429e9 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Sat, 25 Jan 2025 11:41:05 -0500 Subject: [PATCH] [3d] animation node UI change (#2347) --- src/extensions/core/load3d.ts | 100 +------------ src/extensions/core/load3d/Load3dAnimation.ts | 140 ++++++++++++++++++ 2 files changed, 141 insertions(+), 99 deletions(-) diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts index ff6240f68..9e3dbfa19 100644 --- a/src/extensions/core/load3d.ts +++ b/src/extensions/core/load3d.ts @@ -258,15 +258,6 @@ app.registerExtension({ modelWidget.value = '' } - const animationSelect = node.widgets?.find( - (w: IWidget) => w.name === 'animation' - ) - - if (animationSelect) { - animationSelect.options.values = [] - animationSelect.value = '' - } - const speedSelect = node.widgets?.find( (w: IWidget) => w.name === 'animation_speed' ) @@ -276,44 +267,6 @@ app.registerExtension({ } }) - node.addWidget( - 'button', - 'Play/Pause Animation', - 'toggle_animation', - () => { - load3d.toggleAnimation() - } - ) - - const animationSelect = node.addWidget( - 'combo', - 'animation', - '', - () => '', - { - values: [] - } - ) as IWidget - - animationSelect.callback = (value: string) => { - const names = load3d.getAnimationNames() - const index = names.indexOf(value) - - if (index !== -1) { - const wasPlaying = load3d.isAnimationPlaying - - if (wasPlaying) { - load3d.toggleAnimation(false) - } - - load3d.updateSelectedAnimation(index) - - if (wasPlaying) { - load3d.toggleAnimation(true) - } - } - } - return { widget: node.addDOMWidget(inputName, 'LOAD_3D_ANIMATION', container) } @@ -379,20 +332,7 @@ app.registerExtension({ lightIntensity, upDirection, fov, - cameraState, - (load3d: Load3d) => { - const animationLoad3d = load3d as Load3dAnimation - const names = animationLoad3d.getAnimationNames() - - const animationSelect = node.widgets.find( - (w: IWidget) => w.name === 'animation' - ) - - animationSelect.options.values = names - if (names.length) { - animationSelect.value = names[0] - } - } + cameraState ) const w = node.widgets.find((w: IWidget) => w.name === 'width') @@ -592,44 +532,6 @@ app.registerExtension({ load3d.renderer.domElement.hidden = this.flags.collapsed ?? false } - node.addWidget( - 'button', - 'Play/Pause Animation', - 'toggle_animation', - () => { - load3d.toggleAnimation() - } - ) - - const animationSelect = node.addWidget( - 'combo', - 'animation', - '', - () => '', - { - values: [] - } - ) as IWidget - - animationSelect.callback = (value: string) => { - const names = load3d.getAnimationNames() - const index = names.indexOf(value) - - if (index !== -1) { - const wasPlaying = load3d.isAnimationPlaying - - if (wasPlaying) { - load3d.toggleAnimation(false) - } - - load3d.updateSelectedAnimation(index) - - if (wasPlaying) { - load3d.toggleAnimation(true) - } - } - } - return { widget: node.addDOMWidget( inputName, diff --git a/src/extensions/core/load3d/Load3dAnimation.ts b/src/extensions/core/load3d/Load3dAnimation.ts index e472d31f4..a5f264dc5 100644 --- a/src/extensions/core/load3d/Load3dAnimation.ts +++ b/src/extensions/core/load3d/Load3dAnimation.ts @@ -10,9 +10,116 @@ class Load3dAnimation extends Load3d { isAnimationPlaying: boolean = false animationSpeed: number = 1.0 + playPauseContainer: HTMLDivElement = {} as HTMLDivElement + animationSelect: HTMLSelectElement = {} as HTMLSelectElement constructor(container: Element | HTMLElement) { super(container) + this.createPlayPauseButton(container) + this.createAnimationList(container) + } + + createAnimationList(container: Element | HTMLElement) { + this.animationSelect = document.createElement('select') + Object.assign(this.animationSelect.style, { + position: 'absolute', + top: '3px', + left: '50%', + transform: 'translateX(15px)', + width: '90px', + height: '20px', + backgroundColor: 'rgba(0, 0, 0, 0.3)', + color: 'white', + border: 'none', + borderRadius: '4px', + fontSize: '12px', + padding: '0 8px', + cursor: 'pointer', + display: 'none', + outline: 'none' + }) + + this.animationSelect.addEventListener('mouseenter', () => { + this.animationSelect.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' + }) + + this.animationSelect.addEventListener('mouseleave', () => { + this.animationSelect.style.backgroundColor = 'rgba(0, 0, 0, 0.3)' + }) + + this.animationSelect.addEventListener('change', (event) => { + const select = event.target as HTMLSelectElement + this.updateSelectedAnimation(select.selectedIndex) + }) + + container.appendChild(this.animationSelect) + } + + updateAnimationList() { + this.animationSelect.innerHTML = '' + this.animationClips.forEach((clip, index) => { + const option = document.createElement('option') + option.value = index.toString() + option.text = clip.name || `Animation ${index + 1}` + option.selected = index === this.selectedAnimationIndex + this.animationSelect.appendChild(option) + }) + } + + createPlayPauseButton(container: Element | HTMLElement) { + this.playPauseContainer = document.createElement('div') + this.playPauseContainer.style.position = 'absolute' + this.playPauseContainer.style.top = '3px' + this.playPauseContainer.style.left = '50%' + this.playPauseContainer.style.transform = 'translateX(-50%)' + this.playPauseContainer.style.width = '20px' + this.playPauseContainer.style.height = '20px' + this.playPauseContainer.style.cursor = 'pointer' + this.playPauseContainer.style.alignItems = 'center' + this.playPauseContainer.style.justifyContent = 'center' + + const updateButtonState = () => { + const icon = this.playPauseContainer.querySelector('svg') + if (icon) { + if (this.isAnimationPlaying) { + icon.innerHTML = ` + + ` + this.playPauseContainer.title = 'Pause Animation' + } else { + icon.innerHTML = ` + + ` + this.playPauseContainer.title = 'Play Animation' + } + } + } + + const playIcon = document.createElement('div') + playIcon.innerHTML = ` + + + + ` + + this.playPauseContainer.addEventListener('mouseenter', () => { + this.playPauseContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' + }) + + this.playPauseContainer.addEventListener('mouseleave', () => { + this.playPauseContainer.style.backgroundColor = 'transparent' + }) + + this.playPauseContainer.addEventListener('click', (event) => { + event.stopPropagation() + this.toggleAnimation() + updateButtonState() + }) + + this.playPauseContainer.appendChild(playIcon) + container.appendChild(this.playPauseContainer) + + this.playPauseContainer.style.display = 'none' } protected async setupModel(model: THREE.Object3D) { @@ -44,6 +151,21 @@ class Load3dAnimation extends Load3d { this.updateSelectedAnimation(0) } } + + if (this.animationClips.length > 0) { + this.playPauseContainer.style.display = 'block' + } else { + this.playPauseContainer.style.display = 'none' + } + + if (this.animationClips.length > 0) { + this.playPauseContainer.style.display = 'block' + this.animationSelect.style.display = 'block' + this.updateAnimationList() + } else { + this.playPauseContainer.style.display = 'none' + this.animationSelect.style.display = 'none' + } } setAnimationSpeed(speed: number) { @@ -88,6 +210,8 @@ class Load3dAnimation extends Load3d { } this.animationActions = [action] + + this.updateAnimationList() } clearModel() { @@ -104,6 +228,11 @@ class Load3dAnimation extends Load3d { this.animationSpeed = 1.0 super.clearModel() + + if (this.animationSelect) { + this.animationSelect.style.display = 'none' + this.animationSelect.innerHTML = '' + } } getAnimationNames(): string[] { @@ -120,6 +249,17 @@ class Load3dAnimation extends Load3d { this.isAnimationPlaying = play ?? !this.isAnimationPlaying + const icon = this.playPauseContainer.querySelector('svg') + if (icon) { + if (this.isAnimationPlaying) { + icon.innerHTML = '' + this.playPauseContainer.title = 'Pause Animation' + } else { + icon.innerHTML = '' + this.playPauseContainer.title = 'Play Animation' + } + } + this.animationActions.forEach((action) => { if (this.isAnimationPlaying) { action.paused = false