turbo-editors/turbo-corepublic Fork 0
v1.0.2
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

painter.go · 145 lines · 4.5 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 20h ago1package ui
2
3import (
4 "github.com/gdamore/tcell/v2"
5)
6
7// Painter draws into a region of the screen, clipping everything to it.
8//
9// Coordinates passed to a painter are local: (0, 0) is the top-left cell of
10// its own region, wherever that sits on the screen. Sub returns a painter for
11// a part of the region, which is how a window hands its interior to the widget
12// inside it without that widget ever knowing where it is.
13type Painter struct {
14 screen tcell.Screen
15 originX int
16 originY int
17 clip Rect // in absolute screen coordinates
18 size Rect // the painter's own region, in local coordinates
19}
20
21// NewPainter returns a painter covering the whole screen.
22func NewPainter(screen tcell.Screen) *Painter {
23 width, height := screen.Size()
24 area := Rect{W: width, H: height}
25 return &Painter{screen: screen, clip: area, size: area}
26}
27
28// Sub returns a painter for the screen rectangle r.
29//
30// Unlike every other method here, r is in absolute screen coordinates: widget
31// bounds are absolute throughout this package, so laying a child out and
32// hit-testing a mouse click both work without translating anything. The result
33// is clipped to r *and* to whatever this painter was already clipped to, so a
34// child can never draw outside the box its parent was given.
35func (p *Painter) Sub(r Rect) *Painter {
36 return &Painter{
37 screen: p.screen,
38 originX: r.X,
39 originY: r.Y,
40 clip: p.clip.Intersect(r),
41 size: Rect{W: r.W, H: r.H},
42 }
43}
44
45// Size returns the painter's region in local coordinates, so its width and
46// height are what a widget lays itself out in.
47func (p *Painter) Size() Rect { return p.size }
48
49// SetCell draws one character, doing nothing when it falls outside the clip.
50func (p *Painter) SetCell(x, y int, r rune, style tcell.Style) {
51 absX, absY := p.originX+x, p.originY+y
52 if !p.clip.Contains(absX, absY) {
53 return
54 }
55 p.screen.SetContent(absX, absY, r, nil, style)
56}
57
58// CellAt returns the character and style currently drawn at a local position,
59// which is what shading a shadow over existing content needs.
60func (p *Painter) CellAt(x, y int) (rune, tcell.Style) {
61 absX, absY := p.originX+x, p.originY+y
62 if !p.clip.Contains(absX, absY) {
63 return ' ', tcell.StyleDefault
64 }
65 r, _, style, _ := p.screen.GetContent(absX, absY)
66 return r, style
67}
68
69// Text draws a string from left to right, one cell per rune, and returns the
70// column just after the last one drawn.
71//
72// Runes past the right edge are clipped away rather than wrapped: a widget
73// that overflows its box is a layout bug, and wrapping would hide it.
74func (p *Painter) Text(x, y int, text string, style tcell.Style) int {
75 for _, r := range text {
76 p.SetCell(x, y, r, style)
77 x++
78 }
79 return x
80}
81
82// TextLimited draws a string, cutting it short with an ellipsis when it does
83// not fit in width cells. It is what draws a file name in a title bar.
84func (p *Painter) TextLimited(x, y, width int, text string, style tcell.Style) int {
85 runes := []rune(text)
86 if width <= 0 {
87 return x
88 }
89 if len(runes) > width {
90 runes = runes[:width-1]
91 return p.Text(x, y, string(runes)+"…", style)
92 }
93 return p.Text(x, y, string(runes), style)
94}
95
96// Fill covers a rectangle with one character.
97func (p *Painter) Fill(r Rect, ch rune, style tcell.Style) {
98 for y := r.Y; y < r.Bottom(); y++ {
99 for x := r.X; x < r.Right(); x++ {
100 p.SetCell(x, y, ch, style)
101 }
102 }
103}
104
105// Clear covers the painter's whole region with spaces in the given style.
106func (p *Painter) Clear(style tcell.Style) {
107 p.Fill(p.size, ' ', style)
108}
109
110// HLine draws a horizontal run of one character.
111func (p *Painter) HLine(x, y, width int, ch rune, style tcell.Style) {
112 for i := range width {
113 p.SetCell(x+i, y, ch, style)
114 }
115}
116
117// VLine draws a vertical run of one character.
118func (p *Painter) VLine(x, y, height int, ch rune, style tcell.Style) {
119 for i := range height {
120 p.SetCell(x, y+i, ch, style)
121 }
122}
123
124// Shade restyles the characters already drawn in a rectangle without changing
125// them, which is how a window casts a shadow over the desktop behind it.
126func (p *Painter) Shade(r Rect, style tcell.Style) {
127 for y := r.Y; y < r.Bottom(); y++ {
128 for x := r.X; x < r.Right(); x++ {
129 ch, _ := p.CellAt(x, y)
130 p.SetCell(x, y, ch, style)
131 }
132 }
133}
134
135// ShowCursor puts the terminal cursor at a local position, or hides it when
136// the position falls outside the clip — which is what happens when the focused
137// widget is scrolled out of sight.
138func (p *Painter) ShowCursor(x, y int) {
139 absX, absY := p.originX+x, p.originY+y
140 if !p.clip.Contains(absX, absY) {
141 p.screen.HideCursor()
142 return
143 }
144 p.screen.ShowCursor(absX, absY)
145}