widget: track minWidth of editor for alignment

This commit extends the editor to keep track of its own minimum constraint
and to provide that value to the text shaper for the purpose of aligning
text. Without this, the shaper does not know how much of the width of the
editor to use for alignment purposes.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
Chris Waldon
2022-12-19 10:09:00 -05:00
committed by Elias Naur
parent 5d1d1df206
commit fe5878bc63
2 changed files with 55 additions and 36 deletions
+21 -16
View File
@@ -52,21 +52,21 @@ type Editor struct {
// all characters are allowed. // all characters are allowed.
Filter string Filter string
eventKey int eventKey int
font text.Font font text.Font
shaper *text.Shaper shaper *text.Shaper
textSize fixed.Int26_6 textSize fixed.Int26_6
blinkStart time.Time blinkStart time.Time
focused bool focused bool
rr editBuffer rr editBuffer
maskReader maskReader maskReader maskReader
lastMask rune lastMask rune
maxWidth int maxWidth, minWidth int
viewSize image.Point viewSize image.Point
valid bool valid bool
regions []region regions []region
dims layout.Dimensions dims layout.Dimensions
requestFocus bool requestFocus bool
// offIndex is an index of rune index to byte offsets. // offIndex is an index of rune index to byte offsets.
offIndex []offEntry offIndex []offEntry
@@ -547,10 +547,15 @@ func (e *Editor) Layout(gtx layout.Context, lt *text.Shaper, font text.Font, siz
if e.SingleLine { if e.SingleLine {
maxWidth = math.MaxInt maxWidth = math.MaxInt
} }
minWidth := gtx.Constraints.Min.X
if maxWidth != e.maxWidth { if maxWidth != e.maxWidth {
e.maxWidth = maxWidth e.maxWidth = maxWidth
e.invalidate() e.invalidate()
} }
if minWidth != e.minWidth {
e.minWidth = minWidth
e.invalidate()
}
if lt != e.shaper { if lt != e.shaper {
e.shaper = lt e.shaper = lt
e.invalidate() e.invalidate()
@@ -891,7 +896,7 @@ func (e *Editor) layoutText(lt *text.Shaper) {
Font: e.font, Font: e.font,
PxPerEm: e.textSize, PxPerEm: e.textSize,
Alignment: e.Alignment, Alignment: e.Alignment,
}, 0, e.maxWidth, e.locale, r) }, e.minWidth, e.maxWidth, e.locale, r)
for glyph, ok := it.processGlyph(lt.NextGlyph()); ok; glyph, ok = it.processGlyph(lt.NextGlyph()) { for glyph, ok := it.processGlyph(lt.NextGlyph()); ok; glyph, ok = it.processGlyph(lt.NextGlyph()) {
e.index.Glyph(glyph) e.index.Glyph(glyph)
} }
+34 -20
View File
@@ -115,7 +115,7 @@ func TestEditorZeroDimensions(t *testing.T) {
func TestEditorConfigurations(t *testing.T) { func TestEditorConfigurations(t *testing.T) {
gtx := layout.Context{ gtx := layout.Context{
Ops: new(op.Ops), Ops: new(op.Ops),
Constraints: layout.Exact(image.Pt(100, 100)), Constraints: layout.Exact(image.Pt(300, 300)),
Locale: english, Locale: english,
} }
cache := text.NewShaper(gofont.Collection()) cache := text.NewShaper(gofont.Collection())
@@ -126,27 +126,41 @@ func TestEditorConfigurations(t *testing.T) {
// Ensure that both ends of the text are reachable in all permutations // Ensure that both ends of the text are reachable in all permutations
// of settings that influence layout. // of settings that influence layout.
for _, lineMode := range []bool{true, false} { for _, singleLine := range []bool{true, false} {
for _, alignment := range []text.Alignment{text.Start, text.Middle, text.End} { for _, alignment := range []text.Alignment{text.Start, text.Middle, text.End} {
t.Run(fmt.Sprintf("SingleLine: %v Alignment: %v", lineMode, alignment), func(t *testing.T) { for _, zeroMin := range []bool{true, false} {
defer func() { t.Run(fmt.Sprintf("SingleLine: %v Alignment: %v ZeroMinConstraint: %v", singleLine, alignment, zeroMin), func(t *testing.T) {
if err := recover(); err != nil { defer func() {
t.Error(err) if err := recover(); err != nil {
t.Error(err)
}
}()
if zeroMin {
gtx.Constraints.Min = image.Point{}
} else {
gtx.Constraints.Min = gtx.Constraints.Max
} }
}() e := new(Editor)
e := new(Editor) e.SingleLine = singleLine
e.SingleLine = lineMode e.Alignment = alignment
e.Alignment = alignment e.SetText(sentence)
e.SetText(sentence) e.SetCaret(0, 0)
e.SetCaret(0, 0) dims := e.Layout(gtx, cache, font, fontSize, nil)
e.Layout(gtx, cache, font, fontSize, nil) if dims.Size.X < gtx.Constraints.Min.X || dims.Size.Y < gtx.Constraints.Min.Y {
e.SetCaret(runes, runes) t.Errorf("expected min size %#+v, got %#+v", gtx.Constraints.Min, dims.Size)
e.Layout(gtx, cache, font, fontSize, nil) }
coords := e.CaretCoords() coords := e.CaretCoords()
if int(coords.X) > gtx.Constraints.Max.X || int(coords.Y) > gtx.Constraints.Max.Y { if halfway := float32(gtx.Constraints.Min.X) * .5; !singleLine && alignment == text.Middle && !zeroMin && coords.X != halfway {
t.Errorf("caret coordinates %v exceed constraints %v", coords, gtx.Constraints.Max) t.Errorf("expected caret X to be %f, got %f", halfway, coords.X)
} }
}) e.SetCaret(runes, runes)
e.Layout(gtx, cache, font, fontSize, nil)
coords = e.CaretCoords()
if int(coords.X) > gtx.Constraints.Max.X || int(coords.Y) > gtx.Constraints.Max.Y {
t.Errorf("caret coordinates %v exceed constraints %v", coords, gtx.Constraints.Max)
}
})
}
} }
} }
} }