Overhauled the element interfaces

Instead of the previous parenting model where parents would set
child callbacks during adoption by probing for callback setters,
child elements will instead probe their parents for notify methods
listed in the standard parent interfaces. This means that an
element cannot be half-parented to something, nor can it be
parented to two things at once. Parent elements may themselves
fulfill these interfaces, or they can pass a hook that fulfills
them to the child.
This commit is contained in:
Sasha Koshka 2023-03-14 17:08:39 -04:00
parent 9d84c50db3
commit b08cbea320
3 changed files with 84 additions and 52 deletions

47
elements/container.go Normal file
View File

@ -0,0 +1,47 @@
package element
import "git.tebibyte.media/sashakoshka/tomo/input"
// Parent represents a type capable of containing child elements.
type Parent interface {
// NotifyMinimumSizeChange notifies the container that a child element's
// minimum size has changed. This method is expected to be called by
// child elements when their minimum size changes.
NotifyMinimumSizeChange (child Element)
}
// FocusableParent represents a parent with keyboard navigation support.
type FocusableParent interface {
Parent
// RequestFocus notifies the parent that a child element is requesting
// keyboard focus. If the parent grants the request, the method will
// return true and the child element should behave as if a HandleFocus
// call was made.
RequestFocus (child Focusable, direction input.KeynavDirection) (granted bool)
}
// FlexibleParent represents a parent that accounts for elements with
// flexible height.
type FlexibleParent interface {
Parent
// NotifyFlexibleHeightChange notifies the parent that the parameters
// affecting a child's flexible height have changed. This method is
// expected to be called by flexible child element when their content
// changes.
NotifyFlexibleHeightChange (child Flexible)
}
// ScrollableParent represents a parent that can change the scroll
// position of its child element(s).
type ScrollableParent interface {
Parent
// NotifyScrollBoundsChange notifies the parent that a child's scroll
// content bounds or viewport bounds have changed. This is expected to
// be called by child elements when they change their supported scroll
// axes, their scroll position (either autonomously or as a result of a
// call to ScrollTo()), or their content size.
NotifyScrollBoundsChange (child Scrollable)
}

View File

@ -10,27 +10,26 @@ import "git.tebibyte.media/sashakoshka/tomo/config"
type Element interface {
// Bounds reports the element's bounding box. This must reflect the
// bounding last given to the element by DrawTo.
Bounds () (bounds image.Rectangle)
// DrawTo gives the element a canvas to draw on, along with a bounding
// box to be used for laying out the element. This should only be called
// by the parent element. This is typically a region of the parent
// element's canvas.
DrawTo (canvas canvas.Canvas, bounds image.Rectangle)
// OnDamage sets a function to be called when an area of the element is
// drawn on and should be pushed to the screen.
OnDamage (callback func (region canvas.Canvas))
Bounds () image.Rectangle
// MinimumSize specifies the minimum amount of pixels this element's
// width and height may be set to. If the element is given a resize
// event with dimensions smaller than this, it will use its minimum
// instead of the offending dimension(s).
MinimumSize () (width, height int)
// OnMinimumSizeChange sets a function to be called when the element's
// minimum size is changed.
OnMinimumSizeChange (callback func ())
// SetParent sets the parent container of the element. This should only
// be called by the parent when the element is adopted. If parent is set
// to nil, it will mark itself as not having a parent. If this method is
// passed a non-nil value and the element already has a parent, it will
// panic.
SetParent (Parent)
// DrawTo gives the element a canvas to draw on, along with a bounding
// box to be used for laying out the element. This should only be called
// by the parent element. This is typically a region of the parent
// element's canvas.
DrawTo (canvas canvas.Canvas, bounds image.Rectangle, onDamage func (region image.Rectangle))
}
// Focusable represents an element that has keyboard navigation support. This
@ -41,7 +40,7 @@ type Focusable interface {
// Focused returns whether or not this element or any of its children
// are currently focused.
Focused () (selected bool)
Focused () bool
// Focus focuses this element, if its parent element grants the
// request.
@ -57,20 +56,6 @@ type Focusable interface {
// HandleDeselection causes this element to mark itself and all of its
// children as unfocused.
HandleUnfocus ()
// OnFocusRequest sets a function to be called when this element wants
// its parent element to focus it. Parent elements should return true if
// the request was granted, and false if it was not. If the parent
// element returns true, the element must act as if a HandleFocus call
// was made with KeynavDirectionNeutral.
OnFocusRequest (func () (granted bool))
// OnFocusMotionRequest sets a function to be called when this
// element wants its parent element to focus the element behind or in
// front of it, depending on the specified direction. Parent elements
// should return true if the request was granted, and false if it was
// not.
OnFocusMotionRequest (func (direction input.KeynavDirection) (granted bool))
}
// KeyboardTarget represents an element that can receive keyboard input.
@ -93,9 +78,6 @@ type KeyboardTarget interface {
type MouseTarget interface {
Element
// Each of these handler methods is passed the position of the mouse
// cursor at the time of the event as x, y.
// HandleMouseDown is called when a mouse button is pressed down on this
// element.
HandleMouseDown (x, y int, button input.Button)
@ -103,15 +85,25 @@ type MouseTarget interface {
// HandleMouseUp is called when a mouse button is released that was
// originally pressed down on this element.
HandleMouseUp (x, y int, button input.Button)
}
// HandleMouseMove is called when the mouse is moved over this element,
// MotionTarget represents an element that can receive mouse motion events.
type MotionTarget interface {
Element
// HandleMotion is called when the mouse is moved over this element,
// or the mouse is moving while being held down and originally pressed
// down on this element.
HandleMouseMove (x, y int)
HandleMotion (x, y int)
}
// ScrollTarget represents an element that can receive mouse scroll events.
type ScrollTarget interface {
Element
// HandleScroll is called when the mouse is scrolled. The X and Y
// direction of the scroll event are passed as deltaX and deltaY.
HandleMouseScroll (x, y int, deltaX, deltaY float64)
HandleScroll (x, y int, deltaX, deltaY float64)
}
// Flexible represents an element who's preferred minimum height can change in
@ -132,11 +124,7 @@ type Flexible interface {
//
// It is important to note that if a parent container checks for
// flexible chilren, it itself will likely need to be flexible.
FlexibleHeightFor (width int) (height int)
// OnFlexibleHeightChange sets a function to be called when the
// parameters affecting this element's flexible height are changed.
OnFlexibleHeightChange (callback func ())
FlexibleHeightFor (width int) int
}
// Scrollable represents an element that can be scrolled. It acts as a viewport
@ -145,11 +133,11 @@ type Scrollable interface {
Element
// ScrollContentBounds returns the full content size of the element.
ScrollContentBounds () (bounds image.Rectangle)
ScrollContentBounds () image.Rectangle
// ScrollViewportBounds returns the size and position of the element's
// viewport relative to ScrollBounds.
ScrollViewportBounds () (bounds image.Rectangle)
ScrollViewportBounds () image.Rectangle
// ScrollTo scrolls the viewport to the specified point relative to
// ScrollBounds.
@ -157,10 +145,6 @@ type Scrollable interface {
// ScrollAxes returns the supported axes for scrolling.
ScrollAxes () (horizontal, vertical bool)
// OnScrollBoundsChange sets a function to be called when the element's
// ScrollContentBounds, ScrollViewportBounds, or ScrollAxes are changed.
OnScrollBoundsChange (callback func ())
}
// Collapsible represents an element who's minimum width and height can be

View File

@ -4,19 +4,20 @@ import "image"
// Window represents a top-level container generated by the currently running
// backend. It can contain a single element. It is hidden by default, and must
// be explicitly shown with the Show() method. If it contains no element, it
// displays a black (or transprent) background.
// be explicitly shown with the Show() method.
type Window interface {
Parent
// Adopt sets the root element of the window. There can only be one of
// these at one time.
Adopt (child Element)
Adopt (Element)
// Child returns the root element of the window.
Child () (child Element)
Child () Element
// SetTitle sets the title that appears on the window's title bar. This
// method might have no effect with some backends.
SetTitle (title string)
SetTitle (string)
// SetIcon taks in a list different sizes of the same icon and selects
// the best one to display on the window title bar, dock, or whatever is