fix: drag speed on pan

This commit is contained in:
Ilia Denisov
2026-03-17 15:39:30 +02:00
committed by GitHub
parent 3db0c9afda
commit dee4b4da56
3 changed files with 110 additions and 5 deletions
+15 -1
View File
@@ -82,6 +82,10 @@ func (e *client) renderRasterImage(viewportW, viewportH int, p world.RenderParam
return image.NewRGBA(image.Rect(0, 0, 0, 0))
}
// Keep the incoming zoom snapshot so we can safely sync corrected zoom back
// to base params only when no newer zoom was written concurrently.
inputZoom := p.CameraZoom
// Record current raster pixel size (used for event coordinate conversion).
e.metaMu.Lock()
e.lastRasterPxW = viewportW
@@ -95,7 +99,17 @@ func (e *client) renderRasterImage(viewportW, viewportH int, p world.RenderParam
p.MarginYPx = viewportH / 4
// Correct zoom for viewport/world constraints, and clamp camera for no-wrap.
p.CameraZoom = e.world.CorrectCameraZoom(p.CameraZoom, viewportW, viewportH)
correctedZoom := e.world.CorrectCameraZoom(inputZoom, viewportW, viewportH)
p.CameraZoom = correctedZoom
// Sync corrected zoom to the canonical UI-facing params snapshot.
// Guard prevents stale render snapshots from overwriting a newer zoom value
// that may have been set by another UI event.
e.mu.Lock()
if e.wp.CameraZoom == inputZoom {
e.wp.CameraZoom = correctedZoom
}
e.mu.Unlock()
// Ensure indexing is up-to-date when viewport size or zoom changes.
zoomFp, err := p.CameraZoomFp()
+3 -4
View File
@@ -11,9 +11,8 @@ import (
/*
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.
- DragEvent.Dragged provides per-event delta in Fyne logical units.
- Client knows canvasScale (pixels per Fyne unit) and converts to pixels.
- We update camera center in world-fixed (CameraXWorldFp/YWorldFp).
Sign convention (map follows pointer):
@@ -24,7 +23,7 @@ Sign convention (map follows pointer):
// 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 returns pixels per Fyne logical unit.
CanvasScale() float32
// UpdateParams applies a mutation and schedules refresh through your coalescer.
@@ -1,6 +1,7 @@
package client
import (
"image"
"testing"
"fyne.io/fyne/v2"
@@ -107,3 +108,94 @@ func TestFyneTestDriverIsUsable(t *testing.T) {
t.Parallel()
_ = test.NewApp()
}
type immediateExecutor struct{}
func (immediateExecutor) Post(fn func()) {
if fn != nil {
fn()
}
}
type noopRefresher struct{}
func (noopRefresher) Refresh() {}
func newZoomSyncTestClient(t *testing.T, worldW, worldH int, cameraZoom float64) *client {
t.Helper()
w := world.NewWorld(worldW, worldH)
e := &client{
world: w,
drawer: &world.GGDrawer{},
wp: &world.RenderParams{
CameraZoom: cameraZoom,
CameraXWorldFp: w.W / 2,
CameraYWorldFp: w.H / 2,
Options: &world.RenderOptions{DisableWrapScroll: false},
},
hits: make([]world.Hit, 5),
}
e.co = NewRasterCoalescer(
immediateExecutor{},
noopRefresher{},
func(wPx, hPx int, _ world.RenderParams) image.Image {
return image.NewRGBA(image.Rect(0, 0, wPx, hPx))
},
)
return e
}
func TestRenderRasterImage_SyncsCorrectedZoomToBaseParams(t *testing.T) {
t.Parallel()
e := newZoomSyncTestClient(t, 10, 10, 1.0)
p := *e.wp
correctedZoom := e.world.CorrectCameraZoom(p.CameraZoom, 100, 100)
require.NotEqual(t, p.CameraZoom, correctedZoom)
_ = e.renderRasterImage(100, 100, p)
require.Equal(t, correctedZoom, e.wp.CameraZoom)
}
func TestRenderRasterImage_DoesNotOverrideNewerBaseZoom(t *testing.T) {
t.Parallel()
e := newZoomSyncTestClient(t, 10, 10, 1.0)
p := *e.wp
// Simulate a newer UI update that happened after this render snapshot was taken.
e.wp.CameraZoom = 3.0
_ = e.renderRasterImage(100, 100, p)
require.Equal(t, 3.0, e.wp.CameraZoom)
}
func TestPanController_Dragged_AfterRenderZoomCorrection_UsesSyncedZoom(t *testing.T) {
t.Parallel()
e := newZoomSyncTestClient(t, 10, 10, 1.0)
// Initial render corrects zoom and syncs it into base params.
_ = e.renderRasterImage(100, 100, *e.wp)
syncedZoom := e.wp.CameraZoom
require.NotEqual(t, 1.0, syncedZoom)
zoomFp, err := world.CameraZoomToWorldFixed(syncedZoom)
require.NoError(t, err)
startX := e.wp.CameraXWorldFp
pan := NewPanController(e)
pan.Dragged(&fyne.DragEvent{
Dragged: fyne.Delta{DX: 1, DY: 0},
})
expectedShift := world.PixelSpanToWorldFixed(1, zoomFp)
require.Equal(t, startX-expectedShift, e.wp.CameraXWorldFp)
}