From bb9c5df088b5c3ad6093efa8e4c162a4d958e1c3 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 13 Apr 2023 02:22:54 -0400 Subject: [PATCH] X backend entity --- backends/x/entity.go | 154 +++++++++++++++++++++++++++++++++++++++++++ backends/x/window.go | 4 +- element.go | 24 +++++++ entity.go | 8 ++- 4 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 backends/x/entity.go diff --git a/backends/x/entity.go b/backends/x/entity.go new file mode 100644 index 0000000..6252d55 --- /dev/null +++ b/backends/x/entity.go @@ -0,0 +1,154 @@ +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 + + drawDirty bool + layoutDirty bool + + bounds image.Rectangle + minWidth int + minHeight int +} + +func bind (element tomo.Element) *entity { + entity := &entity { drawDirty: true } + if _, ok := element.(tomo.Container); ok { + entity.layoutDirty = true + } + + element.Bind(entity) + return entity +} + +func (entity *entity) unbind () { + entity.element.Bind(nil) + for _, childEntity := range entity.children { + childEntity.unbind() + } +} + +// ----------- Entity ----------- // + +func (entity *entity) Invalidate () { + entity.drawDirty = true +} + +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 + if entity.parent == nil { return } + entity.parent.element.(tomo.Container).HandleChildMinimumSizeChange() +} + +func (entity *entity) DrawBackground (destination canvas.Canvas, bounds image.Rectangle) { + if entity.parent == nil { return } + entity.parent.element.(tomo.Container).DrawBackground(destination, bounds) +} + +// ----------- ContainerEntity ----------- // + +func (entity *entity) InvalidateLayout () { + entity.layoutDirty = true +} + +func (entity *entity) Adopt (child tomo.Element) { + entity.children = append(entity.children, bind(child)) +} + +func (entity *entity) Insert (index int, child tomo.Element) { + entity.children = append ( + entity.children[:index + 1], + entity.children[index:]...) + entity.children[index] = bind(child) +} + +func (entity *entity) Disown (index int) { + entity.children[index].unbind() + 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) { + entity.children[index].bounds = bounds +} + +func (entity *entity) ChildMinimumSize (index int) (width, height int) { + childEntity := entity.children[index] + return childEntity.minWidth, childEntity.minHeight +} + +// ----------- FocusableEntity ----------- // + +func (entity *entity) Focused () bool { + return entity.window.focused == entity +} + +func (entity *entity) Focus () { + previous := entity.window.focused + entity.window.focused = entity + if previous != nil { + previous.element.(tomo.Focusable).HandleFocusChange() + } + entity.element.(tomo.Focusable).HandleFocusChange() +} + +func (entity *entity) FocusNext () { + // TODO +} + +func (entity *entity) FocusPrevious () { + // TODO +} + +// ----------- FlexibleEntity ----------- // + +func (entity *entity) NotifyFlexibleHeightChange () { + if entity.parent == nil { return } + if parent, ok := entity.parent.element.(tomo.FlexibleContainer); ok { + parent.HandleChildFlexibleHeightChange() + } +} + +// ----------- ScrollableEntity ----------- // + +func (entity *entity) NotifyScrollBoundsChange () { + if entity.parent == nil { return } + if parent, ok := entity.parent.element.(tomo.ScrollableContainer); ok { + parent.HandleChildScrollBoundsChange() + } +} diff --git a/backends/x/window.go b/backends/x/window.go index 980d63d..0253994 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -24,9 +24,9 @@ type window struct { xWindow *xwindow.Window xCanvas *xgraphics.Image canvas canvas.BasicCanvas - child tomo.Element + child *entity + focused *entity onClose func () - skipChildDrawCallback bool title, application string diff --git a/element.go b/element.go index 5045462..66b05a9 100644 --- a/element.go +++ b/element.go @@ -25,6 +25,10 @@ type Container interface { // DrawBackground draws this element's background pattern at the // specified bounds to any canvas. DrawBackground (destination canvas.Canvas, bounds image.Rectangle) + + // HandleChildMinimumSizeChange is called when a child's minimum size is + // changed. + HandleChildMinimumSizeChange () } // Focusable represents an element that has keyboard navigation support. @@ -104,6 +108,16 @@ type Flexible interface { FlexibleHeightFor (width int) int } +// FlexibleContainer represents an element that is capable of containing +// flexible children. +type FlexibleContainer interface { + Container + + // HandleChildFlexibleHeightChange is called when the parameters + // affecting a child's flexible height are changed. + HandleChildFlexibleHeightChange () +} + // Scrollable represents an element that can be scrolled. It acts as a viewport // through which its contents can be observed. type Scrollable interface { @@ -124,6 +138,16 @@ type Scrollable interface { ScrollAxes () (horizontal, vertical bool) } +// ScrollableContainer represents an element that is capable of containing +// scrollable children. +type ScrollableContainer interface { + Container + + // HandleChildScrollBoundsChange is called when the content bounds, + // viewport bounds, or scroll axes of a child are changed. + HandleChildScrollBoundsChange() +} + // Collapsible represents an element who's minimum width and height can be // manually resized. Scrollable elements should implement this if possible. type Collapsible interface { diff --git a/entity.go b/entity.go index 27bddab..3a1b611 100644 --- a/entity.go +++ b/entity.go @@ -1,6 +1,7 @@ package tomo import "image" +import "git.tebibyte.media/sashakoshka/tomo/canvas" // Entity is a handle given to elements by the backend. Different types of // entities may be assigned to elements that support different capabilities. @@ -25,13 +26,18 @@ type Entity interface { // DrawBackground asks the parent element to draw its background pattern // within the specified rectangle. This should be used for transparent // elements like text labels. - DrawBackground (bounds image.Rectangle) + DrawBackground (destination canvas.Canvas, bounds image.Rectangle) } // ContainerEntity is given to elements that support the Container interface. type ContainerEntity interface { Entity + // InvalidateLayout marks the element's layout as invalid. At the end of + // every event, the backend will ask all invalid containers to + // recalculate their layouts. + InvalidateLayout () + // Adopt adds an element as a child. Adopt (child Element)