package giowrap import ( "image/color" "log" "gioui.org/ui" "gioui.org/ui/app" "gioui.org/ui/input" "gioui.org/ui/layout" "gioui.org/ui/measure" "gioui.org/ui/text" "gioui.org/ui/f32" gdraw "gioui.org/ui/draw" "gioui.org/ui/gesture" "gioui.org/ui/pointer" ) type Context struct { Faces measure.Faces w *app.Window c *app.Config q input.Queue ops *ui.Ops cs layout.Constraints dims layout.Dimens extra map[string]interface{} } func NewContext(w *app.Window) Context { return Context{ w: w, ops: new(ui.Ops), q: w.Queue(), extra: make(map[string]interface{}), } } func (ctx Context) Reset(e app.DrawEvent) Context { ctx.c = &e.Config ctx.ops.Reset() ctx.cs = layout.RigidConstraints(e.Size) ctx.Faces.Reset(ctx.c) return ctx } func (ctx Context) Draw() { ctx.w.Draw(ctx.ops) } type Layout func(Context) Context type Widget interface { Layout(Context) Context } type WidgetCombinator func(...Widget) Widget type Label struct { l *text.Label } func NewLabel(face text.Face, t string, alignment text.Alignment) *Label { ret := &Label{} ret.l = &text.Label{ Face: face, Text: t, Alignment: alignment, } return ret } func (l *Label) Layout(ctx Context) Context { ctx.dims = l.l.Layout(ctx.ops, ctx.cs) return ctx } type Editor struct { e *text.Editor } func NewEditor(face text.Face, singleline bool) *Editor { ret := &Editor{} ret.e = &text.Editor{ Face: face, SingleLine: singleline } return ret } func (e *Editor) Layout(ctx Context) Context { ctx.dims = e.e.Layout(ctx.c, ctx.q, ctx.ops, ctx.cs) return ctx } func (e *Editor) Text() string { return e.e.Text() } func (e *Editor) SetText(s string) { e.e.SetText(s) } func (e *Editor) Focus() { e.e.Focus() } type fWidget struct { l Layout } func NewfWidget(l Layout) fWidget { return fWidget{ l: l } } func (fw fWidget) Layout(ctx Context) Context { return fw.l(ctx) } type Flex WidgetCombinator // This "Widget" does nothing except set the Flexible key of ctx.extra. func Flexible(v float32) Widget { return NewfWidget(func(ctx Context) Context { ctx.extra["Flexible"] = v return ctx }) } // NewFlex returns a WidgetCombinator that wraps the layout.Flex element. func NewFlex(axis layout.Axis, mainAxisAlignment layout.MainAxisAlignment, crossAxisAlignment layout.CrossAxisAlignment) Flex { f := layout.Flex{ Axis: axis, MainAxisAlignment: mainAxisAlignment, CrossAxisAlignment: crossAxisAlignment, } return func(ws ...Widget) Widget { return NewfWidget(func(ctx Context) Context { f.Init(ctx.ops, ctx.cs) fcs := make([]layout.FlexChild,len(ws)) for i, w := range ws { if v,ok := ctx.extra["Flexible"]; ok { switch v := v.(type) { case float32: ctx.cs = f.Flexible(v) default: log.Fatal("Type error") } } else { ctx.cs = f.Rigid() } ctx = w.Layout(ctx) fcs[i] = f.End(ctx.dims) } ctx.dims = f.Layout(fcs...) delete(ctx.extra, "Flexible") return ctx }) } } //NewInset returns a WidgetCombinator that wraps the layout.Inset element. func NewInset(top, right, bottom, left ui.Value) WidgetCombinator { ins := layout.Inset{ Top: top, Right: right, Bottom: bottom, Left: left } return func(ws ...Widget) Widget { return NewfWidget(func(ctx Context) Context { ctx.cs = ins.Begin(ctx.c, ctx.ops, ctx.cs) var dims layout.Dimens for _, w := range ws { ctx = w.Layout(ctx) } ctx.dims = ins.End(dims) return ctx }) } } type Background struct { Color color.RGBA Radius ui.Value Inset layout.Inset macro ui.MacroOp } type Enclosure interface { Begin(Context) Context End(Context) Context } func Enclose(e Enclosure, ws ...Widget) Widget { return NewfWidget(func(ctx Context) Context { ctx = e.Begin(ctx) for _,w := range ws { ctx = w.Layout(ctx) } ctx = e.End(ctx) return ctx }) } func NewBackground(color color.RGBA, radius ui.Value) WidgetCombinator { bg := &Background{ Color: color, Radius: radius, Inset: layout.UniformInset(radius), // FIXME: need to be able to } // do math ops on Values return func(ws ...Widget) Widget { return Enclose(bg, ws...) } } func (bg *Background) Begin(ctx Context) Context { bg.macro.Record(ctx.ops) ctx.cs = bg.Inset.Begin(ctx.c, ctx.ops, ctx.cs) return ctx } func (bg *Background) End(ctx Context) Context { ctx.dims = bg.Inset.End(ctx.dims) bg.macro.Stop() //var stack ui.StackOp w, h := float32(ctx.dims.Size.X), float32(ctx.dims.Size.Y) if r := float32(ctx.c.Px(bg.Radius)); r > 0 { if r > w / 2 { r = w / 2 } if r > h / 2 { r = h / 2 } rrect(ctx.ops, w, h, r, r, r, r) } gdraw.ColorOp{Color: bg.Color}.Add(ctx.ops) gdraw.DrawOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(ctx.ops) bg.macro.Add(ctx.ops) return ctx } // https://pomax.github.io/bezierinfo/#circles_cubic. func rrect(ops *ui.Ops, width, height, se, sw, nw, ne float32) { w, h := float32(width), float32(height) const c = 0.55228475 // 4*(sqrt(2)-1)/3 var b gdraw.PathBuilder b.Init(ops) b.Move(f32.Point{X: w, Y: h - se}) b.Cube(f32.Point{X: 0, Y: se * c}, f32.Point{X: -se + se*c, Y: se}, f32.Point{X: -se, Y: se}) // SE b.Line(f32.Point{X: sw - w + se, Y: 0}) b.Cube(f32.Point{X: -sw * c, Y: 0}, f32.Point{X: -sw, Y: -sw + sw*c}, f32.Point{X: -sw, Y: -sw}) // SW b.Line(f32.Point{X: 0, Y: nw - h + sw}) b.Cube(f32.Point{X: 0, Y: -nw * c}, f32.Point{X: nw - nw*c, Y: -nw}, f32.Point{X: nw, Y: -nw}) // NW b.Line(f32.Point{X: w - ne - nw, Y: 0}) b.Cube(f32.Point{X: ne * c, Y: 0}, f32.Point{X: ne, Y: ne - ne*c}, f32.Point{X: ne, Y: ne}) // NE b.End() } //cWidget is a clickable Widget that provides the Clicked() method. type cWidget struct { w Widget click *gesture.Click } func (w cWidget) Layout(ctx Context) Context { ctx = w.w.Layout(ctx) pointer.RectAreaOp{Size: ctx.dims.Size}.Add(ctx.ops) w.click.Add(ctx.ops) return ctx } func (w cWidget) Clicked(ctx Context) bool { for _,e := range w.click.Events(ctx.q) { if e.Type == gesture.TypeClick { return true } } return false } //Clickable converts any Widget into a clickable Widget. func Clickable(w Widget) cWidget { return cWidget{ w: w, click: new(gesture.Click) } }