ecs #15
@ -15,6 +15,7 @@ type entity struct {
|
|||||||
minWidth int
|
minWidth int
|
||||||
minHeight int
|
minHeight int
|
||||||
|
|
||||||
|
selected bool
|
||||||
layoutInvalid bool
|
layoutInvalid bool
|
||||||
isContainer bool
|
isContainer bool
|
||||||
}
|
}
|
||||||
@ -42,6 +43,11 @@ func (ent *entity) unlink () {
|
|||||||
}
|
}
|
||||||
ent.parent = nil
|
ent.parent = nil
|
||||||
ent.window = nil
|
ent.window = nil
|
||||||
|
|
||||||
|
if element, ok := ent.element.(tomo.Selectable); ok {
|
||||||
|
ent.selected = false
|
||||||
|
element.HandleSelectionChange()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entity *entity) link (parent *entity) {
|
func (entity *entity) link (parent *entity) {
|
||||||
@ -96,6 +102,14 @@ func (entity *entity) scrollTargetChildAt (point image.Point) *entity {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (entity *entity) forMouseTargetContainers (callback func (tomo.MouseTargetContainer)) {
|
||||||
|
if entity.parent == nil { return }
|
||||||
|
if parent, ok := entity.parent.element.(tomo.MouseTargetContainer); ok {
|
||||||
|
callback(parent)
|
||||||
|
}
|
||||||
|
entity.parent.forMouseTargetContainers(callback)
|
||||||
|
}
|
||||||
|
|
||||||
// ----------- Entity ----------- //
|
// ----------- Entity ----------- //
|
||||||
|
|
||||||
func (entity *entity) Invalidate () {
|
func (entity *entity) Invalidate () {
|
||||||
@ -195,6 +209,14 @@ func (entity *entity) PlaceChild (index int, bounds image.Rectangle) {
|
|||||||
child.InvalidateLayout()
|
child.InvalidateLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (entity *entity) SelectChild (index int, selected bool) {
|
||||||
|
child := entity.children[index]
|
||||||
|
if element, ok := entity.element.(tomo.Selectable); ok {
|
||||||
|
child.selected = selected
|
||||||
|
element.HandleSelectionChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (entity *entity) ChildMinimumSize (index int) (width, height int) {
|
func (entity *entity) ChildMinimumSize (index int) (width, height int) {
|
||||||
childEntity := entity.children[index]
|
childEntity := entity.children[index]
|
||||||
return childEntity.minWidth, childEntity.minHeight
|
return childEntity.minWidth, childEntity.minHeight
|
||||||
@ -220,6 +242,12 @@ func (entity *entity) FocusPrevious () {
|
|||||||
entity.window.system.focusPrevious()
|
entity.window.system.focusPrevious()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------- SelectableEntity ----------- //
|
||||||
|
|
||||||
|
func (entity *entity) Selected () bool {
|
||||||
|
return entity.selected
|
||||||
|
}
|
||||||
|
|
||||||
// ----------- FlexibleEntity ----------- //
|
// ----------- FlexibleEntity ----------- //
|
||||||
|
|
||||||
func (entity *entity) NotifyFlexibleHeightChange () {
|
func (entity *entity) NotifyFlexibleHeightChange () {
|
||||||
|
@ -206,12 +206,19 @@ func (window *window) handleButtonPress (
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
underneath := window.system.childAt(point)
|
underneath := window.system.childAt(point)
|
||||||
|
window.system.drags[buttonEvent.Detail] = underneath
|
||||||
if child, ok := underneath.element.(tomo.MouseTarget); ok {
|
if child, ok := underneath.element.(tomo.MouseTarget); ok {
|
||||||
window.system.drags[buttonEvent.Detail] = child
|
|
||||||
child.HandleMouseDown (
|
child.HandleMouseDown (
|
||||||
point.X, point.Y,
|
point.X, point.Y,
|
||||||
input.Button(buttonEvent.Detail))
|
input.Button(buttonEvent.Detail))
|
||||||
}
|
}
|
||||||
|
callback := func (container tomo.MouseTargetContainer) {
|
||||||
|
container.HandleChildMouseDown (
|
||||||
|
point.X, point.Y,
|
||||||
|
input.Button(buttonEvent.Detail),
|
||||||
|
underneath.element)
|
||||||
|
}
|
||||||
|
underneath.forMouseTargetContainers(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.system.afterEvent()
|
window.system.afterEvent()
|
||||||
@ -223,12 +230,22 @@ func (window *window) handleButtonRelease (
|
|||||||
) {
|
) {
|
||||||
buttonEvent := *event.ButtonReleaseEvent
|
buttonEvent := *event.ButtonReleaseEvent
|
||||||
if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { return }
|
if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { return }
|
||||||
child := window.system.drags[buttonEvent.Detail]
|
dragging := window.system.drags[buttonEvent.Detail]
|
||||||
if child != nil {
|
if dragging != nil {
|
||||||
child.HandleMouseUp (
|
if child, ok := dragging.element.(tomo.MouseTarget); ok {
|
||||||
|
child.HandleMouseUp (
|
||||||
|
int(buttonEvent.EventX),
|
||||||
|
int(buttonEvent.EventY),
|
||||||
|
input.Button(buttonEvent.Detail))
|
||||||
|
}
|
||||||
|
callback := func (container tomo.MouseTargetContainer) {
|
||||||
|
container.HandleChildMouseUp (
|
||||||
int(buttonEvent.EventX),
|
int(buttonEvent.EventX),
|
||||||
int(buttonEvent.EventY),
|
int(buttonEvent.EventY),
|
||||||
input.Button(buttonEvent.Detail))
|
input.Button(buttonEvent.Detail),
|
||||||
|
dragging.element)
|
||||||
|
}
|
||||||
|
dragging.forMouseTargetContainers(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.system.afterEvent()
|
window.system.afterEvent()
|
||||||
@ -244,7 +261,7 @@ func (window *window) handleMotionNotify (
|
|||||||
|
|
||||||
handled := false
|
handled := false
|
||||||
for _, child := range window.system.drags {
|
for _, child := range window.system.drags {
|
||||||
if child, ok := child.(tomo.MotionTarget); ok {
|
if child, ok := child.element.(tomo.MotionTarget); ok {
|
||||||
child.HandleMotion(x, y)
|
child.HandleMotion(x, y)
|
||||||
handled = true
|
handled = true
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ type system struct {
|
|||||||
drawingInvalid entitySet
|
drawingInvalid entitySet
|
||||||
anyLayoutInvalid bool
|
anyLayoutInvalid bool
|
||||||
|
|
||||||
drags [10]tomo.MouseTarget
|
drags [10]*entity
|
||||||
|
|
||||||
pushFunc func (image.Rectangle)
|
pushFunc func (image.Rectangle)
|
||||||
}
|
}
|
||||||
|
48
element.go
48
element.go
@ -40,17 +40,43 @@ type Container interface {
|
|||||||
HandleChildMinimumSizeChange (child Element)
|
HandleChildMinimumSizeChange (child Element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enableable represents an element that can be enabled and disabled. Disabled
|
||||||
|
// elements typically appear greyed out.
|
||||||
|
type Enableable interface {
|
||||||
|
Element
|
||||||
|
|
||||||
|
// Enabled returns whether or not the element is enabled.
|
||||||
|
Enabled () bool
|
||||||
|
|
||||||
|
// SetEnabled sets whether or not the element is enabled.
|
||||||
|
SetEnabled (bool)
|
||||||
|
}
|
||||||
|
|
||||||
// Focusable represents an element that has keyboard navigation support.
|
// Focusable represents an element that has keyboard navigation support.
|
||||||
type Focusable interface {
|
type Focusable interface {
|
||||||
Element
|
Element
|
||||||
|
Enableable
|
||||||
// Enabled returns whether or not the element can currently accept focus.
|
|
||||||
Enabled () bool
|
|
||||||
|
|
||||||
// HandleFocusChange is called when the element is focused or unfocused.
|
// HandleFocusChange is called when the element is focused or unfocused.
|
||||||
HandleFocusChange ()
|
HandleFocusChange ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Selectable represents an element that can be selected. This includes things
|
||||||
|
// like list items, files, etc. The difference between this and Focusable is
|
||||||
|
// that multiple Selectable elements may be selected at the same time, whereas
|
||||||
|
// only one Focusable element may be focused at the same time. Containers who's
|
||||||
|
// purpose is to contain selectable elements can determine when to select them
|
||||||
|
// by implementing MouseTargetContainer and listening for HandleChildMouseDown
|
||||||
|
// events.
|
||||||
|
type Selectable interface {
|
||||||
|
Element
|
||||||
|
Enableable
|
||||||
|
|
||||||
|
// HandleSelectionChange is called when the element is selected or
|
||||||
|
// deselected.
|
||||||
|
HandleSelectionChange ()
|
||||||
|
}
|
||||||
|
|
||||||
// KeyboardTarget represents an element that can receive keyboard input.
|
// KeyboardTarget represents an element that can receive keyboard input.
|
||||||
type KeyboardTarget interface {
|
type KeyboardTarget interface {
|
||||||
Element
|
Element
|
||||||
@ -80,6 +106,22 @@ type MouseTarget interface {
|
|||||||
HandleMouseUp (x, y int, button input.Button)
|
HandleMouseUp (x, y int, button input.Button)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MouseTargetContainer represents an element that wants to know when one
|
||||||
|
// of its children is clicked. Children do not have to implement MouseTarget for
|
||||||
|
// a container satisfying MouseTargetContainer to be notified that they have
|
||||||
|
// been clicked.
|
||||||
|
type MouseTargetContainer interface {
|
||||||
|
Container
|
||||||
|
|
||||||
|
// HandleMouseDown is called when a mouse button is pressed down on a
|
||||||
|
// child element.
|
||||||
|
HandleChildMouseDown (x, y int, button input.Button, child Element)
|
||||||
|
|
||||||
|
// HandleMouseUp is called when a mouse button is released that was
|
||||||
|
// originally pressed down on a child element.
|
||||||
|
HandleChildMouseUp (x, y int, button input.Button, child Element)
|
||||||
|
}
|
||||||
|
|
||||||
// MotionTarget represents an element that can receive mouse motion events.
|
// MotionTarget represents an element that can receive mouse motion events.
|
||||||
type MotionTarget interface {
|
type MotionTarget interface {
|
||||||
Element
|
Element
|
||||||
|
142
elements/entry.go
Normal file
142
elements/entry.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package elements
|
||||||
|
|
||||||
|
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/default/theme"
|
||||||
|
|
||||||
|
type cellEntity interface {
|
||||||
|
tomo.ContainerEntity
|
||||||
|
tomo.SelectableEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cell is a single-element container that satisfies tomo.Selectable. It
|
||||||
|
// provides styling based on whether or not it is selected.
|
||||||
|
type Cell struct {
|
||||||
|
entity cellEntity
|
||||||
|
child tomo.Element
|
||||||
|
enabled bool
|
||||||
|
padding bool
|
||||||
|
theme theme.Wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCell creates a new cell element. If padding is true, the cell will have
|
||||||
|
// padding on all sides. Child can be nil and added later with the Adopt()
|
||||||
|
// method.
|
||||||
|
func NewCell (child tomo.Element, padding bool) (element *Cell) {
|
||||||
|
element = &Cell { padding: padding }
|
||||||
|
element.theme.Case = tomo.C("tomo", "cell")
|
||||||
|
element.entity = tomo.NewEntity(element).(cellEntity)
|
||||||
|
element.Adopt(child)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity returns this element's entity.
|
||||||
|
func (element *Cell) Entity () tomo.Entity {
|
||||||
|
return element.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw causes the element to draw to the specified destination canvas.
|
||||||
|
func (element *Cell) Draw (destination canvas.Canvas) {
|
||||||
|
bounds := element.entity.Bounds()
|
||||||
|
pattern := element.theme.Pattern(tomo.PatternTableCell, element.state())
|
||||||
|
if element.child == nil {
|
||||||
|
pattern.Draw(destination, bounds)
|
||||||
|
} else if element.padding {
|
||||||
|
artist.DrawShatter (
|
||||||
|
destination, pattern, bounds,
|
||||||
|
element.child.Entity().Bounds())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw causes the element to perform a layout operation.
|
||||||
|
func (element *Cell) Layout () {
|
||||||
|
if element.child == nil { return }
|
||||||
|
|
||||||
|
bounds := element.entity.Bounds()
|
||||||
|
if element.padding {
|
||||||
|
bounds = element.theme.Padding(tomo.PatternTableCell).Apply(bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
element.entity.PlaceChild(0, bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawBackground draws this element's background pattern to the specified
|
||||||
|
// destination canvas.
|
||||||
|
func (element *Cell) DrawBackground (destination canvas.Canvas) {
|
||||||
|
element.theme.Pattern(tomo.PatternTableCell, element.state()).
|
||||||
|
Draw(destination, element.entity.Bounds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adopt sets this element's child. If nil is passed, any child is removed.
|
||||||
|
func (element *Cell) Adopt (child tomo.Element) {
|
||||||
|
if element.child != nil {
|
||||||
|
element.entity.Disown(element.entity.IndexOf(element.child))
|
||||||
|
}
|
||||||
|
if child != nil {
|
||||||
|
element.entity.Adopt(child)
|
||||||
|
}
|
||||||
|
element.child = child
|
||||||
|
|
||||||
|
element.updateMinimumSize()
|
||||||
|
element.entity.Invalidate()
|
||||||
|
element.entity.InvalidateLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled returns whether this cell is enabled or not.
|
||||||
|
func (element *Cell) Enabled () bool {
|
||||||
|
return element.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnabled sets whether this cell can be selected or not.
|
||||||
|
func (element *Cell) SetEnabled (enabled bool) {
|
||||||
|
if element.enabled == enabled { return }
|
||||||
|
element.enabled = enabled
|
||||||
|
element.entity.Invalidate()
|
||||||
|
if child, ok := element.child.(tomo.Enableable); ok {
|
||||||
|
child.SetEnabled(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTheme sets this element's theme.
|
||||||
|
func (element *Cell) SetTheme (theme tomo.Theme) {
|
||||||
|
if theme == element.theme.Theme { return }
|
||||||
|
element.theme.Theme = theme
|
||||||
|
element.updateMinimumSize()
|
||||||
|
element.entity.Invalidate()
|
||||||
|
element.entity.InvalidateLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Cell) HandleSelectionChange () {
|
||||||
|
element.entity.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Cell) HandleChildMinimumSizeChange (tomo.Element) {
|
||||||
|
element.updateMinimumSize()
|
||||||
|
element.entity.Invalidate()
|
||||||
|
element.entity.InvalidateLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Cell) state () tomo.State {
|
||||||
|
return tomo.State {
|
||||||
|
Disabled: !element.enabled,
|
||||||
|
On: element.entity.Selected(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Cell) updateMinimumSize () {
|
||||||
|
width, height := 0, 0
|
||||||
|
|
||||||
|
if element.child != nil {
|
||||||
|
childWidth, childHeight := element.entity.ChildMinimumSize(0)
|
||||||
|
width += childWidth
|
||||||
|
height += childHeight
|
||||||
|
}
|
||||||
|
if element.padding {
|
||||||
|
padding := element.theme.Padding(tomo.PatternTableCell)
|
||||||
|
width += padding.Horizontal()
|
||||||
|
height += padding.Vertical()
|
||||||
|
}
|
||||||
|
|
||||||
|
element.entity.SetMinimumSize(width, height)
|
||||||
|
}
|
12
entity.go
12
entity.go
@ -69,6 +69,10 @@ type ContainerEntity interface {
|
|||||||
// index to a bounding rectangle.
|
// index to a bounding rectangle.
|
||||||
PlaceChild (index int, bounds image.Rectangle)
|
PlaceChild (index int, bounds image.Rectangle)
|
||||||
|
|
||||||
|
// SelectChild marks a child as selected or unselected, if it is
|
||||||
|
// selectable.
|
||||||
|
SelectChild (index int, selected bool)
|
||||||
|
|
||||||
// ChildMinimumSize returns the minimum size of the child at the
|
// ChildMinimumSize returns the minimum size of the child at the
|
||||||
// specified index.
|
// specified index.
|
||||||
ChildMinimumSize (index int) (width, height int)
|
ChildMinimumSize (index int) (width, height int)
|
||||||
@ -94,6 +98,14 @@ type FocusableEntity interface {
|
|||||||
FocusPrevious ()
|
FocusPrevious ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SelectableEntity is given to elements that support the Selectable interface.
|
||||||
|
type SelectableEntity interface {
|
||||||
|
Entity
|
||||||
|
|
||||||
|
// Selected returns whether this element is currently selected.
|
||||||
|
Selected () bool
|
||||||
|
}
|
||||||
|
|
||||||
// FlexibleEntity is given to elements that support the Flexible interface.
|
// FlexibleEntity is given to elements that support the Flexible interface.
|
||||||
type FlexibleEntity interface {
|
type FlexibleEntity interface {
|
||||||
Entity
|
Entity
|
||||||
|
Reference in New Issue
Block a user