Pad/cmd/pad/main.go
Greg Pomerantz 556f72643f Implement full architecture scaffolding with logic and frame receiver goroutines
- Logic goroutine: receives config events, recomputes layout, sends frames
- Frame receiver goroutine: reads frames, stores under mutex, calls w.Invalidate()
- Main event loop: renders frames under mutex, sends config events to logic
- Proper channel topology: configChan, frameChan, inputChan
- Mutex around elems shared between goroutines
- Window resize triggers layout recomputation in logic goroutine

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-05-08 17:03:21 -04:00

113 lines
2.5 KiB
Go

package main
import (
"log"
"os"
"sync"
"gioui.org/app"
"gioui.org/op"
"gioui.org/text"
"gioui.org/unit"
"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()
renderer := ui.New(ui.Theme{FontSize: 14}, shaper)
// Channels for communication between goroutines
configChan := make(chan ui.ConfigEvent)
frameChan := make(chan []ui.Element)
inputChan := make(chan []ui.InputEvent)
// Shared state protected by mutex
var mu sync.Mutex
var elems []ui.Element
// Initial screen size
screenWidth := unit.Dp(390)
screenHeight := unit.Dp(844)
// Compute initial elements
elems = ui.EditorLayout(screenWidth, screenHeight)
// Start logic goroutine
go logic(w, shaper, screenWidth, screenHeight, configChan, frameChan, inputChan)
// Start frame receiver goroutine
go frameReceiver(w, &mu, &elems, frameChan)
// Main event loop
for {
switch e := w.Event().(type) {
case app.DestroyEvent:
return e.Err
case app.ConfigEvent:
// Send config event to logic goroutine
configChan <- ui.ConfigEvent{
Width: unit.Dp(e.Config.Size.X),
Height: unit.Dp(e.Config.Size.Y),
}
case app.FrameEvent:
// Acquire mutex, read frame, draw, release mutex
mu.Lock()
currentElems := elems
mu.Unlock()
gtx := app.NewContext(&ops, e)
renderer.Draw(gtx, currentElems)
e.Frame(&ops)
}
}
}
// logic runs in a separate goroutine and owns all application state.
func logic(w *app.Window, shaper *text.Shaper, screenWidth, screenHeight unit.Dp,
configChan <-chan ui.ConfigEvent, frameChan chan<- []ui.Element, inputChan <-chan []ui.InputEvent) {
// Initial elements
elems := ui.EditorLayout(screenWidth, screenHeight)
for {
select {
case cfg := <-configChan:
// Update screen size and recompute layout
screenWidth = cfg.Width
screenHeight = cfg.Height
elems = ui.EditorLayout(screenWidth, screenHeight)
frameChan <- elems
case <-inputChan:
// Process input (not implemented in mockup)
frameChan <- elems
}
}
}
// 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()
}
}