Files
gio/widget/material/label.go
T
Chris Waldon 959f5889a1 go.*,text,widget{,/material}: implement text truncators
This commit adds support for the idea of a text "Truncator", a string
that is shown at the end of truncated text to indicate that it has been
shortened because it would not fit within the requested number of lines.

When specifying a maximum number of lines, a truncator symbol is always
used. If the user does not provide one, the rune `…` is used. This
requirement results in a better user experience and significantly simpler
code, as we can rely upon the presence of one or more truncator glyphs in
the output glyph stream when truncation has occurred.

When interacting with truncated text, the truncator glyphs all act as
a single, indivisible unit. They can be selected or not, and if selected
they act as the entire contents of the truncated portion of the text.
This means that copying all of a truncated label will copy the entire
label text content, with the truncator symbol not appearing at all.

Concretely, the exposed text API now accepts a Truncator string in
text.Parameters, and there is a new glyph flag FlagTruncator which indicates
that the glyph is part of the truncator run. The truncator run will only
have a single FlagClusterBreak (even if the run would usually have many),
and the glyph with both FlagClusterBreak and FlagTruncator will have the
quantity of truncated runes in its Runes field. This necessitated increasing
the size of the Runes field from a byte to an int, as it's theoretically possible
for quite a lot of text to be truncated.

This commit necessarily bumps our go-text/typesetting dependency to the version
exposing truncation in the exported API.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-03-28 09:25:28 -06:00

115 lines
2.9 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package material
import (
"image/color"
"gioui.org/internal/f32color"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget"
)
type LabelStyle struct {
// Face defines the text style.
Font text.Font
// Color is the text color.
Color color.NRGBA
// SelectionColor is the color of the background for selected text.
SelectionColor color.NRGBA
// Alignment specify the text alignment.
Alignment text.Alignment
// MaxLines limits the number of lines. Zero means no limit.
MaxLines int
// 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
Text string
TextSize unit.Sp
shaper *text.Shaper
State *widget.Selectable
}
func H1(th *Theme, txt string) LabelStyle {
label := Label(th, th.TextSize*96.0/16.0, txt)
label.Font.Weight = text.Light
return label
}
func H2(th *Theme, txt string) LabelStyle {
label := Label(th, th.TextSize*60.0/16.0, txt)
label.Font.Weight = text.Light
return label
}
func H3(th *Theme, txt string) LabelStyle {
return Label(th, th.TextSize*48.0/16.0, txt)
}
func H4(th *Theme, txt string) LabelStyle {
return Label(th, th.TextSize*34.0/16.0, txt)
}
func H5(th *Theme, txt string) LabelStyle {
return Label(th, th.TextSize*24.0/16.0, txt)
}
func H6(th *Theme, txt string) LabelStyle {
label := Label(th, th.TextSize*20.0/16.0, txt)
label.Font.Weight = text.Medium
return label
}
func Subtitle1(th *Theme, txt string) LabelStyle {
return Label(th, th.TextSize*16.0/16.0, txt)
}
func Subtitle2(th *Theme, txt string) LabelStyle {
label := Label(th, th.TextSize*14.0/16.0, txt)
label.Font.Weight = text.Medium
return label
}
func Body1(th *Theme, txt string) LabelStyle {
return Label(th, th.TextSize, txt)
}
func Body2(th *Theme, txt string) LabelStyle {
return Label(th, th.TextSize*14.0/16.0, txt)
}
func Caption(th *Theme, txt string) LabelStyle {
return Label(th, th.TextSize*12.0/16.0, txt)
}
func Overline(th *Theme, txt string) LabelStyle {
return Label(th, th.TextSize*10.0/16.0, txt)
}
func Label(th *Theme, size unit.Sp, txt string) LabelStyle {
return LabelStyle{
Text: txt,
Color: th.Palette.Fg,
SelectionColor: f32color.MulAlpha(th.Palette.ContrastBg, 0x60),
TextSize: size,
shaper: th.Shaper,
}
}
func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
textColorMacro := op.Record(gtx.Ops)
paint.ColorOp{Color: l.Color}.Add(gtx.Ops)
textColor := textColorMacro.Stop()
selectColorMacro := op.Record(gtx.Ops)
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)
}