package client import ( "image" "testing" "fyne.io/fyne/v2" "fyne.io/fyne/v2/test" "github.com/stretchr/testify/require" "galaxy/client/world" ) type fakeClient struct { scale float32 p world.RenderParams forced bool updates int refresh int } func (e *fakeClient) CanvasScale() float32 { return e.scale } func (e *fakeClient) UpdateParams(fn func(p *world.RenderParams)) { fn(&e.p) e.updates++ } func (e *fakeClient) RequestRefresh() { e.refresh++ } func (e *fakeClient) ForceFullRedraw() { e.forced = true } func TestPanController_DraggedUpdatesCameraByDeltaPx(t *testing.T) { t.Parallel() fe := &fakeClient{ scale: 1.0, // 1 fyne unit == 1 px for the test p: world.RenderParams{ CameraZoom: 1.0, CameraXWorldFp: 5 * world.SCALE, CameraYWorldFp: 5 * world.SCALE, }, } pc := NewPanController(fe) // Drag right by +3 px and down by +2 px. pc.Dragged(&fyne.DragEvent{ Dragged: fyne.Delta{DX: 3, DY: 2}, }) require.Equal(t, 1, fe.updates) // Map follows pointer => camera moves opposite to pointer delta. require.Equal(t, 5*world.SCALE-3*world.SCALE, fe.p.CameraXWorldFp) require.Equal(t, 5*world.SCALE-2*world.SCALE, fe.p.CameraYWorldFp) } func TestPanController_DraggedUsesCanvasScaleByMultiplying(t *testing.T) { t.Parallel() fe := &fakeClient{ scale: 2.0, // 2 px per fyne unit p: world.RenderParams{ CameraZoom: 1.0, CameraXWorldFp: 0, CameraYWorldFp: 0, }, } pc := NewPanController(fe) // Dragged.DX=1 fyne unit => 2 px after scaling. pc.Dragged(&fyne.DragEvent{ Dragged: fyne.Delta{DX: 1, DY: 0}, }) require.Equal(t, -2*world.SCALE, fe.p.CameraXWorldFp) } func TestPanController_DragEndForcesFullRedrawAndRefresh(t *testing.T) { t.Parallel() fe := &fakeClient{ scale: 1.0, p: world.RenderParams{ CameraZoom: 1.0, CameraXWorldFp: 0, CameraYWorldFp: 0, }, } pc := NewPanController(fe) // Simulate a drag start. pc.Dragged(&fyne.DragEvent{PointEvent: fyne.PointEvent{Position: fyne.Position{X: 1, Y: 1}}}) pc.DragEnd() require.True(t, fe.forced) require.Equal(t, 1, fe.refresh) } // Optional: demonstrate use of fyne/test package to ensure types are available. // (Not strictly needed, but keeps fyne dependency "active" in tests.) 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) }