M text/gotext.go => text/gotext.go +11 -11
@@ 401,7 401,7 @@ func (s *shaperImpl) shapeText(faces []font.Face, ppem fixed.Int26_6, lc system.
}
// shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format.
-func (s *shaperImpl) shapeAndWrapText(faces []font.Face, params Parameters, maxWidth int, lc system.Locale, txt []rune) (_ []shaping.Line, truncated int) {
+func (s *shaperImpl) shapeAndWrapText(faces []font.Face, params Parameters, txt []rune) (_ []shaping.Line, truncated int) {
wc := shaping.WrapConfig{
TruncateAfterLines: params.MaxLines,
}
@@ 411,10 411,10 @@ func (s *shaperImpl) shapeAndWrapText(faces []font.Face, params Parameters, maxW
}
// We only permit a single run as the truncator, regardless of whether more were generated.
// Just use the first one.
- wc.Truncator = s.shapeText(faces, params.PxPerEm, lc, []rune(params.Truncator))[0]
+ wc.Truncator = s.shapeText(faces, params.PxPerEm, params.Locale, []rune(params.Truncator))[0]
}
// Wrap outputs into lines.
- return s.wrapper.WrapParagraph(wc, maxWidth, txt, s.shapeText(faces, params.PxPerEm, lc, txt)...)
+ return s.wrapper.WrapParagraph(wc, params.MaxWidth, txt, s.shapeText(faces, params.PxPerEm, params.Locale, txt)...)
}
// replaceControlCharacters replaces problematic unicode
@@ 443,17 443,17 @@ func replaceControlCharacters(in []rune) []rune {
}
// Layout shapes and wraps the text, and returns the result in Gio's shaped text format.
-func (s *shaperImpl) LayoutString(params Parameters, minWidth, maxWidth int, lc system.Locale, txt string) document {
- return s.LayoutRunes(params, minWidth, maxWidth, lc, []rune(txt))
+func (s *shaperImpl) LayoutString(params Parameters, txt string) document {
+ return s.LayoutRunes(params, []rune(txt))
}
// Layout shapes and wraps the text, and returns the result in Gio's shaped text format.
-func (s *shaperImpl) Layout(params Parameters, minWidth, maxWidth int, lc system.Locale, txt io.RuneReader) document {
+func (s *shaperImpl) Layout(params Parameters, txt io.RuneReader) document {
s.scratchRunes = s.scratchRunes[:0]
for r, _, err := txt.ReadRune(); err != nil; r, _, err = txt.ReadRune() {
s.scratchRunes = append(s.scratchRunes, r)
}
- return s.LayoutRunes(params, minWidth, maxWidth, lc, s.scratchRunes)
+ return s.LayoutRunes(params, s.scratchRunes)
}
func calculateYOffsets(lines []line) {
@@ 468,12 468,12 @@ func calculateYOffsets(lines []line) {
}
// LayoutRunes shapes and wraps the text, and returns the result in Gio's shaped text format.
-func (s *shaperImpl) LayoutRunes(params Parameters, minWidth, maxWidth int, lc system.Locale, txt []rune) document {
+func (s *shaperImpl) LayoutRunes(params Parameters, txt []rune) document {
hasNewline := len(txt) > 0 && txt[len(txt)-1] == '\n'
if hasNewline {
txt = txt[:len(txt)-1]
}
- ls, truncated := s.shapeAndWrapText(s.orderer.sortedFacesForStyle(params.Font), params, maxWidth, lc, replaceControlCharacters(txt))
+ ls, truncated := s.shapeAndWrapText(s.orderer.sortedFacesForStyle(params.Font), params, replaceControlCharacters(txt))
if truncated > 0 && hasNewline {
// We've truncated the newline, since it was at the end and we've truncated some amount of runes
@@ 484,7 484,7 @@ func (s *shaperImpl) LayoutRunes(params Parameters, minWidth, maxWidth int, lc s
// Convert to Lines.
textLines := make([]line, len(ls))
for i := range ls {
- otLine := toLine(&s.orderer, ls[i], lc.Direction)
+ otLine := toLine(&s.orderer, ls[i], params.Locale.Direction)
isFinalLine := i == len(ls)-1
if isFinalLine && hasNewline {
// If there was a trailing newline update the rune counts to include
@@ 536,7 536,7 @@ func (s *shaperImpl) LayoutRunes(params Parameters, minWidth, maxWidth int, lc s
return document{
lines: textLines,
alignment: params.Alignment,
- alignWidth: alignWidth(minWidth, textLines),
+ alignWidth: alignWidth(params.MinWidth, textLines),
}
}
M text/gotext_test.go => text/gotext_test.go +38 -10
@@ 37,7 37,11 @@ func TestEmptyString(t *testing.T) {
ltrFace, _ := opentype.Parse(goregular.TTF)
shaper := testShaper(ltrFace)
- lines := shaper.LayoutRunes(Parameters{PxPerEm: ppem}, 0, 2000, english, []rune{})
+ lines := shaper.LayoutRunes(Parameters{
+ PxPerEm: ppem,
+ MaxWidth: 2000,
+ Locale: english,
+ }, []rune{})
if len(lines.lines) == 0 {
t.Fatalf("Layout returned no lines for empty string; expected 1")
}
@@ 110,7 114,11 @@ func TestShapingAlignWidth(t *testing.T) {
},
} {
t.Run(tc.name, func(t *testing.T) {
- lines := shaper.LayoutString(Parameters{PxPerEm: ppem}, tc.minWidth, tc.maxWidth, english, tc.str)
+ lines := shaper.LayoutString(Parameters{PxPerEm: ppem,
+ MinWidth: tc.minWidth,
+ MaxWidth: tc.maxWidth,
+ Locale: english,
+ }, tc.str)
if lines.alignWidth != tc.expected {
t.Errorf("expected line alignWidth to be %d, got %d", tc.expected, lines.alignWidth)
}
@@ 155,7 163,11 @@ func TestNewlineSynthesis(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
- doc := shaper.LayoutRunes(Parameters{PxPerEm: ppem}, 0, 200, tc.locale, []rune(tc.txt))
+ doc := shaper.LayoutRunes(Parameters{
+ PxPerEm: ppem,
+ MaxWidth: 200,
+ Locale: tc.locale,
+ }, []rune(tc.txt))
for lineIdx, line := range doc.lines {
lastRunIdx := len(line.runs) - 1
lastRun := line.runs[lastRunIdx]
@@ 256,8 268,16 @@ func makeTestText(shaper *shaperImpl, primaryDir system.TextDirection, fontSize,
rtlSource = string(complexRunes[:runeLimit])
}
}
- simpleText, _ := shaper.shapeAndWrapText(shaper.orderer.sortedFacesForStyle(Font{}), Parameters{PxPerEm: fixed.I(fontSize)}, lineWidth, locale, []rune(simpleSource))
- complexText, _ := shaper.shapeAndWrapText(shaper.orderer.sortedFacesForStyle(Font{}), Parameters{PxPerEm: fixed.I(fontSize)}, lineWidth, locale, []rune(complexSource))
+ simpleText, _ := shaper.shapeAndWrapText(shaper.orderer.sortedFacesForStyle(Font{}), Parameters{
+ PxPerEm: fixed.I(fontSize),
+ MaxWidth: lineWidth,
+ Locale: locale,
+ }, []rune(simpleSource))
+ complexText, _ := shaper.shapeAndWrapText(shaper.orderer.sortedFacesForStyle(Font{}), Parameters{
+ PxPerEm: fixed.I(fontSize),
+ MaxWidth: lineWidth,
+ Locale: locale,
+ }, []rune(complexSource))
testShaper(rtlFace, ltrFace)
return simpleText, complexText
}
@@ 536,7 556,11 @@ func FuzzLayout(f *testing.F) {
if fontSize < 1 {
fontSize = 1
}
- lines := shaper.LayoutRunes(Parameters{PxPerEm: fixed.I(int(fontSize))}, 0, int(width), locale, []rune(txt))
+ lines := shaper.LayoutRunes(Parameters{
+ PxPerEm: fixed.I(int(fontSize)),
+ MaxWidth: int(width),
+ Locale: locale,
+ }, []rune(txt))
validateLines(t, lines.lines, len([]rune(txt)))
})
}
@@ 595,11 619,15 @@ func TestTextAppend(t *testing.T) {
shaper := testShaper(ltrFace, rtlFace)
text1 := shaper.LayoutString(Parameters{
- PxPerEm: fixed.I(14),
- }, 0, 200, english, "د عرمثال dstي met لم aqل جدmوpمg lرe dرd لو عل ميrةsdiduntut lab renنيتذدagلaaiua.ئPocttأior رادرsاي mيrbلmnonaيdتد ماةعcلخ.")
+ PxPerEm: fixed.I(14),
+ MaxWidth: 200,
+ Locale: english,
+ }, "د عرمثال dstي met لم aqل جدmوpمg lرe dرd لو عل ميrةsdiduntut lab renنيتذدagلaaiua.ئPocttأior رادرsاي mيrbلmnonaيdتد ماةعcلخ.")
text2 := shaper.LayoutString(Parameters{
- PxPerEm: fixed.I(14),
- }, 0, 200, english, "د عرمثال dstي met لم aqل جدmوpمg lرe dرd لو عل ميrةsdiduntut lab renنيتذدagلaaiua.ئPocttأior رادرsاي mيrbلmnonaيdتد ماةعcلخ.")
+ PxPerEm: fixed.I(14),
+ MaxWidth: 200,
+ Locale: english,
+ }, "د عرمثال dstي met لم aqل جدmوpمg lرe dرd لو عل ميrةsdiduntut lab renنيتذدagلaaiua.ئPocttأior رادرsاي mيrbلmnonaيdتد ماةعcلخ.")
text1.append(text2)
curY := math.MinInt
M text/shaper.go => text/shaper.go +19 -14
@@ 31,6 31,11 @@ type Parameters struct {
// can currently ohly happen if MaxLines is nonzero and the text on the final line is
// truncated.
Truncator string
+ // MinWidth and MaxWidth provide the minimum and maximum horizontal space constraints
+ // for the shaped text.
+ MinWidth, MaxWidth int
+ // Locale provides primary direction and language information for the shaped text.
+ Locale system.Locale
}
// A FontFace is a Font and a matching Face.
@@ 194,13 199,13 @@ func NewShaper(collection []FontFace) *Shaper {
// Layout text from an io.Reader according to a set of options. Results can be retrieved by
// iteratively calling NextGlyph.
-func (l *Shaper) Layout(params Parameters, minWidth, maxWidth int, lc system.Locale, txt io.Reader) {
- l.layoutText(params, minWidth, maxWidth, lc, bufio.NewReader(txt), "")
+func (l *Shaper) Layout(params Parameters, txt io.Reader) {
+ l.layoutText(params, bufio.NewReader(txt), "")
}
// LayoutString is Layout for strings.
-func (l *Shaper) LayoutString(params Parameters, minWidth, maxWidth int, lc system.Locale, str string) {
- l.layoutText(params, minWidth, maxWidth, lc, nil, str)
+func (l *Shaper) LayoutString(params Parameters, str string) {
+ l.layoutText(params, nil, str)
}
func (l *Shaper) reset(align Alignment) {
@@ 213,10 218,10 @@ func (l *Shaper) reset(align Alignment) {
// layoutText lays out a large text document by breaking it into paragraphs and laying
// out each of them separately. This allows the shaping results to be cached independently
// by paragraph. Only one of txt and str should be provided.
-func (l *Shaper) layoutText(params Parameters, minWidth, maxWidth int, lc system.Locale, txt io.RuneReader, str string) {
+func (l *Shaper) layoutText(params Parameters, txt io.RuneReader, str string) {
l.reset(params.Alignment)
if txt == nil && len(str) == 0 {
- l.txt.append(l.layoutParagraph(params, minWidth, maxWidth, lc, "", nil))
+ l.txt.append(l.layoutParagraph(params, "", nil))
return
}
truncating := params.MaxLines > 0
@@ 250,7 255,7 @@ func (l *Shaper) layoutText(params Parameters, minWidth, maxWidth int, lc system
done = endByte == len(str)
}
if startByte != endByte || (len(l.paragraph) > 0 || len(l.txt.lines) == 0) {
- lines := l.layoutParagraph(params, minWidth, maxWidth, lc, str[startByte:endByte], l.paragraph)
+ lines := l.layoutParagraph(params, str[startByte:endByte], l.paragraph)
if truncating {
params.MaxLines -= len(lines.lines)
if params.MaxLines == 0 {
@@ 285,7 290,7 @@ func (l *Shaper) layoutText(params Parameters, minWidth, maxWidth int, lc system
}
}
-func (l *Shaper) layoutParagraph(params Parameters, minWidth, maxWidth int, lc system.Locale, asStr string, asRunes []rune) document {
+func (l *Shaper) layoutParagraph(params Parameters, asStr string, asRunes []rune) document {
if l == nil {
return document{}
}
@@ 294,14 299,14 @@ func (l *Shaper) layoutParagraph(params Parameters, minWidth, maxWidth int, lc s
}
// Alignment is not part of the cache key because changing it does not impact shaping.
lk := layoutKey{
- truncator: params.Truncator,
ppem: params.PxPerEm,
- maxWidth: maxWidth,
- minWidth: minWidth,
+ maxWidth: params.MaxWidth,
+ minWidth: params.MinWidth,
maxLines: params.MaxLines,
- str: asStr,
- locale: lc,
+ truncator: params.Truncator,
+ locale: params.Locale,
font: params.Font,
+ str: asStr,
}
if l, ok := l.layoutCache.Get(lk); ok {
return l
@@ 309,7 314,7 @@ func (l *Shaper) layoutParagraph(params Parameters, minWidth, maxWidth int, lc s
if len(asRunes) == 0 && len(asStr) > 0 {
asRunes = []rune(asStr)
}
- lines := l.shaper.LayoutRunes(params, minWidth, maxWidth, lc, asRunes)
+ lines := l.shaper.LayoutRunes(params, asRunes)
l.layoutCache.Put(lk, lines)
return lines
}
M text/shaper_test.go => text/shaper_test.go +38 -14
@@ 25,7 25,10 @@ func TestWrappingTruncation(t *testing.T) {
cache.LayoutString(Parameters{
Alignment: Middle,
PxPerEm: fixed.I(10),
- }, 200, 200, english, textInput)
+ MinWidth: 200,
+ MaxWidth: 200,
+ Locale: english,
+ }, textInput)
untruncatedCount := len(cache.txt.lines)
for i := untruncatedCount + 1; i > 0; i-- {
@@ 34,7 37,10 @@ func TestWrappingTruncation(t *testing.T) {
Alignment: Middle,
PxPerEm: fixed.I(10),
MaxLines: i,
- }, 200, 200, english, textInput)
+ MinWidth: 200,
+ MaxWidth: 200,
+ Locale: english,
+ }, textInput)
lineCount := 0
lastGlyphWasLineBreak := false
glyphs := []Glyph{}
@@ 130,7 136,10 @@ func TestShapingNewlineHandling(t *testing.T) {
cache.LayoutString(Parameters{
Alignment: Middle,
PxPerEm: fixed.I(10),
- }, 200, 200, english, tc.textInput)
+ MinWidth: 200,
+ MaxWidth: 200,
+ Locale: english,
+ }, tc.textInput)
if lineCount := len(cache.txt.lines); lineCount > tc.expectedLines {
t.Errorf("shaping string %q created %d lines", tc.textInput, lineCount)
}
@@ 139,7 148,10 @@ func TestShapingNewlineHandling(t *testing.T) {
cache.Layout(Parameters{
Alignment: Middle,
PxPerEm: fixed.I(10),
- }, 200, 200, english, strings.NewReader(tc.textInput))
+ MinWidth: 200,
+ MaxWidth: 200,
+ Locale: english,
+ }, strings.NewReader(tc.textInput))
if lineCount := len(cache.txt.lines); lineCount > tc.expectedLines {
t.Errorf("shaping reader %q created %d lines", tc.textInput, lineCount)
}
@@ 157,7 169,10 @@ func TestCacheEmptyString(t *testing.T) {
cache.LayoutString(Parameters{
Alignment: Middle,
PxPerEm: fixed.I(10),
- }, 200, 200, english, "")
+ MinWidth: 200,
+ MaxWidth: 200,
+ Locale: english,
+ }, "")
glyphs := make([]Glyph, 0, 1)
for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() {
glyphs = append(glyphs, g)
@@ 190,31 205,38 @@ func TestCacheAlignment(t *testing.T) {
ltrFace, _ := opentype.Parse(goregular.TTF)
collection := []FontFace{{Face: ltrFace}}
cache := NewShaper(collection)
- params := Parameters{Alignment: Start, PxPerEm: fixed.I(10)}
- cache.LayoutString(params, 200, 200, english, "A")
+ params := Parameters{
+ Alignment: Start,
+ PxPerEm: fixed.I(10),
+ MinWidth: 200,
+ MaxWidth: 200,
+ Locale: english,
+ }
+ cache.LayoutString(params, "A")
glyph, _ := cache.NextGlyph()
startX := glyph.X
params.Alignment = Middle
- cache.LayoutString(params, 200, 200, english, "A")
+ cache.LayoutString(params, "A")
glyph, _ = cache.NextGlyph()
middleX := glyph.X
params.Alignment = End
- cache.LayoutString(params, 200, 200, english, "A")
+ cache.LayoutString(params, "A")
glyph, _ = cache.NextGlyph()
endX := glyph.X
if startX == middleX || startX == endX || endX == middleX {
t.Errorf("[LTR] shaping with with different alignments should not produce the same X, start %d, middle %d, end %d", startX, middleX, endX)
}
+ params.Locale = arabic
params.Alignment = Start
- cache.LayoutString(params, 200, 200, arabic, "A")
+ cache.LayoutString(params, "A")
glyph, _ = cache.NextGlyph()
rtlStartX := glyph.X
params.Alignment = Middle
- cache.LayoutString(params, 200, 200, arabic, "A")
+ cache.LayoutString(params, "A")
glyph, _ = cache.NextGlyph()
rtlMiddleX := glyph.X
params.Alignment = End
- cache.LayoutString(params, 200, 200, arabic, "A")
+ cache.LayoutString(params, "A")
glyph, _ = cache.NextGlyph()
rtlEndX := glyph.X
if rtlStartX == rtlMiddleX || rtlStartX == rtlEndX || rtlEndX == rtlMiddleX {
@@ 250,8 272,10 @@ func TestCacheGlyphConverstion(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
cache := NewShaper(collection)
cache.LayoutString(Parameters{
- PxPerEm: fixed.I(10),
- }, 0, 200, tc.locale, tc.text)
+ PxPerEm: fixed.I(10),
+ MaxWidth: 200,
+ Locale: tc.locale,
+ }, tc.text)
doc := cache.txt
glyphs := make([]Glyph, 0, len(tc.expected))
for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() {
M widget/index_test.go => widget/index_test.go +31 -8
@@ 32,17 32,30 @@ func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (source string
// bidiSource is crafted to contain multiple consecutive RTL runs (by
// changing scripts within the RTL).
bidiSource := "The quick سماء שלום لا fox تمط שלום غير the lazy dog."
- ltrParams := text.Parameters{Font: text.Font{Typeface: "LTR"}, PxPerEm: fixed.I(fontSize)}
- rtlParams := text.Parameters{Alignment: text.End, Font: text.Font{Typeface: "RTL"}, PxPerEm: fixed.I(fontSize)}
+ ltrParams := text.Parameters{
+ Font: text.Font{Typeface: "LTR"},
+ PxPerEm: fixed.I(fontSize),
+ MaxWidth: lineWidth,
+ MinWidth: lineWidth,
+ Locale: english,
+ }
+ rtlParams := text.Parameters{
+ Alignment: text.End,
+ Font: text.Font{Typeface: "RTL"},
+ PxPerEm: fixed.I(fontSize),
+ MaxWidth: lineWidth,
+ MinWidth: lineWidth,
+ Locale: arabic,
+ }
if alignOpposite {
ltrParams.Alignment = text.End
rtlParams.Alignment = text.Start
}
- shaper.LayoutString(ltrParams, lineWidth, lineWidth, english, bidiSource)
+ shaper.LayoutString(ltrParams, bidiSource)
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
bidiLTR = append(bidiLTR, g)
}
- shaper.LayoutString(rtlParams, lineWidth, lineWidth, arabic, bidiSource)
+ shaper.LayoutString(rtlParams, bidiSource)
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
bidiRTL = append(bidiRTL, g)
}
@@ 64,8 77,12 @@ func makeAccountingTestText(str string, fontSize, lineWidth int) (txt []text.Gly
Face: rtlFace,
},
})
- params := text.Parameters{PxPerEm: fixed.I(fontSize)}
- shaper.LayoutString(params, 0, lineWidth, english, str)
+ params := text.Parameters{
+ PxPerEm: fixed.I(fontSize),
+ MaxWidth: lineWidth,
+ Locale: english,
+ }
+ shaper.LayoutString(params, str)
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
txt = append(txt, g)
}
@@ 86,8 103,14 @@ func getGlyphs(fontSize, minWidth, lineWidth int, align text.Alignment, str stri
Face: rtlFace,
},
})
- params := text.Parameters{PxPerEm: fixed.I(fontSize), Alignment: align}
- shaper.LayoutString(params, minWidth, lineWidth, english, str)
+ params := text.Parameters{
+ PxPerEm: fixed.I(fontSize),
+ Alignment: align,
+ MinWidth: minWidth,
+ MaxWidth: lineWidth,
+ Locale: english,
+ }
+ shaper.LayoutString(params, str)
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
txt = append(txt, g)
}
M widget/label.go => widget/label.go +4 -1
@@ 38,7 38,10 @@ func (l Label) Layout(gtx layout.Context, lt *text.Shaper, font text.Font, size
MaxLines: l.MaxLines,
Truncator: l.Truncator,
Alignment: l.Alignment,
- }, cs.Min.X, cs.Max.X, gtx.Locale, txt)
+ MaxWidth: cs.Max.X,
+ MinWidth: cs.Min.X,
+ Locale: gtx.Locale,
+ }, txt)
m := op.Record(gtx.Ops)
viewport := image.Rectangle{Max: cs.Max}
it := textIterator{
M widget/material/label.go => widget/material/label.go +8 -1
@@ 116,8 116,15 @@ func (l LabelStyle) Layout(gtx layout.Context) layout.Dimensions {
if l.State.Text() != l.Text {
l.State.SetText(l.Text)
}
+ l.State.Alignment = l.Alignment
+ l.State.MaxLines = l.MaxLines
+ l.State.Truncator = l.Truncator
return l.State.Layout(gtx, l.shaper, l.Font, l.TextSize, textColor, selectColor)
}
- tl := widget.Label{Alignment: l.Alignment, MaxLines: l.MaxLines}
+ tl := widget.Label{
+ Alignment: l.Alignment,
+ MaxLines: l.MaxLines,
+ Truncator: l.Truncator,
+ }
return tl.Layout(gtx, l.shaper, l.Font, l.TextSize, l.Text, textColor)
}
M widget/selectable.go => widget/selectable.go +11 -1
@@ 48,8 48,15 @@ func (s stringSource) ReadAt(b []byte, offset int64) (int, error) {
func (s stringSource) ReplaceRunes(byteOffset, runeCount int64, str string) {
}
-// Selectable holds text selection state.
+// Selectable displays selectable text.
type Selectable struct {
+ // Alignment controls the alignment of the text.
+ Alignment text.Alignment
+ // MaxLines is the maximum number of lines of text to be displayed.
+ MaxLines int
+ // Truncator is the symbol to use at the end of the final line of text
+ // if text was cut off. Defaults to "…" if left empty.
+ Truncator string
initialized bool
source stringSource
// scratch is a buffer reused to efficiently read text out of the
@@ 171,6 178,9 @@ func (l *Selectable) Truncated() bool {
// paint material for the text and selection rectangles, respectively.
func (l *Selectable) Layout(gtx layout.Context, lt *text.Shaper, font text.Font, size unit.Sp, textMaterial, selectionMaterial op.CallOp) layout.Dimensions {
l.initialize()
+ l.text.Alignment = l.Alignment
+ l.text.MaxLines = l.MaxLines
+ l.text.Truncator = l.Truncator
l.text.Update(gtx, lt, font, size, l.handleEvents)
dims := l.text.Dimensions()
defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
M widget/selectable_test.go => widget/selectable_test.go +1 -1
@@ 100,7 100,7 @@ func TestSelectableConfigurations(t *testing.T) {
gtx.Constraints.Min = gtx.Constraints.Max
}
s := new(Selectable)
- s.text.Alignment = alignment
+ s.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{})
M widget/text.go => widget/text.go +30 -29
@@ 10,7 10,6 @@ import (
"unicode/utf8"
"gioui.org/f32"
- "gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
@@ 58,22 57,20 @@ type textView struct {
// are accessed by Len, Text, and SetText.
Mask rune
- font text.Font
+ params text.Parameters
shaper *text.Shaper
- textSize fixed.Int26_6
seekCursor int64
rr textSource
maskReader maskReader
// graphemes tracks the indices of grapheme cluster boundaries within rr.
graphemes []int
// paragraphReader is used to populate graphemes.
- paragraphReader graphemeReader
- lastMask rune
- maxWidth, minWidth int
- viewSize image.Point
- valid bool
- regions []Region
- dims layout.Dimensions
+ paragraphReader graphemeReader
+ lastMask rune
+ viewSize image.Point
+ valid bool
+ regions []Region
+ dims layout.Dimensions
// offIndex is an index of rune index to byte offsets.
offIndex []offEntry
@@ 93,8 90,6 @@ type textView struct {
}
scrollOff image.Point
-
- locale system.Locale
}
func (e *textView) Changed() bool {
@@ 228,27 223,27 @@ func (e *textView) calculateViewSize(gtx layout.Context) image.Point {
// allow parent widgets to adapt to any changes in text content or positioning. If eventHandling modifies the contents
// of the textView, it is guaranteed to be reshaped (and ready for painting) before Update returns.
func (e *textView) Update(gtx layout.Context, lt *text.Shaper, font text.Font, size unit.Sp, eventHandling func(gtx layout.Context)) {
- if e.locale != gtx.Locale {
- e.locale = gtx.Locale
+ if e.params.Locale != gtx.Locale {
+ e.params.Locale = gtx.Locale
e.invalidate()
}
textSize := fixed.I(gtx.Sp(size))
- if e.font != font || e.textSize != textSize {
+ if e.params.Font != font || e.params.PxPerEm != textSize {
e.invalidate()
- e.font = font
- e.textSize = textSize
+ e.params.Font = font
+ e.params.PxPerEm = textSize
}
maxWidth := gtx.Constraints.Max.X
if e.SingleLine {
maxWidth = math.MaxInt
}
minWidth := gtx.Constraints.Min.X
- if maxWidth != e.maxWidth {
- e.maxWidth = maxWidth
+ if maxWidth != e.params.MaxWidth {
+ e.params.MaxWidth = maxWidth
e.invalidate()
}
- if minWidth != e.minWidth {
- e.minWidth = minWidth
+ if minWidth != e.params.MinWidth {
+ e.params.MinWidth = minWidth
e.invalidate()
}
if lt != e.shaper {
@@ 259,6 254,18 @@ func (e *textView) Update(gtx layout.Context, lt *text.Shaper, font text.Font, s
e.lastMask = e.Mask
e.invalidate()
}
+ if e.Alignment != e.params.Alignment {
+ e.params.Alignment = e.Alignment
+ e.invalidate()
+ }
+ if e.Truncator != e.params.Truncator {
+ e.params.Truncator = e.Truncator
+ e.invalidate()
+ }
+ if e.MaxLines != e.params.MaxLines {
+ e.params.MaxLines = e.MaxLines
+ e.invalidate()
+ }
e.makeValid()
if eventHandling != nil {
@@ 463,13 470,7 @@ func (e *textView) layoutText(lt *text.Shaper) {
e.index.reset()
it := textIterator{viewport: image.Rectangle{Max: image.Point{X: math.MaxInt, Y: math.MaxInt}}}
if lt != nil {
- lt.Layout(text.Parameters{
- Font: e.font,
- PxPerEm: e.textSize,
- Alignment: e.Alignment,
- MaxLines: e.MaxLines,
- Truncator: e.Truncator,
- }, e.minWidth, e.maxWidth, e.locale, r)
+ lt.Layout(e.params, r)
for glyph, ok := it.processGlyph(lt.NextGlyph()); ok; glyph, ok = it.processGlyph(lt.NextGlyph()) {
e.index.Glyph(glyph)
}
@@ 635,7 636,7 @@ func (e *textView) MoveEnd(selAct selectionAction) {
caret := e.closestToRune(e.caret.start)
caret = e.closestToLineCol(caret.lineCol.line, math.MaxInt)
e.caret.start = caret.runes
- e.caret.xoff = fixed.I(e.maxWidth) - caret.x
+ e.caret.xoff = fixed.I(e.params.MaxWidth) - caret.x
e.updateSelection(selAct)
e.clampCursorToGraphemes()
}