From bc8ee0584460a1cca1168e5ab82b7affc4fcd290 Mon Sep 17 00:00:00 2001 From: codetypes Date: Fri, 21 Mar 2025 09:44:07 +0800 Subject: [PATCH] new style layout --- public/icons/Action.svg | 2 +- public/icons/Condition.svg | 2 +- public/icons/Decorator.svg | 2 +- sample/vars/declare-core.json | 11 ++- src/behavior3 | 2 +- src/components/editor.tsx | 8 +- src/components/register-node.ts | 144 ++++++++++++++++++++---------- src/contexts/workspace-context.ts | 1 + 8 files changed, 116 insertions(+), 56 deletions(-) diff --git a/public/icons/Action.svg b/public/icons/Action.svg index 3e6376e..1acff76 100644 --- a/public/icons/Action.svg +++ b/public/icons/Action.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/icons/Condition.svg b/public/icons/Condition.svg index 01a615c..57552db 100644 --- a/public/icons/Condition.svg +++ b/public/icons/Condition.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/icons/Decorator.svg b/public/icons/Decorator.svg index c6fafe1..4b10007 100644 --- a/public/icons/Decorator.svg +++ b/public/icons/Decorator.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/sample/vars/declare-core.json b/sample/vars/declare-core.json index 47f1701..3be9dcd 100644 --- a/sample/vars/declare-core.json +++ b/sample/vars/declare-core.json @@ -1,10 +1,11 @@ { + "version": "1.8.0", "name": "declare-core", "desc": "", "firstid": 1, "export": true, - "import": [], "group": [], + "import": [], "declvar": [ { "name": "context", @@ -17,6 +18,12 @@ ], "root": { "id": 1, - "name": "Sequence" + "name": "Wait", + "args": { + "time": 1 + }, + "input": [ + "version" + ] } } \ No newline at end of file diff --git a/src/behavior3 b/src/behavior3 index 3322396..14563ec 160000 --- a/src/behavior3 +++ b/src/behavior3 @@ -1 +1 @@ -Subproject commit 3322396a42af40140bd75a43352fa0c81505967a +Subproject commit 14563ec920d6463b1a2ba87202a07c1630911426 diff --git a/src/components/editor.tsx b/src/components/editor.tsx index 6d6e5c0..ec66b48 100644 --- a/src/components/editor.tsx +++ b/src/components/editor.tsx @@ -943,9 +943,13 @@ export const Editor: FC = ({ onUpdate: updateState, data: editor, . layout: { type: "compactBox", direction: "LR", - getHGap: () => 60, + getHGap: () => 30, getWidth: (d: TreeGraphData) => { - return 160; + if (d.size) { + return d.size[0]; + } else { + return 200; + } }, getHeight: (d: TreeGraphData) => { if (d.size) { diff --git a/src/components/register-node.ts b/src/components/register-node.ts index a4b2170..0c77f0b 100644 --- a/src/components/register-node.ts +++ b/src/components/register-node.ts @@ -4,11 +4,11 @@ import { useWorkspace } from "../contexts/workspace-context"; import { TreeGraphData, getNodeType, isExprType } from "../misc/b3type"; import { checkTreeData, nodeDefs, parseExpr, usingGroups, usingVars } from "../misc/b3util"; import i18n from "../misc/i18n"; -import { isMacos } from "../misc/keys"; let ctx: CanvasRenderingContext2D | null = null; let defaultFontSize = ""; const textWidthMap = new Map(); +const textLines: Record = {}; const calcTextWith = (text: string, fontSize?: string) => { fontSize = fontSize ?? defaultFontSize; @@ -30,45 +30,68 @@ const calcTextWith = (text: string, fontSize?: string) => { css ||= getComputedStyle(b3Workspace); ctx = ctx || document.createElement("canvas").getContext("2d")!; ctx.font = `${fontSize} ${css.fontFamily}`; + ctx.wordSpacing = "0px"; + ctx.letterSpacing = "-0.5px"; const metrics = ctx.measureText(text); width = metrics.width; - width = width - (isMacos ? 1.6 : 0.8); + // width = width - (isMacos ? 1.6 : 0.8); + // width *= isMacos ? 0.88 : 0.95; textWidthMap.set(key, width); } } return width ?? 13; }; -const cutWordTo = (str: string, maxWidth: number, fontSize?: string) => { - let i = 0; - for (; i < str.length; i++) { - maxWidth -= calcTextWith(str.charAt(i), fontSize); - if (maxWidth < 0) { - i--; - break; +const calcTextLines = (str: string, maxWidth: number, fontSize?: string) => { + const key = `${str}-${maxWidth}-${fontSize}`; + let lines = textLines[key]; + if (!lines) { + lines = []; + textLines[key] = lines; + while (str.length > 0) { + let left = 0; + let right = str.length; + let mid = 0; + let width = 0; + + while (left < right) { + mid = Math.floor((left + right + 1) / 2); + const substr = str.substring(0, mid); + width = calcTextWith(substr, fontSize); + + if (width <= maxWidth) { + left = mid; + } else { + right = mid - 1; + } + } + + if (left > 0) { + lines.push(str.substring(0, left)); + str = str.substring(left); + } else { + // Handle case where even single character exceeds maxWidth + lines.push(str.substring(0, 1)); + str = str.substring(1); + } } } - return str.slice(0, i) + (i < str.length - 1 ? "..." : ""); + return lines; +}; + +const cutWordTo = (str: string, maxWidth: number, fontSize?: string) => { + const lines = calcTextLines(str, maxWidth, fontSize); + if (lines.length > 1) { + return lines[0].slice(0, -1) + "..."; + } + return lines[0]; }; const toBreakWord = (str: string, maxWidth: number, fontSize?: string) => { - const chars: string[] = []; - let line = str.length > 0 ? 1 : 0; - let width = maxWidth; - for (let i = 0; i < str.length; i++) { - width -= calcTextWith(str.charAt(i), fontSize); - if (width > 0) { - chars.push(str.charAt(i)); - } else { - width = maxWidth; - line++; - chars.push("\n"); - i--; - } - } + const lines = calcTextLines(str, maxWidth, fontSize); return { - str: chars.join(""), - line, + str: lines.join("\n"), + line: lines.length, }; }; @@ -120,7 +143,7 @@ export const calcTreeDataSize = (data: TreeGraphData) => { updateHeight(data.args); updateHeight(data.input); updateHeight(data.output); - return [220, height]; + return [260, height]; }; const NODE_COLORS = { @@ -193,7 +216,7 @@ G6.registerNode( classify = "Error"; color = NODE_COLORS[classify]; } - const size = data.size ? data.size : [150, 40]; + const size = data.size ? data.size : [200, 40]; const w = size[0]; const h = size[1]; const r = 4; @@ -248,6 +271,18 @@ G6.registerNode( true ); + // name line + addShape("path", { + attrs: { + path: [ + ["M", 46, 23], + ["L", w - 40, 23], + ], + stroke: "#666", + lineWidth: 1, + }, + }); + // is subtree if (data.path && data.id !== "1") { addShape("rect", { @@ -271,10 +306,10 @@ G6.registerNode( attrs: { x: 0, y: 0, - width: w, - height: 25, + width: 40, + height: h, fill: color, - radius: [r, r, 0, 0], + radius: [r, 0, 0, r], }, name: "name-bg", draggable: true, @@ -304,9 +339,9 @@ G6.registerNode( addShape("image", { attrs: { x: 5, - y: 3, - height: 18, - width: 18, + y: h / 2 - 16, + height: 30, + width: 30, img, }, name: "node-icon", @@ -316,10 +351,10 @@ G6.registerNode( const status = ((data.status ?? 0) & 0b111).toString(2).padStart(3, "0"); addShape("image", { attrs: { - x: 204, + x: w - 18, y: 3, - height: 18, - width: 18, + height: 20, + width: 20, img: `./icons/status${status}.svg`, }, name: "status-icon", @@ -329,12 +364,12 @@ G6.registerNode( addShape("text", { attrs: { textBaseline: "top", - x: 26, + x: 46, y: 5, - fontWeight: 800, + fontWeight: 900, text: data.name, fill: textColor, - fontSize: 13, + fontSize: 14, }, name: "name-text", }); @@ -343,7 +378,7 @@ G6.registerNode( if (data.debug) { addShape("image", { attrs: { - x: 192, + x: w - 30, y: 4, height: 16, width: 16, @@ -356,7 +391,7 @@ G6.registerNode( if (data.disabled) { addShape("image", { attrs: { - x: 200 - (data.debug ? 25 : 8), + x: w - 30 - (data.debug ? 18 : 0), y: 4, height: 16, width: 16, @@ -366,13 +401,13 @@ G6.registerNode( }); } - const x = 6; + const x = 46; let y = 32; // desc text let desc = (data.desc || nodeDef.desc) as string; if (desc || desc === "") { desc = i18n.t("regnode.mark") + desc; - desc = cutWordTo(desc, w - 15); + desc = cutWordTo(desc, w - 40 - 15); addShape("text", { attrs: { textBaseline: "top", @@ -395,7 +430,7 @@ G6.registerNode( attrs: { x: x - 2, y: y + 17, - width: w - 6, + width: w - 40 - 6, height: 18, fill: "#0d1117", radius: [r, r, r, r], @@ -430,7 +465,7 @@ G6.registerNode( attrs: { x: x - 2, y: y + 17, - width: w - 6, + width: w - 40 - 6, height: 18, fill: "#0d1117", radius: [r, r, r, r], @@ -468,7 +503,7 @@ G6.registerNode( attrs: { x: x - 2, y: y + 17, - width: w - 6, + width: w - 40 - 6, height: 18, fill: "#0d1117", radius: [r, r, r, r], @@ -497,7 +532,7 @@ G6.registerNode( if (data.path) { let path = (i18n.t("regnode.subtree") + data.path) as string; - path = cutWordTo(path, w - 15); + path = cutWordTo(path, w - 40 - 15); addShape("text", { attrs: { textBaseline: "top", @@ -578,6 +613,19 @@ G6.registerNode( }); } + // restore stroke color and lineWidth + // after lose focus, the main box stroke and color is not the node color + addShape("path", { + attrs: { + path: [ + ["M", 0, 0], + ["L", 0, 0], + ], + stroke: color, + lineWidth: 2, + }, + }); + return shape; }, }, diff --git a/src/contexts/workspace-context.ts b/src/contexts/workspace-context.ts index 7c888ec..b315f39 100644 --- a/src/contexts/workspace-context.ts +++ b/src/contexts/workspace-context.ts @@ -515,6 +515,7 @@ export const useWorkspace = create((set, get) => ({ } set({ editing: editor, editingNode: null, editingTree: null }); if (editor) { + workspace.refresh(editor.path); if (!editor.editNode) { workspace.onEditingTree(editor); } else if (typeof editor.editNode === "object") {