piss/elements/grid.go

245 lines
5.8 KiB
Go
Raw Normal View History

2023-04-07 21:33:39 -06:00
package elements
import "image"
import "image/color"
2023-04-07 21:33:39 -06:00
import "golang.org/x/image/font"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
2023-04-07 21:33:39 -06:00
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
type Cell struct {
Rune rune
Style tomo.FontStyle
Background tomo.Color
Foreground tomo.Color
2023-04-07 21:33:39 -06:00
}
type gridCell struct {
Cell
clean bool
2023-04-07 21:33:39 -06:00
}
func (cell *Cell) initColor () {
cell.Background = tomo.ColorBackground
cell.Foreground = tomo.ColorForeground
2023-04-07 21:33:39 -06:00
}
// Grid is an array of monospaced character cells. Each one has a foreground and
// background color.
2023-04-07 21:33:39 -06:00
type Grid struct {
*core.Core
*core.FocusableCore
core core.CoreControl
focusableControl core.FocusableCoreControl
2023-04-07 21:33:39 -06:00
cells []gridCell
stride int
cellWidth int
cellHeight int
cursor image.Point
face font.Face
2023-04-07 21:33:39 -06:00
config config.Wrapped
theme theme.Wrapped
colors [19]color.RGBA
2023-04-07 21:33:39 -06:00
onResize func ()
}
func NewGrid () (element *Grid) {
element = &Grid { }
element.theme.Case = tomo.C("tomo", "grid")
element.Core, element.core = core.NewCore(element, element.drawAllAndPush)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.core, element.drawAndPush)
element.updateFontAndColors()
2023-04-07 21:33:39 -06:00
element.updateMinimumSize()
return
}
func (element *Grid) Size () (columns, rows int) {
columns = element.stride
if element.stride > 0 {
rows = len(element.cells) / element.stride
}
return
2023-04-07 21:33:39 -06:00
}
func (element *Grid) At (point image.Point) Cell {
if !element.inBounds(point) { return Cell { } }
return element.cells[element.index(point)].Cell
}
func (element *Grid) Set (point image.Point, cell Cell) {
if !element.inBounds(point) { return }
element.cells[element.index(point)].Cell = cell
element.cells[element.index(point)].clean = false
}
func (element *Grid) Push () {
element.drawAndPush()
}
func (element *Grid) OnResize (callback func ()) {
element.onResize = callback
2023-04-07 21:33:39 -06:00
}
func (element *Grid) HandleMouseDown (x, y int, button input.Button) {
}
func (element *Grid) HandleMouseUp (x, y int, button input.Button) {
}
func (element *Grid) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
// TODO we need to grab shift ctrl c for copying text
}
func (element *Grid) HandleKeyUp(key input.Key, modifiers input.Modifiers) { }
// SetTheme sets the element's theme.
func (element *Grid) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.updateFontAndColors()
2023-04-07 21:33:39 -06:00
element.updateMinimumSize()
element.drawAndPush()
}
// SetConfig sets the element's configuration.
func (element *Grid) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.drawAndPush()
}
// -------- private methods -------- //
func (element *Grid) inBounds (point image.Point) bool {
if point.X < 0 { return false }
if point.Y < 0 { return false }
width, height := element.Size()
if point.X >= width { return false }
if point.Y >= height { return false }
return true
}
func (element *Grid) index (point image.Point) int {
return point.X + element.stride * point.Y
}
2023-04-07 21:33:39 -06:00
func (element *Grid) alloc () bool {
bounds := element.Bounds()
width := bounds.Dx() / element.cellWidth
height := bounds.Dy() / element.cellHeight
oldWidth, oldHeight := element.Size()
if width == oldWidth && height == oldHeight { return false }
2023-04-07 21:33:39 -06:00
oldCells := element.cells
heightLarger := height < oldHeight
element.stride = width
element.cells = make([]gridCell, width * height)
// TODO: attempt to wrap text?
if heightLarger {
for index := range element.cells[oldHeight * width:] {
element.cells[index].initColor()
}}
commonHeight := height
if heightLarger { commonHeight = oldHeight }
for index := range element.cells[:commonHeight * width] {
x := index % width
if x < oldWidth {
element.cells[index] = oldCells[x + index / oldWidth]
} else {
element.cells[index].initColor()
}
}
if element.onResize != nil { element.onResize() }
return true
}
func (element *Grid) updateFontAndColors () {
2023-04-07 21:33:39 -06:00
element.face = element.theme.FontFace (
tomo.FontStyleMonospace,
tomo.FontSizeNormal)
emSpace, _ := element.face.GlyphAdvance('M')
metrics := element.face.Metrics()
element.cellWidth = emSpace.Round()
element.cellHeight = metrics.Height.Round()
for index := range element.colors {
element.colors[index] = element.theme.Color (
tomo.Color(index),
element.state())
}
2023-04-07 21:33:39 -06:00
}
func (element *Grid) updateMinimumSize () {
element.core.SetMinimumSize(element.cellWidth, element.cellHeight)
}
func (element *Grid) state () tomo.State {
return tomo.State {
Focused: element.Focused(),
2023-04-07 21:33:39 -06:00
}
}
func (element *Grid) drawAndPush () {
if element.core.HasImage () {
element.core.DamageRegion(element.draw(element.alloc()))
}
}
func (element *Grid) drawAllAndPush () {
element.alloc()
2023-04-07 21:33:39 -06:00
if element.core.HasImage () {
element.core.DamageRegion(element.draw(true))
}
}
func (element *Grid) corner (index int) image.Point {
return image.Point {
X: (index % element.stride) * element.cellWidth,
Y: (index / element.stride) * element.cellHeight,
}.Add(element.Bounds().Min)
}
func (element *Grid) bound (index int) image.Rectangle {
corner := element.corner(index)
return image.Rectangle {
Min: corner,
Max: image.Pt (
corner.X + element.cellWidth,
corner.Y + element.cellHeight),
}
}
2023-04-07 21:33:39 -06:00
func (element *Grid) draw (force bool) image.Rectangle {
bounds := element.Bounds()
for index, cell := range element.cells {
if force || !cell.clean {
// TODO
shapes.FillColorRectangle (
element.core,
element.colors[cell.Background],
element.bound(index))
}}
return bounds // FIXME
2023-04-07 21:33:39 -06:00
}