turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
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.

🛟 Updated. 28d5985 · on 28d59854361aeda8541d853093e732126f3d7bff · k33g · 15h ago
painter.go · 145 lines · 4.5 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package ui

import (
	"github.com/gdamore/tcell/v2"
)

// Painter draws into a region of the screen, clipping everything to it.
//
// Coordinates passed to a painter are local: (0, 0) is the top-left cell of
// its own region, wherever that sits on the screen. Sub returns a painter for
// a part of the region, which is how a window hands its interior to the widget
// inside it without that widget ever knowing where it is.
type Painter struct {
	screen  tcell.Screen
	originX int
	originY int
	clip    Rect // in absolute screen coordinates
	size    Rect // the painter's own region, in local coordinates
}

// NewPainter returns a painter covering the whole screen.
func NewPainter(screen tcell.Screen) *Painter {
	width, height := screen.Size()
	area := Rect{W: width, H: height}
	return &Painter{screen: screen, clip: area, size: area}
}

// Sub returns a painter for the screen rectangle r.
//
// Unlike every other method here, r is in absolute screen coordinates: widget
// bounds are absolute throughout this package, so laying a child out and
// hit-testing a mouse click both work without translating anything. The result
// is clipped to r *and* to whatever this painter was already clipped to, so a
// child can never draw outside the box its parent was given.
func (p *Painter) Sub(r Rect) *Painter {
	return &Painter{
		screen:  p.screen,
		originX: r.X,
		originY: r.Y,
		clip:    p.clip.Intersect(r),
		size:    Rect{W: r.W, H: r.H},
	}
}

// Size returns the painter's region in local coordinates, so its width and
// height are what a widget lays itself out in.
func (p *Painter) Size() Rect { return p.size }

// SetCell draws one character, doing nothing when it falls outside the clip.
func (p *Painter) SetCell(x, y int, r rune, style tcell.Style) {
	absX, absY := p.originX+x, p.originY+y
	if !p.clip.Contains(absX, absY) {
		return
	}
	p.screen.SetContent(absX, absY, r, nil, style)
}

// CellAt returns the character and style currently drawn at a local position,
// which is what shading a shadow over existing content needs.
func (p *Painter) CellAt(x, y int) (rune, tcell.Style) {
	absX, absY := p.originX+x, p.originY+y
	if !p.clip.Contains(absX, absY) {
		return ' ', tcell.StyleDefault
	}
	r, _, style, _ := p.screen.GetContent(absX, absY)
	return r, style
}

// Text draws a string from left to right, one cell per rune, and returns the
// column just after the last one drawn.
//
// Runes past the right edge are clipped away rather than wrapped: a widget
// that overflows its box is a layout bug, and wrapping would hide it.
func (p *Painter) Text(x, y int, text string, style tcell.Style) int {
	for _, r := range text {
		p.SetCell(x, y, r, style)
		x++
	}
	return x
}

// TextLimited draws a string, cutting it short with an ellipsis when it does
// not fit in width cells. It is what draws a file name in a title bar.
func (p *Painter) TextLimited(x, y, width int, text string, style tcell.Style) int {
	runes := []rune(text)
	if width <= 0 {
		return x
	}
	if len(runes) > width {
		runes = runes[:width-1]
		return p.Text(x, y, string(runes)+"…", style)
	}
	return p.Text(x, y, string(runes), style)
}

// Fill covers a rectangle with one character.
func (p *Painter) Fill(r Rect, ch rune, style tcell.Style) {
	for y := r.Y; y < r.Bottom(); y++ {
		for x := r.X; x < r.Right(); x++ {
			p.SetCell(x, y, ch, style)
		}
	}
}

// Clear covers the painter's whole region with spaces in the given style.
func (p *Painter) Clear(style tcell.Style) {
	p.Fill(p.size, ' ', style)
}

// HLine draws a horizontal run of one character.
func (p *Painter) HLine(x, y, width int, ch rune, style tcell.Style) {
	for i := range width {
		p.SetCell(x+i, y, ch, style)
	}
}

// VLine draws a vertical run of one character.
func (p *Painter) VLine(x, y, height int, ch rune, style tcell.Style) {
	for i := range height {
		p.SetCell(x, y+i, ch, style)
	}
}

// Shade restyles the characters already drawn in a rectangle without changing
// them, which is how a window casts a shadow over the desktop behind it.
func (p *Painter) Shade(r Rect, style tcell.Style) {
	for y := r.Y; y < r.Bottom(); y++ {
		for x := r.X; x < r.Right(); x++ {
			ch, _ := p.CellAt(x, y)
			p.SetCell(x, y, ch, style)
		}
	}
}

// ShowCursor puts the terminal cursor at a local position, or hides it when
// the position falls outside the clip — which is what happens when the focused
// widget is scrolled out of sight.
func (p *Painter) ShowCursor(x, y int) {
	absX, absY := p.originX+x, p.originY+y
	if !p.clip.Contains(absX, absY) {
		p.screen.HideCursor()
		return
	}
	p.screen.ShowCursor(absX, absY)
}