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