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() } }