Fix the Inspector hit test at canvas zooms other than 100%
Over a DOM widget such as a prompt text box, node_over is empty and the Inspector falls back to its own hit test. That divided the offset by the scale, so it missed at any zoom but 100%. It now uses LiteGraph's ds.convertCanvasToOffset, and the try/catch that hid this is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bf4aecb581
commit
95060a4f3f
+1
-1
@@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- **Nodes 2.0 (Vue nodes)**: The glass shows the nodes, painted from the page: boxes, borders, text, widget values and prompts (icons are left out). The Inspector finds the node under the cursor, also in pan mode, and the highlight fits collapsed nodes. Turning Nodes 2.0 on and off no longer makes the highlight recurse without end.
|
||||
- **Node Title Emphasis** sits on the title bar instead of the first slot row, and is not drawn on nodes without a title bar such as Reroute.
|
||||
- The glass background matches the page past the canvas edge and at canvas zoom of 1.5 and above.
|
||||
- **Subgraphs**: The panel, picker, highlight and editing use the graph on screen, so they work inside subgraphs. Title bars and collapsed nodes are hit.
|
||||
- **Subgraphs**: The panel, picker, highlight and editing use the graph on screen, so they work inside subgraphs. Title bars and collapsed nodes are hit. The fallback hit test converts the pointer the way LiteGraph draws, so prompt text boxes and other DOM widgets find their node at any canvas zoom.
|
||||
- **Widget edits** run the widget's callback, redraw the canvas and record undo. CLIPLoader's `type` widget shows again. The frontend's internal `$$` preview widgets (e.g. on Load Image) are no longer listed.
|
||||
- Dropdowns no longer get orphaned or keep writing after the panel re-renders, and they can be filtered by typing.
|
||||
- The panel re-renders only when the shown node changes, so hover, focus and text selection survive.
|
||||
|
||||
@@ -37,28 +37,11 @@ export class InformationGatherer {
|
||||
return app.canvas.node_over;
|
||||
}
|
||||
|
||||
try {
|
||||
const canvasRect = app.canvas.canvas.getBoundingClientRect();
|
||||
const mouseX = magnifyGlass.lastKnownMousePosition.x - canvasRect.left;
|
||||
const mouseY = magnifyGlass.lastKnownMousePosition.y - canvasRect.top;
|
||||
|
||||
let graphPos: [number, number];
|
||||
if (app.canvas.convertOffsetToCanvasPos) {
|
||||
graphPos = app.canvas.convertOffsetToCanvasPos([mouseX, mouseY]);
|
||||
} else {
|
||||
const ds = app.canvas.ds || { scale: 1, offset: [0, 0] };
|
||||
graphPos = [
|
||||
(mouseX / ds.scale) - (ds.offset[0] / ds.scale),
|
||||
(mouseY / ds.scale) - (ds.offset[1] / ds.scale)
|
||||
];
|
||||
}
|
||||
|
||||
return graph.getNodeOnPos(graphPos[0], graphPos[1]) ?? null;
|
||||
} catch (err) {
|
||||
console.warn("Error in node detection:", err);
|
||||
}
|
||||
|
||||
return null;
|
||||
const canvasRect = app.canvas.canvas.getBoundingClientRect();
|
||||
const { x, y } = magnifyGlass.lastKnownMousePosition;
|
||||
// LiteGraph draws a graph position at (pos + offset) * scale from the canvas corner
|
||||
const [graphX, graphY] = app.canvas.ds.convertCanvasToOffset([x - canvasRect.left, y - canvasRect.top]);
|
||||
return graph.getNodeOnPos(graphX, graphY) ?? null;
|
||||
}
|
||||
|
||||
getDetailedNodeInfo(node: ComfyNode): NodeInfo {
|
||||
|
||||
Vendored
+2
-1
@@ -237,12 +237,13 @@ export interface ComfyCanvas {
|
||||
ds: {
|
||||
scale: number;
|
||||
offset: [number, number];
|
||||
/** A position on the canvas element to graph coordinates */
|
||||
convertCanvasToOffset(pos: [number, number]): [number, number];
|
||||
};
|
||||
node_over: ComfyNode | null;
|
||||
/** True in pan mode: space held or the hand tool */
|
||||
read_only: boolean;
|
||||
selected_nodes: Record<string, ComfyNode>;
|
||||
convertOffsetToCanvasPos?(pos: [number, number]): [number, number];
|
||||
centerOnNode(node: ComfyNode): void;
|
||||
openSubgraph(subgraph: ComfyGraph, fromNode: ComfyNode): void;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,8 @@ describe('Info Panel lifecycle', () => {
|
||||
// Node's own localStorage global hides happy-dom's
|
||||
vi.stubGlobal('localStorage', new Storage());
|
||||
addSetting.mockClear();
|
||||
(window as any).app = { canvas: { onDrawForeground: null, setDirty: vi.fn() }, graph: null };
|
||||
const ds = { convertCanvasToOffset: (pos: [number, number]) => pos };
|
||||
(window as any).app = { canvas: { canvas: document.createElement('canvas'), ds, onDrawForeground: null, setDirty: vi.fn() }, graph: null };
|
||||
mg = new FakeGlass();
|
||||
});
|
||||
|
||||
@@ -996,7 +997,7 @@ describe('Info Panel lifecycle', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
nodes = [makeNode(1, 'Loader'), makeNode(2, 'Sampler')];
|
||||
const graph = { _nodes: nodes, getNodeById: (id: number) => nodes.find(n => n.id === id) ?? null };
|
||||
const graph = { _nodes: nodes, getNodeById: (id: number) => nodes.find(n => n.id === id) ?? null, getNodeOnPos: () => null };
|
||||
Object.assign((window as any).app.canvas, { graph, node_over: nodes[0], selected_nodes: {}, centerOnNode: vi.fn() });
|
||||
(mg as any).state = { active: true, x: 0, y: 0, canvasScale: 1 };
|
||||
(mg as any).config = { showCursorPreview: false };
|
||||
@@ -1204,7 +1205,7 @@ describe('Info Panel lifecycle', () => {
|
||||
panelManager().updateInfo();
|
||||
expect(shown()).toBe('Loader (undone)');
|
||||
|
||||
canvas.graph = { _nodes: [], getNodeById: () => nodes[0] };
|
||||
canvas.graph = { _nodes: [], getNodeById: () => nodes[0], getNodeOnPos: () => null };
|
||||
panelManager().updateInfo();
|
||||
expect(state().currentNodeId).toBeNull();
|
||||
});
|
||||
|
||||
@@ -17,7 +17,11 @@ const mockApp: any = {
|
||||
getBoundingClientRect: () => ({ left: 0, top: 0, width: 800, height: 600, right: 800, bottom: 600 })
|
||||
},
|
||||
node_over: null,
|
||||
ds: { scale: 1, offset: [0, 0] }
|
||||
ds: {
|
||||
scale: 1,
|
||||
offset: [0, 0],
|
||||
convertCanvasToOffset(pos: number[]) { return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,6 +82,12 @@ describe('InformationGatherer', () => {
|
||||
|
||||
expect(getNodeOnPos).toHaveBeenCalledWith(200, 200);
|
||||
expect(hit).toBe(node);
|
||||
|
||||
// Zoomed and panned: graph = canvas / scale - offset, as LiteGraph draws (pos + offset) * scale
|
||||
Object.assign(mockApp.canvas.ds, { scale: 2, offset: [10, 20] });
|
||||
gatherer.getNodeUnderCursor();
|
||||
expect(getNodeOnPos).toHaveBeenLastCalledWith(90, 80);
|
||||
Object.assign(mockApp.canvas.ds, { scale: 1, offset: [0, 0] });
|
||||
mockApp.canvas.graph = canvasGraph;
|
||||
});
|
||||
|
||||
|
||||
@@ -9,32 +9,17 @@ class InformationGatherer {
|
||||
return null;
|
||||
}
|
||||
if (((_b = window.LiteGraph) == null ? void 0 : _b.vueNodesMode) && !app.canvas.read_only) {
|
||||
const { x, y } = magnifyGlass.lastKnownMousePosition;
|
||||
const nodeEl = document.elementsFromPoint(x, y).map((el) => el.closest(".lg-node[data-node-id]")).find(Boolean);
|
||||
const { x: x2, y: y2 } = magnifyGlass.lastKnownMousePosition;
|
||||
const nodeEl = document.elementsFromPoint(x2, y2).map((el) => el.closest(".lg-node[data-node-id]")).find(Boolean);
|
||||
return nodeEl ? graph.getNodeById(Number(nodeEl.dataset.nodeId)) ?? null : null;
|
||||
}
|
||||
if (app.canvas.node_over) {
|
||||
return app.canvas.node_over;
|
||||
}
|
||||
try {
|
||||
const canvasRect = app.canvas.canvas.getBoundingClientRect();
|
||||
const mouseX = magnifyGlass.lastKnownMousePosition.x - canvasRect.left;
|
||||
const mouseY = magnifyGlass.lastKnownMousePosition.y - canvasRect.top;
|
||||
let graphPos;
|
||||
if (app.canvas.convertOffsetToCanvasPos) {
|
||||
graphPos = app.canvas.convertOffsetToCanvasPos([mouseX, mouseY]);
|
||||
} else {
|
||||
const ds = app.canvas.ds || { scale: 1, offset: [0, 0] };
|
||||
graphPos = [
|
||||
mouseX / ds.scale - ds.offset[0] / ds.scale,
|
||||
mouseY / ds.scale - ds.offset[1] / ds.scale
|
||||
];
|
||||
}
|
||||
return graph.getNodeOnPos(graphPos[0], graphPos[1]) ?? null;
|
||||
} catch (err) {
|
||||
console.warn("Error in node detection:", err);
|
||||
}
|
||||
return null;
|
||||
const canvasRect = app.canvas.canvas.getBoundingClientRect();
|
||||
const { x, y } = magnifyGlass.lastKnownMousePosition;
|
||||
const [graphX, graphY] = app.canvas.ds.convertCanvasToOffset([x - canvasRect.left, y - canvasRect.top]);
|
||||
return graph.getNodeOnPos(graphX, graphY) ?? null;
|
||||
}
|
||||
getDetailedNodeInfo(node) {
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
|
||||
Reference in New Issue
Block a user