package main import ( "git.wow.st/gmp/giowrap" "fmt" "image" "image/color" "log" "os" "runtime" "runtime/pprof" "sync" "time" "gioui.org/ui" "gioui.org/ui/app" "gioui.org/ui/f32" "gioui.org/ui/gesture" "gioui.org/ui/layout" "gioui.org/ui/measure" "gioui.org/ui/paint" "gioui.org/ui/pointer" "gioui.org/ui/text" "golang.org/x/image/font/gofont/goregular" "golang.org/x/image/font/sfnt" ) var ( FPS int = 100 frames int64 frametime int64 // nanoseconds maxframetime int64 mux sync.Mutex Profile bool = true RecordMallocs bool = false ) func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable { lbl := giowrap.NewLabel(t, giowrap.Face(face), giowrap.Align(text.Middle)) bg := giowrap.NewBackground(giowrap.Color(c), giowrap.Radius(ui.Dp(4))) return giowrap.AsClickable(bg(lbl)) } func main() { go func() { ms := &runtime.MemStats{} var a1 uint64 for { time.Sleep(time.Second * 3) mux.Lock() if frames == 0 { mux.Unlock() continue } runtime.ReadMemStats(ms) a1 = ms.Alloc runtime.GC() runtime.ReadMemStats(ms) log.Printf("Alloc: %d - %d = %d (%d per frame)", a1, ms.Alloc, a1-ms.Alloc, (a1-ms.Alloc)/(3*uint64(FPS))) log.Printf("Frametime: %d max Frametime: %d us\n", frametime/(frames*1000), maxframetime/1000) frames = 0 frametime = 0 maxframetime = 0 mux.Unlock() } }() switch { case len(os.Args) < 2: fallthrough default: log.Fatal(`Usage: hello [main1|main2|main3|main4] `) case os.Args[1] == "main1": log.Print("main1()") go main1() case os.Args[1] == "main2": log.Print("main2()") go main2() case os.Args[1] == "main3": log.Print("main3()") go main3() case os.Args[1] == "main4": log.Print("main4()") go main4() } app.Main() } // Example with functional layout func main1() { w := app.NewWindow() regular, err := sfnt.Parse(goregular.TTF) if err != nil { log.Fatal("Cannot parse font.") } ctx := giowrap.NewContext(w) t := time.NewTicker(time.Second / time.Duration(FPS)) e1 := giowrap.NewEditor("text 1", giowrap.Face(ctx.Faces.For(regular, ui.Sp(24)))) e1.Focus() e2 := giowrap.NewEditor("text 2", giowrap.Face(ctx.Faces.For(regular, ui.Sp(24)))) f1 := giowrap.NewFlex(giowrap.Vertical) OuterInset := giowrap.NewInset(giowrap.Size(ui.Dp(10))) InnerInset := giowrap.NewInset(giowrap.Size(ui.Dp(10))) f2 := giowrap.NewFlex(giowrap.Horizontal) btn1 := NewButton(ctx.Faces.For(regular, ui.Sp(24)), "push1", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}) btn2 := NewButton(ctx.Faces.For(regular, ui.Sp(24)), "push2", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}) profiled := false startTime := time.Now() page := OuterInset( f1( e1, giowrap.Flexible(0.33), InnerInset(e2), giowrap.Flexible(0.67), f2( InnerInset(btn1), giowrap.Flexible(0.50), InnerInset(btn2), ), ), ) for { select { case <-t.C: w.Invalidate() case e := <-w.Events(): mux.Lock() stime := time.Now() switch e := e.(type) { case app.DestroyEvent: return case app.UpdateEvent: profileNow := false var ms runtime.MemStats if Profile && time.Since(startTime) > time.Second*2 { startTime = time.Now() profileNow = true } if profileNow { if !profiled { prof(0) } runtime.ReadMemStats(&ms) if RecordMallocs { giowrap.RecordMallocs() } } giowrap.Mallocs("pre-reset") ctx.Reset(&e) giowrap.Mallocs("pre-layout") page.Layout(ctx) giowrap.Mallocs("post-layout") ctx.Update() giowrap.Mallocs("post-update") if btn1.Clicked(ctx) { log.Print("Clicked: " + e1.Text()) } if btn2.Clicked(ctx) { log.Print("Clicked: " + e2.Text()) } giowrap.Mallocs("post-clicked()") if profileNow { for _, i := range giowrap.AllMallocs { fmt.Printf("mallocs: %d (%s)\n", i.Value, i.Text) } giowrap.AllMallocs = nil mallocs := ms.Mallocs runtime.ReadMemStats(&ms) mallocs = ms.Mallocs - mallocs log.Printf("Mallocs = %d\n", mallocs) if !profiled { prof(1) profiled = true } } } dur := time.Since(stime).Nanoseconds() frames = frames + 1 frametime = frametime + dur if dur > maxframetime { maxframetime = dur } mux.Unlock() } } } func prof(x int) { names := []string{"memprofile-1.pprof", "memprofile-2.pprof"} f1, err := os.Create(names[x]) if err != nil { log.Fatal("could not create memory profile: ", err) } if err := pprof.WriteHeapProfile(f1); err != nil { log.Fatal("could not write memory profile: ", err) } f1.Close() log.Print("Memory profile written") } // Example showing use of WidgetCombinators in the main loop. func main2() { w := app.NewWindow() regular, err := sfnt.Parse(goregular.TTF) if err != nil { log.Fatal("Cannot parse font.") } ctx := giowrap.NewContext(w) t := time.NewTicker(time.Second / time.Duration(FPS)) e1 := giowrap.NewEditor("text 1", giowrap.Face(ctx.Faces.For(regular, ui.Sp(24)))) e1.Focus() e2 := giowrap.NewEditor("text 2", giowrap.Face(ctx.Faces.For(regular, ui.Sp(24)))) f1 := giowrap.NewFlex(giowrap.Vertical) OuterInset := giowrap.NewInset(giowrap.Size(ui.Dp(10))) InnerInset := giowrap.NewInset(giowrap.Size(ui.Dp(10))) f2 := giowrap.NewFlex(giowrap.Horizontal) btn1 := NewButton(ctx.Faces.For(regular, ui.Sp(24)), "push1", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}) btn2 := NewButton(ctx.Faces.For(regular, ui.Sp(24)), "push2", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}) profiled := false startTime := time.Now() for { select { case <-t.C: w.Invalidate() case e := <-w.Events(): mux.Lock() stime := time.Now() switch e := e.(type) { case app.DestroyEvent: return case app.UpdateEvent: profileNow := false var ms runtime.MemStats if Profile && time.Since(startTime) > time.Second*2 { startTime = time.Now() profileNow = true } if profileNow { if !profiled { prof(0) } runtime.ReadMemStats(&ms) if RecordMallocs { giowrap.RecordMallocs() } } giowrap.Mallocs("pre-reset") ctx.Reset(&e) giowrap.Mallocs("pre-layout") OuterInset( f1( e1, giowrap.Flexible(0.33), InnerInset(e2), giowrap.Flexible(0.67), f2( InnerInset(btn1), giowrap.Flexible(0.50), InnerInset(btn2), ), ), ).Layout(ctx) giowrap.Mallocs("post-layout") ctx.Update() giowrap.Mallocs("post-update") if btn1.Clicked(ctx) { log.Print("Clicked: " + e1.Text()) } if btn2.Clicked(ctx) { log.Print("Clicked: " + e2.Text()) } giowrap.Mallocs("post-clicked()") if profileNow { for _, i := range giowrap.AllMallocs { fmt.Printf("mallocs: %d (%s)\n", i.Value, i.Text) } giowrap.AllMallocs = nil mallocs := ms.Mallocs runtime.ReadMemStats(&ms) mallocs = ms.Mallocs - mallocs log.Printf("Mallocs = %d\n", mallocs) if !profiled { prof(1) profiled = true } } } dur := time.Since(stime).Nanoseconds() frames = frames + 1 frametime = frametime + dur if dur > maxframetime { maxframetime = dur } mux.Unlock() } } } // Example showing widget allocation within the main loop (causes unnecessary // memory allocations) func main3() { w := app.NewWindow() regular, err := sfnt.Parse(goregular.TTF) if err != nil { log.Fatal("Cannot parse font.") } ctx := giowrap.NewContext(w) t := time.NewTicker(time.Second / time.Duration(FPS)) e1 := giowrap.NewEditor("text 1", giowrap.Face(ctx.Faces.For(regular, ui.Sp(24)))) e1.Focus() e2 := giowrap.NewEditor("text 2", giowrap.Face(ctx.Faces.For(regular, ui.Sp(24)))) btn1 := NewButton(ctx.Faces.For(regular, ui.Sp(24)), "push1", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}) btn2 := NewButton(ctx.Faces.For(regular, ui.Sp(24)), "push2", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}) profiled := false startTime := time.Now() for { select { case <-t.C: w.Invalidate() case e := <-w.Events(): stime := time.Now() switch e := e.(type) { case app.DestroyEvent: return case app.UpdateEvent: profileNow := false var ms runtime.MemStats if Profile && time.Since(startTime) > time.Second*2 { profileNow = true startTime = time.Now() } if profileNow { if !profiled { prof(0) } runtime.ReadMemStats(&ms) if RecordMallocs { giowrap.RecordMallocs() } } ctx.Reset(&e) giowrap.NewInset(giowrap.Size(ui.Dp(10)))( giowrap.NewFlex(giowrap.Vertical)( e1, //giowrap.Flexible(0.33), giowrap.NewInset(giowrap.Size(ui.Dp(10)))(e2), //giowrap.Flexible(0.67), giowrap.NewFlex(giowrap.Horizontal)( giowrap.NewInset(giowrap.Size(ui.Dp(10)))(btn1), //giowrap.Flexible(0.50), giowrap.NewInset(giowrap.Size(ui.Dp(10)))(btn2), ), )).Layout(ctx) ctx.Update() if btn1.Clicked(ctx) { log.Print("Clicked: " + e1.Text()) } if btn2.Clicked(ctx) { log.Print("Clicked: " + e2.Text()) } if profileNow { for _, i := range giowrap.AllMallocs { fmt.Printf("mallocs: %d (%s)\n", i.Value, i.Text) } giowrap.AllMallocs = nil mallocs := ms.Mallocs runtime.ReadMemStats(&ms) mallocs = ms.Mallocs - mallocs log.Printf("Mallocs = %d\n", mallocs) if !profiled { prof(1) profiled = true } } } dur := time.Since(stime).Nanoseconds() mux.Lock() frames = frames + 1 frametime = frametime + dur if dur > maxframetime { maxframetime = dur } mux.Unlock() } } } // Example using the base Gio API. func main4() { w := app.NewWindow() q := w.Queue() ops := new(ui.Ops) var faces measure.Faces regular, err := sfnt.Parse(goregular.TTF) if err != nil { log.Fatal("Cannot parse font.") } t := time.NewTicker(time.Second / time.Duration(FPS)) e1 := &text.Editor{ Face: faces.For(regular, ui.Sp(24)), SingleLine: true, } e1.Focus() e1.SetText("text 1") e2 := &text.Editor{ Face: faces.For(regular, ui.Sp(24)), SingleLine: true, } e2.Focus() e2.SetText("text 2") btn1 := &Button{ Face: faces.For(regular, ui.Sp(24)), Label: "push1", } btn2 := &Button{ Face: faces.For(regular, ui.Sp(24)), Label: "push2", } profiled := false startTime := time.Now() for { select { case <-t.C: w.Invalidate() case e := <-w.Events(): stime := time.Now() switch e := e.(type) { case app.DestroyEvent: return case app.UpdateEvent: profileNow := false var ms runtime.MemStats if Profile && time.Since(startTime) > time.Second*2 { profileNow = true startTime = time.Now() } if profileNow { if !profiled { prof(0) } runtime.ReadMemStats(&ms) } c := &e.Config ops.Reset() faces.Reset(c) for ev, ok := btn1.Click.Next(q); ok; ev, ok = btn1.Click.Next(q) { if ev.Type == gesture.TypeClick { log.Print("Clicked: " + e1.Text()) } } for ev, ok := btn2.Click.Next(q); ok; ev, ok = btn2.Click.Next(q) { if ev.Type == gesture.TypeClick { log.Print("Clicked: " + e2.Text()) } } var dims layout.Dimens cs := layout.RigidConstraints(e.Size) { f1 := layout.Flex{Axis: layout.Vertical} ins := layout.UniformInset(ui.Dp(10)) f1.Init(ops, ins.Begin(c, ops, cs)) c1 := f1.End(e1.Layout(c, q, ops, f1.Rigid())) { cs = f1.Flexible(0.33) ins := layout.UniformInset(ui.Dp(10)) dims = ins.End(e2.Layout(c, q, ops, ins.Begin(c, ops, cs))) } c2 := f1.End(dims) { cs = f1.Flexible(0.67) f2 := layout.Flex{Axis: layout.Horizontal} f2.Init(ops, cs) c1 := f2.End(btn1.Layout(c, ops, f2.Rigid())) c2 := f2.End(btn2.Layout(c, ops, f2.Flexible(0.5))) dims = f2.Layout(c1, c2) } c3 := f1.End(dims) dims = ins.End(f1.Layout(c1, c2, c3)) } w.Update(ops) if profileNow { mallocs := ms.Mallocs runtime.ReadMemStats(&ms) mallocs = ms.Mallocs - mallocs log.Printf("Mallocs = %d\n", mallocs) if !profiled { prof(1) profiled = true } } } dur := time.Since(stime).Nanoseconds() mux.Lock() frames = frames + 1 frametime = frametime + dur if dur > maxframetime { maxframetime = dur } mux.Unlock() } } } func layoutRRect(c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimens { r := float32(c.Px(ui.Dp(4))) sz := image.Point{X: cs.Width.Min, Y: cs.Height.Min} w, h := float32(sz.X), float32(sz.Y) giowrap.Rrect(ops, w, h, r, r, r, r) paint.ColorOp{Color: color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}}.Add(ops) paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(ops) return layout.Dimens{Size: sz} } type Button struct { Face text.Face Label string Click gesture.Click } func (b *Button) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimens { ins := layout.UniformInset(ui.Dp(10)) cs = ins.Begin(c, ops, cs) var dims layout.Dimens st := layout.Stack{} st.Init(ops, cs) { cs = st.Rigid() l := text.Label{ Face: b.Face, Text: b.Label, } ins := layout.UniformInset(ui.Dp(4)) dims = ins.End(l.Layout(ops, ins.Begin(c, ops, cs))) pointer.RectAreaOp{image.Rect(0, 0, dims.Size.X, dims.Size.Y)}.Add(ops) b.Click.Add(ops) } c2 := st.End(dims) c1 := st.End(layoutRRect(c, ops, st.Expand())) dims = st.Layout(c1, c2) return ins.End(dims) }