From 9d0a53fc9f8e9c8d8cd72b8482a450667d5705a9 Mon Sep 17 00:00:00 2001 From: Chris Waldon Date: Mon, 27 Mar 2023 12:35:01 -0400 Subject: [PATCH] widget{,/material}: [API] split interactive and non-interactive text widgets This commit separates the types for interactive and non-interactive text within package widget. widget.Selectable is used for all interactive text. widget.Label is used for all non-interactive text. There is no longer a field on widget.Label to provide it with a Selectable. If you want selectable text and are not relying upon the material pacakge API, you need to create widget.Selectables instead of widget.Labels. The material package's LabelStyle API is unchanged. Signed-off-by: Chris Waldon --- widget/label.go | 25 ++++--------------------- widget/material/button.go | 2 +- widget/material/checkable.go | 2 +- widget/material/editor.go | 2 +- widget/material/label.go | 13 +++++++++++-- widget/selectable_test.go | 20 ++++++++------------ widget/text_bench_test.go | 4 ++-- 7 files changed, 28 insertions(+), 40 deletions(-) diff --git a/widget/label.go b/widget/label.go index 6fedbacb..90a6d7d4 100644 --- a/widget/label.go +++ b/widget/label.go @@ -16,7 +16,8 @@ import ( "golang.org/x/image/math/fixed" ) -// Label is a widget for laying out and drawing text. +// Label is a widget for laying out and drawing text. Labels are always +// non-interactive text. They cannot be selected or copied. type Label struct { // Alignment specifies the text alignment. Alignment text.Alignment @@ -25,28 +26,10 @@ type Label struct { // Truncator is the text that will be shown at the end of the final // line if MaxLines is exceeded. Defaults to "…" if empty. Truncator string - // Selectable optionally provides text selection state. If nil, - // text will not be selectable. - Selectable *Selectable } -// Layout the label with the given shaper, font, size, text, and materials. If the Selectable field is -// populated, the label will support text selection. Otherwise, it will be non-interactive. The textMaterial -// and selectionMaterial op.CallOps are responsible for setting the painting material for the text glyphs -// and the text selection rectangles, respectively. -func (l Label) Layout(gtx layout.Context, lt *text.Shaper, font text.Font, size unit.Sp, txt string, textMaterial, selectionMaterial op.CallOp) layout.Dimensions { - if l.Selectable == nil { - return l.layout(gtx, lt, font, size, txt, textMaterial, selectionMaterial) - } - l.Selectable.text.Alignment = l.Alignment - l.Selectable.text.MaxLines = l.MaxLines - l.Selectable.text.Truncator = l.Truncator - l.Selectable.SetText(txt) - return l.Selectable.Layout(gtx, lt, font, size, textMaterial, selectionMaterial) -} - -// layout the text as non-interactive. -func (l Label) layout(gtx layout.Context, lt *text.Shaper, font text.Font, size unit.Sp, txt string, textMaterial, selectionMaterial op.CallOp) layout.Dimensions { +// Layout the label with the given shaper, font, size, text, and material. +func (l Label) Layout(gtx layout.Context, lt *text.Shaper, font text.Font, size unit.Sp, txt string, textMaterial op.CallOp) layout.Dimensions { cs := gtx.Constraints textSize := fixed.I(gtx.Sp(size)) lt.LayoutString(text.Parameters{ diff --git a/widget/material/button.go b/widget/material/button.go index 156d967c..8ef552ea 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -119,7 +119,7 @@ func (b ButtonStyle) Layout(gtx layout.Context) layout.Dimensions { return b.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { colMacro := op.Record(gtx.Ops) paint.ColorOp{Color: b.Color}.Add(gtx.Ops) - return widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text, colMacro.Stop(), op.CallOp{}) + return widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.TextSize, b.Text, colMacro.Stop()) }) }) } diff --git a/widget/material/checkable.go b/widget/material/checkable.go index 76bb7d49..93fb0934 100644 --- a/widget/material/checkable.go +++ b/widget/material/checkable.go @@ -76,7 +76,7 @@ func (c *checkable) layout(gtx layout.Context, checked, hovered bool) layout.Dim return layout.UniformInset(2).Layout(gtx, func(gtx layout.Context) layout.Dimensions { colMacro := op.Record(gtx.Ops) paint.ColorOp{Color: c.Color}.Add(gtx.Ops) - return widget.Label{}.Layout(gtx, c.shaper, c.Font, c.TextSize, c.Label, colMacro.Stop(), op.CallOp{}) + return widget.Label{}.Layout(gtx, c.shaper, c.Font, c.TextSize, c.Label, colMacro.Stop()) }) }), ) diff --git a/widget/material/editor.go b/widget/material/editor.go index 2d3c486a..3d75a0b0 100644 --- a/widget/material/editor.go +++ b/widget/material/editor.go @@ -61,7 +61,7 @@ func (e EditorStyle) Layout(gtx layout.Context) layout.Dimensions { macro := op.Record(gtx.Ops) tl := widget.Label{Alignment: e.Editor.Alignment, MaxLines: maxlines} - dims := tl.Layout(gtx, e.shaper, e.Font, e.TextSize, e.Hint, hintColor, selectionColor) + dims := tl.Layout(gtx, e.shaper, e.Font, e.TextSize, e.Hint, hintColor) call := macro.Stop() if w := dims.Size.X; gtx.Constraints.Min.X < w { diff --git a/widget/material/label.go b/widget/material/label.go index 241da042..cd835bc9 100644 --- a/widget/material/label.go +++ b/widget/material/label.go @@ -14,6 +14,9 @@ import ( "gioui.org/widget" ) +// LabelStyle configures the presentation of text. If the State field is set, the +// label will be laid out as interactive (able to be selected and copied). Otherwise, +// the label will be non-interactive. type LabelStyle struct { // Face defines the text style. Font text.Font @@ -109,6 +112,12 @@ func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions { paint.ColorOp{Color: l.SelectionColor}.Add(gtx.Ops) selectColor := selectColorMacro.Stop() - tl := widget.Label{Alignment: l.Alignment, MaxLines: l.MaxLines, Selectable: l.State} - return tl.Layout(gtx, l.shaper, l.Font, l.TextSize, l.Text, textColor, selectColor) + if l.State != nil { + if l.State.Text() != l.Text { + l.State.SetText(l.Text) + } + return l.State.Layout(gtx, l.shaper, l.Font, l.TextSize, textColor, selectColor) + } + tl := widget.Label{Alignment: l.Alignment, MaxLines: l.MaxLines} + return tl.Layout(gtx, l.shaper, l.Font, l.TextSize, l.Text, textColor) } diff --git a/widget/selectable_test.go b/widget/selectable_test.go index 1941ebe6..c514bfe9 100644 --- a/widget/selectable_test.go +++ b/widget/selectable_test.go @@ -46,9 +46,8 @@ func TestSelectableMove(t *testing.T) { gtx.Queue = newQueue(key.FocusEvent{Focus: true}) s := new(Selectable) - Label{ - Selectable: s, - }.Layout(gtx, cache, text.Font{}, fontSize, str, op.CallOp{}, op.CallOp{}) + s.SetText(str) + s.Layout(gtx, cache, text.Font{}, fontSize, op.CallOp{}, op.CallOp{}) testKey := func(keyName string) { // Select 345 @@ -62,9 +61,8 @@ func TestSelectableMove(t *testing.T) { // Press the key gtx.Queue = newQueue(key.Event{State: key.Press, Name: keyName}) - Label{ - Selectable: s, - }.Layout(gtx, cache, font, fontSize, str, op.CallOp{}, op.CallOp{}) + s.SetText(str) + s.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) if expected, got := "", s.SelectedText(); expected != got { t.Errorf("KeyName %s, expected %q, got %q", keyName, expected, got) @@ -102,12 +100,10 @@ func TestSelectableConfigurations(t *testing.T) { gtx.Constraints.Min = gtx.Constraints.Max } s := new(Selectable) - label := Label{ - Alignment: alignment, - Selectable: s, - } - interactiveDims := label.Layout(gtx, cache, font, fontSize, sentence, op.CallOp{}, op.CallOp{}) - staticDims := label.Layout(gtx, cache, font, fontSize, sentence, op.CallOp{}, op.CallOp{}) + s.text.Alignment = alignment + s.SetText(sentence) + interactiveDims := s.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) + staticDims := Label{Alignment: alignment}.Layout(gtx, cache, font, fontSize, sentence, op.CallOp{}) if interactiveDims != staticDims { t.Errorf("expected consistent dimensions, static returned %#+v, interactive returned %#+v", staticDims, interactiveDims) diff --git a/widget/text_bench_test.go b/widget/text_bench_test.go index 9e2f2764..2fe11d7c 100644 --- a/widget/text_bench_test.go +++ b/widget/text_bench_test.go @@ -97,7 +97,7 @@ func BenchmarkLabelStatic(b *testing.B) { l := Label{} b.ResetTimer() for i := 0; i < b.N; i++ { - l.Layout(gtx, cache, font, fontSize, runesStr, op.CallOp{}, op.CallOp{}) + l.Layout(gtx, cache, font, fontSize, runesStr, op.CallOp{}) if render { win.Frame(gtx.Ops) } @@ -132,7 +132,7 @@ func BenchmarkLabelDynamic(b *testing.B) { a := rand.Intn(len(runes)) b := rand.Intn(len(runes)) runes[a], runes[b] = runes[b], runes[a] - l.Layout(gtx, cache, font, fontSize, string(runes), op.CallOp{}, op.CallOp{}) + l.Layout(gtx, cache, font, fontSize, string(runes), op.CallOp{}) if render { win.Frame(gtx.Ops) }