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) {
|
||||
request.callback(nil, err)
|
||||
request.window.system.afterEvent()
|
||||
request.state = selReqStateClosed
|
||||
}
|
||||
|
||||
func (request *selectionRequest) finalize (data data.Data) {
|
||||
request.callback(data, nil)
|
||||
request.window.system.afterEvent()
|
||||
request.state = selReqStateClosed
|
||||
}
|
||||
|
||||
|
@ -141,11 +141,13 @@ func (system *system) afterEvent () {
|
||||
}
|
||||
|
||||
func (system *system) layout (entity *entity, force bool) {
|
||||
if entity == nil || !entity.isContainer { return }
|
||||
if entity == nil { return }
|
||||
if entity.layoutInvalid == true || force {
|
||||
entity.element.(tomo.Container).Layout()
|
||||
entity.layoutInvalid = false
|
||||
force = true
|
||||
if element, ok := entity.element.(tomo.Layoutable); ok {
|
||||
element.Layout()
|
||||
entity.layoutInvalid = false
|
||||
force = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, child := range entity.children {
|
||||
|
15
element.go
15
element.go
@ -15,12 +15,19 @@ type Element interface {
|
||||
Entity () Entity
|
||||
}
|
||||
|
||||
// Container is an element capable of containing child elements.
|
||||
// Layoutable represents an element that needs to perform layout calculations
|
||||
// before it can draw itself.
|
||||
type Layoutable interface {
|
||||
Element
|
||||
|
||||
// Layout causes this element to perform a layout operation.
|
||||
Layout ()
|
||||
}
|
||||
|
||||
// Container represents an element capable of containing child elements.
|
||||
type Container interface {
|
||||
Element
|
||||
|
||||
// Layout causes this element to arrange its children.
|
||||
Layout ()
|
||||
Layoutable
|
||||
|
||||
// DrawBackground causes the element to draw its background pattern to
|
||||
// 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 }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
tomo.FocusableEntity
|
||||
tomo.ScrollableEntity
|
||||
tomo.LayoutEntity
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (element *TextBox) Draw (destination canvas.Canvas) {
|
||||
bounds := element.entity.Bounds()
|
||||
element.scrollToCursor()
|
||||
|
||||
state := element.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 () {
|
||||
element.entity.Invalidate()
|
||||
}
|
||||
@ -497,8 +502,8 @@ func (element *TextBox) scrollToCursor () {
|
||||
} else if cursorPosition.X < minX {
|
||||
element.scroll -= minX - cursorPosition.X
|
||||
if element.scroll < 0 { element.scroll = 0 }
|
||||
element.entity.Invalidate()
|
||||
element.entity.NotifyScrollBoundsChange()
|
||||
element.entity.Invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
|
16
entity.go
16
entity.go
@ -31,14 +31,20 @@ type Entity interface {
|
||||
DrawBackground (canvas.Canvas)
|
||||
}
|
||||
|
||||
// LayoutEntity is given to elements that support the Layoutable interface.
|
||||
type LayoutEntity interface {
|
||||
Entity
|
||||
|
||||
// InvalidateLayout marks the element's layout as invalid. At the end of
|
||||
// every event, the backend will ask all invalid elements to recalculate
|
||||
// their layouts.
|
||||
InvalidateLayout ()
|
||||
}
|
||||
|
||||
// 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 ()
|
||||
LayoutEntity
|
||||
|
||||
// Adopt adds an element as a child.
|
||||
Adopt (child Element)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package main
|
||||
|
||||
import "image"
|
||||
// import "image"
|
||||
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/backends/all"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
|
||||
@ -14,58 +13,56 @@ func main () {
|
||||
func run () {
|
||||
window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 360, 240))
|
||||
window.SetTitle("Scroll")
|
||||
container := containers.NewContainer(layouts.Vertical { true, true })
|
||||
container := containers.NewVBox(true, true)
|
||||
window.Adopt(container)
|
||||
|
||||
textBox := elements.NewTextBox("", copypasta)
|
||||
scrollContainer := containers.NewScrollContainer(true, false)
|
||||
scrollContainer := containers.NewScroll(true, false)
|
||||
|
||||
disconnectedContainer := containers.NewContainer (layouts.Horizontal {
|
||||
Gap: true,
|
||||
})
|
||||
list := elements.NewList (
|
||||
elements.NewListEntry("This is list item 0", nil),
|
||||
elements.NewListEntry("This is list item 1", nil),
|
||||
elements.NewListEntry("This is list item 2", nil),
|
||||
elements.NewListEntry("This is list item 3", nil),
|
||||
elements.NewListEntry("This is list item 4", nil),
|
||||
elements.NewListEntry("This is list item 5", nil),
|
||||
elements.NewListEntry("This is list item 6", nil),
|
||||
elements.NewListEntry("This is list item 7", nil),
|
||||
elements.NewListEntry("This is list item 8", nil),
|
||||
elements.NewListEntry("This is list item 9", nil),
|
||||
elements.NewListEntry("This is list item 10", nil),
|
||||
elements.NewListEntry("This is list item 11", nil),
|
||||
elements.NewListEntry("This is list item 12", nil),
|
||||
elements.NewListEntry("This is list item 13", nil),
|
||||
elements.NewListEntry("This is list item 14", nil),
|
||||
elements.NewListEntry("This is list item 15", nil),
|
||||
elements.NewListEntry("This is list item 16", nil),
|
||||
elements.NewListEntry("This is list item 17", nil),
|
||||
elements.NewListEntry("This is list item 18", nil),
|
||||
elements.NewListEntry("This is list item 19", nil),
|
||||
elements.NewListEntry("This is list item 20", nil))
|
||||
list.Collapse(0, 32)
|
||||
scrollBar := elements.NewScrollBar(true)
|
||||
list.OnScrollBoundsChange (func () {
|
||||
scrollBar.SetBounds (
|
||||
list.ScrollContentBounds(),
|
||||
list.ScrollViewportBounds())
|
||||
})
|
||||
scrollBar.OnScroll (func (viewport image.Point) {
|
||||
list.ScrollTo(viewport)
|
||||
})
|
||||
disconnectedContainer := containers.NewHBox(false, true)
|
||||
// list := elements.NewList (
|
||||
// elements.NewListEntry("This is list item 0", nil),
|
||||
// elements.NewListEntry("This is list item 1", nil),
|
||||
// elements.NewListEntry("This is list item 2", nil),
|
||||
// elements.NewListEntry("This is list item 3", nil),
|
||||
// elements.NewListEntry("This is list item 4", nil),
|
||||
// elements.NewListEntry("This is list item 5", nil),
|
||||
// elements.NewListEntry("This is list item 6", nil),
|
||||
// elements.NewListEntry("This is list item 7", nil),
|
||||
// elements.NewListEntry("This is list item 8", nil),
|
||||
// elements.NewListEntry("This is list item 9", nil),
|
||||
// elements.NewListEntry("This is list item 10", nil),
|
||||
// elements.NewListEntry("This is list item 11", nil),
|
||||
// elements.NewListEntry("This is list item 12", nil),
|
||||
// elements.NewListEntry("This is list item 13", nil),
|
||||
// elements.NewListEntry("This is list item 14", nil),
|
||||
// elements.NewListEntry("This is list item 15", nil),
|
||||
// elements.NewListEntry("This is list item 16", nil),
|
||||
// elements.NewListEntry("This is list item 17", nil),
|
||||
// elements.NewListEntry("This is list item 18", nil),
|
||||
// elements.NewListEntry("This is list item 19", nil),
|
||||
// elements.NewListEntry("This is list item 20", nil))
|
||||
// list.Collapse(0, 32)
|
||||
// scrollBar := elements.NewScrollBar(true)
|
||||
// list.OnScrollBoundsChange (func () {
|
||||
// scrollBar.SetBounds (
|
||||
// list.ScrollContentBounds(),
|
||||
// list.ScrollViewportBounds())
|
||||
// })
|
||||
// scrollBar.OnScroll (func (viewport image.Point) {
|
||||
// list.ScrollTo(viewport)
|
||||
// })
|
||||
|
||||
scrollContainer.Adopt(textBox)
|
||||
container.Adopt(elements.NewLabel("A ScrollContainer:", false), false)
|
||||
container.Adopt(scrollContainer, false)
|
||||
disconnectedContainer.Adopt(list, false)
|
||||
// disconnectedContainer.Adopt(list, false)
|
||||
disconnectedContainer.Adopt (elements.NewLabel (
|
||||
"Notice how the scroll bar to the right can be used to " +
|
||||
"control the list, despite not even touching it. It is " +
|
||||
"indeed a thing you can do. It is also terrible UI design so " +
|
||||
"don't do it.", true), true)
|
||||
disconnectedContainer.Adopt(scrollBar, false)
|
||||
// disconnectedContainer.Adopt(scrollBar, false)
|
||||
container.Adopt(disconnectedContainer, true)
|
||||
|
||||
window.OnClose(tomo.Stop)
|
||||
|
Reference in New Issue
Block a user