Files
galaxy-game/client/editor.go
T
2026-03-07 00:29:06 +03:00

184 lines
4.2 KiB
Go

package client
import (
"image"
"sync"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"github.com/iliadenisov/galaxy/client/world"
)
type Editor interface {
BuildUI(fyne.Window)
}
type editor struct {
world *world.World
drawer *world.GGDrawer
raster *canvas.Raster
canvasScale float32
canvas *interactiveRaster
win fyne.Window
// Coalescer for latest-wins refresh scheduling.
co *RasterCoalescer[world.RenderParams]
pan *PanController
// Protected render params state. Stored as value to avoid aliasing issues.
mu sync.RWMutex
wp *world.RenderParams
// Last viewport size we indexed the world for.
lastViewportW int
lastViewportH int
// Optional: you can keep the last expanded canvas size to avoid reallocations.
lastCanvasW int
lastCanvasH int
// Reusable viewport buffer to avoid per-frame allocations.
viewportImg *image.RGBA
viewportW int
viewportH int
}
func (e *editor) CanvasScale() float32 { return e.canvasScale }
func (e *editor) ForceFullRedraw() {
e.world.ForceFullRedrawNext()
}
func (e *editor) buildUI() fyne.CanvasObject {
return e.canvas
}
// здесь определяю, изменились ли границы raster, если да - обновляю размеры viewport, margin и корректирую zoom
func (e *editor) updateSizes() {
canvas := fyne.CurrentApp().Driver().CanvasForObject(e.raster)
if canvas == nil {
return
}
size := e.raster.Size()
e.canvasScale = canvas.Scale()
width := int(size.Width * e.canvasScale)
height := int(size.Height * e.canvasScale)
if width > 0 && height > 0 && (width != e.wp.ViewportWidthPx || height != e.wp.ViewportHeightPx) {
e.wp.ViewportWidthPx = width
e.wp.ViewportHeightPx = height
e.wp.MarginXPx = e.wp.ViewportWidthPx / 4
e.wp.MarginYPx = e.wp.ViewportHeightPx / 4
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.co.Request(*e.wp)
}
}
func (e *editor) onDragged(ev *fyne.DragEvent) {
e.pan.Dragged(ev)
}
func (e *editor) onDradEnd() {
e.pan.DragEnd()
}
func (e *editor) wheelZoom(stepDelta int) {}
func (e *editor) InitImage() {
s := fyne.NewSize(292, 292)
e.canvas.SetMinSize(s)
e.updateSizes()
}
func (e *editor) onMapLayout(s fyne.Size) {
e.updateSizes()
}
func (e *editor) BuildUI(w fyne.Window) {
e.win = w
content := container.New(layout.NewStackLayout(), e.buildUI())
w.CenterOnScreen()
w.SetContent(content)
}
func NewEditor() *editor {
w := world.NewWorld(300, 300)
testWorldInit(w)
e := &editor{
world: w,
wp: &world.RenderParams{
CameraZoom: 1.0,
CameraXWorldFp: 300 * world.SCALE,
CameraYWorldFp: 300 * world.SCALE,
// Viewport sizes and margins will be filled from draw(w,h).
},
canvasScale: 1.0,
}
// Create a drawer with some initial context; real size will be adjusted on first draw.
e.drawer = &world.GGDrawer{DC: nil}
// Create raster; its draw callback delegates to coalescer.
e.raster = canvas.NewRaster(func(wPx, hPx int) image.Image {
return e.draw(wPx, hPx)
})
e.canvas = newInteractiveRaster(e, e.raster, e.onMapLayout, e.onDragged, e.onDradEnd)
e.pan = NewPanController(e)
// Wire coalescer: it schedules raster.Refresh() on UI thread and renders once per draw call.
exec := FyneExecutor{}
e.co = NewRasterCoalescer(
exec,
e.raster, // Refresher
func(wPx, hPx int, p world.RenderParams) image.Image {
// This runs on UI thread (inside draw). It must return an image.
return e.renderRasterImage(wPx, hPx, p)
},
)
// Kick initial draw.
e.RequestRefresh()
e.InitImage()
return e
}
func testWorldInit(w *world.World) {
if _, err := w.AddCircle(150, 150, 50); err != nil {
panic(err)
}
if _, err := w.AddCircle(150, 299, 30); err != nil {
panic(err)
}
if _, err := w.AddCircle(299, 150, 30); err != nil {
panic(err)
}
if _, err := w.AddLine(100, 20, 200, 30); err != nil {
panic(err)
}
if _, err := w.AddLine(50, 50, 250, 100); err != nil {
panic(err)
}
if _, err := w.AddPoint(10, 10); err != nil {
panic(err)
}
if _, err := w.AddPoint(25, 255); err != nil {
panic(err)
}
}