Sasha Koshka
ff8875535d
Boxes that need their minimum size to be updated now use a map like for layout and drawing. Size set with MinimumSize is now treated as separate from the content size and the larger size is used.
418 lines
10 KiB
Go
418 lines
10 KiB
Go
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
|
|
parent parent
|
|
outer anyBox
|
|
|
|
bounds image.Rectangle
|
|
minSize image.Point
|
|
userMinSize image.Point
|
|
|
|
minSizeQueued bool
|
|
focusQueued *bool
|
|
|
|
padding tomo.Inset
|
|
border []tomo.Border
|
|
color color.Color
|
|
|
|
dndData data.Data
|
|
dndAccept []data.Mime
|
|
focused bool
|
|
focusable bool
|
|
|
|
canvas canvas.Canvas
|
|
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 (outer anyBox) *box {
|
|
box := &box {
|
|
backend: backend,
|
|
color: color.White,
|
|
outer: outer,
|
|
drawer: outer,
|
|
}
|
|
if outer == nil {
|
|
box.drawer = box
|
|
box.outer = box
|
|
}
|
|
box.invalidateMinimum()
|
|
return box
|
|
}
|
|
|
|
func (backend *Backend) NewBox() tomo.Box {
|
|
box := backend.newBox(nil)
|
|
return box
|
|
}
|
|
|
|
func (this *box) GetBox () tomo.Box {
|
|
return this.outer
|
|
}
|
|
|
|
func (this *box) Window () tomo.Window {
|
|
return this.parent.window()
|
|
}
|
|
|
|
func (this *box) Bounds () image.Rectangle {
|
|
return this.bounds
|
|
}
|
|
|
|
func (this *box) InnerBounds () image.Rectangle {
|
|
return this.padding.Apply(this.innerClippingBounds())
|
|
}
|
|
|
|
func (this *box) MinimumSize () image.Point {
|
|
return this.minSize
|
|
}
|
|
|
|
func (this *box) borderSum () tomo.Inset {
|
|
sum := tomo.Inset { }
|
|
for _, border := range this.border {
|
|
sum[0] += border.Width[0]
|
|
sum[1] += border.Width[1]
|
|
sum[2] += border.Width[2]
|
|
sum[3] += border.Width[3]
|
|
}
|
|
return sum
|
|
}
|
|
|
|
func (this *box) innerClippingBounds () image.Rectangle {
|
|
return this.borderSum().Apply(this.bounds)
|
|
}
|
|
|
|
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()
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
func (this *box) SetMinimumSize (size image.Point) {
|
|
if this.userMinSize == size { return }
|
|
this.userMinSize = size
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
func (this *box) SetPadding (padding tomo.Inset) {
|
|
if this.padding == padding { return }
|
|
this.padding = padding
|
|
this.invalidateLayout()
|
|
this.invalidateMinimum()
|
|
}
|
|
|
|
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) {
|
|
if this.parent == nil || this.parent.window () == nil {
|
|
focusedCopy := focused
|
|
this.focusQueued = &focusedCopy
|
|
return
|
|
}
|
|
if !this.focusable { return }
|
|
|
|
if this.Focused () && !focused {
|
|
this.parent.window().focus(nil)
|
|
} else if !this.Focused() && focused {
|
|
this.parent.window().focus(this.outer)
|
|
}
|
|
}
|
|
|
|
func (this *box) SetFocusable (focusable bool) {
|
|
if this.focusable == focusable { return }
|
|
this.focusable = focusable
|
|
if !focusable {
|
|
this.SetFocused(false)
|
|
}
|
|
}
|
|
|
|
func (this *box) Focused () bool {
|
|
if this.parent == nil { return false }
|
|
parentWindow := this.parent.window()
|
|
if parentWindow == nil { return false }
|
|
return this.outer == parentWindow.focused
|
|
}
|
|
|
|
func (this *box) Modifiers () input.Modifiers {
|
|
return this.parent.window().modifiers
|
|
}
|
|
|
|
func (this *box) MousePosition () image.Point {
|
|
return this.parent.window().mousePosition
|
|
}
|
|
|
|
// ----- event handler setters ---------------------------------------------- //
|
|
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) {
|
|
if can == nil { return }
|
|
pen := can.Pen()
|
|
pen.Fill(this.color)
|
|
pen.Rectangle(this.bounds)
|
|
}
|
|
|
|
func (this *box) drawBorders (can canvas.Canvas) {
|
|
if can == nil { return }
|
|
pen := can.Pen()
|
|
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)
|
|
}
|
|
}
|
|
|
|
func (this *box) contentMinimum () image.Point {
|
|
var minimum image.Point
|
|
minimum.X += this.padding.Horizontal()
|
|
minimum.Y += this.padding.Vertical()
|
|
borderSum := this.borderSum()
|
|
minimum.X += borderSum.Horizontal()
|
|
minimum.Y += borderSum.Vertical()
|
|
return minimum
|
|
}
|
|
|
|
func (this *box) doMinimumSize () {
|
|
this.minSize = this.outer.contentMinimum()
|
|
if this.minSize.X < this.userMinSize.X {
|
|
this.minSize.X = this.userMinSize.X
|
|
}
|
|
if this.minSize.Y < this.userMinSize.Y {
|
|
this.minSize.Y = this.userMinSize.Y
|
|
}
|
|
|
|
if this.parent != nil {
|
|
this.parent.notifyMinimumSizeChange(this)
|
|
}
|
|
}
|
|
|
|
func (this *box) doDraw () {
|
|
if this.canvas == nil { return }
|
|
if this.drawer != nil {
|
|
this.drawBorders(this.canvas)
|
|
this.drawer.Draw(this.canvas.Clip(this.innerClippingBounds()))
|
|
}
|
|
}
|
|
|
|
func (this *box) doLayout () {
|
|
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)
|
|
}
|
|
|
|
func (this *box) setParent (parent parent) {
|
|
if this.parent != parent && this.Focused() {
|
|
this.SetFocused(false)
|
|
}
|
|
this.parent = parent
|
|
}
|
|
|
|
func (this *box) flushActionQueue () {
|
|
if this.parent == nil || this.parent.window() == nil { return }
|
|
|
|
if this.minSizeQueued {
|
|
this.invalidateMinimum()
|
|
}
|
|
if this.focusQueued != nil {
|
|
this.SetFocused(*this.focusQueued)
|
|
}
|
|
}
|
|
|
|
func (this *box) recursiveRedo () {
|
|
this.doLayout()
|
|
this.doDraw()
|
|
}
|
|
|
|
func (this *box) invalidateLayout () {
|
|
if this.parent == nil || this.parent.window() == nil { return }
|
|
this.parent.window().invalidateLayout(this.outer)
|
|
}
|
|
|
|
func (this *box) invalidateDraw () {
|
|
if this.parent == nil || this.parent.window() == nil { return }
|
|
this.parent.window().invalidateDraw(this.outer)
|
|
}
|
|
|
|
func (this *box) invalidateMinimum () {
|
|
if this.parent == nil || this.parent.window() == nil {
|
|
this.minSizeQueued = true
|
|
return
|
|
}
|
|
this.parent.window().invalidateMinimum(this.outer)
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func (this *box) handleFocusEnter () {
|
|
this.on.focusEnter.Broadcast()
|
|
}
|
|
|
|
func (this *box) handleFocusLeave () {
|
|
this.on.focusLeave.Broadcast()
|
|
}
|
|
|
|
func (this *box) handleMouseEnter () {
|
|
this.on.mouseEnter.Broadcast()
|
|
}
|
|
|
|
func (this *box) handleMouseLeave () {
|
|
this.on.mouseLeave.Broadcast()
|
|
}
|
|
|
|
func (this *box) handleMouseMove () {
|
|
this.on.mouseMove.Broadcast()
|
|
}
|
|
|
|
func (this *box) handleMouseDown (button input.Button) {
|
|
if this.focusable {
|
|
this.SetFocused(true)
|
|
} else {
|
|
if this.parent == nil { return }
|
|
window := this.parent.window()
|
|
if window == nil { return }
|
|
window.focus(nil)
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
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.outer)
|
|
}
|
|
|
|
func (this *box) propagateAlt (callback func (anyBox) bool) bool {
|
|
return callback(this.outer)
|
|
}
|