diff --git a/docs/visual-editor/setup.md b/docs/visual-editor/setup.md index e15f5560..bdb208bb 100644 --- a/docs/visual-editor/setup.md +++ b/docs/visual-editor/setup.md @@ -71,29 +71,4 @@ const baklava = useBaklava(); baklava.settings.displayValueOnHover = true; ``` -Available settings are: - -```ts -interface IViewSettings { - /** Use straight connections instead of bezier curves */ - useStraightConnections: boolean; - /** Show a minimap */ - enableMinimap: boolean; - /** Background settings */ - background: { - gridSize: number; - gridDivision: number; - subGridVisibleThreshold: number; - }; - /** Sidebar settings */ - sidebar: { - /** Width of the sidebar in pixels */ - width: number; - /** Whether users should be able to resize the sidebar */ - resizable: boolean; - }; - /** Show interface value on port hover */ - displayValueOnHover: boolean; -} -``` - +You can find all available settings here. diff --git a/packages/renderer-vue/playground/App.vue b/packages/renderer-vue/playground/App.vue index ee24b827..8bcd00c1 100644 --- a/packages/renderer-vue/playground/App.vue +++ b/packages/renderer-vue/playground/App.vue @@ -51,6 +51,7 @@ const editor = baklavaView.editor; baklavaView.settings.enableMinimap = true; baklavaView.settings.sidebar.resizable = false; baklavaView.settings.displayValueOnHover = true; +baklavaView.settings.nodes.resizable = true; const engine = new DependencyEngine(editor); engine.events.afterRun.subscribe(token, (r) => { engine.pause(); diff --git a/packages/renderer-vue/src/components/Minimap.vue b/packages/renderer-vue/src/components/Minimap.vue index e2fa2df6..768f385c 100644 --- a/packages/renderer-vue/src/components/Minimap.vue +++ b/packages/renderer-vue/src/components/Minimap.vue @@ -231,8 +231,9 @@ watch([showViewBounds, graph.value.panning, () => graph.value.scaling, () => gra }); const nodePositions = computed(() => graph.value.nodes.map((n) => n.position)); +const nodeSizes = computed(() => graph.value.nodes.map((n) => n.width)); watch( - nodePositions, + [nodePositions, nodeSizes], () => { updateCanvas(); }, diff --git a/packages/renderer-vue/src/graph/createSubgraph.command.ts b/packages/renderer-vue/src/graph/createSubgraph.command.ts index c8318398..98336b1a 100644 --- a/packages/renderer-vue/src/graph/createSubgraph.command.ts +++ b/packages/renderer-vue/src/graph/createSubgraph.command.ts @@ -11,10 +11,11 @@ import { import { v4 as uuidv4 } from "uuid"; import { reactive, Ref } from "vue"; import type { ICommand, ICommandHandler } from "../commands"; +import { useViewModel } from "../utility"; +import { IViewNodeState } from "../node/viewNode"; import { SaveSubgraphCommand, SAVE_SUBGRAPH_COMMAND } from "./saveSubgraph.command"; import type { SwitchGraph } from "./switchGraph"; import { SubgraphInputNode, SubgraphOutputNode } from "./subgraphInterfaceNodes"; -import { IViewNodeState } from "../node/viewNode"; export const CREATE_SUBGRAPH_COMMAND = "CREATE_SUBGRAPH"; export type CreateSubgraphCommand = ICommand; @@ -31,6 +32,7 @@ export function registerCreateSubgraphCommand( }; const createSubgraph = () => { + const { viewModel } = useViewModel(); const graph = displayedGraph.value; const editor = displayedGraph.value.editor; @@ -68,7 +70,7 @@ export function registerCreateSubgraphCommand( inputNode.inputs.name.value = conn.to.name; nodeStates.push({ ...inputNode.save(), - position: { x: xRight - 300, y: yTop + idx * 200 }, + position: { x: xRight - viewModel.value.settings.nodes.defaultWidth - 100, y: yTop + idx * 200 }, } as Omit); connectionStates.push({ id: uuidv4(), from: inputNode.outputs.placeholder.id, to: conn.to.id }); interfaceIdMap.set(conn.to.id, inputNode.graphInterfaceId); diff --git a/packages/renderer-vue/src/node/Node.vue b/packages/renderer-vue/src/node/Node.vue index ab3c1ea3..d7688a72 100644 --- a/packages/renderer-vue/src/node/Node.vue +++ b/packages/renderer-vue/src/node/Node.vue @@ -8,6 +8,8 @@ :data-node-type="node.type" @pointerdown="select" > +
+
diff --git a/packages/renderer-vue/src/node/viewNode.ts b/packages/renderer-vue/src/node/viewNode.ts index 21afaed8..54d9120e 100644 --- a/packages/renderer-vue/src/node/viewNode.ts +++ b/packages/renderer-vue/src/node/viewNode.ts @@ -6,9 +6,13 @@ export interface IViewNodeState extends INodeState { twoColumn: boolean; } -export function setViewNodeProperties(node: AbstractNode) { +export interface SetViewNodePropertiesSettings { + defaultWidth: number; +} + +export function setViewNodeProperties(node: AbstractNode, settings: SetViewNodePropertiesSettings) { node.position = node.position ?? { x: 0, y: 0 }; node.disablePointerEvents = false; node.twoColumn = node.twoColumn ?? false; - node.width = node.width ?? 200; + node.width = node.width ?? settings.defaultWidth; } diff --git a/packages/renderer-vue/src/viewModel.ts b/packages/renderer-vue/src/viewModel.ts index accd7e55..bc1df656 100644 --- a/packages/renderer-vue/src/viewModel.ts +++ b/packages/renderer-vue/src/viewModel.ts @@ -31,8 +31,40 @@ export interface IViewSettings { }; /** Show interface value on port hover */ displayValueOnHover: boolean; + /** Node settings */ + nodes: { + /** Minimum width of a node */ + minWidth: number; + /** Maximum width of a node */ + maxWidth: number; + /** Default width of a node */ + defaultWidth: number; + /** Whether users should be able to resize nodes */ + resizable: boolean; + }; } +const DEFAULT_SETTINGS: () => IViewSettings = () => ({ + useStraightConnections: false, + enableMinimap: false, + background: { + gridSize: 100, + gridDivision: 5, + subGridVisibleThreshold: 0.6, + }, + sidebar: { + width: 300, + resizable: true, + }, + displayValueOnHover: false, + nodes: { + defaultWidth: 200, + maxWidth: 320, + minWidth: 150, + resizable: false, + }, +}); + export interface IBaklavaViewModel extends IBaklavaTapable { editor: Editor; /** Currently displayed graph */ @@ -62,20 +94,7 @@ export function useBaklava(existingEditor?: Editor): IBaklavaViewModel { const isSubgraph = computed(() => displayedGraph.value && displayedGraph.value !== editor.value.graph); - const settings: IViewSettings = reactive({ - useStraightConnections: false, - enableMinimap: false, - background: { - gridSize: 100, - gridDivision: 5, - subGridVisibleThreshold: 0.6, - }, - sidebar: { - width: 300, - resizable: true, - }, - displayValueOnHover: false, - } satisfies IViewSettings); + const settings: IViewSettings = reactive(DEFAULT_SETTINGS()); const commandHandler = useCommandHandler(); const history = useHistory(displayedGraph, commandHandler); @@ -107,7 +126,7 @@ export function useBaklava(existingEditor?: Editor): IBaklavaViewModel { if (newValue) { newValue.nodeHooks.beforeLoad.subscribe(token, (state, node) => { node.position = (state as IViewNodeState).position ?? { x: 0, y: 0 }; - node.width = (state as IViewNodeState).width ?? 200; + node.width = (state as IViewNodeState).width ?? settings.nodes.defaultWidth; node.twoColumn = (state as IViewNodeState).twoColumn ?? false; return state; }); @@ -138,7 +157,9 @@ export function useBaklava(existingEditor?: Editor): IBaklavaViewModel { return state; }); - newValue.graphEvents.beforeAddNode.subscribe(token, (node) => setViewNodeProperties(node)); + newValue.graphEvents.beforeAddNode.subscribe(token, (node) => + setViewNodeProperties(node, { defaultWidth: settings.nodes.defaultWidth }), + ); editor.value.registerNodeType(SubgraphInputNode, { category: "Subgraphs" }); editor.value.registerNodeType(SubgraphOutputNode, { category: "Subgraphs" }); diff --git a/packages/themes/src/classic/components/node.scss b/packages/themes/src/classic/components/node.scss index 71395da9..77c90a86 100644 --- a/packages/themes/src/classic/components/node.scss +++ b/packages/themes/src/classic/components/node.scss @@ -1,15 +1,22 @@ .baklava-node { - max-width: 20rem; background: var(--baklava-node-color-background); color: var(--baklava-node-color-foreground); border: 1px solid transparent; border-radius: var(--baklava-node-border-radius); position: absolute; box-shadow: 0 0 4px #000000cc; - transition: border-color var(--baklava-visual-transition), box-shadow var(--baklava-visual-transition); + transition: + border-color var(--baklava-visual-transition), + box-shadow var(--baklava-visual-transition); + + width: var(--width); &:hover { border-color: var(--baklava-node-color-hover); + + & .__resize-handle::after { + opacity: 1; + } } &.--selected { @@ -76,4 +83,41 @@ grid-column: 2; } } + + & .__resize-handle { + position: absolute; + right: 0; + bottom: 0; + width: 1rem; + height: 1rem; + transform: translateX(50%); + cursor: ew-resize; + + &::after { + content: ""; + position: absolute; + bottom: 0; + left: -0.5rem; + width: 1rem; + height: 1rem; + opacity: 0; + border-bottom-right-radius: var(--baklava-node-border-radius); + transition: opacity var(--baklava-visual-transition); + background: linear-gradient( + -45deg, + transparent 10%, + var(--baklava-node-color-resize-handle) 10%, + var(--baklava-node-color-resize-handle) 15%, + transparent 15%, + transparent 30%, + var(--baklava-node-color-resize-handle) 30%, + var(--baklava-node-color-resize-handle) 35%, + transparent 35%, + transparent 50%, + var(--baklava-node-color-resize-handle) 50%, + var(--baklava-node-color-resize-handle) 55%, + transparent 55% + ); + } + } } diff --git a/packages/themes/src/classic/variables.scss b/packages/themes/src/classic/variables.scss index cf8bbdf7..17e79a3b 100644 --- a/packages/themes/src/classic/variables.scss +++ b/packages/themes/src/classic/variables.scss @@ -18,6 +18,7 @@ --baklava-node-color-foreground: white; --baklava-node-color-hover: #5379b577; --baklava-node-color-selected: var(--baklava-control-color-primary); + --baklava-node-color-resize-handle: var(--baklava-control-color-background); --baklava-node-title-color-background: black; --baklava-node-title-color-foreground: white; --baklava-group-node-title-color-background: rgb(5, 75, 5); diff --git a/packages/themes/src/syrup-dark/variables.scss b/packages/themes/src/syrup-dark/variables.scss index 8c45d1f1..761bdabe 100644 --- a/packages/themes/src/syrup-dark/variables.scss +++ b/packages/themes/src/syrup-dark/variables.scss @@ -18,6 +18,7 @@ --baklava-node-color-foreground: white; --baklava-node-color-hover: #e28c4677; --baklava-node-color-selected: var(--baklava-control-color-primary); + --baklava-node-color-resize-handle: var(--baklava-control-color-background); --baklava-node-title-color-background: #151a24; --baklava-node-title-color-foreground: white; --baklava-group-node-title-color-background: #215636;