//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 }