Pad/internal/editor/logic.go
Greg Pomerantz 22c1d5bed1 fix: correct text layout and rendering pipeline
- Fix render.go: replace broken material.Label with shaper-based text
  rendering (LayoutString + Shape + glyph iteration) per Gio's paintGlyph
- Fix render.go: only apply clip rects at container boundaries; leaf
  elements have zero-size regions that were clipping all text out
- Fix render.go: call r.drawElement recursively for container children
  so nested containers work and clips are applied correctly
- Fix render.go: add drawLineText/drawLine helpers matching Gio's
  paintGlyph offset calculation (x + first.X, y + first.Y)
- Fix element.go: Button.Draw now uses r.drawText instead of duplicate
  broken material.Label code; Label defaults to black when color is unset
- Fix state.go: container children use relative coordinates instead of
  mixed screen-space/container-relative positions
- Fix main.go: ConfigEvent carries raw pixel dimensions only; ScaleEvent
  carries scale only; layout is computed once per frame using both,
  eliminating the infinite Invalidate() loop
2026-05-18 21:03:22 -04:00

114 lines
2.6 KiB
Go

package editor
import (
"sync"
"pad/internal/ui"
)
// ConfigEvent represents a window configuration change (resize, orientation).
// PixelWidth and PixelHeight are the raw pixel dimensions from Gio.
type ConfigEvent struct {
PixelWidth int
PixelHeight int
}
// ScaleEvent represents a metric change (HiDPI scale factor).
type ScaleEvent struct {
Scale float32
}
// ConfigUpdate is a common interface for all configuration updates.
// Both ConfigEvent and ScaleEvent implement this interface.
type ConfigUpdate interface {
apply(*State)
}
func (e ConfigEvent) apply(s *State) {
s.PixelWidth = e.PixelWidth
s.PixelHeight = e.PixelHeight
}
func (e ScaleEvent) apply(s *State) {
s.SetScale(e.Scale)
}
// InputEvent represents a user input event routed to an element.
type InputEvent struct {
ElementID string
Type ui.InputType
Data any
}
// ResultEvent represents a completed async task result.
type ResultEvent struct {
// Future: add result fields here
}
// Logic runs the logic goroutine and provides channels for communication.
type Logic struct {
state *State
configChan chan ConfigUpdate
frameChan chan []ui.Element
inputChan chan []InputEvent
resultChan chan ResultEvent
mu sync.Mutex
}
// NewLogic creates a new Logic instance.
func NewLogic() *Logic {
return &Logic{
state: NewState(),
configChan: make(chan ConfigUpdate),
frameChan: make(chan []ui.Element),
inputChan: make(chan []InputEvent),
resultChan: make(chan ResultEvent),
}
}
// ConfigChan returns the unified config channel for the logic goroutine.
// Accepts ConfigEvent (size) and ScaleEvent (scale factor).
func (l *Logic) ConfigChan() chan<- ConfigUpdate {
return l.configChan
}
// FrameChan returns the frame channel for the logic goroutine.
func (l *Logic) FrameChan() <-chan []ui.Element {
return l.frameChan
}
// InputChan returns the input channel for the logic goroutine.
func (l *Logic) InputChan() chan<- []InputEvent {
return l.inputChan
}
// ResultChan returns the result channel for the logic goroutine.
func (l *Logic) ResultChan() chan<- ResultEvent {
return l.resultChan
}
// Scale returns the current scale factor (pixels per DP).
func (l *Logic) Scale() float32 {
return l.state.Scale()
}
// Run runs the logic goroutine loop.
func (l *Logic) Run() {
for {
select {
case update := <-l.configChan:
update.apply(l.state)
l.frameChan <- l.state.layout()
case <-l.inputChan:
l.frameChan <- l.state.layout()
case <-l.resultChan:
l.frameChan <- l.state.layout()
}
}
}
// State returns the current state.
func (l *Logic) State() *State {
return l.state
}