Vertical layout partially works
This commit is contained in:
368
elements/containers/notdone/document.go
Normal file
368
elements/containers/notdone/document.go
Normal file
@@ -0,0 +1,368 @@
|
||||
package containers
|
||||
|
||||
import "image"
|
||||
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/elements/core"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||
|
||||
// DocumentContainer is a scrollable container capable of containing flexible
|
||||
// elements.
|
||||
type DocumentContainer struct {
|
||||
*core.Core
|
||||
*core.Propagator
|
||||
core core.CoreControl
|
||||
|
||||
children []tomo.LayoutEntry
|
||||
scroll image.Point
|
||||
warping bool
|
||||
contentBounds image.Rectangle
|
||||
|
||||
config config.Wrapped
|
||||
theme theme.Wrapped
|
||||
|
||||
onScrollBoundsChange func ()
|
||||
}
|
||||
|
||||
// NewDocumentContainer creates a new document container.
|
||||
func NewDocumentContainer () (element *DocumentContainer) {
|
||||
element = &DocumentContainer { }
|
||||
element.theme.Case = tomo.C("tomo", "documentContainer")
|
||||
element.Core, element.core = core.NewCore(element, element.redoAll)
|
||||
element.Propagator = core.NewPropagator(element, element.core)
|
||||
return
|
||||
}
|
||||
|
||||
// Adopt adds a new child element to the container. If expand is true, then the
|
||||
// element will stretch to either side of the container (much like a css block
|
||||
// element). If expand is false, the element will share a line with other inline
|
||||
// elements.
|
||||
func (element *DocumentContainer) Adopt (child tomo.Element, expand bool) {
|
||||
// set event handlers
|
||||
if child0, ok := child.(tomo.Themeable); ok {
|
||||
child0.SetTheme(element.theme.Theme)
|
||||
}
|
||||
if child0, ok := child.(tomo.Configurable); ok {
|
||||
child0.SetConfig(element.config.Config)
|
||||
}
|
||||
|
||||
// add child
|
||||
element.children = append (element.children, tomo.LayoutEntry {
|
||||
Element: child,
|
||||
Expand: expand,
|
||||
})
|
||||
|
||||
child.SetParent(element)
|
||||
|
||||
// refresh stale data
|
||||
element.updateMinimumSize()
|
||||
if element.core.HasImage() && !element.warping {
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
// Warp runs the specified callback, deferring all layout and rendering updates
|
||||
// until the callback has finished executing. This allows for aplications to
|
||||
// perform batch gui updates without flickering and stuff.
|
||||
func (element *DocumentContainer) Warp (callback func ()) {
|
||||
if element.warping {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
element.warping = true
|
||||
callback()
|
||||
element.warping = false
|
||||
|
||||
if element.core.HasImage() {
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
// Disown removes the given child from the container if it is contained within
|
||||
// it.
|
||||
func (element *DocumentContainer) Disown (child tomo.Element) {
|
||||
for index, entry := range element.children {
|
||||
if entry.Element == child {
|
||||
element.clearChildEventHandlers(entry.Element)
|
||||
element.children = append (
|
||||
element.children[:index],
|
||||
element.children[index + 1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
element.updateMinimumSize()
|
||||
if element.core.HasImage() && !element.warping {
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *DocumentContainer) clearChildEventHandlers (child tomo.Element) {
|
||||
child.DrawTo(nil, image.Rectangle { }, nil)
|
||||
child.SetParent(nil)
|
||||
|
||||
if child, ok := child.(tomo.Focusable); ok {
|
||||
if child.Focused() {
|
||||
child.HandleUnfocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DisownAll removes all child elements from the container at once.
|
||||
func (element *DocumentContainer) DisownAll () {
|
||||
for _, entry := range element.children {
|
||||
element.clearChildEventHandlers(entry.Element)
|
||||
}
|
||||
element.children = nil
|
||||
|
||||
element.updateMinimumSize()
|
||||
if element.core.HasImage() && !element.warping {
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
// Children returns a slice containing this element's children.
|
||||
func (element *DocumentContainer) Children () (children []tomo.Element) {
|
||||
children = make([]tomo.Element, len(element.children))
|
||||
for index, entry := range element.children {
|
||||
children[index] = entry.Element
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CountChildren returns the amount of children contained within this element.
|
||||
func (element *DocumentContainer) CountChildren () (count int) {
|
||||
return len(element.children)
|
||||
}
|
||||
|
||||
// Child returns the child at the specified index. If the index is out of
|
||||
// bounds, this method will return nil.
|
||||
func (element *DocumentContainer) Child (index int) (child tomo.Element) {
|
||||
if index < 0 || index > len(element.children) { return }
|
||||
return element.children[index].Element
|
||||
}
|
||||
|
||||
// ChildAt returns the child that contains the specified x and y coordinates. If
|
||||
// there are no children at the coordinates, this method will return nil.
|
||||
func (element *DocumentContainer) ChildAt (point image.Point) (child tomo.Element) {
|
||||
for _, entry := range element.children {
|
||||
if point.In(entry.Bounds) {
|
||||
child = entry.Element
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (element *DocumentContainer) redoAll () {
|
||||
if !element.core.HasImage() { return }
|
||||
|
||||
// do a layout
|
||||
element.doLayout()
|
||||
|
||||
maxScrollHeight := element.maxScrollHeight()
|
||||
if element.scroll.Y > maxScrollHeight {
|
||||
element.scroll.Y = maxScrollHeight
|
||||
element.doLayout()
|
||||
}
|
||||
|
||||
// draw a background
|
||||
rocks := make([]image.Rectangle, len(element.children))
|
||||
for index, entry := range element.children {
|
||||
rocks[index] = entry.Bounds
|
||||
}
|
||||
pattern := element.theme.Pattern (
|
||||
tomo.PatternBackground,
|
||||
tomo.State { })
|
||||
artist.DrawShatter(element.core, pattern, element.Bounds(), rocks...)
|
||||
|
||||
element.partition()
|
||||
if parent, ok := element.core.Parent().(tomo.ScrollableParent); ok {
|
||||
parent.NotifyScrollBoundsChange(element)
|
||||
}
|
||||
if element.onScrollBoundsChange != nil {
|
||||
element.onScrollBoundsChange()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *DocumentContainer) partition () {
|
||||
for _, entry := range element.children {
|
||||
entry.DrawTo(nil, entry.Bounds, nil)
|
||||
}
|
||||
|
||||
// cut our canvas up and give peices to child elements
|
||||
for _, entry := range element.children {
|
||||
if entry.Bounds.Overlaps(element.Bounds()) {
|
||||
entry.DrawTo (
|
||||
canvas.Cut(element.core, entry.Bounds),
|
||||
entry.Bounds, func (region image.Rectangle) {
|
||||
element.core.DamageRegion(region)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *DocumentContainer) Window () tomo.Window {
|
||||
return element.core.Window()
|
||||
}
|
||||
|
||||
// NotifyMinimumSizeChange notifies the container that the minimum size of a
|
||||
// child element has changed.
|
||||
func (element *DocumentContainer) NotifyMinimumSizeChange (child tomo.Element) {
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
|
||||
// DrawBackground draws a portion of the container's background pattern within
|
||||
// the specified bounds. The container will not push these changes.
|
||||
func (element *DocumentContainer) DrawBackground (bounds image.Rectangle) {
|
||||
element.core.DrawBackgroundBounds (
|
||||
element.theme.Pattern(tomo.PatternBackground, tomo.State { }),
|
||||
bounds)
|
||||
}
|
||||
|
||||
// NotifyFlexibleHeightChange notifies the parent that the parameters
|
||||
// affecting a child's flexible height have changed. This method is
|
||||
// expected to be called by flexible child element when their content
|
||||
// changes.
|
||||
func (element *DocumentContainer) NotifyFlexibleHeightChange (child tomo.Flexible) {
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
|
||||
// SetTheme sets the element's theme.
|
||||
func (element *DocumentContainer) SetTheme (new tomo.Theme) {
|
||||
if new == element.theme.Theme { return }
|
||||
element.theme.Theme = new
|
||||
element.Propagator.SetTheme(new)
|
||||
element.redoAll()
|
||||
}
|
||||
|
||||
// SetConfig sets the element's configuration.
|
||||
func (element *DocumentContainer) SetConfig (new tomo.Config) {
|
||||
if new == element.config.Config { return }
|
||||
element.Propagator.SetConfig(new)
|
||||
element.redoAll()
|
||||
}
|
||||
|
||||
// ScrollContentBounds returns the full content size of the element.
|
||||
func (element *DocumentContainer) ScrollContentBounds () image.Rectangle {
|
||||
return element.contentBounds
|
||||
}
|
||||
|
||||
// ScrollViewportBounds returns the size and position of the element's
|
||||
// viewport relative to ScrollBounds.
|
||||
func (element *DocumentContainer) ScrollViewportBounds () image.Rectangle {
|
||||
padding := element.theme.Padding(tomo.PatternBackground)
|
||||
bounds := padding.Apply(element.Bounds())
|
||||
bounds = bounds.Sub(bounds.Min).Add(element.scroll)
|
||||
return bounds
|
||||
}
|
||||
|
||||
// ScrollTo scrolls the viewport to the specified point relative to
|
||||
// ScrollBounds.
|
||||
func (element *DocumentContainer) ScrollTo (position image.Point) {
|
||||
if position.Y < 0 {
|
||||
position.Y = 0
|
||||
}
|
||||
maxScrollHeight := element.maxScrollHeight()
|
||||
if position.Y > maxScrollHeight {
|
||||
position.Y = maxScrollHeight
|
||||
}
|
||||
element.scroll = position
|
||||
if element.core.HasImage() && !element.warping {
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
// OnScrollBoundsChange sets a function to be called when the element's viewport
|
||||
// bounds, content bounds, or scroll axes change.
|
||||
func (element *DocumentContainer) OnScrollBoundsChange (callback func ()) {
|
||||
element.onScrollBoundsChange = callback
|
||||
}
|
||||
|
||||
func (element *DocumentContainer) maxScrollHeight () (height int) {
|
||||
padding := element.theme.Padding(tomo.PatternSunken)
|
||||
viewportHeight := element.Bounds().Dy() - padding.Vertical()
|
||||
height = element.contentBounds.Dy() - viewportHeight
|
||||
if height < 0 { height = 0 }
|
||||
return
|
||||
}
|
||||
|
||||
// ScrollAxes returns the supported axes for scrolling.
|
||||
func (element *DocumentContainer) ScrollAxes () (horizontal, vertical bool) {
|
||||
return false, true
|
||||
}
|
||||
|
||||
func (element *DocumentContainer) doLayout () {
|
||||
margin := element.theme.Margin(tomo.PatternBackground)
|
||||
padding := element.theme.Padding(tomo.PatternBackground)
|
||||
bounds := padding.Apply(element.Bounds())
|
||||
element.contentBounds = image.Rectangle { }
|
||||
|
||||
dot := bounds.Min.Sub(element.scroll)
|
||||
xStart := dot.X
|
||||
rowHeight := 0
|
||||
|
||||
nextLine := func () {
|
||||
dot.X = xStart
|
||||
dot.Y += margin.Y
|
||||
dot.Y += rowHeight
|
||||
rowHeight = 0
|
||||
}
|
||||
|
||||
for index, entry := range element.children {
|
||||
if dot.X > xStart && entry.Expand {
|
||||
nextLine()
|
||||
}
|
||||
|
||||
width, height := entry.MinimumSize()
|
||||
if width + dot.X > bounds.Dx() && !entry.Expand {
|
||||
nextLine()
|
||||
}
|
||||
if width < bounds.Dx() && entry.Expand {
|
||||
width = bounds.Dx()
|
||||
}
|
||||
if typedChild, ok := entry.Element.(tomo.Flexible); ok {
|
||||
height = typedChild.FlexibleHeightFor(width)
|
||||
}
|
||||
if rowHeight < height {
|
||||
rowHeight = height
|
||||
}
|
||||
|
||||
entry.Bounds.Min = dot
|
||||
entry.Bounds.Max = image.Pt(dot.X + width, dot.Y + height)
|
||||
element.children[index] = entry
|
||||
element.contentBounds = element.contentBounds.Union(entry.Bounds)
|
||||
|
||||
if entry.Expand {
|
||||
nextLine()
|
||||
} else {
|
||||
dot.X += width + margin.X
|
||||
}
|
||||
}
|
||||
|
||||
element.contentBounds =
|
||||
element.contentBounds.Sub(element.contentBounds.Min)
|
||||
}
|
||||
|
||||
func (element *DocumentContainer) updateMinimumSize () {
|
||||
padding := element.theme.Padding(tomo.PatternBackground)
|
||||
minimumWidth := 0
|
||||
for _, entry := range element.children {
|
||||
width, _ := entry.MinimumSize()
|
||||
if width > minimumWidth {
|
||||
minimumWidth = width
|
||||
}
|
||||
}
|
||||
element.core.SetMinimumSize (
|
||||
minimumWidth + padding.Horizontal(),
|
||||
padding.Vertical())
|
||||
}
|
||||
332
elements/containers/notdone/scroll.go
Normal file
332
elements/containers/notdone/scroll.go
Normal file
@@ -0,0 +1,332 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
587
elements/containers/notdone/table.go
Normal file
587
elements/containers/notdone/table.go
Normal file
@@ -0,0 +1,587 @@
|
||||
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/artist"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||
|
||||
// TODO: using the event propagator core might not be the best idea here. we
|
||||
// should have slightly different behavior to sync the focused element with the
|
||||
// selected cell. alternatively we could pass a callback to the propagator that
|
||||
// fires when the focused child changes. this would also allow things like
|
||||
// scrolling to the focused child (for this element and others).
|
||||
|
||||
type tableCell struct {
|
||||
tomo.Element
|
||||
tomo.Pattern
|
||||
image.Rectangle
|
||||
}
|
||||
|
||||
// TableContainer is a container that lays its contents out in a table. It can
|
||||
// be scrolled.
|
||||
type TableContainer struct {
|
||||
*core.Core
|
||||
*core.Propagator
|
||||
core core.CoreControl
|
||||
|
||||
topHeading bool
|
||||
leftHeading bool
|
||||
|
||||
columns int
|
||||
rows int
|
||||
scroll image.Point
|
||||
warping bool
|
||||
grid [][]tableCell
|
||||
children []tomo.Element
|
||||
|
||||
contentBounds image.Rectangle
|
||||
forcedMinimumWidth int
|
||||
forcedMinimumHeight int
|
||||
|
||||
selectedColumn int
|
||||
selectedRow int
|
||||
|
||||
config config.Wrapped
|
||||
theme theme.Wrapped
|
||||
|
||||
onSelect func ()
|
||||
onScrollBoundsChange func ()
|
||||
}
|
||||
|
||||
// NewTable creates a new table element with the specified amount of columns and
|
||||
// rows. If top or left heading is set to true, the first row or column
|
||||
// respectively will display as a table header.
|
||||
func NewTableContainer (
|
||||
columns, rows int,
|
||||
topHeading, leftHeading bool,
|
||||
) (
|
||||
element *TableContainer,
|
||||
) {
|
||||
element = &TableContainer {
|
||||
topHeading: topHeading,
|
||||
leftHeading: leftHeading,
|
||||
selectedColumn: -1,
|
||||
selectedRow: -1,
|
||||
}
|
||||
|
||||
element.theme.Case = tomo.C("tomo", "tableContainer")
|
||||
element.Core, element.core = core.NewCore(element, element.redoAll)
|
||||
element.Propagator = core.NewPropagator(element, element.core)
|
||||
element.Resize(columns, rows)
|
||||
return
|
||||
}
|
||||
|
||||
// Set places an element at the specified column and row. If the element passed
|
||||
// is nil, whatever element occupies the cell currently is removed.
|
||||
func (element *TableContainer) Set (column, row int, child tomo.Element) {
|
||||
if row < 0 || row >= element.rows { return }
|
||||
if column < 0 || column >= element.columns { return }
|
||||
|
||||
childList := element.children
|
||||
if child == nil {
|
||||
if element.grid[row][column].Element == nil {
|
||||
// no-op
|
||||
return
|
||||
} else {
|
||||
// removing the child that is currently in a slow
|
||||
element.unhook(element.grid[row][column].Element)
|
||||
childList = childList[:len(childList) - 1]
|
||||
element.grid[row][column].Element = child
|
||||
}
|
||||
} else {
|
||||
element.hook(child)
|
||||
if element.grid[row][column].Element == nil {
|
||||
// putting the child in an empty slot
|
||||
childList = append(childList, nil)
|
||||
element.grid[row][column].Element = child
|
||||
} else {
|
||||
// replacing the child that is currently in a slow
|
||||
element.unhook(element.grid[row][column].Element)
|
||||
element.grid[row][column].Element = child
|
||||
}
|
||||
}
|
||||
|
||||
element.rebuildChildList(childList)
|
||||
element.children = childList
|
||||
element.redoAll()
|
||||
}
|
||||
|
||||
// Resize changes the amount of columns and rows in the table. If the table is
|
||||
// resized to be smaller, children in cells that do not exist anymore will be
|
||||
// removed. The minimum size for a TableContainer is 1x1.
|
||||
func (element *TableContainer) Resize (columns, rows int) {
|
||||
if columns < 1 { columns = 1 }
|
||||
if rows < 1 { rows = 1 }
|
||||
if element.columns == columns && element.rows == rows { return }
|
||||
amountRemoved := 0
|
||||
|
||||
// handle rows as a whole
|
||||
if rows < element.rows {
|
||||
// disown children in bottom rows
|
||||
for _, row := range element.grid[rows:] {
|
||||
for index, child := range row {
|
||||
if child.Element != nil {
|
||||
element.unhook(child.Element)
|
||||
amountRemoved ++
|
||||
row[index].Element = nil
|
||||
}}}
|
||||
// cut grid to size
|
||||
element.grid = element.grid[:rows]
|
||||
} else {
|
||||
// expand grid
|
||||
newGrid := make([][]tableCell, rows)
|
||||
copy(newGrid, element.grid)
|
||||
element.grid = newGrid
|
||||
}
|
||||
|
||||
// handle each row individually
|
||||
for rowIndex, row := range element.grid {
|
||||
if columns < element.columns {
|
||||
// disown children in the far right of the row
|
||||
for index, child := range row[columns:] {
|
||||
if child.Element != nil {
|
||||
element.unhook(child.Element)
|
||||
amountRemoved ++
|
||||
row[index].Element = nil
|
||||
}}
|
||||
// cut row to size
|
||||
element.grid[rowIndex] = row[:columns]
|
||||
} else {
|
||||
// expand row
|
||||
newRow := make([]tableCell, columns)
|
||||
copy(newRow, row)
|
||||
element.grid[rowIndex] = newRow
|
||||
}
|
||||
}
|
||||
|
||||
element.columns = columns
|
||||
element.rows = rows
|
||||
|
||||
if amountRemoved > 0 {
|
||||
childList := element.children[:len(element.children) - amountRemoved]
|
||||
element.rebuildChildList(childList)
|
||||
element.children = childList
|
||||
}
|
||||
element.redoAll()
|
||||
}
|
||||
|
||||
// Selected returns the column and row of the cell that is currently selected.
|
||||
// If no cell is selected, this method will return (-1, -1).
|
||||
func (element *TableContainer) Selected () (column, row int) {
|
||||
return element.selectedColumn, element.selectedRow
|
||||
}
|
||||
|
||||
// OnSelect sets a function to be called when the user selects a table cell.
|
||||
func (element *TableContainer) OnSelect (callback func ()) {
|
||||
element.onSelect = callback
|
||||
}
|
||||
|
||||
// Warp runs the specified callback, deferring all layout and rendering updates
|
||||
// until the callback has finished executing. This allows for aplications to
|
||||
// perform batch gui updates without flickering and stuff.
|
||||
func (element *TableContainer) Warp (callback func ()) {
|
||||
if element.warping {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
element.warping = true
|
||||
callback()
|
||||
element.warping = false
|
||||
|
||||
element.redoAll()
|
||||
}
|
||||
|
||||
// Collapse collapses the element's minimum width and height. A value of zero
|
||||
// for either means that the element's normal value is used.
|
||||
func (element *TableContainer) Collapse (width, height int) {
|
||||
if
|
||||
element.forcedMinimumWidth == width &&
|
||||
element.forcedMinimumHeight == height {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
element.forcedMinimumWidth = width
|
||||
element.forcedMinimumHeight = height
|
||||
element.updateMinimumSize()
|
||||
}
|
||||
|
||||
// CountChildren returns the amount of children contained within this element.
|
||||
func (element *TableContainer) CountChildren () (count int) {
|
||||
return len(element.children)
|
||||
}
|
||||
|
||||
// Child returns the child at the specified index. If the index is out of
|
||||
// bounds, this method will return nil.
|
||||
func (element *TableContainer) Child (index int) (child tomo.Element) {
|
||||
if index < 0 || index > len(element.children) { return }
|
||||
return element.children[index]
|
||||
}
|
||||
|
||||
func (element *TableContainer) Window () tomo.Window {
|
||||
return element.core.Window()
|
||||
}
|
||||
|
||||
// NotifyMinimumSizeChange notifies the container that the minimum size of a
|
||||
// child element has changed.
|
||||
func (element *TableContainer) NotifyMinimumSizeChange (child tomo.Element) {
|
||||
element.updateMinimumSize()
|
||||
element.redoAll()
|
||||
}
|
||||
|
||||
// DrawBackground draws a portion of the container's background pattern within
|
||||
// the specified bounds. The container will not push these changes.
|
||||
func (element *TableContainer) DrawBackground (bounds image.Rectangle) {
|
||||
if !bounds.Overlaps(element.core.Bounds()) { return }
|
||||
|
||||
for rowIndex, row := range element.grid {
|
||||
for columnIndex, child := range row {
|
||||
if bounds.Overlaps(child.Rectangle) {
|
||||
element.theme.Pattern (
|
||||
child.Pattern,
|
||||
element.state(columnIndex, rowIndex)).
|
||||
Draw(canvas.Cut(element.core, bounds), child.Rectangle)
|
||||
return
|
||||
}}}
|
||||
}
|
||||
|
||||
func (element *TableContainer) HandleMouseDown (x, y int, button input.Button) {
|
||||
element.Focus()
|
||||
element.Propagator.HandleMouseDown(x, y, button)
|
||||
if button != input.ButtonLeft { return }
|
||||
|
||||
for rowIndex, row := range element.grid {
|
||||
for columnIndex, child := range row {
|
||||
if image.Pt(x, y).In(child.Rectangle) {
|
||||
element.selectCell(columnIndex, rowIndex)
|
||||
return
|
||||
}}}
|
||||
}
|
||||
|
||||
func (element *TableContainer) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
|
||||
switch key {
|
||||
case input.KeyLeft: element.changeSelectionBy(-1, 0)
|
||||
case input.KeyRight: element.changeSelectionBy(1, 0)
|
||||
case input.KeyUp: element.changeSelectionBy(0, -1)
|
||||
case input.KeyDown: element.changeSelectionBy(0, 1)
|
||||
case input.KeyEscape: element.selectCell(-1, -1)
|
||||
default: element.Propagator.HandleKeyDown(key, modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
// ScrollContentBounds returns the full content size of the element.
|
||||
func (element *TableContainer) ScrollContentBounds () image.Rectangle {
|
||||
return element.contentBounds
|
||||
}
|
||||
|
||||
// ScrollViewportBounds returns the size and position of the element's
|
||||
// viewport relative to ScrollBounds.
|
||||
func (element *TableContainer) ScrollViewportBounds () image.Rectangle {
|
||||
bounds := element.Bounds()
|
||||
bounds = bounds.Sub(bounds.Min).Add(element.scroll)
|
||||
return bounds
|
||||
}
|
||||
|
||||
// ScrollTo scrolls the viewport to the specified point relative to
|
||||
// ScrollBounds.
|
||||
func (element *TableContainer) ScrollTo (position image.Point) {
|
||||
if position.Y < 0 {
|
||||
position.Y = 0
|
||||
}
|
||||
maxScrollHeight := element.maxScrollHeight()
|
||||
if position.Y > maxScrollHeight {
|
||||
position.Y = maxScrollHeight
|
||||
}
|
||||
if position.X < 0 {
|
||||
position.X = 0
|
||||
}
|
||||
maxScrollWidth := element.maxScrollWidth()
|
||||
if position.X > maxScrollWidth {
|
||||
position.X = maxScrollWidth
|
||||
}
|
||||
element.scroll = position
|
||||
if element.core.HasImage() && !element.warping {
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
// OnScrollBoundsChange sets a function to be called when the element's viewport
|
||||
// bounds, content bounds, or scroll axes change.
|
||||
func (element *TableContainer) OnScrollBoundsChange (callback func ()) {
|
||||
element.onScrollBoundsChange = callback
|
||||
}
|
||||
|
||||
// ScrollAxes returns the supported axes for scrolling.
|
||||
func (element *TableContainer) ScrollAxes () (horizontal, vertical bool) {
|
||||
return true, true
|
||||
}
|
||||
|
||||
func (element *TableContainer) changeSelectionBy (column, row int) {
|
||||
column += element.selectedColumn
|
||||
row += element.selectedRow
|
||||
if column < 0 { column = 0 }
|
||||
if row < 0 { row = 0 }
|
||||
element.selectCell(column, row)
|
||||
}
|
||||
|
||||
func (element *TableContainer) selectCell (column, row int) {
|
||||
if column < -1 { column = -1 }
|
||||
if row < -1 { row = -1 }
|
||||
if column >= element.columns { column = element.columns - 1 }
|
||||
if row >= element.rows { row = element.rows - 1 }
|
||||
|
||||
if column == element.selectedColumn && row == element.selectedRow {
|
||||
return
|
||||
}
|
||||
|
||||
oldColumn, oldRow := element.selectedColumn, element.selectedRow
|
||||
element.selectedColumn = column
|
||||
element.selectedRow = row
|
||||
if oldColumn >= 0 && oldRow >= 0 {
|
||||
element.core.DamageRegion(element.redoCell(oldColumn, oldRow))
|
||||
}
|
||||
if column >= 0 && row >= 0 {
|
||||
element.core.DamageRegion(element.redoCell(column, row))
|
||||
}
|
||||
if element.onSelect != nil {
|
||||
element.onSelect()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *TableContainer) maxScrollHeight () (height int) {
|
||||
viewportHeight := element.Bounds().Dy()
|
||||
height = element.contentBounds.Dy() - viewportHeight
|
||||
if height < 0 { height = 0 }
|
||||
return
|
||||
}
|
||||
|
||||
func (element *TableContainer) maxScrollWidth () (width int) {
|
||||
viewportWidth := element.Bounds().Dx()
|
||||
width = element.contentBounds.Dx() - viewportWidth
|
||||
if width < 0 { width = 0 }
|
||||
return
|
||||
}
|
||||
|
||||
func (element *TableContainer) hook (child tomo.Element) {
|
||||
if child0, ok := child.(tomo.Themeable); ok {
|
||||
child0.SetTheme(element.theme.Theme)
|
||||
}
|
||||
if child0, ok := child.(tomo.Configurable); ok {
|
||||
child0.SetConfig(element.config.Config)
|
||||
}
|
||||
child.SetParent(element)
|
||||
}
|
||||
|
||||
func (element *TableContainer) unhook (child tomo.Element) {
|
||||
child.SetParent(nil)
|
||||
child.DrawTo(nil, image.Rectangle { }, nil)
|
||||
}
|
||||
|
||||
func (element *TableContainer) rebuildChildList (list []tomo.Element) {
|
||||
index := 0
|
||||
for _, row := range element.grid {
|
||||
for _, child := range row {
|
||||
if child.Element == nil { continue }
|
||||
list[index] = child.Element
|
||||
index ++
|
||||
}}
|
||||
}
|
||||
|
||||
func (element *TableContainer) state (column, row int) (state tomo.State) {
|
||||
if column == element.selectedColumn && row == element.selectedRow {
|
||||
state.On = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (element *TableContainer) redoCell (column, row int) image.Rectangle {
|
||||
padding := element.theme.Padding(tomo.PatternTableCell)
|
||||
cell := element.grid[row][column]
|
||||
pattern := element.theme.Pattern (
|
||||
cell.Pattern, element.state(column, row))
|
||||
|
||||
if cell.Element != nil {
|
||||
// give child canvas portion
|
||||
innerCellBounds := padding.Apply(cell.Rectangle)
|
||||
artist.DrawShatter (
|
||||
element.core, pattern,
|
||||
cell.Rectangle, innerCellBounds)
|
||||
cell.DrawTo (
|
||||
canvas.Cut(element.core, innerCellBounds),
|
||||
innerCellBounds,
|
||||
element.childDrawCallback)
|
||||
} else {
|
||||
// draw cell pattern in empty cells
|
||||
pattern.Draw(element.core, cell.Rectangle)
|
||||
}
|
||||
return cell.Rectangle
|
||||
}
|
||||
|
||||
func (element *TableContainer) redoAll () {
|
||||
if element.warping || !element.core.HasImage() {
|
||||
element.updateMinimumSize()
|
||||
return
|
||||
}
|
||||
|
||||
maxScrollHeight := element.maxScrollHeight()
|
||||
if element.scroll.Y > maxScrollHeight {
|
||||
element.scroll.Y = maxScrollHeight
|
||||
}
|
||||
maxScrollWidth := element.maxScrollWidth()
|
||||
if element.scroll.X > maxScrollWidth {
|
||||
element.scroll.X = maxScrollWidth
|
||||
}
|
||||
|
||||
// calculate the minimum size of each column and row
|
||||
var minWidth, minHeight float64
|
||||
columnWidths := make([]float64, element.columns)
|
||||
rowHeights := make([]float64, element.rows)
|
||||
padding := element.theme.Padding(tomo.PatternTableCell)
|
||||
|
||||
for rowIndex, row := range element.grid {
|
||||
for columnIndex, child := range row {
|
||||
width, height := padding.Horizontal(), padding.Vertical()
|
||||
|
||||
if child.Element != nil {
|
||||
minWidth, minHeight := child.MinimumSize()
|
||||
width += minWidth
|
||||
height += minHeight
|
||||
fwidth := float64(width)
|
||||
fheight := float64(height)
|
||||
if fwidth > columnWidths[columnIndex] {
|
||||
columnWidths[columnIndex] = fwidth
|
||||
}
|
||||
if fheight > rowHeights[rowIndex] {
|
||||
rowHeights[rowIndex] = fheight
|
||||
}
|
||||
}
|
||||
}}
|
||||
for _, width := range columnWidths { minWidth += width }
|
||||
for _, height := range rowHeights { minHeight += height }
|
||||
|
||||
// ignore given bounds for layout if they are below minimum size. we do
|
||||
// this because we are scrollable in both directions and we might be
|
||||
// collapsed.
|
||||
bounds := element.Bounds().Sub(element.scroll)
|
||||
if bounds.Dx() < int(minWidth) {
|
||||
bounds.Max.X = bounds.Min.X + int(minWidth)
|
||||
}
|
||||
if bounds.Dy() < int(minHeight) {
|
||||
bounds.Max.Y = bounds.Min.Y + int(minHeight)
|
||||
}
|
||||
element.contentBounds = bounds
|
||||
|
||||
// scale up those minimum sizes to an actual size.
|
||||
// FIXME: replace this with a more accurate algorithm
|
||||
widthRatio := float64(bounds.Dx()) / minWidth
|
||||
heightRatio := float64(bounds.Dy()) / minHeight
|
||||
for index := range columnWidths {
|
||||
columnWidths[index] *= widthRatio
|
||||
}
|
||||
for index := range rowHeights {
|
||||
rowHeights[index] *= heightRatio
|
||||
}
|
||||
|
||||
// cut up canvas
|
||||
x := float64(bounds.Min.X)
|
||||
y := float64(bounds.Min.Y)
|
||||
for rowIndex, row := range element.grid {
|
||||
for columnIndex, _ := range row {
|
||||
width := columnWidths[columnIndex]
|
||||
height := rowHeights[rowIndex]
|
||||
cellBounds := image.Rect (
|
||||
int(x), int(y),
|
||||
int(x + width), int(y + height))
|
||||
|
||||
var id tomo.Pattern
|
||||
isHeading :=
|
||||
rowIndex == 0 && element.topHeading ||
|
||||
columnIndex == 0 && element.leftHeading
|
||||
if isHeading {
|
||||
id = tomo.PatternTableHead
|
||||
} else {
|
||||
id = tomo.PatternTableCell
|
||||
}
|
||||
element.grid[rowIndex][columnIndex].Rectangle = cellBounds
|
||||
element.grid[rowIndex][columnIndex].Pattern = id
|
||||
|
||||
element.redoCell(columnIndex, rowIndex)
|
||||
x += float64(width)
|
||||
}
|
||||
|
||||
x = float64(bounds.Min.X)
|
||||
y += rowHeights[rowIndex]
|
||||
}
|
||||
|
||||
element.core.DamageAll()
|
||||
|
||||
// update the minimum size of the element
|
||||
if element.forcedMinimumHeight > 0 {
|
||||
minHeight = float64(element.forcedMinimumHeight)
|
||||
}
|
||||
if element.forcedMinimumWidth > 0 {
|
||||
minWidth = float64(element.forcedMinimumWidth)
|
||||
}
|
||||
element.core.SetMinimumSize(int(minWidth), int(minHeight))
|
||||
|
||||
// notify parent of scroll bounds change
|
||||
if parent, ok := element.core.Parent().(tomo.ScrollableParent); ok {
|
||||
parent.NotifyScrollBoundsChange(element)
|
||||
}
|
||||
if element.onScrollBoundsChange != nil {
|
||||
element.onScrollBoundsChange()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *TableContainer) updateMinimumSize () {
|
||||
if element.forcedMinimumHeight > 0 && element.forcedMinimumWidth > 0 {
|
||||
element.core.SetMinimumSize (
|
||||
element.forcedMinimumWidth,
|
||||
element.forcedMinimumHeight)
|
||||
return
|
||||
}
|
||||
|
||||
columnWidths := make([]int, element.columns)
|
||||
rowHeights := make([]int, element.rows)
|
||||
padding := element.theme.Padding(tomo.PatternTableCell)
|
||||
|
||||
for rowIndex, row := range element.grid {
|
||||
for columnIndex, child := range row {
|
||||
width, height := padding.Horizontal(), padding.Vertical()
|
||||
|
||||
if child.Element != nil {
|
||||
minWidth, minHeight := child.MinimumSize()
|
||||
width += minWidth
|
||||
height += minHeight
|
||||
if width > columnWidths[columnIndex] {
|
||||
columnWidths[columnIndex] = width
|
||||
}
|
||||
if height > rowHeights[rowIndex] {
|
||||
rowHeights[rowIndex] = height
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
var minWidth, minHeight int
|
||||
for _, width := range columnWidths { minWidth += width }
|
||||
for _, height := range rowHeights { minHeight += height }
|
||||
|
||||
if element.forcedMinimumHeight > 0 {
|
||||
minHeight = element.forcedMinimumHeight
|
||||
}
|
||||
if element.forcedMinimumWidth > 0 {
|
||||
minWidth = element.forcedMinimumWidth
|
||||
}
|
||||
|
||||
element.core.SetMinimumSize(minWidth, minHeight)
|
||||
}
|
||||
|
||||
func (element *TableContainer) childDrawCallback (region image.Rectangle) {
|
||||
element.core.DamageRegion(region)
|
||||
}
|
||||
Reference in New Issue
Block a user