diff --git a/cmd/pad/main.go b/cmd/pad/main.go index 1e6dcd1..dc36188 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -38,14 +38,15 @@ func run(w *app.Window) error { initialDPW := ui.Dp(390) initialDPH := ui.Dp(844) - // 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) + // Set State reference on Renderer — it reads scale/window size from State directly + renderer.SetState(logic.State()) + // Shared state protected by mutex var mu sync.Mutex var elems []ui.Element @@ -62,19 +63,14 @@ func run(w *app.Window) error { case app.DestroyEvent: return e.Err case app.ConfigEvent: - // Store pixel dimensions from ConfigEvent - pixelW = e.Config.Size.X - pixelH = e.Config.Size.Y - // Convert to DP using current scale (starts at 1.0) - dpW := ui.ToDp(ui.Px(pixelW), 1.0) - dpH := ui.ToDp(ui.Px(pixelH), 1.0) + // Convert pixel dimensions to DP using current scale (starts at 1.0) + dpW := ui.ToDp(ui.Px(e.Config.Size.X), 1.0) + dpH := ui.ToDp(ui.Px(e.Config.Size.Y), 1.0) // Send config event to logic goroutine (in DP) logic.ConfigChan() <- editor.ConfigEvent{ 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 @@ -85,9 +81,6 @@ func run(w *app.Window) error { prevScale = newScale } - // Update renderer with current scale from State - renderer.SetScale(logic.Scale()) - // Acquire mutex, read frame, draw, release mutex mu.Lock() currentElems := elems diff --git a/internal/editor/logic.go b/internal/editor/logic.go index f334bf0..f20db5c 100644 --- a/internal/editor/logic.go +++ b/internal/editor/logic.go @@ -25,9 +25,7 @@ type ConfigUpdate interface { } func (e ConfigEvent) apply(s *State) { - s.ScreenWidth = e.Width - s.ScreenHeight = e.Height - s.Elems = EditorLayout(e.Width, e.Height) + s.SetSize(e.Width, e.Height) } func (e ScaleEvent) apply(s *State) { @@ -93,14 +91,14 @@ func (l *Logic) ResultChan() <-chan ResultEvent { func (l *Logic) ScreenSize() (ui.Dp, ui.Dp) { l.mu.Lock() defer l.mu.Unlock() - return l.state.ScreenWidth, l.state.ScreenHeight + return l.state.ScreenWidth(), l.state.ScreenHeight() } // Scale returns the current scale factor (pixels per DP). func (l *Logic) Scale() float32 { l.mu.Lock() defer l.mu.Unlock() - return l.state.Scale + return l.state.Scale() } // Run runs the logic goroutine loop. diff --git a/internal/editor/state.go b/internal/editor/state.go index e9783e0..641c259 100644 --- a/internal/editor/state.go +++ b/internal/editor/state.go @@ -7,9 +7,9 @@ import ( // State holds all application state owned by the logic goroutine. // All dimensions are in device-independent pixels (Dp). type State struct { - ScreenWidth ui.Dp - ScreenHeight ui.Dp - Scale float32 // pixels per DP, default 1.0 + screenWidth ui.Dp + screenHeight ui.Dp + scale float32 // pixels per DP, default 1.0 Elems []ui.Element } @@ -18,17 +18,33 @@ type State struct { // scale defaults to 1.0 until updated by ScaleEvent. func NewState(screenWidth, screenHeight ui.Dp) *State { return &State{ - ScreenWidth: screenWidth, - ScreenHeight: screenHeight, - Scale: 1.0, + screenWidth: screenWidth, + screenHeight: screenHeight, + scale: 1.0, Elems: EditorLayout(screenWidth, screenHeight), } } +// Scale returns the current scale factor (pixels per DP). +func (s *State) Scale() float32 { return s.scale } + +// ScreenWidth returns the current screen width in Dp. +func (s *State) ScreenWidth() ui.Dp { return s.screenWidth } + +// ScreenHeight returns the current screen height in Dp. +func (s *State) ScreenHeight() ui.Dp { return s.screenHeight } + // SetScale updates the scale factor and recomputes layout. func (s *State) SetScale(scale float32) { - s.Scale = scale - s.Elems = EditorLayout(s.ScreenWidth, s.ScreenHeight) + s.scale = scale + s.Elems = EditorLayout(s.screenWidth, s.screenHeight) +} + +// SetSize updates the screen dimensions and recomputes layout. +func (s *State) SetSize(width, height ui.Dp) { + s.screenWidth = width + s.screenHeight = height + s.Elems = EditorLayout(width, height) } // EditorLayout computes regions for the editor page. diff --git a/internal/ui/render.go b/internal/ui/render.go index c6c24fd..aaadb60 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -15,50 +15,52 @@ import ( "golang.org/x/image/math/fixed" ) +// StateReader provides access to scale and window dimensions from State. +// Implemented by editor.State to avoid import cycles. +type StateReader interface { + Scale() float32 + ScreenWidth() Dp + ScreenHeight() Dp +} + // Renderer consumes a slice of elements and draws them. 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 + state StateReader // read-only access to State for scale/window size } // New creates a new Renderer. func New(th Theme, shp *text.Shaper) *Renderer { - return &Renderer{theme: th, shp: shp, scale: 1.0} + return &Renderer{theme: th, shp: shp} } -// SetScale updates the renderer's scale factor from State.Scale. -func (r *Renderer) SetScale(scale float32) { - r.scale = scale +// SetState sets the State reference for the Renderer. +// The Renderer reads scale and window size from State directly. +func (r *Renderer) SetState(state StateReader) { + r.state = state } -// 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. +// toPx converts Dp to physical pixels using the scale from State. func (r *Renderer) toPx(dp Dp) Px { - return ToPx(dp, r.scale) + return ToPx(dp, r.state.Scale()) } -// toDp converts physical pixels to Dp using the renderer's scale. +// toDp converts physical pixels to Dp using the scale from State. func (r *Renderer) toDp(px Px) Dp { - return ToDp(px, r.scale) + return ToDp(px, r.state.Scale()) } // Draw iterates elements and draws each in slice order (back-to-front). -// Window constraints come from State (set via SetWindowSize), not from gtx. -// Scale comes from State (set via SetScale), not from gtx.Metric.PxPerDp. +// All scale and window size values come from State, never from gtx. func (r *Renderer) Draw(gtx layout.Context, elems []Element) { - // Use window dimensions from State — never from gtx.Constraints + // Use window dimensions from State — convert DP to pixels using State.Scale + state := r.state + windowWPx := int(r.toPx(state.ScreenWidth())) + windowHPx := int(r.toPx(state.ScreenHeight())) initialConstraints := WindowConstraints{ Min: image.Point{X: 0, Y: 0}, - Max: image.Point{X: r.windowW, Y: r.windowH}, + Max: image.Point{X: windowWPx, Y: windowHPx}, } for _, e := range elems {