diff --git a/text/editor.go b/text/editor.go index 4603e97f..8bcf4fdd 100644 --- a/text/editor.go +++ b/text/editor.go @@ -4,11 +4,11 @@ package text import ( "image" - "image/color" "math" "time" "unicode/utf8" + "gioui.org/f32" "gioui.org/gesture" "gioui.org/io/event" "gioui.org/io/key" @@ -23,15 +23,6 @@ import ( // Editor implements an editable and scrollable text area. type Editor struct { - Font Font - // Material for drawing the text. - Material op.MacroOp - // Hint contains the text displayed to the user when the - // Editor is empty. - Hint string - // Material is used to draw the hint. - HintMaterial op.MacroOp - Alignment Alignment // SingleLine force the text to stay on a single line. // SingleLine also sets the scrolling direction to @@ -50,6 +41,7 @@ type Editor struct { viewSize image.Point valid bool lines []Line + shapes []line dims layout.Dimensions carWidth fixed.Int26_6 requestFocus bool @@ -80,6 +72,11 @@ type ChangeEvent struct{} // and a carriage return key is pressed. type SubmitEvent struct{} +type line struct { + offset f32.Point + clip paint.ClipOp +} + const ( blinksPerSecond = 1 maxBlinkDuration = 10 * time.Second @@ -168,31 +165,17 @@ func (e *Editor) Focus() { } // Layout flushes any remaining events and lays out the editor. -func (e *Editor) Layout(gtx *layout.Context, s *Shaper) { - e.layout(gtx, s, e.Font) - var stack op.StackOp - stack.Push(gtx.Ops) - if e.Len() > 0 { - paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops) - e.Material.Add(gtx.Ops) - } else { - paint.ColorOp{Color: color.RGBA{A: 0xaa}}.Add(gtx.Ops) - e.HintMaterial.Add(gtx.Ops) - } - e.draw(gtx, s, e.Font) - paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops) - e.Material.Add(gtx.Ops) - e.drawCaret(gtx) - stack.Pop() -} - -func (e *Editor) layout(gtx *layout.Context, s *Shaper, font Font) { - for _, ok := e.Event(gtx); ok; _, ok = e.Event(gtx) { - } +func (e *Editor) Layout(gtx *layout.Context, sh *Shaper, font Font) { if e.font != font { e.invalidate() e.font = font } + e.layout(gtx, sh) +} + +func (e *Editor) layout(gtx *layout.Context, sh *Shaper) { + for _, ok := e.Event(gtx); ok; _, ok = e.Event(gtx) { + } // Crude configuration change detection. if scale := gtx.Px(unit.Sp(100)); scale != e.scale { e.invalidate() @@ -211,7 +194,7 @@ func (e *Editor) layout(gtx *layout.Context, s *Shaper, font Font) { } if !e.valid { - e.layoutText(gtx, s, font) + e.layoutText(gtx, sh, e.font) e.valid = true } @@ -223,6 +206,29 @@ func (e *Editor) layout(gtx *layout.Context, s *Shaper, font Font) { e.scrollToCaret() } + off := image.Point{ + X: -e.scrollOff.X, + Y: -e.scrollOff.Y, + } + clip := textPadding(e.lines) + clip.Max = clip.Max.Add(e.viewSize) + it := lineIterator{ + Lines: e.lines, + Clip: clip, + Alignment: e.Alignment, + Width: e.viewSize.X, + Offset: off, + } + e.shapes = e.shapes[:0] + for { + str, off, ok := it.Next() + if !ok { + break + } + path := sh.Shape(gtx, e.font, str) + e.shapes = append(e.shapes, line{off, path}) + } + key.InputOp{Key: e, Focus: e.requestFocus}.Add(gtx.Ops) e.requestFocus = false pointerPadding := gtx.Px(unit.Dp(4)) @@ -251,38 +257,20 @@ func (e *Editor) layout(gtx *layout.Context, s *Shaper, font Font) { gtx.Dimensions = layout.Dimensions{Size: e.viewSize, Baseline: e.dims.Baseline} } -func (e *Editor) draw(gtx *layout.Context, s *Shaper, font Font) { - var stack op.StackOp - stack.Push(gtx.Ops) - off := image.Point{ - X: -e.scrollOff.X, - Y: -e.scrollOff.Y, - } +func (e *Editor) PaintText(gtx *layout.Context) { clip := textPadding(e.lines) clip.Max = clip.Max.Add(e.viewSize) - it := lineIterator{ - Lines: e.lines, - Clip: clip, - Alignment: e.Alignment, - Width: e.viewSize.X, - Offset: off, - } - for { - str, lineOff, ok := it.Next() - if !ok { - break - } + for _, shape := range e.shapes { var stack op.StackOp stack.Push(gtx.Ops) - op.TransformOp{}.Offset(lineOff).Add(gtx.Ops) - s.Shape(gtx, font, str).Add(gtx.Ops) - paint.PaintOp{Rect: toRectF(clip).Sub(lineOff)}.Add(gtx.Ops) + op.TransformOp{}.Offset(shape.offset).Add(gtx.Ops) + shape.clip.Add(gtx.Ops) + paint.PaintOp{Rect: toRectF(clip).Sub(shape.offset)}.Add(gtx.Ops) stack.Pop() } - stack.Pop() } -func (e *Editor) drawCaret(gtx *layout.Context) { +func (e *Editor) PaintCaret(gtx *layout.Context) { if !e.caretOn { return } @@ -387,9 +375,6 @@ func (e *Editor) moveCoord(c unit.Converter, pos image.Point) { func (e *Editor) layoutText(c unit.Converter, s *Shaper, font Font) { txt := e.rr.String() - if txt == "" { - txt = e.Hint - } opts := LayoutOptions{SingleLine: e.SingleLine, MaxWidth: e.maxWidth} textLayout := s.Layout(c, font, txt, opts) lines := textLayout.Lines diff --git a/text/label.go b/text/label.go index ce4e4fc7..c21eeb76 100644 --- a/text/label.go +++ b/text/label.go @@ -5,7 +5,6 @@ package text import ( "image" - "image/color" "unicode/utf8" "gioui.org/f32" @@ -18,15 +17,8 @@ import ( // Label is a widget for laying out and drawing text. type Label struct { - Font Font - - // Material is a macro recording the material to draw the - // text. Use a ColorOp for colored text. - Material op.MacroOp // Alignment specify the text alignment. Alignment Alignment - // Text is the string to draw. - Text string // MaxLines limits the number of lines. Zero means no limit. MaxLines int } @@ -89,9 +81,9 @@ func (l *lineIterator) Next() (String, f32.Point, bool) { return String{}, f32.Point{}, false } -func (l Label) Layout(gtx *layout.Context, s *Shaper) { +func (l Label) Layout(gtx *layout.Context, s *Shaper, font Font, txt string) { cs := gtx.Constraints - textLayout := s.Layout(gtx, l.Font, l.Text, LayoutOptions{MaxWidth: cs.Width.Max}) + textLayout := s.Layout(gtx, font, txt, LayoutOptions{MaxWidth: cs.Width.Max}) lines := textLayout.Lines if max := l.MaxLines; max > 0 && len(lines) > max { lines = lines[:max] @@ -115,10 +107,7 @@ func (l Label) Layout(gtx *layout.Context, s *Shaper) { var stack op.StackOp stack.Push(gtx.Ops) op.TransformOp{}.Offset(off).Add(gtx.Ops) - s.Shape(gtx, l.Font, str).Add(gtx.Ops) - // Set a default color in case the material is empty. - paint.ColorOp{Color: color.RGBA{A: 0xff}}.Add(gtx.Ops) - l.Material.Add(gtx.Ops) + s.Shape(gtx, font, str).Add(gtx.Ops) paint.PaintOp{Rect: lclip}.Add(gtx.Ops) stack.Pop() }