This repository has been archived on 2024-06-03. You can view files and clone it, but cannot push or open issues or pull requests.
x/containerbox.go

373 lines
9.2 KiB
Go
Raw Permalink Normal View History

2023-07-02 06:52:14 +00:00
package x
2023-07-05 07:25:50 +00:00
import "image"
import "image/color"
2023-07-02 06:52:14 +00:00
import "git.tebibyte.media/tomo/tomo"
2023-07-05 07:25:50 +00:00
import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/canvas"
type containerBox struct {
*box
hOverflow, vOverflow bool
2023-07-13 16:48:09 +00:00
hAlign, vAlign tomo.Align
2023-07-05 07:25:50 +00:00
contentBounds image.Rectangle
scroll image.Point
2023-09-09 19:05:08 +00:00
capture [4]bool
2023-07-16 04:42:47 +00:00
gap image.Point
children []tomo.Box
layout tomo.Layout
2023-07-05 07:25:50 +00:00
on struct {
contentBoundsChange event.FuncBroadcaster
}
}
2023-07-02 06:52:14 +00:00
func (backend *Backend) NewContainerBox() tomo.ContainerBox {
2023-09-09 19:05:08 +00:00
this := &containerBox { }
this.box = backend.newBox(this)
return this
2023-07-05 07:25:50 +00:00
}
func (this *containerBox) SetColor (c color.Color) {
if this.color == c { return }
this.box.SetColor(c)
this.invalidateTransparentChildren()
}
2024-05-26 19:20:11 +00:00
func (this *containerBox) SetTextureTile (texture canvas.Texture) {
if this.texture == texture { return }
2024-05-26 19:20:11 +00:00
this.box.SetTextureTile(texture)
this.invalidateTransparentChildren()
}
func (this *containerBox) SetTextureCenter (texture canvas.Texture) {
if this.texture == texture { return }
this.box.SetTextureTile(texture)
this.invalidateTransparentChildren()
}
2023-07-05 07:25:50 +00:00
func (this *containerBox) SetOverflow (horizontal, vertical bool) {
if this.hOverflow == horizontal && this.vOverflow == vertical { return }
this.hOverflow = horizontal
this.vOverflow = vertical
this.invalidateLayout()
}
2023-07-13 16:48:09 +00:00
func (this *containerBox) SetAlign (x, y tomo.Align) {
if this.hAlign == x && this.vAlign == y { return }
this.hAlign = x
this.vAlign = y
this.invalidateLayout()
}
2023-07-05 07:25:50 +00:00
func (this *containerBox) ContentBounds () image.Rectangle {
return this.contentBounds
}
func (this *containerBox) ScrollTo (point image.Point) {
2024-05-13 21:43:26 +00:00
if this.scroll == point { return }
2023-07-05 07:25:50 +00:00
this.scroll = point
this.invalidateLayout()
}
func (this *containerBox) OnContentBoundsChange (callback func()) event.Cookie {
return this.on.contentBoundsChange.Connect(callback)
}
2024-05-13 21:43:26 +00:00
func (this *containerBox) CaptureDND (capture bool) {
2023-09-09 19:05:08 +00:00
this.capture[eventCategoryDND] = capture
}
2024-05-13 21:43:26 +00:00
func (this *containerBox) CaptureMouse (capture bool) {
2023-09-09 19:05:08 +00:00
this.capture[eventCategoryMouse] = capture
}
2024-05-13 21:43:26 +00:00
func (this *containerBox) CaptureScroll (capture bool) {
2023-09-09 19:05:08 +00:00
this.capture[eventCategoryScroll] = capture
}
2024-05-13 21:43:26 +00:00
func (this *containerBox) CaptureKeyboard (capture bool) {
2023-09-09 19:05:08 +00:00
this.capture[eventCategoryKeyboard] = capture
2023-07-16 04:42:47 +00:00
}
2023-07-06 06:08:10 +00:00
func (this *containerBox) SetGap (gap image.Point) {
2023-07-05 07:25:50 +00:00
if this.gap == gap { return }
this.gap = gap
this.invalidateLayout()
this.invalidateMinimum()
2023-07-05 07:25:50 +00:00
}
func (this *containerBox) Add (child tomo.Object) {
2023-08-12 05:03:34 +00:00
box := assertAnyBox(child.GetBox())
if indexOf(this.children, tomo.Box(box)) > -1 { return }
2023-07-05 07:25:50 +00:00
box.setParent(this)
box.flushActionQueue()
2023-07-05 07:25:50 +00:00
this.children = append(this.children, box)
this.invalidateLayout()
this.invalidateMinimum()
2023-07-05 07:25:50 +00:00
}
func (this *containerBox) Remove (child tomo.Object) {
2023-08-12 05:03:34 +00:00
box := assertAnyBox(child.GetBox())
index := indexOf(this.children, tomo.Box(box))
2023-07-05 07:25:50 +00:00
if index < 0 { return }
2023-07-05 07:25:50 +00:00
box.setParent(nil)
this.children = remove(this.children, index)
this.invalidateLayout()
this.invalidateMinimum()
2023-07-05 07:25:50 +00:00
}
func (this *containerBox) Insert (child, before tomo.Object) {
2023-08-12 05:03:34 +00:00
box := assertAnyBox(child.GetBox())
if indexOf(this.children, tomo.Box(box)) > -1 { return }
2023-07-05 07:25:50 +00:00
2023-08-12 05:03:34 +00:00
beforeBox := assertAnyBox(before.GetBox())
index := indexOf(this.children, tomo.Box(beforeBox))
2024-05-08 00:11:58 +00:00
if index < 0 {
this.children = append(this.children, tomo.Box(box))
} else {
this.children = insert(this.children, index, tomo.Box(box))
}
2023-07-05 07:25:50 +00:00
box.setParent(this)
2024-05-08 00:11:58 +00:00
2023-07-05 07:25:50 +00:00
this.invalidateLayout()
this.invalidateMinimum()
2023-07-05 07:25:50 +00:00
}
func (this *containerBox) Clear () {
for _, box := range this.children {
box.(anyBox).setParent(nil)
2023-07-05 07:25:50 +00:00
}
this.children = nil
this.invalidateLayout()
this.invalidateMinimum()
2023-07-05 07:25:50 +00:00
}
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()
this.invalidateMinimum()
2023-07-05 07:25:50 +00:00
}
func (this *containerBox) Draw (can canvas.Canvas) {
2023-07-12 05:17:12 +00:00
if can == nil { return }
2023-07-05 07:25:50 +00:00
rocks := make([]image.Rectangle, len(this.children))
for index, box := range this.children {
rocks[index] = box.Bounds()
}
2023-07-05 08:11:24 +00:00
for _, tile := range canvas.Shatter(this.bounds, rocks...) {
2024-05-26 19:18:27 +00:00
clipped := can.SubCanvas(tile)
if this.transparent() && this.parent != nil {
2023-08-29 19:52:24 +00:00
this.parent.drawBackgroundPart(clipped)
}
2023-08-29 19:52:24 +00:00
if clipped == nil { continue }
pen := clipped.Pen()
pen.Fill(this.color)
pen.Texture(this.texture)
pen.Rectangle(this.innerClippingBounds)
}
}
func (this *containerBox) drawBackgroundPart (can canvas.Canvas) {
if can == nil { return }
pen := can.Pen()
pen.Fill(this.color)
pen.Texture(this.texture)
if this.transparent() && this.parent != nil {
this.parent.drawBackgroundPart(can)
2023-07-05 07:25:50 +00:00
}
2023-08-29 19:52:24 +00:00
pen.Rectangle(this.innerClippingBounds)
2023-07-05 07:25:50 +00:00
}
func (this *containerBox) invalidateTransparentChildren () {
window := this.window()
2023-09-09 19:05:08 +00:00
if window == nil { return }
for _, box := range this.children {
box := assertAnyBox(box)
if box.transparent() {
window.invalidateDraw(box)
}
}
}
func (this *containerBox) flushActionQueue () {
for _, box := range this.children {
box.(anyBox).flushActionQueue()
}
this.box.flushActionQueue()
}
2023-07-05 07:25:50 +00:00
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) notifyMinimumSizeChange (child anyBox) {
this.invalidateMinimum()
size := child.MinimumSize()
bounds := child.Bounds()
if bounds.Dx() < size.X || bounds.Dy() < size.Y {
this.invalidateLayout()
}
}
2023-07-06 06:08:10 +00:00
func (this *containerBox) layoutHints () tomo.LayoutHints {
return tomo.LayoutHints {
OverflowX: this.hOverflow,
OverflowY: this.vOverflow,
2023-07-13 16:51:38 +00:00
AlignX: this.hAlign,
AlignY: this.vAlign,
2023-07-06 06:08:10 +00:00
Gap: this.gap,
}
}
func (this *containerBox) contentMinimum () image.Point {
minimum := this.box.contentMinimum()
if this.layout != nil {
2024-05-13 21:43:26 +00:00
layoutMinimum := this.layout.MinimumSize (
this.layoutHints(),
2024-05-13 21:43:26 +00:00
this.children)
if this.hOverflow { layoutMinimum.X = 0 }
if this.vOverflow { layoutMinimum.Y = 0 }
minimum = minimum.Add(layoutMinimum)
}
return minimum
2023-07-12 05:17:12 +00:00
}
2023-07-05 07:25:50 +00:00
func (this *containerBox) doLayout () {
this.box.doLayout()
previousContentBounds := this.contentBounds
2024-05-13 21:43:26 +00:00
// by default, use innerBounds (translated to 0, 0) for contentBounds.
// if a direction overflows, use the layout's minimum size for it.
2024-05-13 21:43:26 +00:00
var minimum image.Point
if this.layout != nil {
minimum = this.layout.MinimumSize (
this.layoutHints(),
this.children)
}
innerBounds := this.InnerBounds()
this.contentBounds = innerBounds.Sub(innerBounds.Min)
2024-05-13 21:43:26 +00:00
if this.hOverflow { this.contentBounds.Max.X = this.contentBounds.Min.X + minimum.X }
if this.vOverflow { this.contentBounds.Max.Y = this.contentBounds.Min.Y + minimum.Y }
// arrange children
2023-07-05 07:25:50 +00:00
if this.layout != nil {
layoutHints := this.layoutHints()
layoutHints.Bounds = this.contentBounds
this.layout.Arrange(layoutHints, this.children)
}
// build an accurate contentBounds by unioning the bounds of all child
// boxes
this.contentBounds = image.Rectangle { }
for _, box := range this.children {
bounds := box.Bounds()
this.contentBounds = this.contentBounds.Union(bounds)
}
// constrain the scroll
this.constrainScroll()
// offset children and contentBounds by scroll
for _, box := range this.children {
box.SetBounds(box.Bounds().Add(this.scroll).Add(innerBounds.Min))
2023-07-05 07:25:50 +00:00
}
this.contentBounds = this.contentBounds.Add(this.scroll)
2024-05-13 21:43:26 +00:00
2023-07-05 07:25:50 +00:00
if previousContentBounds != this.contentBounds {
this.on.contentBoundsChange.Broadcast()
2024-05-13 21:43:26 +00:00
}
}
func (this *containerBox) constrainScroll () {
innerBounds := this.InnerBounds()
width := this.contentBounds.Dx()
height := this.contentBounds.Dy()
// X
if width <= innerBounds.Dx() {
this.scroll.X = 0
2024-05-13 23:35:03 +00:00
} else if this.scroll.X > 0 {
2024-05-13 21:43:26 +00:00
this.scroll.X = 0
2024-05-13 23:35:03 +00:00
} else if this.scroll.X < innerBounds.Dx() - width {
this.scroll.X = innerBounds.Dx() - width
2024-05-13 21:43:26 +00:00
}
// Y
if height <= innerBounds.Dy() {
this.scroll.Y = 0
2024-05-13 23:35:03 +00:00
} else if this.scroll.Y > 0 {
2024-05-13 21:43:26 +00:00
this.scroll.Y = 0
2024-05-13 23:35:03 +00:00
} else if this.scroll.Y < innerBounds.Dy() - height {
this.scroll.Y = innerBounds.Dy() - height
2023-07-05 07:25:50 +00:00
}
}
func (this *containerBox) recursiveRedo () {
this.doLayout()
this.doDraw()
for _, child := range this.children {
child.(anyBox).recursiveRedo()
2023-07-05 07:25:50 +00:00
}
2023-07-02 06:52:14 +00:00
}
2023-07-16 04:42:47 +00:00
2023-09-09 19:05:08 +00:00
func (this *containerBox) boxUnder (point image.Point, category eventCategory) anyBox {
2024-05-17 19:36:49 +00:00
if !point.In(this.bounds) { return nil }
2023-09-09 19:05:08 +00:00
if !this.capture[category] {
2023-07-16 04:42:47 +00:00
for _, box := range this.children {
2023-09-09 19:05:08 +00:00
candidate := box.(anyBox).boxUnder(point, category)
2023-07-16 04:42:47 +00:00
if candidate != nil { return candidate }
}
}
2024-05-17 19:36:49 +00:00
return this
2023-07-16 04:42:47 +00:00
}
2023-07-22 03:22:03 +00:00
func (this *containerBox) propagate (callback func (anyBox) bool) bool {
for _, box := range this.children {
box := box.(anyBox)
if !box.propagate(callback) { return false }
}
return callback(this)
}
func (this *containerBox) propagateAlt (callback func (anyBox) bool) bool {
if !callback(this) { return false}
2023-07-22 03:22:03 +00:00
for _, box := range this.children {
box := box.(anyBox)
if !box.propagateAlt(callback) { return false }
}
2023-07-22 03:22:03 +00:00
return true
}
2023-09-09 19:05:08 +00:00
func (this *containerBox) captures (category eventCategory) bool {
return this.capture[category]
}