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>
This commit is contained in:
144
ui/canvas_geometry.go
Normal file
144
ui/canvas_geometry.go
Normal file
@@ -0,0 +1,144 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user