no-wrap option; pivoted exponential zoom
This commit is contained in:
+82
-2
@@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
@@ -78,11 +79,89 @@ func (e *editor) updateSizes() {
|
||||
|
||||
e.wp.CameraZoom = e.world.CorrectCameraZoom(e.wp.CameraZoom, e.wp.ViewportWidthPx, e.wp.ViewportHeightPx)
|
||||
e.world.IndexOnViewportChange(e.wp.ViewportWidthPx, e.wp.ViewportHeightPx, e.wp.CameraZoom)
|
||||
|
||||
e.world.ClampRenderParamsNoWrap(e.wp)
|
||||
e.co.Request(*e.wp)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *editor) onScrolled(s *fyne.ScrollEvent) {
|
||||
vw := e.wp.ViewportWidthPx
|
||||
vh := e.wp.ViewportHeightPx
|
||||
if vw <= 0 || vh <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Cursor position in viewport pixels (Fyne units -> px by multiplying).
|
||||
cxPx := int(float32(s.Position.X) * e.canvasScale)
|
||||
cyPx := int(float32(s.Position.Y) * e.canvasScale)
|
||||
|
||||
if cxPx < 0 {
|
||||
cxPx = 0
|
||||
} else if cxPx > vw {
|
||||
cxPx = vw
|
||||
}
|
||||
if cyPx < 0 {
|
||||
cyPx = 0
|
||||
} else if cyPx > vh {
|
||||
cyPx = vh
|
||||
}
|
||||
|
||||
oldZoom := e.wp.CameraZoom
|
||||
|
||||
// Exponential zoom:
|
||||
// - Each "notch" multiplies zoom by a fixed factor.
|
||||
// - Using DY directly as exponent gives smooth trackpad behavior too.
|
||||
//
|
||||
// Tune base:
|
||||
// base=1.10 => ~10% per wheel step (if DY≈1 per step)
|
||||
// base=1.05 => ~5% per step
|
||||
//
|
||||
// In user settings, better store on percents userZoomLevelPercent = (0,100]: base = 1 + (userZoomLevelPercent) / 10
|
||||
const base = 1.005
|
||||
|
||||
// Negative DY => zoom out, positive DY => zoom in (depending on platform settings).
|
||||
// If you want inverted direction, negate float64(s.Scrolled.DY).
|
||||
delta := float64(s.Scrolled.DY)
|
||||
|
||||
newZoom := oldZoom * math.Pow(base, delta)
|
||||
|
||||
// Clamp/correct (min/max + prevent wrap if needed).
|
||||
newZoom = e.world.CorrectCameraZoom(newZoom, vw, vh)
|
||||
if newZoom == oldZoom {
|
||||
return
|
||||
}
|
||||
|
||||
oldZoomFp, err := world.CameraZoomToWorldFixed(oldZoom)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newZoomFp, err := world.CameraZoomToWorldFixed(newZoom)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
newCamX, newCamY := world.PivotZoomCameraNoWrap(
|
||||
e.wp.CameraXWorldFp, e.wp.CameraYWorldFp,
|
||||
vw, vh,
|
||||
cxPx, cyPx,
|
||||
oldZoomFp, newZoomFp,
|
||||
)
|
||||
|
||||
e.wp.CameraZoom = newZoom
|
||||
e.wp.CameraXWorldFp = newCamX
|
||||
e.wp.CameraYWorldFp = newCamY
|
||||
|
||||
e.world.IndexOnViewportChange(vw, vh, e.wp.CameraZoom)
|
||||
|
||||
// No-wrap clamp to avoid "detaching" from borders.
|
||||
e.world.ClampRenderParamsNoWrap(e.wp)
|
||||
|
||||
// Zoom changes are best done as full redraw.
|
||||
e.world.ForceFullRedrawNext()
|
||||
|
||||
e.co.Request(*e.wp)
|
||||
}
|
||||
|
||||
func (e *editor) onDragged(ev *fyne.DragEvent) {
|
||||
e.pan.Dragged(ev)
|
||||
}
|
||||
@@ -120,6 +199,7 @@ func NewEditor() *editor {
|
||||
CameraXWorldFp: 300 * world.SCALE,
|
||||
CameraYWorldFp: 300 * world.SCALE,
|
||||
// Viewport sizes and margins will be filled from draw(w,h).
|
||||
Options: &world.RenderOptions{DisableWrapScroll: false},
|
||||
},
|
||||
canvasScale: 1.0,
|
||||
}
|
||||
@@ -132,7 +212,7 @@ func NewEditor() *editor {
|
||||
return e.draw(wPx, hPx)
|
||||
})
|
||||
|
||||
e.canvas = newInteractiveRaster(e, e.raster, e.onMapLayout, e.onDragged, e.onDradEnd)
|
||||
e.canvas = newInteractiveRaster(e, e.raster, e.onMapLayout, e.onScrolled, e.onDragged, e.onDradEnd)
|
||||
e.pan = NewPanController(e)
|
||||
|
||||
// Wire coalescer: it schedules raster.Refresh() on UI thread and renders once per draw call.
|
||||
|
||||
Reference in New Issue
Block a user