From 665e23693ff1e1bc4b58db0eadea0f805cd04e4f Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 29 Nov 2021 16:28:19 +0100 Subject: [PATCH] widget: [API] add child widget argument to Clickable.Layout To make the semantic relation between the clickable area and its content clear, it will be important for the clickable clip operation to cover all of the clickable content. API change: users of widget.Clickable must now pass the clickable content to Layout. Signed-off-by: Elias Naur --- widget/bool.go | 4 +- widget/button.go | 11 ++- widget/example_test.go | 5 +- widget/material/button.go | 153 +++++++++++++++++++----------------- widget/material/checkbox.go | 11 +-- widget/material/switch.go | 5 +- 6 files changed, 98 insertions(+), 91 deletions(-) diff --git a/widget/bool.go b/widget/bool.go index a15b50c1..1a52ece1 100644 --- a/widget/bool.go +++ b/widget/bool.go @@ -36,8 +36,8 @@ func (b *Bool) History() []Press { return b.clk.History() } -func (b *Bool) Layout(gtx layout.Context) layout.Dimensions { - dims := b.clk.Layout(gtx) +func (b *Bool) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { + dims := b.clk.Layout(gtx, w) for b.clk.Clicked() { b.Value = !b.Value b.changed = true diff --git a/widget/button.go b/widget/button.go index 17e00a44..0775462e 100644 --- a/widget/button.go +++ b/widget/button.go @@ -10,6 +10,7 @@ import ( "gioui.org/gesture" "gioui.org/io/key" "gioui.org/layout" + "gioui.org/op" "gioui.org/op/clip" ) @@ -90,10 +91,14 @@ func (b *Clickable) History() []Press { } // Layout and update the button state -func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions { +func (b *Clickable) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { b.update(gtx) - defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop() + m := op.Record(gtx.Ops) + dims := w(gtx) + c := m.Stop() + defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop() b.click.Add(gtx.Ops) + c.Add(gtx.Ops) for len(b.history) > 0 { c := b.history[0] if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second { @@ -102,7 +107,7 @@ func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions { n := copy(b.history, b.history[1:]) b.history = b.history[:n] } - return layout.Dimensions{Size: gtx.Constraints.Min} + return dims } // update the button state by processing events. diff --git a/widget/example_test.go b/widget/example_test.go index c4714061..f72c186c 100644 --- a/widget/example_test.go +++ b/widget/example_test.go @@ -28,11 +28,12 @@ func ExampleClickable_passthrough() { // widget lays out two buttons on top of each other. widget := func() { - button1.Layout(gtx) + content := func(gtx layout.Context) layout.Dimensions { return layout.Dimensions{Size: gtx.Constraints.Min} } + button1.Layout(gtx, content) // button2 completely covers button1, but pass-through allows pointer // events to pass through to button1. defer pointer.PassOp{}.Push(gtx.Ops).Pop() - button2.Layout(gtx) + button2.Layout(gtx, content) } // The first layout and call to Frame declare the Clickable handlers diff --git a/widget/material/button.go b/widget/material/button.go index 46965369..0136344b 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -86,17 +86,18 @@ func IconButton(th *Theme, button *widget.Clickable, icon *widget.Icon) IconButt // Clickable lays out a rectangular clickable widget without further // decoration. func Clickable(gtx layout.Context, button *widget.Clickable, w layout.Widget) layout.Dimensions { - return layout.Stack{}.Layout(gtx, - layout.Expanded(button.Layout), - layout.Expanded(func(gtx layout.Context) layout.Dimensions { - defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop() - for _, c := range button.History() { - drawInk(gtx, c) - } - return layout.Dimensions{Size: gtx.Constraints.Min} - }), - layout.Stacked(w), - ) + return button.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Stack{}.Layout(gtx, + layout.Expanded(func(gtx layout.Context) layout.Dimensions { + defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop() + for _, c := range button.History() { + drawInk(gtx, c) + } + return layout.Dimensions{Size: gtx.Constraints.Min} + }), + layout.Stacked(w), + ) + }) } func (b ButtonStyle) Layout(gtx layout.Context) layout.Dimensions { @@ -114,74 +115,78 @@ func (b ButtonStyle) Layout(gtx layout.Context) layout.Dimensions { func (b ButtonLayoutStyle) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions { min := gtx.Constraints.Min - return layout.Stack{Alignment: layout.Center}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { - rr := float32(gtx.Px(b.CornerRadius)) - defer clip.UniformRRect(f32.Rectangle{Max: f32.Point{ - X: float32(gtx.Constraints.Min.X), - Y: float32(gtx.Constraints.Min.Y), - }}, rr).Push(gtx.Ops).Pop() - background := b.Background - switch { - case gtx.Queue == nil: - background = f32color.Disabled(b.Background) - case b.Button.Hovered(): - background = f32color.Hovered(b.Background) - } - paint.Fill(gtx.Ops, background) - for _, c := range b.Button.History() { - drawInk(gtx, c) - } - return layout.Dimensions{Size: gtx.Constraints.Min} - }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { - gtx.Constraints.Min = min - return layout.Center.Layout(gtx, w) - }), - layout.Expanded(b.Button.Layout), - ) + return b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Stack{Alignment: layout.Center}.Layout(gtx, + layout.Expanded(func(gtx layout.Context) layout.Dimensions { + rr := float32(gtx.Px(b.CornerRadius)) + defer clip.UniformRRect(f32.Rectangle{Max: f32.Point{ + X: float32(gtx.Constraints.Min.X), + Y: float32(gtx.Constraints.Min.Y), + }}, rr).Push(gtx.Ops).Pop() + background := b.Background + switch { + case gtx.Queue == nil: + background = f32color.Disabled(b.Background) + case b.Button.Hovered(): + background = f32color.Hovered(b.Background) + } + paint.Fill(gtx.Ops, background) + for _, c := range b.Button.History() { + drawInk(gtx, c) + } + return layout.Dimensions{Size: gtx.Constraints.Min} + }), + layout.Stacked(func(gtx layout.Context) layout.Dimensions { + gtx.Constraints.Min = min + return layout.Center.Layout(gtx, w) + }), + ) + }) } func (b IconButtonStyle) Layout(gtx layout.Context) layout.Dimensions { - return layout.Stack{Alignment: layout.Center}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { - sizex, sizey := gtx.Constraints.Min.X, gtx.Constraints.Min.Y - sizexf, sizeyf := float32(sizex), float32(sizey) - rr := (sizexf + sizeyf) * .25 - defer clip.UniformRRect(f32.Rectangle{ - Max: f32.Point{X: sizexf, Y: sizeyf}, - }, rr).Push(gtx.Ops).Pop() - background := b.Background - switch { - case gtx.Queue == nil: - background = f32color.Disabled(b.Background) - case b.Button.Hovered(): - background = f32color.Hovered(b.Background) - } - paint.Fill(gtx.Ops, background) - for _, c := range b.Button.History() { - drawInk(gtx, c) - } - return layout.Dimensions{Size: gtx.Constraints.Min} - }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { - return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - size := gtx.Px(b.Size) - if b.Icon != nil { - gtx.Constraints.Min = image.Point{X: size} - b.Icon.Layout(gtx, b.Color) + m := op.Record(gtx.Ops) + dims := b.Button.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Stack{Alignment: layout.Center}.Layout(gtx, + layout.Expanded(func(gtx layout.Context) layout.Dimensions { + sizex, sizey := gtx.Constraints.Min.X, gtx.Constraints.Min.Y + sizexf, sizeyf := float32(sizex), float32(sizey) + rr := (sizexf + sizeyf) * .25 + defer clip.UniformRRect(f32.Rectangle{ + Max: f32.Point{X: sizexf, Y: sizeyf}, + }, rr).Push(gtx.Ops).Pop() + background := b.Background + switch { + case gtx.Queue == nil: + background = f32color.Disabled(b.Background) + case b.Button.Hovered(): + background = f32color.Hovered(b.Background) } - return layout.Dimensions{ - Size: image.Point{X: size, Y: size}, + paint.Fill(gtx.Ops, background) + for _, c := range b.Button.History() { + drawInk(gtx, c) } - }) - }), - layout.Expanded(func(gtx layout.Context) layout.Dimensions { - bounds := f32.Rectangle{Max: layout.FPt(gtx.Constraints.Min)} - defer clip.Ellipse(bounds).Push(gtx.Ops).Pop() - return b.Button.Layout(gtx) - }), - ) + return layout.Dimensions{Size: gtx.Constraints.Min} + }), + layout.Stacked(func(gtx layout.Context) layout.Dimensions { + return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + size := gtx.Px(b.Size) + if b.Icon != nil { + gtx.Constraints.Min = image.Point{X: size} + b.Icon.Layout(gtx, b.Color) + } + return layout.Dimensions{ + Size: image.Point{X: size, Y: size}, + } + }) + }), + ) + }) + c := m.Stop() + bounds := f32.Rectangle{Max: layout.FPt(dims.Size)} + defer clip.Ellipse(bounds).Push(gtx.Ops).Pop() + c.Add(gtx.Ops) + return dims } func drawInk(gtx layout.Context, c widget.Press) { diff --git a/widget/material/checkbox.go b/widget/material/checkbox.go index 96238459..505966ee 100644 --- a/widget/material/checkbox.go +++ b/widget/material/checkbox.go @@ -3,10 +3,7 @@ package material import ( - "image" - "gioui.org/layout" - "gioui.org/op/clip" "gioui.org/unit" "gioui.org/widget" ) @@ -34,9 +31,7 @@ func CheckBox(th *Theme, checkBox *widget.Bool, label string) CheckBoxStyle { // Layout updates the checkBox and displays it. func (c CheckBoxStyle) Layout(gtx layout.Context) layout.Dimensions { - dims := c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered()) - defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop() - gtx.Constraints.Min = dims.Size - c.CheckBox.Layout(gtx) - return dims + return c.CheckBox.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return c.layout(gtx, c.CheckBox.Value, c.CheckBox.Hovered()) + }) } diff --git a/widget/material/switch.go b/widget/material/switch.go index 1ba182aa..0853b630 100644 --- a/widget/material/switch.go +++ b/widget/material/switch.go @@ -124,8 +124,9 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions { defer op.Offset(clickOff).Push(gtx.Ops).Pop() sz := image.Pt(clickSize, clickSize) defer clip.Ellipse(f32.Rectangle{Max: layout.FPt(sz)}).Push(gtx.Ops).Pop() - gtx.Constraints.Min = sz - s.Switch.Layout(gtx) + s.Switch.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Dimensions{Size: sz} + }) dims := image.Point{X: trackWidth, Y: thumbSize} return layout.Dimensions{Size: dims}