1package ui
2
3import (
4 "math"
5
6 "github.com/bnema/nefergui/internal/css"
7 "github.com/bnema/nefergui/internal/edit"
8 "github.com/bnema/nefergui/internal/layout"
9)
10
11var selectionColor = css.Color{R: 0.22, G: 0.43, B: 0.85, A: 0.4}
12var caretColor = css.Color{R: 0, G: 0, B: 0, A: 1}
13
14func (r *runtime) paintEditors(root *element, out *layout.Output) {
15 var visit func(*element)
16 visit = func(e *element) {
17 if e == nil {
18 return
19 }
20 if e.typ == "input" || e.typ == "textarea" {
21 id := idKey(e.identity)
22 s := r.edits[id]
23 n := resultByID(out.Tree, id)
24 if s != nil && n != nil {
25 var commands []layout.Command
26 if s.Value == "" && s.Preedit == "" && e.placeholder != "" && r.textEngine != nil {
27 commands = placeholderCommand(e, n, r.textEngine)
28 }
29 if !e.disabled {
30 commands = append(commands, editorDecoration(s, n, out.Display, e.password, e.identity.same(r.state.focus))...)
31 if s.Preedit != "" && e.identity.same(r.state.focus) && r.textEngine != nil {
32 commands = append(commands, preeditCommands(s, n, e, r.textEngine, out.Display)...)
33 }
34 }
35 insertClippedCommands(out, id, commands)
36 }
37 }
38 for _, child := range e.children {
39 visit(child)
40 }
41 }
42 visit(root)
43}
44
45// editorDecoration maps logical grapheme selection to visual cluster spans.
46// The caret follows editorGeometry's policy: at an ambiguous boundary, such as
47// a bidi run change, the preceding character's trailing edge is used.
48func editorDecoration(s *edit.State, n *layout.Result, display []layout.Command, password, focused bool) []layout.Command {
49 if n == nil {
50 return nil
51 }
52 start, end := s.Range()
53 geometry := newEditorGeometry(s, n, display, password)
54 lines := geometry.lines
55 var result []layout.Command
56 for i, line := range lines {
57 h := geometry.lineHeight(i)
58 for j := 0; j+1 < len(line); j += 2 {
59 a, b := line[j], line[j+1]
60 if a.byteOffset == b.byteOffset {
61 continue
62 }
63 low, high := a.byteOffset, b.byteOffset
64 if low > high {
65 low, high = high, low
66 }
67 if start < high && end > low && start != end {
68 x := math.Min(a.x, b.x)
69 w := math.Abs(a.x - b.x)
70 if w > 0 {
71 result = append(result, layout.Command{Op: "selection", ID: n.ID, Rect: layout.Rect{X: x, Y: a.y, W: w, H: h}, Color: selectionColor, Opacity: 1})
72 }
73 }
74 }
75 }
76 if focused {
77 // The caret uses the preceding character's trailing edge when an
78 // offset has several visual positions.
79 point, found := geometry.caret(s.Cursor)
80 if !found && len(lines) == 0 {
81 result = append(result, layout.Command{Op: "caret", ID: n.ID, Rect: layout.Rect{X: n.Content.X, Y: n.Content.Y, W: 1, H: n.Content.H}, Color: caretColor, Opacity: 1})
82 }
83 if found {
84 result = append(result, layout.Command{Op: "caret", ID: n.ID, Rect: layout.Rect{X: point.x, Y: point.y, W: 1, H: geometry.lineHeight(point.line)}, Color: caretColor, Opacity: 1})
85 }
86 }
87 return result
88}