2023-07-02 00:52:14 -06:00
|
|
|
package x
|
|
|
|
|
|
|
|
import "image"
|
|
|
|
import "image/color"
|
|
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
|
|
import "git.tebibyte.media/tomo/tomo/data"
|
|
|
|
import "git.tebibyte.media/tomo/tomo/input"
|
|
|
|
import "git.tebibyte.media/tomo/tomo/event"
|
|
|
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
|
|
|
|
|
|
|
type box struct {
|
|
|
|
backend *Backend
|
2023-07-03 22:04:00 -06:00
|
|
|
parent parent
|
2023-07-05 01:25:50 -06:00
|
|
|
outer anyBox
|
2023-07-02 00:52:14 -06:00
|
|
|
|
|
|
|
bounds image.Rectangle
|
|
|
|
minSize image.Point
|
|
|
|
|
|
|
|
padding tomo.Inset
|
|
|
|
border []tomo.Border
|
|
|
|
color color.Color
|
|
|
|
|
|
|
|
dndData data.Data
|
|
|
|
dndAccept []data.Mime
|
|
|
|
focused bool
|
|
|
|
focusable bool
|
|
|
|
|
2023-07-03 22:04:00 -06:00
|
|
|
canvas canvas.Canvas
|
2023-07-02 00:52:14 -06:00
|
|
|
drawer canvas.Drawer
|
|
|
|
|
|
|
|
on struct {
|
|
|
|
focusEnter event.FuncBroadcaster
|
|
|
|
focusLeave event.FuncBroadcaster
|
|
|
|
dndEnter event.FuncBroadcaster
|
|
|
|
dndLeave event.FuncBroadcaster
|
|
|
|
dndDrop event.Broadcaster[func (data.Data)]
|
|
|
|
mouseEnter event.FuncBroadcaster
|
|
|
|
mouseLeave event.FuncBroadcaster
|
|
|
|
mouseMove event.FuncBroadcaster
|
|
|
|
mouseDown event.Broadcaster[func (input.Button)]
|
|
|
|
mouseUp event.Broadcaster[func (input.Button)]
|
|
|
|
scroll event.Broadcaster[func (float64, float64)]
|
|
|
|
keyDown event.Broadcaster[func (input.Key, bool)]
|
|
|
|
keyUp event.Broadcaster[func (input.Key, bool)]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (backend *Backend) NewBox() tomo.Box {
|
2023-07-05 01:25:50 -06:00
|
|
|
box := &box {
|
2023-07-02 00:52:14 -06:00
|
|
|
backend: backend,
|
|
|
|
}
|
2023-07-05 01:25:50 -06:00
|
|
|
box.drawer = box
|
|
|
|
box.outer = box
|
|
|
|
return box
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) Box () tomo.Box {
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) Window () tomo.Window {
|
2023-07-03 22:04:00 -06:00
|
|
|
return this.parent.window()
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) Bounds () image.Rectangle {
|
|
|
|
return this.bounds
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) InnerBounds () image.Rectangle {
|
2023-07-11 23:17:12 -06:00
|
|
|
return this.padding.Apply(this.innerClippingBounds())
|
|
|
|
}
|
|
|
|
|
2023-07-12 16:36:11 -06:00
|
|
|
func (this *box) MinimumSize () image.Point {
|
|
|
|
return this.minSize
|
|
|
|
}
|
|
|
|
|
2023-07-13 10:48:09 -06:00
|
|
|
func (this *box) borderSum () tomo.Inset {
|
|
|
|
sum := tomo.Inset { }
|
2023-07-02 00:52:14 -06:00
|
|
|
for _, border := range this.border {
|
2023-07-13 10:48:09 -06:00
|
|
|
sum[0] += border.Width[0]
|
|
|
|
sum[1] += border.Width[1]
|
|
|
|
sum[2] += border.Width[2]
|
|
|
|
sum[3] += border.Width[3]
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
2023-07-13 10:48:09 -06:00
|
|
|
return sum
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) innerClippingBounds () image.Rectangle {
|
|
|
|
return this.borderSum().Apply(this.bounds)
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) SetBounds (bounds image.Rectangle) {
|
|
|
|
if this.bounds == bounds { return }
|
|
|
|
this.bounds = bounds
|
|
|
|
this.invalidateLayout()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) SetColor (c color.Color) {
|
|
|
|
if this.color == c { return }
|
|
|
|
this.color = c
|
|
|
|
this.invalidateDraw()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) SetBorder (border ...tomo.Border) {
|
|
|
|
this.border = border
|
|
|
|
this.invalidateLayout()
|
2023-07-13 10:48:09 -06:00
|
|
|
this.recalculateMinimumSize()
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
2023-07-12 16:36:11 -06:00
|
|
|
func (this *box) SetMinimumSize (size image.Point) {
|
|
|
|
if this.minSize == size { return }
|
|
|
|
this.minSize = size
|
2023-07-05 01:25:50 -06:00
|
|
|
|
2023-07-12 16:36:11 -06:00
|
|
|
if this.parent != nil {
|
|
|
|
this.parent.notifyMinimumSizeChange(this)
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) SetPadding (padding tomo.Inset) {
|
|
|
|
if this.padding == padding { return }
|
|
|
|
this.padding = padding
|
|
|
|
this.invalidateLayout()
|
2023-07-13 10:48:09 -06:00
|
|
|
this.recalculateMinimumSize()
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) SetDNDData (dat data.Data) {
|
|
|
|
this.dndData = dat
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) SetDNDAccept (types ...data.Mime) {
|
|
|
|
this.dndAccept = types
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) SetFocused (focused bool) {
|
2023-08-03 10:45:21 -06:00
|
|
|
if this.parent == nil { return }
|
|
|
|
window := this.parent.window()
|
|
|
|
if window == nil { return }
|
|
|
|
if !this.focusable { return }
|
|
|
|
|
2023-07-02 00:52:14 -06:00
|
|
|
if this.Focused () && !focused {
|
2023-07-03 22:04:00 -06:00
|
|
|
this.parent.window().focus(nil)
|
2023-07-02 00:52:14 -06:00
|
|
|
} else if !this.Focused() && focused {
|
2023-07-05 01:25:50 -06:00
|
|
|
this.parent.window().focus(this.outer)
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) SetFocusable (focusable bool) {
|
|
|
|
if this.focusable == focusable { return }
|
|
|
|
this.focusable = focusable
|
|
|
|
if !focusable {
|
|
|
|
this.SetFocused(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) Focused () bool {
|
2023-07-15 22:56:13 -06:00
|
|
|
if this.parent == nil { return false }
|
|
|
|
parentWindow := this.parent.window()
|
|
|
|
if parentWindow == nil { return false }
|
|
|
|
return this.outer == parentWindow.focused
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) Modifiers () input.Modifiers {
|
2023-07-03 22:04:00 -06:00
|
|
|
return this.parent.window().modifiers
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) MousePosition () image.Point {
|
2023-07-03 22:04:00 -06:00
|
|
|
return this.parent.window().mousePosition
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
2023-07-05 01:25:50 -06:00
|
|
|
// ----- event handler setters ---------------------------------------------- //
|
2023-07-02 00:52:14 -06:00
|
|
|
func (this *box) OnFocusEnter (callback func()) event.Cookie {
|
|
|
|
return this.on.focusEnter.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnFocusLeave (callback func()) event.Cookie {
|
|
|
|
return this.on.focusLeave.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnDNDEnter (callback func()) event.Cookie {
|
|
|
|
return this.on.dndEnter.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnDNDLeave (callback func()) event.Cookie {
|
|
|
|
return this.on.dndLeave.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnDNDDrop (callback func(data.Data)) event.Cookie {
|
|
|
|
return this.on.dndDrop.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnMouseEnter (callback func()) event.Cookie {
|
|
|
|
return this.on.mouseEnter.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnMouseLeave (callback func()) event.Cookie {
|
|
|
|
return this.on.mouseLeave.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnMouseMove (callback func()) event.Cookie {
|
|
|
|
return this.on.mouseMove.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnMouseDown (callback func(input.Button)) event.Cookie {
|
|
|
|
return this.on.mouseDown.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnMouseUp (callback func(input.Button)) event.Cookie {
|
|
|
|
return this.on.mouseUp.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnScroll (callback func(deltaX, deltaY float64)) event.Cookie {
|
|
|
|
return this.on.scroll.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnKeyDown (callback func(key input.Key, numberPad bool)) event.Cookie {
|
|
|
|
return this.on.keyDown.Connect(callback)
|
|
|
|
}
|
|
|
|
func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Cookie {
|
|
|
|
return this.on.keyUp.Connect(callback)
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
|
|
|
func (this *box) Draw (can canvas.Canvas) {
|
2023-07-11 23:17:12 -06:00
|
|
|
if can == nil { return }
|
2023-07-02 00:52:14 -06:00
|
|
|
pen := can.Pen()
|
2023-07-05 01:25:50 -06:00
|
|
|
pen.Fill(this.color)
|
|
|
|
pen.Rectangle(this.bounds)
|
|
|
|
}
|
2023-07-02 00:52:14 -06:00
|
|
|
|
2023-07-05 01:25:50 -06:00
|
|
|
func (this *box) drawBorders (can canvas.Canvas) {
|
2023-07-11 23:17:12 -06:00
|
|
|
if can == nil { return }
|
2023-07-05 01:25:50 -06:00
|
|
|
pen := can.Pen()
|
2023-07-02 00:52:14 -06:00
|
|
|
bounds := this.bounds
|
|
|
|
for _, border := range this.border {
|
|
|
|
pen.Fill(border.Color[tomo.SideTop])
|
|
|
|
pen.Rectangle(image.Rect (
|
|
|
|
bounds.Min.X,
|
|
|
|
bounds.Min.Y,
|
|
|
|
bounds.Max.X,
|
|
|
|
bounds.Min.Y + border.Width[tomo.SideTop]))
|
|
|
|
pen.Fill(border.Color[tomo.SideBottom])
|
|
|
|
pen.Rectangle(image.Rect (
|
|
|
|
bounds.Min.X,
|
|
|
|
bounds.Max.Y - border.Width[tomo.SideBottom],
|
|
|
|
bounds.Max.X,
|
|
|
|
bounds.Max.Y))
|
|
|
|
pen.Fill(border.Color[tomo.SideLeft])
|
|
|
|
pen.Rectangle(image.Rect (
|
|
|
|
bounds.Min.X,
|
|
|
|
bounds.Min.Y + border.Width[tomo.SideTop],
|
|
|
|
bounds.Min.X + border.Width[tomo.SideLeft],
|
|
|
|
bounds.Max.Y - border.Width[tomo.SideBottom]))
|
|
|
|
pen.Fill(border.Color[tomo.SideRight])
|
|
|
|
pen.Rectangle(image.Rect (
|
|
|
|
bounds.Max.X - border.Width[tomo.SideRight],
|
|
|
|
bounds.Min.Y + border.Width[tomo.SideTop],
|
|
|
|
bounds.Max.X,
|
|
|
|
bounds.Max.Y - border.Width[tomo.SideBottom]))
|
|
|
|
|
|
|
|
bounds = border.Width.Apply(bounds)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-03 22:04:00 -06:00
|
|
|
func (this *box) doDraw () {
|
|
|
|
if this.canvas == nil { return }
|
2023-07-05 01:25:50 -06:00
|
|
|
if this.drawer != nil {
|
2023-07-11 23:17:12 -06:00
|
|
|
this.drawBorders(this.canvas)
|
|
|
|
this.drawer.Draw(this.canvas.Clip(this.innerClippingBounds()))
|
2023-07-03 22:04:00 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) doLayout () {
|
2023-07-15 22:42:47 -06:00
|
|
|
if this.parent == nil { this.canvas = nil; return }
|
|
|
|
parentCanvas := this.parent.canvas()
|
|
|
|
if parentCanvas == nil { this.canvas = nil; return }
|
|
|
|
this.canvas = parentCanvas.Clip(this.bounds)
|
2023-07-03 22:04:00 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) setParent (parent parent) {
|
2023-07-21 21:22:03 -06:00
|
|
|
if this.parent != parent && this.Focused() {
|
|
|
|
this.SetFocused(false)
|
|
|
|
}
|
2023-07-03 22:04:00 -06:00
|
|
|
this.parent = parent
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) recursiveRedo () {
|
|
|
|
this.doLayout()
|
|
|
|
this.doDraw()
|
|
|
|
}
|
|
|
|
|
2023-07-05 01:25:50 -06:00
|
|
|
func (this *box) invalidateLayout () {
|
|
|
|
if this.parent == nil || this.parent.window() == nil { return }
|
|
|
|
this.parent.window().invalidateLayout(this.outer)
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
|
|
|
|
2023-07-05 01:25:50 -06:00
|
|
|
func (this *box) invalidateDraw () {
|
|
|
|
if this.parent == nil || this.parent.window() == nil { return }
|
|
|
|
this.parent.window().invalidateDraw(this.outer)
|
2023-07-02 00:52:14 -06:00
|
|
|
}
|
2023-07-13 10:48:09 -06:00
|
|
|
|
|
|
|
func (this *box) recalculateMinimumSize () {
|
2023-07-15 22:42:47 -06:00
|
|
|
if this.outer != anyBox(this) {
|
2023-07-13 10:48:09 -06:00
|
|
|
this.outer.recalculateMinimumSize()
|
|
|
|
}
|
|
|
|
}
|
2023-07-15 22:42:47 -06:00
|
|
|
|
|
|
|
func (this *box) canBeFocused () bool {
|
|
|
|
return this.focusable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) boxUnder (point image.Point) anyBox {
|
|
|
|
if point.In(this.bounds) {
|
|
|
|
return this.outer
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-15 22:54:25 -06:00
|
|
|
func (this *box) handleFocusEnter () {
|
|
|
|
this.on.focusEnter.Broadcast()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) handleFocusLeave () {
|
|
|
|
this.on.focusLeave.Broadcast()
|
|
|
|
}
|
|
|
|
|
2023-08-05 15:56:15 -06:00
|
|
|
func (this *box) handleMouseEnter () {
|
|
|
|
this.on.mouseEnter.Broadcast()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) handleMouseLeave () {
|
|
|
|
this.on.mouseLeave.Broadcast()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) handleMouseMove () {
|
|
|
|
this.on.mouseMove.Broadcast()
|
|
|
|
}
|
|
|
|
|
2023-07-15 22:42:47 -06:00
|
|
|
func (this *box) handleMouseDown (button input.Button) {
|
2023-08-03 10:45:21 -06:00
|
|
|
if this.focusable {
|
|
|
|
this.SetFocused(true)
|
|
|
|
} else {
|
|
|
|
if this.parent == nil { return }
|
|
|
|
window := this.parent.window()
|
|
|
|
if window == nil { return }
|
|
|
|
window.focus(nil)
|
|
|
|
}
|
2023-07-15 22:42:47 -06:00
|
|
|
for _, listener := range this.on.mouseDown.Listeners() {
|
|
|
|
listener(button)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) handleMouseUp (button input.Button) {
|
|
|
|
for _, listener := range this.on.mouseUp.Listeners() {
|
|
|
|
listener(button)
|
|
|
|
}
|
|
|
|
}
|
2023-07-21 21:22:03 -06:00
|
|
|
|
|
|
|
func (this *box) handleKeyDown (key input.Key, numberPad bool) {
|
|
|
|
for _, listener := range this.on.keyDown.Listeners() {
|
|
|
|
listener(key, numberPad)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) handleKeyUp (key input.Key, numberPad bool) {
|
|
|
|
for _, listener := range this.on.keyUp.Listeners() {
|
|
|
|
listener(key, numberPad)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) propagate (callback func (anyBox) bool) bool {
|
|
|
|
return callback(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *box) propagateAlt (callback func (anyBox) bool) bool {
|
|
|
|
return callback(this)
|
|
|
|
}
|