2023-04-13 00:22:54 -06:00
|
|
|
package x
|
|
|
|
|
|
|
|
import "image"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
|
|
|
|
|
|
|
type entity struct {
|
|
|
|
window *window
|
|
|
|
parent *entity
|
|
|
|
children []*entity
|
|
|
|
element tomo.Element
|
|
|
|
|
2023-04-13 22:25:05 -06:00
|
|
|
bounds image.Rectangle
|
|
|
|
clippedBounds image.Rectangle
|
|
|
|
minWidth int
|
|
|
|
minHeight int
|
|
|
|
|
2023-04-17 00:05:53 -06:00
|
|
|
selected bool
|
2023-04-13 22:25:05 -06:00
|
|
|
layoutInvalid bool
|
|
|
|
isContainer bool
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
2023-04-14 23:14:36 -06:00
|
|
|
func (backend *Backend) NewEntity (owner tomo.Element) tomo.Entity {
|
|
|
|
entity := &entity { element: owner }
|
|
|
|
if _, ok := owner.(tomo.Container); ok {
|
2023-04-13 22:25:05 -06:00
|
|
|
entity.isContainer = true
|
|
|
|
entity.InvalidateLayout()
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
return entity
|
|
|
|
}
|
|
|
|
|
2023-04-14 23:14:36 -06:00
|
|
|
func (ent *entity) unlink () {
|
|
|
|
ent.propagate (func (child *entity) bool {
|
2023-04-15 19:49:40 -06:00
|
|
|
if child.window != nil {
|
|
|
|
delete(ent.window.system.drawingInvalid, child)
|
|
|
|
}
|
2023-04-14 23:14:36 -06:00
|
|
|
child.window = nil
|
|
|
|
return true
|
|
|
|
})
|
2023-04-15 16:09:49 -06:00
|
|
|
|
|
|
|
if ent.window != nil {
|
|
|
|
delete(ent.window.system.drawingInvalid, ent)
|
|
|
|
}
|
2023-04-14 23:14:36 -06:00
|
|
|
ent.parent = nil
|
|
|
|
ent.window = nil
|
2023-04-17 00:05:53 -06:00
|
|
|
|
|
|
|
if element, ok := ent.element.(tomo.Selectable); ok {
|
|
|
|
ent.selected = false
|
|
|
|
element.HandleSelectionChange()
|
|
|
|
}
|
2023-04-14 23:14:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) link (parent *entity) {
|
|
|
|
entity.parent = parent
|
2023-04-18 01:07:06 -06:00
|
|
|
entity.clip(parent.clippedBounds)
|
2023-04-14 23:14:36 -06:00
|
|
|
if parent.window != nil {
|
|
|
|
entity.setWindow(parent.window)
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-14 23:14:36 -06:00
|
|
|
func (ent *entity) setWindow (window *window) {
|
|
|
|
ent.window = window
|
|
|
|
ent.Invalidate()
|
|
|
|
ent.InvalidateLayout()
|
|
|
|
ent.propagate (func (child *entity) bool {
|
|
|
|
child.window = window
|
|
|
|
ent.Invalidate()
|
|
|
|
ent.InvalidateLayout()
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-04-15 19:49:40 -06:00
|
|
|
func (entity *entity) propagate (callback func (*entity) bool) bool {
|
2023-04-14 17:08:14 -06:00
|
|
|
for _, child := range entity.children {
|
2023-04-15 19:49:40 -06:00
|
|
|
if !child.propagate(callback) {
|
|
|
|
return false
|
|
|
|
}
|
2023-04-14 17:08:14 -06:00
|
|
|
}
|
2023-04-15 19:49:40 -06:00
|
|
|
return callback(entity)
|
2023-04-14 17:08:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) childAt (point image.Point) *entity {
|
|
|
|
for _, child := range entity.children {
|
|
|
|
if point.In(child.bounds) {
|
2023-04-15 19:49:40 -06:00
|
|
|
return child.childAt(point)
|
2023-04-14 17:08:14 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return entity
|
|
|
|
}
|
|
|
|
|
2023-04-16 12:12:55 -06:00
|
|
|
func (entity *entity) scrollTargetChildAt (point image.Point) *entity {
|
|
|
|
for _, child := range entity.children {
|
|
|
|
if point.In(child.bounds) {
|
|
|
|
result := child.scrollTargetChildAt(point)
|
|
|
|
if result != nil { return result }
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := entity.element.(tomo.ScrollTarget); ok {
|
|
|
|
return entity
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-18 00:59:44 -06:00
|
|
|
func (entity *entity) forMouseTargetContainers (callback func (tomo.MouseTargetContainer, tomo.Element)) {
|
2023-04-17 00:05:53 -06:00
|
|
|
if entity.parent == nil { return }
|
|
|
|
if parent, ok := entity.parent.element.(tomo.MouseTargetContainer); ok {
|
2023-04-18 00:59:44 -06:00
|
|
|
callback(parent, entity.element)
|
2023-04-17 00:05:53 -06:00
|
|
|
}
|
|
|
|
entity.parent.forMouseTargetContainers(callback)
|
|
|
|
}
|
|
|
|
|
2023-04-18 01:07:06 -06:00
|
|
|
func (entity *entity) clip (bounds image.Rectangle) {
|
|
|
|
entity.clippedBounds = entity.bounds.Intersect(bounds)
|
|
|
|
for _, child := range entity.children {
|
|
|
|
child.clip(entity.clippedBounds)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-13 00:22:54 -06:00
|
|
|
// ----------- Entity ----------- //
|
|
|
|
|
|
|
|
func (entity *entity) Invalidate () {
|
2023-04-14 23:14:36 -06:00
|
|
|
if entity.window == nil { return }
|
2023-04-13 22:25:05 -06:00
|
|
|
if entity.window.system.invalidateIgnore { return }
|
|
|
|
entity.window.drawingInvalid.Add(entity)
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) Bounds () image.Rectangle {
|
|
|
|
return entity.bounds
|
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) Window () tomo.Window {
|
|
|
|
return entity.window
|
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) SetMinimumSize (width, height int) {
|
|
|
|
entity.minWidth = width
|
|
|
|
entity.minHeight = height
|
2023-04-14 21:58:14 -06:00
|
|
|
if entity.parent == nil {
|
2023-04-14 23:14:36 -06:00
|
|
|
if entity.window != nil {
|
|
|
|
entity.window.setMinimumSize(width, height)
|
|
|
|
}
|
2023-04-14 21:58:14 -06:00
|
|
|
} else {
|
2023-04-14 22:02:30 -06:00
|
|
|
entity.parent.element.(tomo.Container).
|
|
|
|
HandleChildMinimumSizeChange(entity.element)
|
2023-04-14 21:58:14 -06:00
|
|
|
}
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
2023-04-14 23:14:36 -06:00
|
|
|
func (entity *entity) DrawBackground (destination canvas.Canvas) {
|
|
|
|
if entity.parent != nil {
|
|
|
|
entity.parent.element.(tomo.Container).DrawBackground(destination)
|
|
|
|
} else if entity.window != nil {
|
|
|
|
entity.window.system.theme.Pattern (
|
|
|
|
tomo.PatternBackground,
|
|
|
|
tomo.State { }).Draw (
|
|
|
|
destination,
|
|
|
|
entity.window.canvas.Bounds())
|
|
|
|
}
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// ----------- ContainerEntity ----------- //
|
|
|
|
|
|
|
|
func (entity *entity) InvalidateLayout () {
|
2023-04-14 23:14:36 -06:00
|
|
|
if entity.window == nil { return }
|
2023-04-13 22:25:05 -06:00
|
|
|
if !entity.isContainer { return }
|
|
|
|
entity.layoutInvalid = true
|
|
|
|
entity.window.system.anyLayoutInvalid = true
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
2023-04-14 23:14:36 -06:00
|
|
|
func (ent *entity) Adopt (child tomo.Element) {
|
|
|
|
childEntity, ok := child.Entity().(*entity)
|
|
|
|
if !ok || childEntity == nil { return }
|
|
|
|
childEntity.link(ent)
|
|
|
|
ent.children = append(ent.children, childEntity)
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
2023-04-14 23:14:36 -06:00
|
|
|
func (ent *entity) Insert (index int, child tomo.Element) {
|
|
|
|
childEntity, ok := child.Entity().(*entity)
|
|
|
|
if !ok || childEntity == nil { return }
|
|
|
|
ent.children = append (
|
|
|
|
ent.children[:index + 1],
|
|
|
|
ent.children[index:]...)
|
|
|
|
ent.children[index] = childEntity
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) Disown (index int) {
|
2023-04-14 23:14:36 -06:00
|
|
|
entity.children[index].unlink()
|
2023-04-13 00:22:54 -06:00
|
|
|
entity.children = append (
|
|
|
|
entity.children[:index],
|
|
|
|
entity.children[index + 1:]...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) IndexOf (child tomo.Element) int {
|
|
|
|
for index, childEntity := range entity.children {
|
|
|
|
if childEntity.element == child {
|
|
|
|
return index
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) Child (index int) tomo.Element {
|
|
|
|
return entity.children[index].element
|
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) CountChildren () int {
|
|
|
|
return len(entity.children)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) PlaceChild (index int, bounds image.Rectangle) {
|
2023-04-13 22:25:05 -06:00
|
|
|
child := entity.children[index]
|
|
|
|
child.bounds = bounds
|
2023-04-18 01:07:06 -06:00
|
|
|
child.clip(entity.clippedBounds)
|
2023-04-13 22:25:05 -06:00
|
|
|
child.Invalidate()
|
2023-04-14 23:14:36 -06:00
|
|
|
child.InvalidateLayout()
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
2023-04-17 00:05:53 -06:00
|
|
|
func (entity *entity) SelectChild (index int, selected bool) {
|
|
|
|
child := entity.children[index]
|
2023-04-18 00:59:44 -06:00
|
|
|
if element, ok := child.element.(tomo.Selectable); ok {
|
|
|
|
if child.selected == selected { return }
|
2023-04-17 00:05:53 -06:00
|
|
|
child.selected = selected
|
|
|
|
element.HandleSelectionChange()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-13 00:22:54 -06:00
|
|
|
func (entity *entity) ChildMinimumSize (index int) (width, height int) {
|
|
|
|
childEntity := entity.children[index]
|
|
|
|
return childEntity.minWidth, childEntity.minHeight
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------- FocusableEntity ----------- //
|
|
|
|
|
|
|
|
func (entity *entity) Focused () bool {
|
2023-04-14 23:14:36 -06:00
|
|
|
if entity.window == nil { return false }
|
2023-04-13 00:22:54 -06:00
|
|
|
return entity.window.focused == entity
|
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) Focus () {
|
2023-04-14 23:14:36 -06:00
|
|
|
if entity.window == nil { return }
|
2023-04-15 19:49:40 -06:00
|
|
|
entity.window.system.focus(entity)
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) FocusNext () {
|
2023-04-14 17:08:14 -06:00
|
|
|
entity.window.system.focusNext()
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (entity *entity) FocusPrevious () {
|
2023-04-14 17:08:14 -06:00
|
|
|
entity.window.system.focusPrevious()
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
|
2023-04-17 00:05:53 -06:00
|
|
|
// ----------- SelectableEntity ----------- //
|
|
|
|
|
|
|
|
func (entity *entity) Selected () bool {
|
|
|
|
return entity.selected
|
|
|
|
}
|
|
|
|
|
2023-04-13 00:22:54 -06:00
|
|
|
// ----------- FlexibleEntity ----------- //
|
|
|
|
|
2023-04-14 22:02:30 -06:00
|
|
|
func (entity *entity) NotifyFlexibleHeightChange () {
|
2023-04-13 00:22:54 -06:00
|
|
|
if entity.parent == nil { return }
|
|
|
|
if parent, ok := entity.parent.element.(tomo.FlexibleContainer); ok {
|
2023-04-14 22:02:30 -06:00
|
|
|
parent.HandleChildFlexibleHeightChange (
|
|
|
|
entity.element.(tomo.Flexible))
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------- ScrollableEntity ----------- //
|
|
|
|
|
|
|
|
func (entity *entity) NotifyScrollBoundsChange () {
|
|
|
|
if entity.parent == nil { return }
|
|
|
|
if parent, ok := entity.parent.element.(tomo.ScrollableContainer); ok {
|
2023-04-14 22:02:30 -06:00
|
|
|
parent.HandleChildScrollBoundsChange (
|
|
|
|
entity.element.(tomo.Scrollable))
|
2023-04-13 00:22:54 -06:00
|
|
|
}
|
|
|
|
}
|