package client import ( "math" "fyne.io/fyne/v2" "galaxy/client/world" ) /* Client pan integration for Fyne Draggable: - DragEvent provides absolute coordinates in "Fyne units". - Client knows canvasScale (Fyne units per pixel) and converts to pixels. - We keep last drag position and compute dx/dy ourselves. - We update camera center in world-fixed (CameraXWorldFp/YWorldFp). Sign convention (map follows pointer): - Drag right (dxPx > 0): move world content right => move camera left => CameraXWorldFp -= dxWorldFp - Drag down (dyPx > 0): move world content down => move camera up => CameraYWorldFp -= dyWorldFp */ // draggableClient is the minimal interface we need from your client implementation. // If your Client already has these methods/fields, you can fold the code directly into it. type draggableClient interface { // CanvasScale returns the fyne-units-per-pixel scale factor. CanvasScale() float32 // UpdateParams applies a mutation and schedules refresh through your coalescer. UpdateParams(fn func(p *world.RenderParams)) // RequestRefresh schedules a refresh with current params (no mutation). RequestRefresh() // ForceFullRedraw forces a full redraw on next Render (used on DragEnd). ForceFullRedraw() } // PanController holds per-drag transient state. type PanController struct { ed draggableClient dragging bool lastFx float32 // last absolute position in Fyne units lastFy float32 // Remainders to keep subpixel fyne->px conversion stable across many events. remPxX float32 remPxY float32 } func NewPanController(ed draggableClient) *PanController { return &PanController{ed: ed} } // Dragged processes one drag event, updates camera center by delta, and schedules redraw. func (p *PanController) Dragged(ev *fyne.DragEvent) { if ev == nil { return } scale := p.ed.CanvasScale() if scale <= 0 { return } // DragEvent.Dragged is delta in Fyne logical units (device independent). // Convert to pixels by multiplying by canvas scale. dxPxF := ev.Dragged.DX * scale dyPxF := ev.Dragged.DY * scale // accumulate subpixel remainder in pixels dxPxF += p.remPxX dyPxF += p.remPxY dxPx := int(math.Round(float64(dxPxF))) dyPx := int(math.Round(float64(dyPxF))) p.remPxX = dxPxF - float32(dxPx) p.remPxY = dyPxF - float32(dyPx) if dxPx == 0 && dyPx == 0 { return } p.ed.UpdateParams(func(rp *world.RenderParams) { zoomFp, err := rp.CameraZoomFp() if err != nil || zoomFp <= 0 { return } dxWorldFp := world.PixelSpanToWorldFixed(dxPx, zoomFp) dyWorldFp := world.PixelSpanToWorldFixed(dyPx, zoomFp) // Map follows pointer rp.CameraXWorldFp -= dxWorldFp rp.CameraYWorldFp -= dyWorldFp }) } // DragEnd ends the drag gesture. We force a full redraw next to eliminate any // possible artifacts from incremental shifting and to "settle" the final state. func (p *PanController) DragEnd() { p.dragging = false p.remPxX = 0 p.remPxY = 0 p.ed.ForceFullRedraw() p.ed.RequestRefresh() }