Manage style of DOM widgets in Vue (#2946)

This commit is contained in:
Chenlei Hu
2025-03-09 16:51:42 -04:00
committed by GitHub
parent 97d9f90374
commit 3a0b337d0c
6 changed files with 339 additions and 167 deletions

View File

@@ -23,6 +23,12 @@ export function useAbsolutePosition() {
height: '0px'
})
/**
* Update the position of the element on the litegraph canvas.
*
* @param config
* @param extraStyle
*/
const updatePosition = (
config: PositionConfig,
extraStyle?: CSSProperties
@@ -41,8 +47,36 @@ export function useAbsolutePosition() {
}
}
/**
* Update the position and size of the element on the litegraph canvas,
* with CSS transform scaling applied.
*
* @param config
* @param extraStyle
*/
const updatePositionWithTransform = (
config: PositionConfig,
extraStyle?: CSSProperties
) => {
const { pos, size, scale = canvasStore.canvas?.ds?.scale ?? 1 } = config
const [left, top] = app.canvasPosToClientPos(pos)
const [width, height] = size
style.value = {
...style.value,
transformOrigin: '0 0',
transform: `scale(${scale})`,
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height}px`,
...extraStyle
}
}
return {
style,
updatePosition
updatePosition,
updatePositionWithTransform
}
}

View File

@@ -0,0 +1,123 @@
import { CSSProperties, ref } from 'vue'
interface Rect {
x: number
y: number
width: number
height: number
}
/**
* Finds the intersection between two rectangles
*/
function intersect(a: Rect, b: Rect): [number, number, number, number] | null {
const x1 = Math.max(a.x, b.x)
const y1 = Math.max(a.y, b.y)
const x2 = Math.min(a.x + a.width, b.x + b.width)
const y2 = Math.min(a.y + a.height, b.y + b.height)
if (x1 >= x2 || y1 >= y2) {
return null
}
return [x1, y1, x2 - x1, y2 - y1]
}
export interface ClippingOptions {
margin?: number
}
export const useDomClipping = (options: ClippingOptions = {}) => {
const style = ref<CSSProperties>({})
const { margin = 4 } = options
/**
* Calculates a clip path for an element based on its intersection with a selected area
*/
const calculateClipPath = (
elementRect: DOMRect,
canvasRect: DOMRect,
isSelected: boolean,
selectedArea?: {
x: number
y: number
width: number
height: number
scale: number
offset: [number, number]
}
): string => {
if (!isSelected && selectedArea) {
const { scale, offset } = selectedArea
// Get intersection in browser space
const intersection = intersect(
{
x: elementRect.left - canvasRect.left,
y: elementRect.top - canvasRect.top,
width: elementRect.width,
height: elementRect.height
},
{
x: (selectedArea.x + offset[0] - margin) * scale,
y: (selectedArea.y + offset[1] - margin) * scale,
width: (selectedArea.width + 2 * margin) * scale,
height: (selectedArea.height + 2 * margin) * scale
}
)
if (!intersection) {
return ''
}
// Convert intersection to canvas scale (element has scale transform)
const clipX =
(intersection[0] - elementRect.left + canvasRect.left) / scale + 'px'
const clipY =
(intersection[1] - elementRect.top + canvasRect.top) / scale + 'px'
const clipWidth = intersection[2] / scale + 'px'
const clipHeight = intersection[3] / scale + 'px'
return `polygon(0% 0%, 0% 100%, ${clipX} 100%, ${clipX} ${clipY}, calc(${clipX} + ${clipWidth}) ${clipY}, calc(${clipX} + ${clipWidth}) calc(${clipY} + ${clipHeight}), ${clipX} calc(${clipY} + ${clipHeight}), ${clipX} 100%, 100% 100%, 100% 0%)`
}
return ''
}
/**
* Updates the clip-path style based on element and selection information
*/
const updateClipPath = (
element: HTMLElement,
canvasElement: HTMLCanvasElement,
isSelected: boolean,
selectedArea?: {
x: number
y: number
width: number
height: number
scale: number
offset: [number, number]
}
) => {
const elementRect = element.getBoundingClientRect()
const canvasRect = canvasElement.getBoundingClientRect()
const clipPath = calculateClipPath(
elementRect,
canvasRect,
isSelected,
selectedArea
)
style.value = {
clipPath: clipPath || 'none',
willChange: 'clip-path'
}
}
return {
style,
updateClipPath
}
}