From 21b4df59a329d58c7d1735fec1253183ae89bac7 Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Sat, 9 May 2026 20:26:10 -0400 Subject: [PATCH] Fix event flow: ConfigEvent sets size, FrameEvent sets scale only - ConfigEvent: ONLY from app.ConfigEvent, sets width/height in State - FrameEvent: gets scale from gtx.Metric.PxPerDp, sends ScaleEvent only if changed - ScaleEvent: updates State.Scale - Renderer reads windowW/windowH from State (SetWindowSize), not gtx.Constraints - Renderer reads scale from State (SetScale), not gtx.Metric.PxPerDp - main.go: ConfigEvent calls SetWindowSize, FrameEvent calls SetScale --- cmd/pad/main.go | 22 ++++++++++------------ internal/ui/render.go | 21 ++++++++++++++------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/cmd/pad/main.go b/cmd/pad/main.go index db1e1aa..1e6dcd1 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -40,6 +40,8 @@ func run(w *app.Window) error { // Track pixel dimensions from ConfigEvent (for resize handling) var pixelW, pixelH int + // Track previous scale to detect changes + prevScale := float32(1.0) // Create logic instance with initial DP size and scale=1.0 logic := editor.NewLogic(initialDPW, initialDPH) @@ -71,24 +73,20 @@ func run(w *app.Window) error { Width: dpW, Height: dpH, } + // Update renderer with window size from State + renderer.SetWindowSize(pixelW, pixelH) case app.FrameEvent: gtx := app.NewContext(&ops, e) + newScale := gtx.Metric.PxPerDp - // Get scale from State — this is updated by ScaleEvent - newScale := logic.Scale() - - // Convert pixel dimensions to DP using actual scale - dpW := ui.ToDp(ui.Px(pixelW), newScale) - dpH := ui.ToDp(ui.Px(pixelH), newScale) - - // Send updated config to logic goroutine - logic.ConfigChan() <- editor.ConfigEvent{ - Width: dpW, - Height: dpH, + // If scale changed, send scale event to logic goroutine + if newScale != prevScale { + logic.ConfigChan() <- editor.ScaleEvent{Scale: newScale} + prevScale = newScale } // Update renderer with current scale from State - renderer.SetScale(newScale) + renderer.SetScale(logic.Scale()) // Acquire mutex, read frame, draw, release mutex mu.Lock() diff --git a/internal/ui/render.go b/internal/ui/render.go index cf34263..c6c24fd 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -20,6 +20,9 @@ type Renderer struct { theme Theme shp *text.Shaper scale float32 // pixels per DP, from State.Scale + // Window dimensions in physical pixels, from State + windowW int + windowH int } // New creates a new Renderer. @@ -32,6 +35,12 @@ func (r *Renderer) SetScale(scale float32) { r.scale = scale } +// SetWindowSize updates the window dimensions in physical pixels from State. +func (r *Renderer) SetWindowSize(w, h int) { + r.windowW = w + r.windowH = h +} + // toPx converts Dp to physical pixels using the renderer's scale. func (r *Renderer) toPx(dp Dp) Px { return ToPx(dp, r.scale) @@ -43,15 +52,13 @@ func (r *Renderer) toDp(px Px) Dp { } // Draw iterates elements and draws each in slice order (back-to-front). -// It captures the initial constraints once at the start, then passes them -// to each render function for consistent positioning. -// The scale is set externally via SetScale() from State.Scale. +// Window constraints come from State (set via SetWindowSize), not from gtx. +// Scale comes from State (set via SetScale), not from gtx.Metric.PxPerDp. func (r *Renderer) Draw(gtx layout.Context, elems []Element) { - // Capture initial constraints once — before any clips modify them. - // These are in physical pixels and serve as the window bounds for all positioning. + // Use window dimensions from State — never from gtx.Constraints initialConstraints := WindowConstraints{ - Min: gtx.Constraints.Min, - Max: gtx.Constraints.Max, + Min: image.Point{X: 0, Y: 0}, + Max: image.Point{X: r.windowW, Y: r.windowH}, } for _, e := range elems {