Scroll containers yay
This commit is contained in:
parent
ed6de3a36f
commit
b9c8350677
@ -97,11 +97,13 @@ func (request *selectionRequest) convertSelection (
|
|||||||
|
|
||||||
func (request *selectionRequest) die (err error) {
|
func (request *selectionRequest) die (err error) {
|
||||||
request.callback(nil, err)
|
request.callback(nil, err)
|
||||||
|
request.window.system.afterEvent()
|
||||||
request.state = selReqStateClosed
|
request.state = selReqStateClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (request *selectionRequest) finalize (data data.Data) {
|
func (request *selectionRequest) finalize (data data.Data) {
|
||||||
request.callback(data, nil)
|
request.callback(data, nil)
|
||||||
|
request.window.system.afterEvent()
|
||||||
request.state = selReqStateClosed
|
request.state = selReqStateClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,11 +141,13 @@ func (system *system) afterEvent () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (system *system) layout (entity *entity, force bool) {
|
func (system *system) layout (entity *entity, force bool) {
|
||||||
if entity == nil || !entity.isContainer { return }
|
if entity == nil { return }
|
||||||
if entity.layoutInvalid == true || force {
|
if entity.layoutInvalid == true || force {
|
||||||
entity.element.(tomo.Container).Layout()
|
if element, ok := entity.element.(tomo.Layoutable); ok {
|
||||||
entity.layoutInvalid = false
|
element.Layout()
|
||||||
force = true
|
entity.layoutInvalid = false
|
||||||
|
force = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, child := range entity.children {
|
for _, child := range entity.children {
|
||||||
|
13
element.go
13
element.go
@ -15,12 +15,19 @@ type Element interface {
|
|||||||
Entity () Entity
|
Entity () Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container is an element capable of containing child elements.
|
// Layoutable represents an element that needs to perform layout calculations
|
||||||
type Container interface {
|
// before it can draw itself.
|
||||||
|
type Layoutable interface {
|
||||||
Element
|
Element
|
||||||
|
|
||||||
// Layout causes this element to arrange its children.
|
// Layout causes this element to perform a layout operation.
|
||||||
Layout ()
|
Layout ()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container represents an element capable of containing child elements.
|
||||||
|
type Container interface {
|
||||||
|
Element
|
||||||
|
Layoutable
|
||||||
|
|
||||||
// DrawBackground causes the element to draw its background pattern to
|
// DrawBackground causes the element to draw its background pattern to
|
||||||
// the specified canvas. The bounds of this canvas specify the area that
|
// the specified canvas. The bounds of this canvas specify the area that
|
||||||
|
@ -98,7 +98,6 @@ func (element *Box) Layout () {
|
|||||||
if element.margin { x += marginSize }
|
if element.margin { x += marginSize }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Box) Adopt (child tomo.Element, expand bool) {
|
func (element *Box) Adopt (child tomo.Element, expand bool) {
|
||||||
|
@ -1,332 +0,0 @@
|
|||||||
package containers
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
|
||||||
|
|
||||||
// ScrollContainer is a container that is capable of holding a scrollable
|
|
||||||
// element.
|
|
||||||
type ScrollContainer struct {
|
|
||||||
*core.Core
|
|
||||||
*core.Propagator
|
|
||||||
core core.CoreControl
|
|
||||||
|
|
||||||
child tomo.Scrollable
|
|
||||||
horizontal *elements.ScrollBar
|
|
||||||
vertical *elements.ScrollBar
|
|
||||||
|
|
||||||
config config.Wrapped
|
|
||||||
theme theme.Wrapped
|
|
||||||
|
|
||||||
onFocusRequest func () (granted bool)
|
|
||||||
onFocusMotionRequest func (input.KeynavDirection) (granted bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewScrollContainer creates a new scroll container with the specified scroll
|
|
||||||
// bars.
|
|
||||||
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
|
|
||||||
element = &ScrollContainer { }
|
|
||||||
element.theme.Case = tomo.C("tomo", "scrollContainer")
|
|
||||||
element.Core, element.core = core.NewCore(element, element.redoAll)
|
|
||||||
element.Propagator = core.NewPropagator(element, element.core)
|
|
||||||
|
|
||||||
if horizontal {
|
|
||||||
element.horizontal = elements.NewScrollBar(false)
|
|
||||||
element.setUpChild(element.horizontal)
|
|
||||||
element.horizontal.OnScroll (func (viewport image.Point) {
|
|
||||||
if element.child != nil {
|
|
||||||
element.child.ScrollTo(viewport)
|
|
||||||
}
|
|
||||||
if element.vertical != nil {
|
|
||||||
element.vertical.SetBounds (
|
|
||||||
element.child.ScrollContentBounds(),
|
|
||||||
element.child.ScrollViewportBounds())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if vertical {
|
|
||||||
element.vertical = elements.NewScrollBar(true)
|
|
||||||
element.setUpChild(element.vertical)
|
|
||||||
element.vertical.OnScroll (func (viewport image.Point) {
|
|
||||||
if element.child != nil {
|
|
||||||
element.child.ScrollTo(viewport)
|
|
||||||
}
|
|
||||||
if element.horizontal != nil {
|
|
||||||
element.horizontal.SetBounds (
|
|
||||||
element.child.ScrollContentBounds(),
|
|
||||||
element.child.ScrollViewportBounds())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Adopt adds a scrollable element to the scroll container. The container can
|
|
||||||
// only contain one scrollable element at a time, and when a new one is adopted
|
|
||||||
// it replaces the last one.
|
|
||||||
func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
|
|
||||||
// disown previous child if it exists
|
|
||||||
if element.child != nil {
|
|
||||||
element.disownChild(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
// adopt new child
|
|
||||||
element.child = child
|
|
||||||
if child != nil {
|
|
||||||
element.setUpChild(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
element.updateEnabled()
|
|
||||||
element.updateMinimumSize()
|
|
||||||
if element.core.HasImage() {
|
|
||||||
element.redoAll()
|
|
||||||
element.core.DamageAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) setUpChild (child tomo.Element) {
|
|
||||||
child.SetParent(element)
|
|
||||||
if child, ok := child.(tomo.Themeable); ok {
|
|
||||||
child.SetTheme(element.theme.Theme)
|
|
||||||
}
|
|
||||||
if child, ok := child.(tomo.Configurable); ok {
|
|
||||||
child.SetConfig(element.config.Config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) disownChild (child tomo.Scrollable) {
|
|
||||||
child.DrawTo(nil, image.Rectangle { }, nil)
|
|
||||||
child.SetParent(nil)
|
|
||||||
if child, ok := child.(tomo.Focusable); ok {
|
|
||||||
if child.Focused() {
|
|
||||||
child.HandleUnfocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) Window () tomo.Window {
|
|
||||||
return element.core.Window()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotifyMinimumSizeChange notifies the container that the minimum size of a
|
|
||||||
// child element has changed.
|
|
||||||
func (element *ScrollContainer) NotifyMinimumSizeChange (child tomo.Element) {
|
|
||||||
element.redoAll()
|
|
||||||
element.core.DamageAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotifyScrollBoundsChange notifies the container that the scroll bounds or
|
|
||||||
// axes of a child have changed.
|
|
||||||
func (element *ScrollContainer) NotifyScrollBoundsChange (child tomo.Scrollable) {
|
|
||||||
element.updateEnabled()
|
|
||||||
viewportBounds := element.child.ScrollViewportBounds()
|
|
||||||
contentBounds := element.child.ScrollContentBounds()
|
|
||||||
if element.horizontal != nil {
|
|
||||||
element.horizontal.SetBounds(contentBounds, viewportBounds)
|
|
||||||
}
|
|
||||||
if element.vertical != nil {
|
|
||||||
element.vertical.SetBounds(contentBounds, viewportBounds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawBackground draws a portion of the container's background pattern within
|
|
||||||
// the specified bounds. The container will not push these changes.
|
|
||||||
func (element *ScrollContainer) DrawBackground (bounds image.Rectangle) {
|
|
||||||
element.core.DrawBackgroundBounds (
|
|
||||||
element.theme.Pattern(tomo.PatternBackground, tomo.State { }),
|
|
||||||
bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTheme sets the element's theme.
|
|
||||||
func (element *ScrollContainer) SetTheme (new tomo.Theme) {
|
|
||||||
if new == element.theme.Theme { return }
|
|
||||||
element.theme.Theme = new
|
|
||||||
element.Propagator.SetTheme(new)
|
|
||||||
element.updateMinimumSize()
|
|
||||||
element.redoAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfig sets the element's configuration.
|
|
||||||
func (element *ScrollContainer) SetConfig (new tomo.Config) {
|
|
||||||
if new == element.config.Config { return }
|
|
||||||
element.Propagator.SetConfig(new)
|
|
||||||
element.updateMinimumSize()
|
|
||||||
element.redoAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleScroll (
|
|
||||||
x, y int,
|
|
||||||
deltaX, deltaY float64,
|
|
||||||
) {
|
|
||||||
horizontal, vertical := element.child.ScrollAxes()
|
|
||||||
if !horizontal { deltaX = 0 }
|
|
||||||
if !vertical { deltaY = 0 }
|
|
||||||
element.scrollChildBy(int(deltaX), int(deltaY))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleKeyDown is called when a key is pressed down or repeated while
|
|
||||||
// this element has keyboard focus. It is important to note that not
|
|
||||||
// every key down event is guaranteed to be paired with exactly one key
|
|
||||||
// up event. This is the reason a list of modifier keys held down at the
|
|
||||||
// time of the key press is given.
|
|
||||||
func (element *ScrollContainer) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
|
|
||||||
switch key {
|
|
||||||
case input.KeyPageUp:
|
|
||||||
viewport := element.child.ScrollViewportBounds()
|
|
||||||
element.HandleScroll(0, 0, 0, float64(-viewport.Dy()))
|
|
||||||
case input.KeyPageDown:
|
|
||||||
viewport := element.child.ScrollViewportBounds()
|
|
||||||
element.HandleScroll(0, 0, 0, float64(viewport.Dy()))
|
|
||||||
default:
|
|
||||||
element.Propagator.HandleKeyDown(key, modifiers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleKeyUp is called when a key is released while this element has
|
|
||||||
// keyboard focus.
|
|
||||||
func (element *ScrollContainer) HandleKeyUp (key input.Key, modifiers input.Modifiers) { }
|
|
||||||
|
|
||||||
// CountChildren returns the amount of children contained within this element.
|
|
||||||
func (element *ScrollContainer) CountChildren () (count int) {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Child returns the child at the specified index. If the index is out of
|
|
||||||
// bounds, this method will return nil.
|
|
||||||
func (element *ScrollContainer) Child (index int) (child tomo.Element) {
|
|
||||||
switch index {
|
|
||||||
case 0: return element.child
|
|
||||||
case 1:
|
|
||||||
if element.horizontal == nil {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return element.horizontal
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
if element.vertical == nil {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return element.vertical
|
|
||||||
}
|
|
||||||
default: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) redoAll () {
|
|
||||||
if !element.core.HasImage() { return }
|
|
||||||
|
|
||||||
zr := image.Rectangle { }
|
|
||||||
if element.child != nil { element.child.DrawTo(nil, zr, nil) }
|
|
||||||
if element.horizontal != nil { element.horizontal.DrawTo(nil, zr, nil) }
|
|
||||||
if element.vertical != nil { element.vertical.DrawTo(nil, zr, nil) }
|
|
||||||
|
|
||||||
childBounds, horizontalBounds, verticalBounds := element.layout()
|
|
||||||
if element.child != nil {
|
|
||||||
element.child.DrawTo (
|
|
||||||
canvas.Cut(element.core, childBounds),
|
|
||||||
childBounds, element.childDamageCallback)
|
|
||||||
}
|
|
||||||
if element.horizontal != nil {
|
|
||||||
element.horizontal.DrawTo (
|
|
||||||
canvas.Cut(element.core, horizontalBounds),
|
|
||||||
horizontalBounds, element.childDamageCallback)
|
|
||||||
}
|
|
||||||
if element.vertical != nil {
|
|
||||||
element.vertical.DrawTo (
|
|
||||||
canvas.Cut(element.core, verticalBounds),
|
|
||||||
verticalBounds, element.childDamageCallback)
|
|
||||||
}
|
|
||||||
element.draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) scrollChildBy (x, y int) {
|
|
||||||
if element.child == nil { return }
|
|
||||||
scrollPoint :=
|
|
||||||
element.child.ScrollViewportBounds().Min.
|
|
||||||
Add(image.Pt(x, y))
|
|
||||||
element.child.ScrollTo(scrollPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) childDamageCallback (region image.Rectangle) {
|
|
||||||
element.core.DamageRegion(region)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) layout () (
|
|
||||||
child image.Rectangle,
|
|
||||||
horizontal image.Rectangle,
|
|
||||||
vertical image.Rectangle,
|
|
||||||
) {
|
|
||||||
bounds := element.Bounds()
|
|
||||||
child = bounds
|
|
||||||
|
|
||||||
if element.horizontal != nil {
|
|
||||||
_, hMinHeight := element.horizontal.MinimumSize()
|
|
||||||
child.Max.Y -= hMinHeight
|
|
||||||
}
|
|
||||||
if element.vertical != nil {
|
|
||||||
vMinWidth, _ := element.vertical.MinimumSize()
|
|
||||||
child.Max.X -= vMinWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
vertical.Min.X = child.Max.X
|
|
||||||
vertical.Max.X = bounds.Max.X
|
|
||||||
vertical.Min.Y = bounds.Min.Y
|
|
||||||
vertical.Max.Y = child.Max.Y
|
|
||||||
|
|
||||||
horizontal.Min.X = bounds.Min.X
|
|
||||||
horizontal.Max.X = child.Max.X
|
|
||||||
horizontal.Min.Y = child.Max.Y
|
|
||||||
horizontal.Max.Y = bounds.Max.Y
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) draw () {
|
|
||||||
if element.horizontal != nil && element.vertical != nil {
|
|
||||||
bounds := element.Bounds()
|
|
||||||
bounds.Min = image.Pt (
|
|
||||||
bounds.Max.X - element.vertical.Bounds().Dx(),
|
|
||||||
bounds.Max.Y - element.horizontal.Bounds().Dy())
|
|
||||||
state := tomo.State { }
|
|
||||||
deadArea := element.theme.Pattern(tomo.PatternDead, state)
|
|
||||||
deadArea.Draw(canvas.Cut(element.core, bounds), bounds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) updateMinimumSize () {
|
|
||||||
var width, height int
|
|
||||||
|
|
||||||
if element.child != nil {
|
|
||||||
width, height = element.child.MinimumSize()
|
|
||||||
}
|
|
||||||
if element.horizontal != nil {
|
|
||||||
hMinWidth, hMinHeight := element.horizontal.MinimumSize()
|
|
||||||
height += hMinHeight
|
|
||||||
if hMinWidth > width {
|
|
||||||
width = hMinWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if element.vertical != nil {
|
|
||||||
vMinWidth, vMinHeight := element.vertical.MinimumSize()
|
|
||||||
width += vMinWidth
|
|
||||||
if vMinHeight > height {
|
|
||||||
height = vMinHeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
element.core.SetMinimumSize(width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) updateEnabled () {
|
|
||||||
horizontal, vertical := element.child.ScrollAxes()
|
|
||||||
if element.horizontal != nil {
|
|
||||||
element.horizontal.SetEnabled(horizontal)
|
|
||||||
}
|
|
||||||
if element.vertical != nil {
|
|
||||||
element.vertical.SetEnabled(vertical)
|
|
||||||
}
|
|
||||||
}
|
|
197
elements/containers/scroll.go
Normal file
197
elements/containers/scroll.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
|
// import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||||
|
|
||||||
|
type Scroll struct {
|
||||||
|
entity tomo.ContainerEntity
|
||||||
|
|
||||||
|
child tomo.Scrollable
|
||||||
|
horizontal *elements.ScrollBar
|
||||||
|
vertical *elements.ScrollBar
|
||||||
|
|
||||||
|
config config.Wrapped
|
||||||
|
theme theme.Wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScroll (horizontal, vertical bool) (element *Scroll) {
|
||||||
|
element = &Scroll { }
|
||||||
|
element.theme.Case = tomo.C("tomo", "scroll")
|
||||||
|
element.entity = tomo.NewEntity(element).(tomo.ContainerEntity)
|
||||||
|
|
||||||
|
if horizontal {
|
||||||
|
element.horizontal = elements.NewScrollBar(false)
|
||||||
|
element.horizontal.OnScroll (func (viewport image.Point) {
|
||||||
|
if element.child != nil {
|
||||||
|
element.child.ScrollTo(viewport)
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
element.vertical.SetBounds (
|
||||||
|
element.child.ScrollContentBounds(),
|
||||||
|
element.child.ScrollViewportBounds())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
element.entity.Adopt(element.horizontal)
|
||||||
|
}
|
||||||
|
if vertical {
|
||||||
|
element.vertical = elements.NewScrollBar(true)
|
||||||
|
element.vertical.OnScroll (func (viewport image.Point) {
|
||||||
|
if element.child != nil {
|
||||||
|
element.child.ScrollTo(viewport)
|
||||||
|
}
|
||||||
|
if element.horizontal != nil {
|
||||||
|
element.horizontal.SetBounds (
|
||||||
|
element.child.ScrollContentBounds(),
|
||||||
|
element.child.ScrollViewportBounds())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
element.entity.Adopt(element.vertical)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) Entity () tomo.Entity {
|
||||||
|
return element.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) Draw (destination canvas.Canvas) {
|
||||||
|
if element.horizontal != nil && element.vertical != nil {
|
||||||
|
bounds := element.entity.Bounds()
|
||||||
|
bounds.Min = image.Pt (
|
||||||
|
bounds.Max.X - element.vertical.Entity().Bounds().Dx(),
|
||||||
|
bounds.Max.Y - element.horizontal.Entity().Bounds().Dy())
|
||||||
|
state := tomo.State { }
|
||||||
|
deadArea := element.theme.Pattern(tomo.PatternDead, state)
|
||||||
|
deadArea.Draw(canvas.Cut(destination, bounds), bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) Layout () {
|
||||||
|
bounds := element.entity.Bounds()
|
||||||
|
child := bounds
|
||||||
|
|
||||||
|
iHorizontal := element.entity.IndexOf(element.horizontal)
|
||||||
|
iVertical := element.entity.IndexOf(element.vertical)
|
||||||
|
iChild := element.entity.IndexOf(element.child)
|
||||||
|
|
||||||
|
var horizontal, vertical image.Rectangle
|
||||||
|
|
||||||
|
if element.horizontal != nil {
|
||||||
|
_, hMinHeight := element.entity.ChildMinimumSize(iHorizontal)
|
||||||
|
child.Max.Y -= hMinHeight
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
vMinWidth, _ := element.entity.ChildMinimumSize(iVertical)
|
||||||
|
child.Max.X -= vMinWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontal.Min.X = bounds.Min.X
|
||||||
|
horizontal.Max.X = child.Max.X
|
||||||
|
horizontal.Min.Y = child.Max.Y
|
||||||
|
horizontal.Max.Y = bounds.Max.Y
|
||||||
|
|
||||||
|
vertical.Min.X = child.Max.X
|
||||||
|
vertical.Max.X = bounds.Max.X
|
||||||
|
vertical.Min.Y = bounds.Min.Y
|
||||||
|
vertical.Max.Y = child.Max.Y
|
||||||
|
|
||||||
|
if element.horizontal != nil {
|
||||||
|
element.entity.PlaceChild (iHorizontal, horizontal)
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
element.entity.PlaceChild(iVertical, vertical)
|
||||||
|
}
|
||||||
|
if element.child != nil {
|
||||||
|
element.entity.PlaceChild(iChild, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) DrawBackground (destination canvas.Canvas) {
|
||||||
|
element.entity.DrawBackground(destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) Adopt (child tomo.Scrollable) {
|
||||||
|
if element.child != nil {
|
||||||
|
element.entity.Disown(element.entity.IndexOf(element.child))
|
||||||
|
}
|
||||||
|
if child != nil {
|
||||||
|
element.entity.Adopt(child)
|
||||||
|
}
|
||||||
|
element.child = child
|
||||||
|
|
||||||
|
element.updateEnabled()
|
||||||
|
element.updateMinimumSize()
|
||||||
|
element.entity.Invalidate()
|
||||||
|
element.entity.InvalidateLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) HandleChildMinimumSizeChange (tomo.Element) {
|
||||||
|
element.updateMinimumSize()
|
||||||
|
element.entity.Invalidate()
|
||||||
|
element.entity.InvalidateLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) HandleChildScrollBoundsChange (tomo.Scrollable) {
|
||||||
|
element.updateEnabled()
|
||||||
|
viewportBounds := element.child.ScrollViewportBounds()
|
||||||
|
contentBounds := element.child.ScrollContentBounds()
|
||||||
|
if element.horizontal != nil {
|
||||||
|
element.horizontal.SetBounds(contentBounds, viewportBounds)
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
element.vertical.SetBounds(contentBounds, viewportBounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) SetTheme (theme tomo.Theme) {
|
||||||
|
if theme == element.theme.Theme { return }
|
||||||
|
element.theme.Theme = theme
|
||||||
|
element.updateMinimumSize()
|
||||||
|
element.entity.Invalidate()
|
||||||
|
element.entity.InvalidateLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) SetConfig (config tomo.Config) {
|
||||||
|
element.config.Config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) updateMinimumSize () {
|
||||||
|
var width, height int
|
||||||
|
|
||||||
|
if element.child != nil {
|
||||||
|
width, height = element.entity.ChildMinimumSize (
|
||||||
|
element.entity.IndexOf(element.child))
|
||||||
|
}
|
||||||
|
if element.horizontal != nil {
|
||||||
|
hMinWidth, hMinHeight := element.entity.ChildMinimumSize (
|
||||||
|
element.entity.IndexOf(element.horizontal))
|
||||||
|
height += hMinHeight
|
||||||
|
if hMinWidth > width {
|
||||||
|
width = hMinWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
vMinWidth, vMinHeight := element.entity.ChildMinimumSize (
|
||||||
|
element.entity.IndexOf(element.vertical))
|
||||||
|
width += vMinWidth
|
||||||
|
if vMinHeight > height {
|
||||||
|
height = vMinHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.entity.SetMinimumSize(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Scroll) updateEnabled () {
|
||||||
|
horizontal, vertical := element.child.ScrollAxes()
|
||||||
|
if element.horizontal != nil {
|
||||||
|
element.horizontal.SetEnabled(horizontal)
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
element.vertical.SetEnabled(vertical)
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
|||||||
type textBoxEntity interface {
|
type textBoxEntity interface {
|
||||||
tomo.FocusableEntity
|
tomo.FocusableEntity
|
||||||
tomo.ScrollableEntity
|
tomo.ScrollableEntity
|
||||||
|
tomo.LayoutEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextBox is a single-line text input.
|
// TextBox is a single-line text input.
|
||||||
@ -72,7 +73,6 @@ func (element *TextBox) Entity () tomo.Entity {
|
|||||||
// Draw causes the element to draw to the specified destination canvas.
|
// Draw causes the element to draw to the specified destination canvas.
|
||||||
func (element *TextBox) Draw (destination canvas.Canvas) {
|
func (element *TextBox) Draw (destination canvas.Canvas) {
|
||||||
bounds := element.entity.Bounds()
|
bounds := element.entity.Bounds()
|
||||||
element.scrollToCursor()
|
|
||||||
|
|
||||||
state := element.state()
|
state := element.state()
|
||||||
pattern := element.theme.Pattern(tomo.PatternInput, state)
|
pattern := element.theme.Pattern(tomo.PatternInput, state)
|
||||||
@ -134,6 +134,11 @@ func (element *TextBox) Draw (destination canvas.Canvas) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Layout causes the element to perform a layout operation.
|
||||||
|
func (element *TextBox) Layout () {
|
||||||
|
element.scrollToCursor()
|
||||||
|
}
|
||||||
|
|
||||||
func (element *TextBox) HandleFocusChange () {
|
func (element *TextBox) HandleFocusChange () {
|
||||||
element.entity.Invalidate()
|
element.entity.Invalidate()
|
||||||
}
|
}
|
||||||
@ -497,8 +502,8 @@ func (element *TextBox) scrollToCursor () {
|
|||||||
} else if cursorPosition.X < minX {
|
} else if cursorPosition.X < minX {
|
||||||
element.scroll -= minX - cursorPosition.X
|
element.scroll -= minX - cursorPosition.X
|
||||||
if element.scroll < 0 { element.scroll = 0 }
|
if element.scroll < 0 { element.scroll = 0 }
|
||||||
element.entity.Invalidate()
|
|
||||||
element.entity.NotifyScrollBoundsChange()
|
element.entity.NotifyScrollBoundsChange()
|
||||||
|
element.entity.Invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
entity.go
14
entity.go
@ -31,14 +31,20 @@ type Entity interface {
|
|||||||
DrawBackground (canvas.Canvas)
|
DrawBackground (canvas.Canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerEntity is given to elements that support the Container interface.
|
// LayoutEntity is given to elements that support the Layoutable interface.
|
||||||
type ContainerEntity interface {
|
type LayoutEntity interface {
|
||||||
Entity
|
Entity
|
||||||
|
|
||||||
// InvalidateLayout marks the element's layout as invalid. At the end of
|
// InvalidateLayout marks the element's layout as invalid. At the end of
|
||||||
// every event, the backend will ask all invalid containers to
|
// every event, the backend will ask all invalid elements to recalculate
|
||||||
// recalculate their layouts.
|
// their layouts.
|
||||||
InvalidateLayout ()
|
InvalidateLayout ()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerEntity is given to elements that support the Container interface.
|
||||||
|
type ContainerEntity interface {
|
||||||
|
Entity
|
||||||
|
LayoutEntity
|
||||||
|
|
||||||
// Adopt adds an element as a child.
|
// Adopt adds an element as a child.
|
||||||
Adopt (child Element)
|
Adopt (child Element)
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "image"
|
// import "image"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/layouts"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
||||||
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
|
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
|
import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
|
||||||
@ -14,58 +13,56 @@ func main () {
|
|||||||
func run () {
|
func run () {
|
||||||
window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 360, 240))
|
window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 360, 240))
|
||||||
window.SetTitle("Scroll")
|
window.SetTitle("Scroll")
|
||||||
container := containers.NewContainer(layouts.Vertical { true, true })
|
container := containers.NewVBox(true, true)
|
||||||
window.Adopt(container)
|
window.Adopt(container)
|
||||||
|
|
||||||
textBox := elements.NewTextBox("", copypasta)
|
textBox := elements.NewTextBox("", copypasta)
|
||||||
scrollContainer := containers.NewScrollContainer(true, false)
|
scrollContainer := containers.NewScroll(true, false)
|
||||||
|
|
||||||
disconnectedContainer := containers.NewContainer (layouts.Horizontal {
|
disconnectedContainer := containers.NewHBox(false, true)
|
||||||
Gap: true,
|
// list := elements.NewList (
|
||||||
})
|
// elements.NewListEntry("This is list item 0", nil),
|
||||||
list := elements.NewList (
|
// elements.NewListEntry("This is list item 1", nil),
|
||||||
elements.NewListEntry("This is list item 0", nil),
|
// elements.NewListEntry("This is list item 2", nil),
|
||||||
elements.NewListEntry("This is list item 1", nil),
|
// elements.NewListEntry("This is list item 3", nil),
|
||||||
elements.NewListEntry("This is list item 2", nil),
|
// elements.NewListEntry("This is list item 4", nil),
|
||||||
elements.NewListEntry("This is list item 3", nil),
|
// elements.NewListEntry("This is list item 5", nil),
|
||||||
elements.NewListEntry("This is list item 4", nil),
|
// elements.NewListEntry("This is list item 6", nil),
|
||||||
elements.NewListEntry("This is list item 5", nil),
|
// elements.NewListEntry("This is list item 7", nil),
|
||||||
elements.NewListEntry("This is list item 6", nil),
|
// elements.NewListEntry("This is list item 8", nil),
|
||||||
elements.NewListEntry("This is list item 7", nil),
|
// elements.NewListEntry("This is list item 9", nil),
|
||||||
elements.NewListEntry("This is list item 8", nil),
|
// elements.NewListEntry("This is list item 10", nil),
|
||||||
elements.NewListEntry("This is list item 9", nil),
|
// elements.NewListEntry("This is list item 11", nil),
|
||||||
elements.NewListEntry("This is list item 10", nil),
|
// elements.NewListEntry("This is list item 12", nil),
|
||||||
elements.NewListEntry("This is list item 11", nil),
|
// elements.NewListEntry("This is list item 13", nil),
|
||||||
elements.NewListEntry("This is list item 12", nil),
|
// elements.NewListEntry("This is list item 14", nil),
|
||||||
elements.NewListEntry("This is list item 13", nil),
|
// elements.NewListEntry("This is list item 15", nil),
|
||||||
elements.NewListEntry("This is list item 14", nil),
|
// elements.NewListEntry("This is list item 16", nil),
|
||||||
elements.NewListEntry("This is list item 15", nil),
|
// elements.NewListEntry("This is list item 17", nil),
|
||||||
elements.NewListEntry("This is list item 16", nil),
|
// elements.NewListEntry("This is list item 18", nil),
|
||||||
elements.NewListEntry("This is list item 17", nil),
|
// elements.NewListEntry("This is list item 19", nil),
|
||||||
elements.NewListEntry("This is list item 18", nil),
|
// elements.NewListEntry("This is list item 20", nil))
|
||||||
elements.NewListEntry("This is list item 19", nil),
|
// list.Collapse(0, 32)
|
||||||
elements.NewListEntry("This is list item 20", nil))
|
// scrollBar := elements.NewScrollBar(true)
|
||||||
list.Collapse(0, 32)
|
// list.OnScrollBoundsChange (func () {
|
||||||
scrollBar := elements.NewScrollBar(true)
|
// scrollBar.SetBounds (
|
||||||
list.OnScrollBoundsChange (func () {
|
// list.ScrollContentBounds(),
|
||||||
scrollBar.SetBounds (
|
// list.ScrollViewportBounds())
|
||||||
list.ScrollContentBounds(),
|
// })
|
||||||
list.ScrollViewportBounds())
|
// scrollBar.OnScroll (func (viewport image.Point) {
|
||||||
})
|
// list.ScrollTo(viewport)
|
||||||
scrollBar.OnScroll (func (viewport image.Point) {
|
// })
|
||||||
list.ScrollTo(viewport)
|
|
||||||
})
|
|
||||||
|
|
||||||
scrollContainer.Adopt(textBox)
|
scrollContainer.Adopt(textBox)
|
||||||
container.Adopt(elements.NewLabel("A ScrollContainer:", false), false)
|
container.Adopt(elements.NewLabel("A ScrollContainer:", false), false)
|
||||||
container.Adopt(scrollContainer, false)
|
container.Adopt(scrollContainer, false)
|
||||||
disconnectedContainer.Adopt(list, false)
|
// disconnectedContainer.Adopt(list, false)
|
||||||
disconnectedContainer.Adopt (elements.NewLabel (
|
disconnectedContainer.Adopt (elements.NewLabel (
|
||||||
"Notice how the scroll bar to the right can be used to " +
|
"Notice how the scroll bar to the right can be used to " +
|
||||||
"control the list, despite not even touching it. It is " +
|
"control the list, despite not even touching it. It is " +
|
||||||
"indeed a thing you can do. It is also terrible UI design so " +
|
"indeed a thing you can do. It is also terrible UI design so " +
|
||||||
"don't do it.", true), true)
|
"don't do it.", true), true)
|
||||||
disconnectedContainer.Adopt(scrollBar, false)
|
// disconnectedContainer.Adopt(scrollBar, false)
|
||||||
container.Adopt(disconnectedContainer, true)
|
container.Adopt(disconnectedContainer, true)
|
||||||
|
|
||||||
window.OnClose(tomo.Stop)
|
window.OnClose(tomo.Stop)
|
||||||
|
Reference in New Issue
Block a user