package elements import "image" import "image/color" 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" 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 } type gridCell struct { Cell clean bool } func (cell *Cell) initColor () { cell.Background = tomo.ColorBackground cell.Foreground = tomo.ColorForeground } // Grid is an array of monospaced character cells. Each one has a foreground and // background color. type Grid struct { *core.Core *core.FocusableCore core core.CoreControl focusableControl core.FocusableCoreControl cells []gridCell stride int cellWidth int cellHeight int cursor image.Point face font.Face config config.Wrapped theme theme.Wrapped colors [19]color.RGBA 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() element.updateMinimumSize() return } func (element *Grid) Size () (columns, rows int) { columns = element.stride if element.stride > 0 { rows = len(element.cells) / element.stride } return } 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 } 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() 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 } 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 } 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 () { 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()) } } func (element *Grid) updateMinimumSize () { element.core.SetMinimumSize(element.cellWidth, element.cellHeight) } func (element *Grid) state () tomo.State { return tomo.State { Focused: element.Focused(), } } func (element *Grid) drawAndPush () { if element.core.HasImage () { element.core.DamageRegion(element.draw(element.alloc())) } } func (element *Grid) drawAllAndPush () { element.alloc() 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), } } 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 }