This commit is contained in:
Sasha Koshka 2023-07-05 03:25:50 -04:00
parent 4c79d6772c
commit 53d4683bd1
7 changed files with 231 additions and 21 deletions

42
box.go
View File

@ -11,6 +11,7 @@ import "git.tebibyte.media/tomo/tomo/canvas"
type box struct { type box struct {
backend *Backend backend *Backend
parent parent parent parent
outer anyBox
bounds image.Rectangle bounds image.Rectangle
minSize image.Point minSize image.Point
@ -45,9 +46,12 @@ type box struct {
} }
func (backend *Backend) NewBox() tomo.Box { func (backend *Backend) NewBox() tomo.Box {
return &box { box := &box {
backend: backend, backend: backend,
} }
box.drawer = box
box.outer = box
return box
} }
func (this *box) Box () tomo.Box { func (this *box) Box () tomo.Box {
@ -88,10 +92,14 @@ func (this *box) SetBorder (border ...tomo.Border) {
} }
func (this *box) SetMinimumSize (width, height int) { func (this *box) SetMinimumSize (width, height int) {
this.minSize = image.Pt(width, height) minSize := image.Pt(width, height)
if this.minSize == minSize { return }
this.minSize = minSize
if this.bounds.Dx() < width || this.bounds.Dy() < height { if this.bounds.Dx() < width || this.bounds.Dy() < height {
this.invalidateLayout() this.invalidateLayout()
} }
// TODO: alert the parent
} }
func (this *box) SetPadding (padding tomo.Inset) { func (this *box) SetPadding (padding tomo.Inset) {
@ -112,7 +120,7 @@ func (this *box) SetFocused (focused bool) {
if this.Focused () && !focused { if this.Focused () && !focused {
this.parent.window().focus(nil) this.parent.window().focus(nil)
} else if !this.Focused() && focused { } else if !this.Focused() && focused {
this.parent.window().focus(this) this.parent.window().focus(this.outer)
} }
} }
@ -125,7 +133,7 @@ func (this *box) SetFocusable (focusable bool) {
} }
func (this *box) Focused () bool { func (this *box) Focused () bool {
return this == this.parent.window().focused return this.outer == this.parent.window().focused
} }
func (this *box) Modifiers () input.Modifiers { func (this *box) Modifiers () input.Modifiers {
@ -136,7 +144,7 @@ func (this *box) MousePosition () image.Point {
return this.parent.window().mousePosition return this.parent.window().mousePosition
} }
// ----- event handlers ----------------------------------------------------- // // ----- event handler setters ---------------------------------------------- //
func (this *box) OnFocusEnter (callback func()) event.Cookie { func (this *box) OnFocusEnter (callback func()) event.Cookie {
return this.on.focusEnter.Connect(callback) return this.on.focusEnter.Connect(callback)
} }
@ -179,8 +187,14 @@ func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Co
// -------------------------------------------------------------------------- // // -------------------------------------------------------------------------- //
func (this *box) Draw (can canvas.Canvas) { func (this *box) Draw (can canvas.Canvas) {
this.drawBorders(can)
pen := can.Pen() pen := can.Pen()
pen.Fill(this.color)
pen.Rectangle(this.bounds)
}
func (this *box) drawBorders (can canvas.Canvas) {
pen := can.Pen()
bounds := this.bounds bounds := this.bounds
for _, border := range this.border { for _, border := range this.border {
pen.Fill(border.Color[tomo.SideTop]) pen.Fill(border.Color[tomo.SideTop])
@ -210,15 +224,11 @@ func (this *box) Draw (can canvas.Canvas) {
bounds = border.Width.Apply(bounds) bounds = border.Width.Apply(bounds)
} }
pen.Fill(this.color)
pen.Rectangle(bounds)
} }
func (this *box) doDraw () { func (this *box) doDraw () {
if this.canvas == nil { return } if this.canvas == nil { return }
if this.drawer == nil { if this.drawer != nil {
this.Draw(this.canvas)
} else {
this.drawer.Draw(this.canvas) this.drawer.Draw(this.canvas)
} }
} }
@ -236,12 +246,12 @@ func (this *box) recursiveRedo () {
this.doDraw() this.doDraw()
} }
func (this *box) invalidateDraw () { func (this *box) invalidateLayout () {
if this.parent == nil { return } if this.parent == nil || this.parent.window() == nil { return }
this.parent.window().invalidateDraw(this) this.parent.window().invalidateLayout(this.outer)
} }
func (this *box) invalidateLayout () { func (this *box) invalidateDraw () {
if this.parent == nil { return } if this.parent == nil || this.parent.window() == nil { return }
this.parent.window().invalidateLayout(this) this.parent.window().invalidateDraw(this.outer)
} }

View File

@ -8,9 +8,11 @@ type canvasBox struct {
} }
func (backend *Backend) NewCanvasBox () tomo.CanvasBox { func (backend *Backend) NewCanvasBox () tomo.CanvasBox {
return &canvasBox { box := &canvasBox {
box: backend.NewBox().(*box), box: backend.NewBox().(*box),
} }
box.outer = box
return box
} }
func (this *canvasBox) Box () tomo.Box { func (this *canvasBox) Box () tomo.Box {

View File

@ -1,8 +1,176 @@
package x package x
import "image"
import "image/color"
import "git.tebibyte.media/tomo/tomo" import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/canvas"
type containerBox struct {
*box
hOverflow, vOverflow bool
contentBounds image.Rectangle
scroll image.Point
gap tomo.Gap
children []anyBox
layout tomo.Layout
on struct {
contentBoundsChange event.FuncBroadcaster
}
}
func (backend *Backend) NewContainerBox() tomo.ContainerBox { func (backend *Backend) NewContainerBox() tomo.ContainerBox {
// TODO box := &containerBox {
return nil box: backend.NewBox().(*box),
}
box.drawer = box
box.outer = box
return box
}
func (this *containerBox) Box () tomo.Box {
return this
}
func (this *containerBox) SetOverflow (horizontal, vertical bool) {
if this.hOverflow == horizontal && this.vOverflow == vertical { return }
this.hOverflow = horizontal
this.vOverflow = vertical
this.invalidateLayout()
}
func (this *containerBox) ContentBounds () image.Rectangle {
return this.contentBounds
}
func (this *containerBox) ScrollTo (point image.Point) {
// TODO: constrain scroll
this.scroll = point
this.invalidateLayout()
}
func (this *containerBox) OnContentBoundsChange (callback func()) event.Cookie {
return this.on.contentBoundsChange.Connect(callback)
}
func (this *containerBox) SetGap (gap tomo.Gap) {
if this.gap == gap { return }
this.gap = gap
this.invalidateLayout()
}
func (this *containerBox) Add (child tomo.Object) {
box := assertAnyBox(child.Box())
if indexOf(this.children, box) > -1 { return }
box.setParent(this)
this.children = append(this.children, box)
this.invalidateLayout()
}
func (this *containerBox) Delete (child tomo.Object) {
box := assertAnyBox(child.Box())
index := indexOf(this.children, box)
if index < 0 { return }
box.setParent(nil)
this.children = remove(this.children, index)
this.invalidateLayout()
}
func (this *containerBox) Insert (child, before tomo.Object) {
box := assertAnyBox(child.Box())
if indexOf(this.children, box) > -1 { return }
beforeBox := assertAnyBox(before.Box())
index := indexOf(this.children, beforeBox)
if index < 0 { return }
box.setParent(this)
this.children = insert(this.children, index, box)
this.invalidateLayout()
}
func (this *containerBox) Clear () {
for _, box := range this.children {
box.setParent(nil)
}
this.children = nil
this.invalidateLayout()
}
func (this *containerBox) Length () int {
return len(this.children)
}
func (this *containerBox) At (index int) tomo.Object {
if index < 0 || index >= len(this.children) {
return nil
}
return this.children[index]
}
func (this *containerBox) SetLayout (layout tomo.Layout) {
this.layout = layout
this.invalidateLayout()
}
func (this *containerBox) Draw (can canvas.Canvas) {
this.drawBorders(can)
pen := can.Pen()
pen.Fill(this.color)
// TODO: do this in doLayout and save the result
rocks := make([]image.Rectangle, len(this.children))
for index, box := range this.children {
rocks[index] = box.Bounds()
}
// TODO: use shatter algorithm here to optimize amount of pixels drawn
// and not draw over child boxes
for index, tile := range canvas.Shatter(this.bounds, rocks...) {
pen.Fill(color.RGBA { R: uint8(32 * (index + 2)), A: 255 })
pen.Rectangle(tile)
}
}
func (this *containerBox) window () *window {
if this.parent == nil { return nil }
return this.parent.window()
}
func (this *containerBox) canvas () canvas.Canvas {
return this.box.canvas
}
func (this *containerBox) doLayout () {
this.box.doLayout()
// TODO: possibly store all children as tomo.Box-es and don't allocate a
// slice here
previousContentBounds := this.contentBounds
boxes := make([]tomo.Box, len(this.children))
for index, box := range this.children {
boxes[index] = box
}
if this.layout != nil {
// TODO maybe we should pass more information into Arrange such
// as overflow information and scroll
this.layout.Arrange (
this.InnerBounds().Sub(this.scroll),
this.gap,
boxes)
}
if previousContentBounds != this.contentBounds {
this.on.contentBoundsChange.Broadcast()
}
}
func (this *containerBox) recursiveRedo () {
this.doLayout()
this.doDraw()
for _, child := range this.children {
child.recursiveRedo()
}
} }

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.20
require ( require (
git.tebibyte.media/tomo/ggfx v0.3.0 git.tebibyte.media/tomo/ggfx v0.3.0
git.tebibyte.media/tomo/tomo v0.7.3 git.tebibyte.media/tomo/tomo v0.8.0
git.tebibyte.media/tomo/xgbkb v1.0.1 git.tebibyte.media/tomo/xgbkb v1.0.1
github.com/jezek/xgb v1.1.0 github.com/jezek/xgb v1.1.0
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0

2
go.sum
View File

@ -21,6 +21,8 @@ git.tebibyte.media/tomo/tomo v0.7.2 h1:15dMJm4Sm339b23o9RZSq87u99SaF2q+b5CRB5P58
git.tebibyte.media/tomo/tomo v0.7.2/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY= git.tebibyte.media/tomo/tomo v0.7.2/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
git.tebibyte.media/tomo/tomo v0.7.3 h1:eHwuYKe+0nLWoEfPZid8njirxmWY3dFmdY+PsPp1RN0= git.tebibyte.media/tomo/tomo v0.7.3 h1:eHwuYKe+0nLWoEfPZid8njirxmWY3dFmdY+PsPp1RN0=
git.tebibyte.media/tomo/tomo v0.7.3/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY= git.tebibyte.media/tomo/tomo v0.7.3/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
git.tebibyte.media/tomo/tomo v0.8.0 h1:Sqvos2Huf0mSHFZ0FJrBZiH8Ro/gmQPHCvK6Qr29SBo=
git.tebibyte.media/tomo/tomo v0.8.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE= git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE=
git.tebibyte.media/tomo/xgbkb v1.0.1/go.mod h1:P5Du0yo5hUsojchW08t+Mds0XPIJXwMi733ZfklzjRw= git.tebibyte.media/tomo/xgbkb v1.0.1/go.mod h1:P5Du0yo5hUsojchW08t+Mds0XPIJXwMi733ZfklzjRw=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA= github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=

View File

@ -44,6 +44,14 @@ type anyBox interface {
recursiveRedo () recursiveRedo ()
} }
func assertAnyBox (unknown tomo.Box) anyBox {
if box, ok := unknown.(anyBox); ok {
return box
} else {
panic("foregin box implementation, i did not make this!")
}
}
func (window *window) SetRoot (root tomo.Object) { func (window *window) SetRoot (root tomo.Object) {
if window.root != nil { if window.root != nil {
window.root.setParent(nil) window.root.setParent(nil)
@ -51,7 +59,7 @@ func (window *window) SetRoot (root tomo.Object) {
if root == nil { if root == nil {
window.root = nil window.root = nil
} else { } else {
box := root.Box().(anyBox) box := assertAnyBox(root.Box())
box.setParent(window) box.setParent(window)
window.invalidateLayout(box) window.invalidateLayout(box)
window.root = box window.root = box

20
util.go Normal file
View File

@ -0,0 +1,20 @@
package x
func indexOf[T comparable] (haystack []T, needle T) int {
for index, test := range haystack {
if test == needle {
return index
}
}
return -1
}
func remove[T any] (slice []T, index int) []T {
return append(slice[:index], slice[index + 1:]...)
}
func insert[T any] (slice []T, index int, element T) []T {
slice = append(slice[:index + 1], slice[index:]...)
slice[index] = element
return slice
}