mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-26 09:44:06 +00:00
Allow node resize from any corner or edge (#1063)
This commit is contained in:
164
test/LGraphNode.resize.test.ts
Normal file
164
test/LGraphNode.resize.test.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { beforeEach, describe, expect } from "vitest"
|
||||
|
||||
import { LGraphNode } from "@/LGraphNode"
|
||||
import { LiteGraph } from "@/litegraph"
|
||||
|
||||
import { test } from "./testExtensions"
|
||||
|
||||
describe("LGraphNode resize functionality", () => {
|
||||
let node: LGraphNode
|
||||
|
||||
beforeEach(() => {
|
||||
// Set up LiteGraph constants needed for measure
|
||||
LiteGraph.NODE_TITLE_HEIGHT = 20
|
||||
|
||||
node = new LGraphNode("Test Node")
|
||||
node.pos = [100, 100]
|
||||
node.size = [200, 150]
|
||||
|
||||
// Create a mock canvas context for updateArea
|
||||
const mockCtx = {} as CanvasRenderingContext2D
|
||||
|
||||
// Call updateArea to populate boundingRect
|
||||
node.updateArea(mockCtx)
|
||||
})
|
||||
|
||||
describe("findResizeDirection", () => {
|
||||
describe("corners", () => {
|
||||
test("should detect NW (top-left) corner", () => {
|
||||
// With title bar, top is at y=80 (100 - 20)
|
||||
// Corner is from (100, 80) to (100 + 15, 80 + 15)
|
||||
expect(node.findResizeDirection(100, 80)).toBe("NW")
|
||||
expect(node.findResizeDirection(110, 90)).toBe("NW")
|
||||
expect(node.findResizeDirection(114, 94)).toBe("NW")
|
||||
})
|
||||
|
||||
test("should detect NE (top-right) corner", () => {
|
||||
// Corner is from (300 - 15, 80) to (300, 80 + 15)
|
||||
expect(node.findResizeDirection(285, 80)).toBe("NE")
|
||||
expect(node.findResizeDirection(290, 90)).toBe("NE")
|
||||
expect(node.findResizeDirection(299, 94)).toBe("NE")
|
||||
})
|
||||
|
||||
test("should detect SW (bottom-left) corner", () => {
|
||||
// Bottom is at y=250 (100 + 150)
|
||||
// Corner is from (100, 250 - 15) to (100 + 15, 250)
|
||||
expect(node.findResizeDirection(100, 235)).toBe("SW")
|
||||
expect(node.findResizeDirection(110, 240)).toBe("SW")
|
||||
expect(node.findResizeDirection(114, 249)).toBe("SW")
|
||||
})
|
||||
|
||||
test("should detect SE (bottom-right) corner", () => {
|
||||
// Corner is from (300 - 15, 250 - 15) to (300, 250)
|
||||
expect(node.findResizeDirection(285, 235)).toBe("SE")
|
||||
expect(node.findResizeDirection(290, 240)).toBe("SE")
|
||||
expect(node.findResizeDirection(299, 249)).toBe("SE")
|
||||
})
|
||||
})
|
||||
|
||||
describe("edges", () => {
|
||||
test("should detect N (top) edge", () => {
|
||||
// Top edge at y=80, but not in corners
|
||||
expect(node.findResizeDirection(150, 80)).toBe("N")
|
||||
expect(node.findResizeDirection(150, 84)).toBe("N")
|
||||
expect(node.findResizeDirection(200, 80)).toBe("N")
|
||||
})
|
||||
|
||||
test("should detect S (bottom) edge", () => {
|
||||
// Bottom edge at y=250, but need to check within the 5px threshold
|
||||
expect(node.findResizeDirection(150, 249)).toBe("S")
|
||||
expect(node.findResizeDirection(150, 246)).toBe("S")
|
||||
expect(node.findResizeDirection(200, 247)).toBe("S")
|
||||
})
|
||||
|
||||
test("should detect W (left) edge", () => {
|
||||
// Left edge at x=100, but not in corners
|
||||
expect(node.findResizeDirection(100, 150)).toBe("W")
|
||||
expect(node.findResizeDirection(104, 150)).toBe("W")
|
||||
expect(node.findResizeDirection(100, 200)).toBe("W")
|
||||
})
|
||||
|
||||
test("should detect E (right) edge", () => {
|
||||
// Right edge at x=300, but need to check within the 5px threshold
|
||||
expect(node.findResizeDirection(299, 150)).toBe("E")
|
||||
expect(node.findResizeDirection(296, 150)).toBe("E")
|
||||
expect(node.findResizeDirection(298, 200)).toBe("E")
|
||||
})
|
||||
})
|
||||
|
||||
describe("priority", () => {
|
||||
test("corners should have priority over edges", () => {
|
||||
// These points are technically on both corner and edge
|
||||
// Corner should win
|
||||
expect(node.findResizeDirection(100, 84)).toBe("NW") // Not "W"
|
||||
expect(node.findResizeDirection(104, 80)).toBe("NW") // Not "N"
|
||||
})
|
||||
})
|
||||
|
||||
describe("negative cases", () => {
|
||||
test("should return undefined when outside node bounds", () => {
|
||||
expect(node.findResizeDirection(50, 50)).toBeUndefined()
|
||||
expect(node.findResizeDirection(350, 300)).toBeUndefined()
|
||||
expect(node.findResizeDirection(99, 150)).toBeUndefined()
|
||||
expect(node.findResizeDirection(301, 150)).toBeUndefined()
|
||||
})
|
||||
|
||||
test("should return undefined when inside node but not on resize areas", () => {
|
||||
// Center of node (accounting for title bar offset)
|
||||
expect(node.findResizeDirection(200, 165)).toBeUndefined()
|
||||
// Just inside the edge threshold
|
||||
expect(node.findResizeDirection(106, 150)).toBeUndefined()
|
||||
expect(node.findResizeDirection(294, 150)).toBeUndefined()
|
||||
expect(node.findResizeDirection(150, 86)).toBeUndefined() // 80 + 6
|
||||
expect(node.findResizeDirection(150, 244)).toBeUndefined()
|
||||
})
|
||||
|
||||
test("should return undefined when node is not resizable", () => {
|
||||
node.resizable = false
|
||||
expect(node.findResizeDirection(100, 100)).toBeUndefined()
|
||||
expect(node.findResizeDirection(300, 250)).toBeUndefined()
|
||||
expect(node.findResizeDirection(150, 100)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("edge cases", () => {
|
||||
test("should handle nodes at origin", () => {
|
||||
node.pos = [0, 0]
|
||||
node.size = [100, 100]
|
||||
|
||||
// Update boundingRect with new position/size
|
||||
const mockCtx = {} as CanvasRenderingContext2D
|
||||
node.updateArea(mockCtx)
|
||||
|
||||
expect(node.findResizeDirection(0, -20)).toBe("NW") // Account for title bar
|
||||
expect(node.findResizeDirection(99, 99)).toBe("SE") // Bottom-right corner (100-1, 100-1)
|
||||
expect(node.findResizeDirection(50, -20)).toBe("N")
|
||||
expect(node.findResizeDirection(0, 50)).toBe("W")
|
||||
})
|
||||
|
||||
test("should handle very small nodes", () => {
|
||||
node.size = [20, 20] // Smaller than corner size
|
||||
|
||||
// Update boundingRect with new size
|
||||
const mockCtx = {} as CanvasRenderingContext2D
|
||||
node.updateArea(mockCtx)
|
||||
|
||||
// Corners still work (accounting for title bar offset)
|
||||
expect(node.findResizeDirection(100, 80)).toBe("NW")
|
||||
expect(node.findResizeDirection(119, 119)).toBe("SE")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("resizeEdgeSize static property", () => {
|
||||
test("should have default value of 5", () => {
|
||||
expect(LGraphNode.resizeEdgeSize).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe("resizeHandleSize static property", () => {
|
||||
test("should have default value of 15", () => {
|
||||
expect(LGraphNode.resizeHandleSize).toBe(15)
|
||||
})
|
||||
})
|
||||
})
|
||||
144
test/infrastructure/Rectangle.resize.test.ts
Normal file
144
test/infrastructure/Rectangle.resize.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { beforeEach, describe, expect, test } from "vitest"
|
||||
|
||||
import { Rectangle } from "@/infrastructure/Rectangle"
|
||||
|
||||
describe("Rectangle resize functionality", () => {
|
||||
let rect: Rectangle
|
||||
|
||||
beforeEach(() => {
|
||||
rect = new Rectangle(100, 200, 300, 400) // x, y, width, height
|
||||
// So: left=100, top=200, right=400, bottom=600
|
||||
})
|
||||
|
||||
describe("findContainingCorner", () => {
|
||||
const cornerSize = 15
|
||||
|
||||
test("should detect NW (top-left) corner", () => {
|
||||
expect(rect.findContainingCorner(100, 200, cornerSize)).toBe("NW")
|
||||
expect(rect.findContainingCorner(110, 210, cornerSize)).toBe("NW")
|
||||
expect(rect.findContainingCorner(114, 214, cornerSize)).toBe("NW")
|
||||
})
|
||||
|
||||
test("should detect NE (top-right) corner", () => {
|
||||
// Top-right corner starts at (right - cornerSize, top) = (385, 200)
|
||||
expect(rect.findContainingCorner(385, 200, cornerSize)).toBe("NE")
|
||||
expect(rect.findContainingCorner(390, 210, cornerSize)).toBe("NE")
|
||||
expect(rect.findContainingCorner(399, 214, cornerSize)).toBe("NE")
|
||||
})
|
||||
|
||||
test("should detect SW (bottom-left) corner", () => {
|
||||
// Bottom-left corner starts at (left, bottom - cornerSize) = (100, 585)
|
||||
expect(rect.findContainingCorner(100, 585, cornerSize)).toBe("SW")
|
||||
expect(rect.findContainingCorner(110, 590, cornerSize)).toBe("SW")
|
||||
expect(rect.findContainingCorner(114, 599, cornerSize)).toBe("SW")
|
||||
})
|
||||
|
||||
test("should detect SE (bottom-right) corner", () => {
|
||||
// Bottom-right corner starts at (right - cornerSize, bottom - cornerSize) = (385, 585)
|
||||
expect(rect.findContainingCorner(385, 585, cornerSize)).toBe("SE")
|
||||
expect(rect.findContainingCorner(390, 590, cornerSize)).toBe("SE")
|
||||
expect(rect.findContainingCorner(399, 599, cornerSize)).toBe("SE")
|
||||
})
|
||||
|
||||
test("should return undefined when not in any corner", () => {
|
||||
// Middle of rectangle
|
||||
expect(rect.findContainingCorner(250, 400, cornerSize)).toBeUndefined()
|
||||
// On edge but not in corner
|
||||
expect(rect.findContainingCorner(200, 200, cornerSize)).toBeUndefined()
|
||||
expect(rect.findContainingCorner(100, 400, cornerSize)).toBeUndefined()
|
||||
// Outside rectangle
|
||||
expect(rect.findContainingCorner(50, 150, cornerSize)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("corner detection methods", () => {
|
||||
const cornerSize = 20
|
||||
|
||||
describe("isInTopLeftCorner", () => {
|
||||
test("should return true when point is in top-left corner", () => {
|
||||
expect(rect.isInTopLeftCorner(100, 200, cornerSize)).toBe(true)
|
||||
expect(rect.isInTopLeftCorner(110, 210, cornerSize)).toBe(true)
|
||||
expect(rect.isInTopLeftCorner(119, 219, cornerSize)).toBe(true)
|
||||
})
|
||||
|
||||
test("should return false when point is outside top-left corner", () => {
|
||||
expect(rect.isInTopLeftCorner(120, 200, cornerSize)).toBe(false)
|
||||
expect(rect.isInTopLeftCorner(100, 220, cornerSize)).toBe(false)
|
||||
expect(rect.isInTopLeftCorner(99, 200, cornerSize)).toBe(false)
|
||||
expect(rect.isInTopLeftCorner(100, 199, cornerSize)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isInTopRightCorner", () => {
|
||||
test("should return true when point is in top-right corner", () => {
|
||||
// Top-right corner area is from (right - cornerSize, top) to (right, top + cornerSize)
|
||||
// That's (380, 200) to (400, 220)
|
||||
expect(rect.isInTopRightCorner(380, 200, cornerSize)).toBe(true)
|
||||
expect(rect.isInTopRightCorner(390, 210, cornerSize)).toBe(true)
|
||||
expect(rect.isInTopRightCorner(399, 219, cornerSize)).toBe(true)
|
||||
})
|
||||
|
||||
test("should return false when point is outside top-right corner", () => {
|
||||
expect(rect.isInTopRightCorner(379, 200, cornerSize)).toBe(false)
|
||||
expect(rect.isInTopRightCorner(400, 220, cornerSize)).toBe(false)
|
||||
expect(rect.isInTopRightCorner(401, 200, cornerSize)).toBe(false)
|
||||
expect(rect.isInTopRightCorner(400, 199, cornerSize)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isInBottomLeftCorner", () => {
|
||||
test("should return true when point is in bottom-left corner", () => {
|
||||
// Bottom-left corner area is from (left, bottom - cornerSize) to (left + cornerSize, bottom)
|
||||
// That's (100, 580) to (120, 600)
|
||||
expect(rect.isInBottomLeftCorner(100, 580, cornerSize)).toBe(true)
|
||||
expect(rect.isInBottomLeftCorner(110, 590, cornerSize)).toBe(true)
|
||||
expect(rect.isInBottomLeftCorner(119, 599, cornerSize)).toBe(true)
|
||||
})
|
||||
|
||||
test("should return false when point is outside bottom-left corner", () => {
|
||||
expect(rect.isInBottomLeftCorner(120, 600, cornerSize)).toBe(false)
|
||||
expect(rect.isInBottomLeftCorner(100, 579, cornerSize)).toBe(false)
|
||||
expect(rect.isInBottomLeftCorner(99, 600, cornerSize)).toBe(false)
|
||||
expect(rect.isInBottomLeftCorner(100, 601, cornerSize)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isInBottomRightCorner", () => {
|
||||
test("should return true when point is in bottom-right corner", () => {
|
||||
// Bottom-right corner area is from (right - cornerSize, bottom - cornerSize) to (right, bottom)
|
||||
// That's (380, 580) to (400, 600)
|
||||
expect(rect.isInBottomRightCorner(380, 580, cornerSize)).toBe(true)
|
||||
expect(rect.isInBottomRightCorner(390, 590, cornerSize)).toBe(true)
|
||||
expect(rect.isInBottomRightCorner(399, 599, cornerSize)).toBe(true)
|
||||
})
|
||||
|
||||
test("should return false when point is outside bottom-right corner", () => {
|
||||
expect(rect.isInBottomRightCorner(379, 600, cornerSize)).toBe(false)
|
||||
expect(rect.isInBottomRightCorner(400, 579, cornerSize)).toBe(false)
|
||||
expect(rect.isInBottomRightCorner(401, 600, cornerSize)).toBe(false)
|
||||
expect(rect.isInBottomRightCorner(400, 601, cornerSize)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("edge cases", () => {
|
||||
test("should handle zero-sized corner areas", () => {
|
||||
expect(rect.findContainingCorner(100, 200, 0)).toBeUndefined()
|
||||
expect(rect.isInTopLeftCorner(100, 200, 0)).toBe(false)
|
||||
})
|
||||
|
||||
test("should handle rectangles at origin", () => {
|
||||
const originRect = new Rectangle(0, 0, 100, 100)
|
||||
expect(originRect.findContainingCorner(0, 0, 10)).toBe("NW")
|
||||
// Bottom-right corner is at (90, 90) to (100, 100)
|
||||
expect(originRect.findContainingCorner(90, 90, 10)).toBe("SE")
|
||||
})
|
||||
|
||||
test("should handle negative coordinates", () => {
|
||||
const negRect = new Rectangle(-50, -50, 100, 100)
|
||||
expect(negRect.findContainingCorner(-50, -50, 10)).toBe("NW")
|
||||
// Bottom-right corner is at (40, 40) to (50, 50)
|
||||
expect(negRect.findContainingCorner(40, 40, 10)).toBe("SE")
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user