Some checks failed
CI / build (push) Failing after 36s
WASM reactive UI framework for Go: - reactive/ - Signal[T], Effect, Runtime - ui/ - Button, Text, Input, View, Canvas, SVG components - navigation/ - Router, guards, history management - auth/ - OIDC client for WASM applications - host/ - Static file server Extracted from arcadia as open-source component. Co-Authored-By: Claude <noreply@anthropic.com>
145 lines
3.8 KiB
Go
145 lines
3.8 KiB
Go
//go:build js && wasm
|
|
|
|
package ui
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"git.flowmade.one/flowmade-one/iris/internal/element"
|
|
)
|
|
|
|
// snapToGrid snaps a position to the grid if enabled
|
|
func snapToGrid(pos Point, gridSize int) Point {
|
|
if gridSize <= 0 {
|
|
return pos
|
|
}
|
|
return Point{
|
|
X: float64(int(pos.X/float64(gridSize)+0.5) * gridSize),
|
|
Y: float64(int(pos.Y/float64(gridSize)+0.5) * gridSize),
|
|
}
|
|
}
|
|
|
|
// findItemUnderPoint finds which canvas item is under the given screen coordinates
|
|
// Returns the item with highest ZIndex that contains the point
|
|
func findItemUnderPoint(state *CanvasState, viewport element.Element, clientX, clientY float64) string {
|
|
// Get viewport bounds
|
|
rect := viewport.Call("getBoundingClientRect")
|
|
viewportLeft := rect.Get("left").Float()
|
|
viewportTop := rect.Get("top").Float()
|
|
|
|
// Convert to canvas coordinates
|
|
x := clientX - viewportLeft
|
|
y := clientY - viewportTop
|
|
canvasPos := state.ScreenToCanvas(Point{X: x, Y: y})
|
|
|
|
// Sort items by ZIndex descending (higher values first = top items first)
|
|
items := state.Items.Get()
|
|
sortedItems := make([]CanvasItem, len(items))
|
|
copy(sortedItems, items)
|
|
sort.Slice(sortedItems, func(i, j int) bool {
|
|
return sortedItems[i].ZIndex > sortedItems[j].ZIndex
|
|
})
|
|
|
|
// Check items in z-order (top to bottom)
|
|
for _, item := range sortedItems {
|
|
if canvasPos.X >= item.Position.X && canvasPos.X <= item.Position.X+item.Size.X &&
|
|
canvasPos.Y >= item.Position.Y && canvasPos.Y <= item.Position.Y+item.Size.Y {
|
|
return item.ID
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// isNearEdge checks if a click position is near the edge of an element
|
|
func isNearEdge(clickX, clickY, elemWidth, elemHeight float64) bool {
|
|
edgeThreshold := DefaultCanvasDefaults.EdgeThreshold
|
|
return clickX < edgeThreshold ||
|
|
clickX > elemWidth-edgeThreshold ||
|
|
clickY < edgeThreshold ||
|
|
clickY > elemHeight-edgeThreshold
|
|
}
|
|
|
|
// calculateEdgePoint calculates the edge point for a connection based on click position
|
|
func calculateEdgePoint(item CanvasItem, clickX, clickY, elemWidth, elemHeight float64) Point {
|
|
// Calculate center of element
|
|
centerX := item.Position.X + item.Size.X/2
|
|
centerY := item.Position.Y + item.Size.Y/2
|
|
|
|
// Determine which edge is closest
|
|
distLeft := clickX
|
|
distRight := elemWidth - clickX
|
|
distTop := clickY
|
|
distBottom := elemHeight - clickY
|
|
|
|
minDist := distLeft
|
|
edgeX := item.Position.X
|
|
edgeY := centerY
|
|
|
|
if distRight < minDist {
|
|
minDist = distRight
|
|
edgeX = item.Position.X + item.Size.X
|
|
edgeY = centerY
|
|
}
|
|
if distTop < minDist {
|
|
minDist = distTop
|
|
edgeX = centerX
|
|
edgeY = item.Position.Y
|
|
}
|
|
if distBottom < minDist {
|
|
edgeX = centerX
|
|
edgeY = item.Position.Y + item.Size.Y
|
|
}
|
|
|
|
return Point{X: edgeX, Y: edgeY}
|
|
}
|
|
|
|
// calculateConnectionEdgePoint calculates where a line from center to target
|
|
// should intersect with the element's bounding box
|
|
func calculateConnectionEdgePoint(center, target, size Point) Point {
|
|
dx := target.X - center.X
|
|
dy := target.Y - center.Y
|
|
|
|
// Handle edge case of overlapping centers
|
|
if dx == 0 && dy == 0 {
|
|
return center
|
|
}
|
|
|
|
// Calculate intersection with bounding box
|
|
halfW := size.X / 2
|
|
halfH := size.Y / 2
|
|
|
|
// Check which edge we intersect
|
|
if dx == 0 {
|
|
// Vertical line
|
|
if dy > 0 {
|
|
return Point{X: center.X, Y: center.Y + halfH}
|
|
}
|
|
return Point{X: center.X, Y: center.Y - halfH}
|
|
}
|
|
|
|
slope := dy / dx
|
|
|
|
// Check horizontal edges
|
|
if abs(slope) <= halfH/halfW {
|
|
// Intersects left or right edge
|
|
if dx > 0 {
|
|
return Point{X: center.X + halfW, Y: center.Y + slope*halfW}
|
|
}
|
|
return Point{X: center.X - halfW, Y: center.Y - slope*halfW}
|
|
}
|
|
|
|
// Intersects top or bottom edge
|
|
if dy > 0 {
|
|
return Point{X: center.X + halfH/slope, Y: center.Y + halfH}
|
|
}
|
|
return Point{X: center.X - halfH/slope, Y: center.Y - halfH}
|
|
}
|
|
|
|
// abs returns the absolute value of x
|
|
func abs(x float64) float64 {
|
|
if x < 0 {
|
|
return -x
|
|
}
|
|
return x
|
|
}
|