diff --git a/elements/core/core.go b/elements/core/core.go deleted file mode 100644 index 9811f14..0000000 --- a/elements/core/core.go +++ /dev/null @@ -1,235 +0,0 @@ -package core - -import "image" -import "image/color" -import "git.tebibyte.media/sashakoshka/tomo" -import "git.tebibyte.media/sashakoshka/tomo/canvas" -import "git.tebibyte.media/sashakoshka/tomo/artist" -import "git.tebibyte.media/sashakoshka/tomo/shatter" - -// Core is a struct that implements some core functionality common to most -// widgets. It is meant to be embedded directly into a struct. -type Core struct { - canvas canvas.Canvas - bounds image.Rectangle - parent tomo.Parent - outer tomo.Element - - metrics struct { - minimumWidth int - minimumHeight int - } - - drawSizeChange func () - onDamage func (region image.Rectangle) -} - -// NewCore creates a new element core and its corresponding control given the -// element that it will be a part of. If outer is nil, this function will return -// nil. -func NewCore ( - outer tomo.Element, - drawSizeChange func (), -) ( - core *Core, - control CoreControl, -) { - if outer == nil { return } - core = &Core { - outer: outer, - drawSizeChange: drawSizeChange, - } - control = CoreControl { core: core } - return -} - -// Bounds fulfills the tomo.Element interface. This should not need to be -// overridden. -func (core *Core) Bounds () (bounds image.Rectangle) { - if core.canvas == nil { return } - return core.bounds -} - -// MinimumSize fulfils the tomo.Element interface. This should not need to be -// overridden. -func (core *Core) MinimumSize () (width, height int) { - return core.metrics.minimumWidth, core.metrics.minimumHeight -} - -// MinimumSize fulfils the tomo.Element interface. This should not need to be -// overridden, unless you want to detect when the element is parented or -// unparented. -func (core *Core) SetParent (parent tomo.Parent) { - if parent != nil && core.parent != nil { - panic("core.SetParent: element already has a parent") - } - - core.parent = parent -} - -// DrawTo fulfills the tomo.Element interface. This should not need to be -// overridden. -func (core *Core) DrawTo ( - canvas canvas.Canvas, - bounds image.Rectangle, - onDamage func (region image.Rectangle), -) { - core.canvas = canvas - core.bounds = bounds - core.onDamage = onDamage - if core.drawSizeChange != nil && core.canvas != nil { - core.drawSizeChange() - } -} - -// CoreControl is a struct that can exert control over a Core struct. It can be -// used as a canvas. It must not be directly embedded into an element, but -// instead kept as a private member. When a Core struct is created, a -// corresponding CoreControl struct is linked to it and returned alongside it. -type CoreControl struct { - core *Core -} - -// ColorModel fulfills the draw.Image interface. -func (control CoreControl) ColorModel () (model color.Model) { - return color.RGBAModel -} - -// At fulfills the draw.Image interface. -func (control CoreControl) At (x, y int) (pixel color.Color) { - if control.core.canvas == nil { return } - return control.core.canvas.At(x, y) -} - -// Bounds fulfills the draw.Image interface. -func (control CoreControl) Bounds () (bounds image.Rectangle) { - if control.core.canvas == nil { return } - return control.core.canvas.Bounds() -} - -// Set fulfills the draw.Image interface. -func (control CoreControl) Set (x, y int, c color.Color) () { - if control.core.canvas == nil { return } - control.core.canvas.Set(x, y, c) -} - -// Buffer fulfills the canvas.Canvas interface. -func (control CoreControl) Buffer () (data []color.RGBA, stride int) { - if control.core.canvas == nil { return } - return control.core.canvas.Buffer() -} - -// Parent returns the element's parent. -func (control CoreControl) Parent () tomo.Parent { - return control.core.parent -} - -// DrawBackground fills the element's canvas with the parent's background -// pattern, if the parent supports it. If it is not supported, the fallback -// pattern will be used instead. -func (control CoreControl) DrawBackground (fallback artist.Pattern) { - control.DrawBackgroundBounds(fallback, control.Bounds()) -} - -// DrawBackgroundBounds is like DrawBackground, but it takes in a bounding -// rectangle instead of using the element's bounds. -func (control CoreControl) DrawBackgroundBounds ( - fallback artist.Pattern, - bounds image.Rectangle, -) { - parent, ok := control.Parent().(tomo.BackgroundParent) - if ok { - parent.DrawBackground(bounds) - } else if fallback != nil { - fallback.Draw(canvas.Cut(control, bounds), control.Bounds()) - } -} - -// DrawBackgroundBoundsShatter is like DrawBackgroundBounds, but uses the -// shattering algorithm to avoid drawing in areas specified by rocks. -func (control CoreControl) DrawBackgroundBoundsShatter ( - fallback artist.Pattern, - bounds image.Rectangle, - rocks ...image.Rectangle, -) { - tiles := shatter.Shatter(bounds, rocks...) - for _, tile := range tiles { - control.DrawBackgroundBounds(fallback, tile) - } -} - -// Window returns the window containing the element. -func (control CoreControl) Window () tomo.Window { - parent := control.Parent() - if parent == nil { - return nil - } else { - return parent.Window() - } -} - -// Outer returns the outer element given when the control was constructed. -func (control CoreControl) Outer () tomo.Element { - return control.core.outer -} - -// HasImage returns true if the core has an allocated image buffer, and false if -// it doesn't. -func (control CoreControl) HasImage () (has bool) { - return control.core.canvas != nil && !control.core.canvas.Bounds().Empty() -} - -// DamageRegion pushes the selected region of pixels to the parent element. This -// does not need to be called when responding to a resize event. -func (control CoreControl) DamageRegion (regions ...image.Rectangle) { - if control.core.canvas == nil { return } - if control.core.onDamage != nil { - for _, region := range regions { - control.core.onDamage(region) - } - } -} - -// DamageAll pushes all pixels to the parent element. This does not need to be -// called when redrawing in response to a change in size. -func (control CoreControl) DamageAll () { - control.DamageRegion(control.core.Bounds()) -} - -// SetMinimumSize sets the minimum size of this element, notifying the parent -// element in the process. -func (control CoreControl) SetMinimumSize (width, height int) { - core := control.core - if width == core.metrics.minimumWidth && - height == core.metrics.minimumHeight { - return - } - - core.metrics.minimumWidth = width - core.metrics.minimumHeight = height - if control.core.parent != nil { - control.core.parent.NotifyMinimumSizeChange(control.core.outer) - } -} - -// ConstrainSize contstrains the specified width and height to the minimum width -// and height, and returns wether or not anything ended up being constrained. -func (control CoreControl) ConstrainSize ( - inWidth, inHeight int, -) ( - outWidth, outHeight int, - constrained bool, -) { - core := control.core - outWidth = inWidth - outHeight = inHeight - if outWidth < core.metrics.minimumWidth { - outWidth = core.metrics.minimumWidth - constrained = true - } - if outHeight < core.metrics.minimumHeight { - outHeight = core.metrics.minimumHeight - constrained = true - } - return -} diff --git a/elements/core/doc.go b/elements/core/doc.go deleted file mode 100644 index 5870f79..0000000 --- a/elements/core/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -// Package core provides tools that allow elements to easily fulfill common -// interfaces without having to duplicate a ton of code. Each "core" is a type -// that can be embedded into an element directly, working to fulfill a -// particular interface. Each one comes with a corresponding core control, which -// provides an interface for elements to exert control over the core. Core -// controls should be kept private. -package core diff --git a/elements/core/focusable.go b/elements/core/focusable.go deleted file mode 100644 index ed3627f..0000000 --- a/elements/core/focusable.go +++ /dev/null @@ -1,101 +0,0 @@ -package core - -// import "runtime/debug" -import "git.tebibyte.media/sashakoshka/tomo" -import "git.tebibyte.media/sashakoshka/tomo/input" - -// FocusableCore is a struct that can be embedded into objects to make them -// focusable, giving them the default keynav behavior. -type FocusableCore struct { - core CoreControl - focused bool - enabled bool - drawFocusChange func () -} - -// NewFocusableCore creates a new focusability core and its corresponding -// control. If your element needs to visually update itself when it's focus -// state changes (which it should), a callback to draw and push the update can -// be specified. -func NewFocusableCore ( - core CoreControl, - drawFocusChange func (), -) ( - focusable *FocusableCore, - control FocusableCoreControl, -) { - focusable = &FocusableCore { - core: core, - drawFocusChange: drawFocusChange, - enabled: true, - } - control = FocusableCoreControl { core: focusable } - return -} - -// Focused returns whether or not this element is currently focused. -func (core *FocusableCore) Focused () (focused bool) { - return core.focused -} - -// Focus focuses this element, if its parent element grants the request. -func (core *FocusableCore) Focus () { - if !core.enabled || core.focused { return } - parent := core.core.Parent() - if parent, ok := parent.(tomo.FocusableParent); ok { - core.focused = parent.RequestFocus ( - core.core.Outer().(tomo.Focusable)) - } -} - -// HandleFocus causes this element to mark itself as focused, if it can -// currently be. Otherwise, it will return false and do nothing. -func (core *FocusableCore) HandleFocus ( - direction input.KeynavDirection, -) ( - accepted bool, -) { - direction = direction.Canon() - if !core.enabled { return false } - if core.focused && direction != input.KeynavDirectionNeutral { - return false - } - - if core.focused == false { - core.focused = true - if core.drawFocusChange != nil { core.drawFocusChange() } - } - return true -} - -// HandleUnfocus causes this element to mark itself as unfocused. -func (core *FocusableCore) HandleUnfocus () { - core.focused = false - // debug.PrintStack() - if core.drawFocusChange != nil { core.drawFocusChange() } -} - -// Enabled returns whether or not the element is enabled. -func (core *FocusableCore) Enabled () (enabled bool) { - return core.enabled -} - -// FocusableCoreControl is a struct that can be used to exert control over a -// focusability core. It must not be directly embedded into an element, but -// instead kept as a private member. When a FocusableCore struct is created, a -// corresponding FocusableCoreControl struct is linked to it and returned -// alongside it. -type FocusableCoreControl struct { - core *FocusableCore -} - -// SetEnabled sets whether the focusability core is enabled. If the state -// changes, this will call drawFocusChange. -func (control FocusableCoreControl) SetEnabled (enabled bool) { - if control.core.enabled == enabled { return } - control.core.enabled = enabled - if !enabled { control.core.focused = false } - if control.core.drawFocusChange != nil { - control.core.drawFocusChange() - } -} diff --git a/elements/core/propagator.go b/elements/core/propagator.go deleted file mode 100644 index 656fce1..0000000 --- a/elements/core/propagator.go +++ /dev/null @@ -1,355 +0,0 @@ -package core - -import "image" -import "git.tebibyte.media/sashakoshka/tomo" -import "git.tebibyte.media/sashakoshka/tomo/input" - -// Container represents an object that can provide access to a list of child -// elements. -type Container interface { - Child (index int) tomo.Element - CountChildren () int -} - -// Propagator is a struct that can be embedded into elements that contain one or -// more children in order to propagate events to them without having to write -// all of the event handlers. It also implements standard behavior for focus -// propagation and keyboard navigation. -type Propagator struct { - core CoreControl - container Container - drags [10]tomo.MouseTarget - focused bool -} - -// NewPropagator creates a new event propagator that uses the specified -// container to access a list of child elements that will have events propagated -// to them. If container is nil, the function will return nil. -func NewPropagator (container Container, core CoreControl) (propagator *Propagator) { - if container == nil { return nil } - propagator = &Propagator { - core: core, - container: container, - } - return -} - -// ----------- Interface fulfillment methods ----------- // - -// Focused returns whether or not this element or any of its children -// are currently focused. -func (propagator *Propagator) Focused () (focused bool) { - return propagator.focused -} - -// Focus focuses this element, if its parent element grants the -// request. -func (propagator *Propagator) Focus () { - if propagator.focused == true { return } - parent := propagator.core.Parent() - if parent, ok := parent.(tomo.FocusableParent); ok && parent != nil { - propagator.focused = parent.RequestFocus ( - propagator.core.Outer().(tomo.Focusable)) - } -} - -// HandleFocus causes this element to mark itself as focused. If the -// element does not have children or there are no more focusable children in -// the given direction, it should return false and do nothing. Otherwise, it -// marks itself as focused along with any applicable children and returns -// true. -func (propagator *Propagator) HandleFocus (direction input.KeynavDirection) (accepted bool) { - direction = direction.Canon() - - firstFocused := propagator.firstFocused() - if firstFocused < 0 { - // no element is currently focused, so we need to focus either - // the first or last focusable element depending on the - // direction. - switch direction { - case input.KeynavDirectionForward: - // if we recieve a forward direction, focus the first - // focusable element. - return propagator.focusFirstFocusableElement(direction) - - case input.KeynavDirectionBackward: - // if we recieve a backward direction, focus the last - // focusable element. - return propagator.focusLastFocusableElement(direction) - - case input.KeynavDirectionNeutral: - // if we recieve a neutral direction, just focus this - // element and nothing else. - propagator.focused = true - return true - } - } else { - // an element is currently focused, so we need to move the - // focus in the specified direction - firstFocusedChild := - propagator.container.Child(firstFocused). - (tomo.Focusable) - - // before we move the focus, the currently focused child - // may also be able to move its focus. if the child is able - // to do that, we will let it and not move ours. - if firstFocusedChild.HandleFocus(direction) { - return true - } - - // find the previous/next focusable element relative to the - // currently focused element, if it exists. - for index := firstFocused + int(direction); - index < propagator.container.CountChildren() && index >= 0; - index += int(direction) { - - child, focusable := - propagator.container.Child(index). - (tomo.Focusable) - if focusable && child.HandleFocus(direction) { - // we have found one, so we now actually move - // the focus. - firstFocusedChild.HandleUnfocus() - propagator.focused = true - return true - } - } - } - - return false -} - -// 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. -func (propagator *Propagator) RequestFocus ( - child tomo.Focusable, -) ( - granted bool, -) { - if parent, ok := propagator.core.Parent().(tomo.FocusableParent); ok { - if parent.RequestFocus(propagator.core.Outer().(tomo.Focusable)) { - propagator.HandleUnfocus() - propagator.focused = true - granted = true - } - } - return -} - -// RequestFocusMotion notifies the parent that a child element wants the -// focus to be moved to the next focusable element. -func (propagator *Propagator) RequestFocusNext (child tomo.Focusable) { - if !propagator.focused { return } - if parent, ok := propagator.core.Parent().(tomo.FocusableParent); ok { - parent.RequestFocusNext(propagator.core.Outer().(tomo.Focusable)) - } -} - -// RequestFocusMotion notifies the parent that a child element wants the -// focus to be moved to the previous focusable element. -func (propagator *Propagator) RequestFocusPrevious (child tomo.Focusable) { - if !propagator.focused { return } - if parent, ok := propagator.core.Parent().(tomo.FocusableParent); ok { - parent.RequestFocusPrevious(propagator.core.Outer().(tomo.Focusable)) - } -} - -// HandleDeselection causes this element to mark itself and all of its children -// as unfocused. -func (propagator *Propagator) HandleUnfocus () { - propagator.forFocusable (func (child tomo.Focusable) bool { - child.HandleUnfocus() - return true - }) - propagator.focused = false -} - -// HandleKeyDown propogates the keyboard event to the currently selected child. -func (propagator *Propagator) HandleKeyDown (key input.Key, modifiers input.Modifiers) { - propagator.forFocused (func (child tomo.Focusable) bool { - typedChild, handlesKeyboard := child.(tomo.KeyboardTarget) - if handlesKeyboard { - typedChild.HandleKeyDown(key, modifiers) - } - return true - }) -} - -// HandleKeyUp propogates the keyboard event to the currently selected child. -func (propagator *Propagator) HandleKeyUp (key input.Key, modifiers input.Modifiers) { - propagator.forFocused (func (child tomo.Focusable) bool { - typedChild, handlesKeyboard := child.(tomo.KeyboardTarget) - if handlesKeyboard { - typedChild.HandleKeyUp(key, modifiers) - } - return true - }) -} - -// HandleMouseDown propagates the mouse event to the element under the mouse -// pointer. -func (propagator *Propagator) HandleMouseDown (x, y int, button input.Button) { - child, handlesMouse := - propagator.childAt(image.Pt(x, y)). - (tomo.MouseTarget) - if handlesMouse { - propagator.drags[button] = child - child.HandleMouseDown(x, y, button) - } -} - -// HandleMouseUp propagates the mouse event to the element that the released -// mouse button was originally pressed on. -func (propagator *Propagator) HandleMouseUp (x, y int, button input.Button) { - child := propagator.drags[button] - if child != nil { - propagator.drags[button] = nil - child.HandleMouseUp(x, y, button) - } -} - -// HandleMotion propagates the mouse event to the element that was last -// pressed down by the mouse if the mouse is currently being held down, else it -// propagates the event to whichever element is underneath the mouse pointer. -func (propagator *Propagator) HandleMotion (x, y int) { - handled := false - for _, child := range propagator.drags { - if child, ok := child.(tomo.MotionTarget); ok { - child.HandleMotion(x, y) - handled = true - } - } - - if !handled { - child := propagator.childAt(image.Pt(x, y)) - if child, ok := child.(tomo.MotionTarget); ok { - child.HandleMotion(x, y) - } - } -} - -// HandleScroll propagates the mouse event to the element under the mouse -// pointer. -func (propagator *Propagator) HandleScroll (x, y int, deltaX, deltaY float64) { - child := propagator.childAt(image.Pt(x, y)) - if child, ok := child.(tomo.ScrollTarget); ok { - child.HandleScroll(x, y, deltaX, deltaY) - } -} - -// SetTheme sets the theme of all children to the specified theme. -func (propagator *Propagator) SetTheme (theme tomo.Theme) { - propagator.forChildren (func (child tomo.Element) bool { - typedChild, themeable := child.(tomo.Themeable) - if themeable { - typedChild.SetTheme(theme) - } - return true - }) -} - -// SetConfig sets the theme of all children to the specified config. -func (propagator *Propagator) SetConfig (config tomo.Config) { - propagator.forChildren (func (child tomo.Element) bool { - typedChild, configurable := child.(tomo.Configurable) - if configurable { - typedChild.SetConfig(config) - } - return true - }) -} - -// ----------- Focusing utilities ----------- // - -func (propagator *Propagator) focusFirstFocusableElement ( - direction input.KeynavDirection, -) ( - ok bool, -) { - propagator.forFocusable (func (child tomo.Focusable) bool { - if child.HandleFocus(direction) { - propagator.focused = true - ok = true - return false - } - return true - }) - return -} - -func (propagator *Propagator) focusLastFocusableElement ( - direction input.KeynavDirection, -) ( - ok bool, -) { - propagator.forChildrenReverse (func (child tomo.Element) bool { - typedChild, focusable := child.(tomo.Focusable) - if focusable && typedChild.HandleFocus(direction) { - propagator.focused = true - ok = true - return false - } - return true - }) - return -} - -// ----------- Iterator utilities ----------- // - -func (propagator *Propagator) forChildren (callback func (child tomo.Element) bool) { - for index := 0; index < propagator.container.CountChildren(); index ++ { - child := propagator.container.Child(index) - if child == nil { continue } - if !callback(child) { break } - } -} - -func (propagator *Propagator) forChildrenReverse (callback func (child tomo.Element) bool) { - for index := propagator.container.CountChildren() - 1; index > 0; index -- { - child := propagator.container.Child(index) - if child == nil { continue } - if !callback(child) { break } - } -} - -func (propagator *Propagator) childAt (position image.Point) (child tomo.Element) { - propagator.forChildren (func (current tomo.Element) bool { - if position.In(current.Bounds()) { - child = current - } - return true - }) - return -} - -func (propagator *Propagator) forFocused (callback func (child tomo.Focusable) bool) { - propagator.forChildren (func (child tomo.Element) bool { - typedChild, focusable := child.(tomo.Focusable) - if focusable && typedChild.Focused() { - if !callback(typedChild) { return false } - } - return true - }) -} - -func (propagator *Propagator) forFocusable (callback func (child tomo.Focusable) bool) { - propagator.forChildren (func (child tomo.Element) bool { - typedChild, focusable := child.(tomo.Focusable) - if focusable { - if !callback(typedChild) { return false } - } - return true - }) -} - -func (propagator *Propagator) firstFocused () int { - for index := 0; index < propagator.container.CountChildren(); index ++ { - child, focusable := propagator.container.Child(index).(tomo.Focusable) - if focusable && child.Focused() { - return index - } - } - return -1 -}