Skip to content

Commit 8ceb49e

Browse files
Scenes v1 (#658)
* scene structure * renamed node types to ___Node * CurrentScene component * Scenes UI * active-scene-id global node * switchable scenes * put back deprecated plugin thingy * separate setupScenes func * renaming * scene-control plugin * plugins as collapsibles * lint * fix id for plugin * css fix for plugin * plugin icons * style fixes * style tweak * component tidy * TS and lint fixes * allow non-input plugins * button disabled state fix * three and deps upgrade * renamed postprocessing to renderPipeline * fix for saves
1 parent 102dbb3 commit 8ceb49e

129 files changed

Lines changed: 3641 additions & 1558 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/desktop/electron.vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default defineConfig({
4040
'@hedron-gl/engine': path.resolve(__dirname, '../../packages/engine/src'),
4141
'@hedron-gl/midi-manager': path.resolve(__dirname, '../../packages/midi-manager/src'),
4242
'@hedron-gl/timeline': path.resolve(__dirname, '../../packages/timeline/src'),
43+
'@hedron-gl/scene-control': path.resolve(__dirname, '../../packages/scene-control/src'),
4344
},
4445
},
4546
},

apps/desktop/package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"@hedron-gl/gamepad-input": "^1.0.0-alpha.4",
3434
"@hedron-gl/lfo-input": "^1.0.0-alpha.4",
3535
"@hedron-gl/midi-input": "^1.0.0-alpha.4",
36+
"@hedron-gl/scene-control": "^1.0.0-alpha.4",
3637
"@hedron-gl/timeline": "^1.0.0-alpha.4",
3738
"@hedron-gl/ui-core": "^1.0.0-alpha.4",
3839
"@tomjs/electron-devtools-installer": "^2.4.0",
@@ -45,18 +46,20 @@
4546
"lodash.debounce": "^4.0.8",
4647
"mime-types": "^3.0.2",
4748
"postprocessing": "6.37.8",
48-
"three": "^0.178.0",
49+
"three": "^0.185.1",
4950
"usehooks-ts": "^3.1.1",
5051
"zustand": "^5.0.8"
5152
},
5253
"devDependencies": {
5354
"@types/fs-extra": "^11.0.4",
5455
"@types/lodash.debounce": "^4.0.9",
5556
"@types/mime-types": "^3.0.1",
56-
"@types/node": "^25.0.0",
57-
"@types/three": "^0.178.1",
57+
"@types/node": "^26.1.1",
58+
"@types/three": "^0.185.1",
5859
"electron": "37.3.0",
5960
"electron-builder": "26.0.12",
60-
"electron-vite": "^4.0.0"
61+
"electron-vite": "^4.0.0",
62+
"vite": "^7.3.1",
63+
"typescript": "^5.9.3"
6164
}
6265
}

apps/desktop/src/renderer/components/ActiveSketch/ActiveSketch.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
display: flex;
33
flex-direction: column;
44
position: relative;
5-
height: 100vh;
5+
height: 100%;
66
}
77

88
.section {

apps/desktop/src/renderer/components/ActiveSketch/ActiveSketch.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from '@hedron-gl/ui-core'
1616

1717
import c from './ActiveSketch.module.css'
18-
import { useActiveSketch } from '@components/hooks/useActiveSketch'
18+
import { useSelectedSketch } from '@components/hooks/useSelectedSketch'
1919
import { engineStore } from '@renderer/engine'
2020
import { SketchControls } from '@components/SketchControls/SketchControls'
2121
import { useGroupedNodes } from '@components/hooks/useGroupedNodes'
@@ -39,7 +39,7 @@ const SelectedNodePanel = ({ node, onClose }: { node: Node; onClose: () => void
3939
}
4040

4141
export const ActiveSketch = () => {
42-
const activeSketch = useActiveSketch()
42+
const activeSketch = useSelectedSketch()
4343

4444
if (!activeSketch) {
4545
throw new Error('ActiveSketch component: No activesketch found')
@@ -70,7 +70,7 @@ export const ActiveSketch = () => {
7070
{
7171
label: 'Delete Sketch',
7272
icon: 'delete',
73-
onClick: () => engineStore.getState().deleteSketch(activeSketch.id),
73+
onClick: () => engineStore.getState().deleteNode(activeSketch.id),
7474
},
7575
]}
7676
>

apps/desktop/src/renderer/components/App/App.module.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,20 @@
6363
flex-shrink: 0;
6464
}
6565

66-
.widgetStrip:last-child {
66+
.pluginControls {
6767
flex: 1;
6868
min-height: 0;
69+
overflow: auto;
70+
padding: 0.25rem 0;
6971
}
7072

7173
.widgetItem {
7274
padding: 0.25rem 0;
7375
}
76+
77+
.plugins {
78+
padding: 0.5rem;
79+
display: flex;
80+
flex-direction: column;
81+
gap: 0.25rem;
82+
}

apps/desktop/src/renderer/components/App/App.tsx

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { AppStoreProvider, EngineProvider, WidgetStrip, useAppStore } from '@hedron-gl/ui-core'
1+
import {
2+
AppStoreProvider,
3+
Collapsible,
4+
EngineProvider,
5+
Icon,
6+
IconName,
7+
useAppStore,
8+
} from '@hedron-gl/ui-core'
9+
import { useState } from 'react'
210
import c from './App.module.css'
311
import { useHandleDrag } from './useHandleDrag'
412
import { GlobalClock } from '@components/GlobalClock/GlobalClock'
@@ -10,6 +18,43 @@ import { WorkArea } from '@components/WorkArea/WorkArea'
1018
import { appStore } from '@renderer/appStore'
1119
import { engine, pluginViews } from '@renderer/engine'
1220

21+
const PluginGlobalControls = () => {
22+
const [openById, setOpenById] = useState<Record<string, boolean>>({})
23+
24+
return (
25+
<div className={c.plugins}>
26+
{Object.entries(pluginViews.globalPanel).map(([pluginId, PluginPanel]) => {
27+
const enginePlugin = engine.plugins[pluginId]
28+
const pluginName = enginePlugin?.name ?? pluginId
29+
const pluginIconName = enginePlugin?.iconName ?? 'extension'
30+
const isOpen = openById[pluginId] ?? false
31+
32+
return (
33+
<Collapsible
34+
type="panel"
35+
className={c.pluginItem}
36+
key={pluginId}
37+
title={
38+
<>
39+
<Icon name={pluginIconName as IconName} /> {pluginName}
40+
</>
41+
}
42+
isOpen={isOpen}
43+
onToggle={(nextOpen) =>
44+
setOpenById((prev) => ({
45+
...prev,
46+
[pluginId]: nextOpen,
47+
}))
48+
}
49+
>
50+
<PluginPanel engine={engine} />
51+
</Collapsible>
52+
)
53+
})}
54+
</div>
55+
)
56+
}
57+
1358
const AppContent = (): JSX.Element => {
1459
const sketchesDir = useAppStore((state) => state.sketchesDir)
1560
const isProjectLoaded = sketchesDir !== null
@@ -34,9 +79,9 @@ const AppContent = (): JSX.Element => {
3479
<VideoControls />
3580
</div>
3681
</div>
37-
{/* The above widgets need to be converted to plugins */}
38-
<div className={c.widgetStrip}>
39-
<WidgetStrip engine={engine} pluginViews={pluginViews.globalPanel} />
82+
83+
<div className={c.pluginControls}>
84+
<PluginGlobalControls />
4085
</div>
4186
</>
4287
)}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.wrapper {
2+
display: flex;
3+
flex-direction: column;
4+
height: 100%;
5+
}
6+
7+
.sceneHeader {
8+
display: flex;
9+
align-items: center;
10+
color: var(--textColorLight2);
11+
gap: var(--gapLarge);
12+
font-size: 0.8rem;
13+
font-weight: 600;
14+
padding: var(--gapSmall) var(--gapLarge);
15+
padding-top: var(--gapSmall);
16+
padding-bottom: var(--gapSmall);
17+
background: var(--bgColorDark4);
18+
}
19+
20+
.content {
21+
flex: 1;
22+
min-height: 0;
23+
}
24+
25+
.intro {
26+
padding: calc(var(--gapMega) - var(--gapLarge));
27+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import {
2+
Button,
3+
Icon,
4+
Panel,
5+
PanelBody,
6+
PanelHeader,
7+
PopoutMenu,
8+
sceneIcon,
9+
useEngineStore,
10+
} from '@hedron-gl/ui-core'
11+
import c from './CurrentScene.module.css'
12+
import { useAppStore } from '@renderer/appStore'
13+
import { Sketches } from '@components/Sketches/Sketches'
14+
15+
export const CurrentScene = () => {
16+
const sceneIds = useEngineStore((state) => state.sceneIds)
17+
const deleteScene = useEngineStore((state) => state.deleteScene)
18+
19+
const selectedSceneId = useAppStore((state) => state.selectedSceneId)
20+
const setSelectedSceneId = useAppStore((state) => state.setSelectedSceneId)
21+
const activeScene = useEngineStore((state) =>
22+
selectedSceneId ? state.nodes[selectedSceneId] : undefined,
23+
)
24+
25+
const handleDeleteCurrentScene = () => {
26+
if (!selectedSceneId) {
27+
return
28+
}
29+
30+
const fallbackSceneId = sceneIds.find((id) => id !== selectedSceneId)
31+
32+
if (fallbackSceneId) {
33+
setSelectedSceneId(fallbackSceneId)
34+
}
35+
36+
deleteScene(selectedSceneId)
37+
}
38+
39+
const hasActiveScene = activeScene?.nodeType === 'scene'
40+
const sceneTitle = hasActiveScene ? activeScene.title : 'No Scene Selected'
41+
42+
return (
43+
<div className={c.wrapper}>
44+
<header className={c.sceneHeader}>
45+
<Icon name={sceneIcon} /> {sceneTitle}
46+
<PopoutMenu
47+
className="ml-auto"
48+
items={[
49+
{
50+
label: 'Delete Scene',
51+
icon: 'delete',
52+
onClick: handleDeleteCurrentScene,
53+
},
54+
]}
55+
>
56+
<Button type="ghost" iconName="more_horiz" disabled={!hasActiveScene} />
57+
</PopoutMenu>
58+
</header>
59+
<div className={c.content}>
60+
{hasActiveScene ? (
61+
<Sketches />
62+
) : (
63+
<div className={c.intro}>
64+
<Panel>
65+
<PanelHeader iconName="info">Add A Scene</PanelHeader>
66+
<PanelBody>
67+
Use the &quot;Add Scene&quot; button in the Scenes panel to create your first scene.
68+
</PanelBody>
69+
</Panel>
70+
</div>
71+
)}
72+
</div>
73+
</div>
74+
)
75+
}

apps/desktop/src/renderer/components/GlobalDialogs/SketchModulesDialog/SketchCard.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
useEngineStore,
1212
} from '@hedron-gl/ui-core'
1313

14-
import { useSetActiveSketchId } from '@components/hooks/useSetActiveSketchId'
14+
import { useSetSelectedSketchId } from '@components/hooks/useSetSelectedSketchId'
15+
import { useAppStore } from '@renderer/appStore'
1516

1617
interface SketchCardProps {
1718
item: SketchModuleItem
@@ -25,14 +26,19 @@ export const SketchCard = ({
2526
},
2627
closeDialog,
2728
}: SketchCardProps) => {
28-
const setActiveSketchId = useSetActiveSketchId()
29-
const addSketch = useEngineStore((state) => state.addSketch)
29+
const setSelectedSketchId = useSetSelectedSketchId()
30+
const selectedSceneId = useAppStore((state) => state.selectedSceneId)
31+
const addSketchToScene = useEngineStore((state) => state.addSketchToScene)
3032

3133
const onButtonClick = useCallback(() => {
32-
const id = addSketch(moduleId)
33-
setActiveSketchId(id)
34+
if (!selectedSceneId) {
35+
return
36+
}
37+
38+
const id = addSketchToScene(selectedSceneId, moduleId)
39+
setSelectedSketchId(id)
3440
closeDialog()
35-
}, [addSketch, closeDialog, moduleId, setActiveSketchId])
41+
}, [selectedSceneId, addSketchToScene, closeDialog, moduleId, setSelectedSketchId])
3642

3743
const numParams = nodes.filter((n) => n.nodeType === 'param').length
3844
const numShots = nodes.filter((n) => n.nodeType === 'shot').length
@@ -51,7 +57,7 @@ export const SketchCard = ({
5157
)}
5258
</CardContent>
5359
<CardActions>
54-
<Button onClick={onButtonClick} iconName="add">
60+
<Button onClick={onButtonClick} iconName="add" disabled={!selectedSceneId}>
5561
Add To Scene
5662
</Button>
5763
</CardActions>

apps/desktop/src/renderer/components/GlobalDialogs/SketchModulesDialog/SketchModulesDialog.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import { useSketchesSearch } from './useSketchesSearch'
55
import { SketchCard } from './SketchCard'
66
import { GlobalDialogProps } from '@components/GlobalDialogs/types'
77
import { useSketchModuleList } from '@components/hooks/useSketchModuleList'
8-
import { useSetActiveSketchId } from '@components/hooks/useSetActiveSketchId'
8+
import { useSetSelectedSketchId } from '@components/hooks/useSetSelectedSketchId'
9+
import { useAppStore } from '@renderer/appStore'
910

1011
export const SketchModulesDialog = ({ closeDialog }: GlobalDialogProps) => {
1112
const sketchModules = useSketchModuleList()
1213
const { searchTerm, setSearchTerm, filteredModules } = useSketchesSearch(sketchModules)
1314
const inputRef = useRef<HTMLInputElement>(null)
14-
const setActiveSketchId = useSetActiveSketchId()
15-
const addSketch = useEngineStore((state) => state.addSketch)
15+
const setSelectedSketchId = useSetSelectedSketchId()
16+
const selectedSceneId = useAppStore((state) => state.selectedSceneId)
17+
const addSketchToScene = useEngineStore((state) => state.addSketchToScene)
1618

1719
useEffect(() => {
1820
// Focus the search input when dialog opens
@@ -23,10 +25,10 @@ export const SketchModulesDialog = ({ closeDialog }: GlobalDialogProps) => {
2325
}, [])
2426

2527
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
26-
if (e.key === 'Enter' && filteredModules.length > 0) {
28+
if (e.key === 'Enter' && filteredModules.length > 0 && selectedSceneId) {
2729
const top = filteredModules[0]
28-
const id = addSketch(top.moduleId)
29-
setActiveSketchId(id)
30+
const id = addSketchToScene(selectedSceneId, top.moduleId)
31+
setSelectedSketchId(id)
3032
closeDialog()
3133
}
3234
}

0 commit comments

Comments
 (0)