Pad/cmd/pad/main.go
Greg Pomerantz b8c6878f46 Introduce distinct Dp/Px types to prevent coordinate mixing at compile time
- Add ui.Dp and ui.Px as distinct named types
- Logic layer works exclusively in Dp (positions, sizes, regions)
- Renderer converts Dp→Px at Gio interop boundaries
- Capture gtx.Constraints once at start of Draw() for consistent positioning
- main.go converts app.ConfigEvent pixels to Dp before passing to logic layer
- Update documentation with new coordinate system architecture
2026-05-09 18:40:47 -04:00

108 lines
2.4 KiB
Go

package main
import (
"log"
"os"
"sync"
"gioui.org/app"
"gioui.org/op"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/font/gofont"
"pad/internal/editor"
"pad/internal/ui"
)
func main() {
go func() {
w := new(app.Window)
w.Option(app.Title("Pad"))
w.Option(app.Size(unit.Dp(390), unit.Dp(844)))
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
var ops op.Ops
shaper := text.NewShaper(text.WithCollection(gofont.Collection()))
renderer := ui.New(ui.Theme{FontSize: 14}, shaper)
// Track window dimensions in pixels (from ConfigEvent) and DP (for logic layer)
var pixelW, pixelH int
var dpW, dpH ui.Dp
var scale float32 = 1.0 // updated on first FrameEvent
// Create logic instance with initial DP size
logic := editor.NewLogic(dpW, dpH)
// Shared state protected by mutex
var mu sync.Mutex
var elems []ui.Element
// Start logic goroutine
go logic.Run()
// Start frame receiver goroutine
go frameReceiver(w, &mu, &elems, logic.FrameChan())
// Main event loop
for {
switch e := w.Event().(type) {
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 (default 1.0 until first FrameEvent)
dpW = ui.ToDp(ui.Px(pixelW), scale)
dpH = ui.ToDp(ui.Px(pixelH), scale)
// Send config event to logic goroutine (in DP)
logic.ConfigChan() <- editor.ConfigEvent{
Width: dpW,
Height: dpH,
}
case app.FrameEvent:
// Update scale from the frame context
gtx := app.NewContext(&ops, e)
scale = gtx.Metric.PxPerDp
// Reconvert pixel dimensions to DP with actual scale
dpW = ui.ToDp(ui.Px(pixelW), scale)
dpH = ui.ToDp(ui.Px(pixelH), scale)
// Send updated config to logic goroutine
logic.ConfigChan() <- editor.ConfigEvent{
Width: dpW,
Height: dpH,
}
// Acquire mutex, read frame, draw, release mutex
mu.Lock()
currentElems := elems
mu.Unlock()
renderer.Draw(gtx, currentElems)
e.Frame(&ops)
}
}
}
// frameReceiver runs in a separate goroutine and bridges frameChan to the main loop.
func frameReceiver(w *app.Window, mu *sync.Mutex, elems *[]ui.Element, frameChan <-chan []ui.Element) {
for {
frame := <-frameChan
mu.Lock()
*elems = frame
w.Invalidate()
mu.Unlock()
}
}