atomize-element-interface #3
|
@ -14,10 +14,11 @@ type characterLayout struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type wordLayout struct {
|
type wordLayout struct {
|
||||||
position image.Point
|
position image.Point
|
||||||
width int
|
width int
|
||||||
spaceAfter int
|
spaceAfter int
|
||||||
text []characterLayout
|
breaksAfter int
|
||||||
|
text []characterLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
// Align specifies a text alignment method.
|
// Align specifies a text alignment method.
|
||||||
|
@ -167,13 +168,16 @@ func (drawer *TextDrawer) LineHeight () (height fixed.Int26_6) {
|
||||||
func (drawer *TextDrawer) ReccomendedHeightFor (width int) (height int) {
|
func (drawer *TextDrawer) ReccomendedHeightFor (width int) (height int) {
|
||||||
if !drawer.layoutClean { drawer.recalculate() }
|
if !drawer.layoutClean { drawer.recalculate() }
|
||||||
metrics := drawer.face.Metrics()
|
metrics := drawer.face.Metrics()
|
||||||
dot := fixed.Point26_6 { 0, 0 }
|
dot := fixed.Point26_6 { 0, metrics.Height }
|
||||||
for _, word := range drawer.layout {
|
for _, word := range drawer.layout {
|
||||||
dot.X += fixed.Int26_6((word.width + word.spaceAfter) << 6)
|
if word.width + dot.X.Round() > width {
|
||||||
|
|
||||||
if word.width + word.position.X > width && word.position.X > 0 {
|
|
||||||
dot.Y += metrics.Height
|
dot.Y += metrics.Height
|
||||||
dot.X = fixed.Int26_6(word.width << 6)
|
dot.X = 0
|
||||||
|
}
|
||||||
|
dot.X += fixed.I(word.width + word.spaceAfter)
|
||||||
|
if word.breaksAfter > 0 {
|
||||||
|
dot.Y += fixed.I(word.breaksAfter).Mul(metrics.Height)
|
||||||
|
dot.X = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,6 +250,7 @@ func (drawer *TextDrawer) recalculate () {
|
||||||
if character == '\n' {
|
if character == '\n' {
|
||||||
dot.Y += metrics.Height
|
dot.Y += metrics.Height
|
||||||
dot.X = 0
|
dot.X = 0
|
||||||
|
word.breaksAfter ++
|
||||||
previousCharacter = character
|
previousCharacter = character
|
||||||
index ++
|
index ++
|
||||||
} else {
|
} else {
|
||||||
|
@ -290,7 +295,6 @@ func (drawer *TextDrawer) recalculate () {
|
||||||
|
|
||||||
if drawer.wrap {
|
if drawer.wrap {
|
||||||
drawer.layoutBounds.Max.X = drawer.width
|
drawer.layoutBounds.Max.X = drawer.width
|
||||||
println("aaa")
|
|
||||||
} else {
|
} else {
|
||||||
drawer.layoutBounds.Max.X = horizontalExtent
|
drawer.layoutBounds.Max.X = horizontalExtent
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ func (window *Window) Adopt (child tomo.Element) {
|
||||||
child.SetParentHooks (tomo.ParentHooks {
|
child.SetParentHooks (tomo.ParentHooks {
|
||||||
Draw: window.childDrawCallback,
|
Draw: window.childDrawCallback,
|
||||||
MinimumSizeChange: window.childMinimumSizeChangeCallback,
|
MinimumSizeChange: window.childMinimumSizeChangeCallback,
|
||||||
|
FlexibleHeightChange: window.resizeChildToFit,
|
||||||
SelectionRequest: window.childSelectionRequestCallback,
|
SelectionRequest: window.childSelectionRequestCallback,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -202,11 +203,27 @@ func (window *Window) redrawChildEntirely () {
|
||||||
|
|
||||||
func (window *Window) resizeChildToFit () {
|
func (window *Window) resizeChildToFit () {
|
||||||
window.skipChildDrawCallback = true
|
window.skipChildDrawCallback = true
|
||||||
if child, ok := window.child.(tomo.Expanding); ok {
|
if child, ok := window.child.(tomo.Flexible); ok {
|
||||||
minimumHeight := child.MinimumHeightFor(window.metrics.width)
|
minimumHeight := child.MinimumHeightFor(window.metrics.width)
|
||||||
_, minimumWidth := child.MinimumSize()
|
minimumWidth, _ := child.MinimumSize()
|
||||||
window.childMinimumSizeChangeCallback (
|
|
||||||
minimumWidth, minimumHeight)
|
icccm.WmNormalHintsSet (
|
||||||
|
window.backend.connection,
|
||||||
|
window.xWindow.Id,
|
||||||
|
&icccm.NormalHints {
|
||||||
|
Flags: icccm.SizeHintPMinSize,
|
||||||
|
MinWidth: uint(minimumWidth),
|
||||||
|
MinHeight: uint(minimumHeight),
|
||||||
|
})
|
||||||
|
|
||||||
|
if window.metrics.height >= minimumHeight &&
|
||||||
|
window.metrics.width >= minimumWidth {
|
||||||
|
|
||||||
|
window.child.Resize (
|
||||||
|
window.metrics.width,
|
||||||
|
window.metrics.height)
|
||||||
|
window.redrawChildEntirely()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
window.child.Resize (
|
window.child.Resize (
|
||||||
window.metrics.width,
|
window.metrics.width,
|
||||||
|
@ -261,6 +278,20 @@ func (window *Window) childSelectionRequestCallback () (granted bool) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (window *Window) childSelectionMotionRequestCallback (
|
||||||
|
direction tomo.SelectionDirection,
|
||||||
|
) (
|
||||||
|
granted bool,
|
||||||
|
) {
|
||||||
|
if child, ok := window.child.(tomo.Selectable); ok {
|
||||||
|
if !child.HandleSelection(direction) {
|
||||||
|
child.HandleDeselection()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (window *Window) pushRegion (region image.Rectangle) {
|
func (window *Window) pushRegion (region image.Rectangle) {
|
||||||
if window.xCanvas == nil { panic("whoopsie!!!!!!!!!!!!!!") }
|
if window.xCanvas == nil { panic("whoopsie!!!!!!!!!!!!!!") }
|
||||||
image, ok := window.xCanvas.SubImage(region).(*xgraphics.Image)
|
image, ok := window.xCanvas.SubImage(region).(*xgraphics.Image)
|
||||||
|
|
38
element.go
38
element.go
|
@ -3,7 +3,8 @@ package tomo
|
||||||
// ParentHooks is a struct that contains callbacks that let child elements send
|
// ParentHooks is a struct that contains callbacks that let child elements send
|
||||||
// information to their parent element without the child element knowing
|
// information to their parent element without the child element knowing
|
||||||
// anything about the parent element or containing any reference to it. When a
|
// anything about the parent element or containing any reference to it. When a
|
||||||
// parent element adopts a child element, it must set these callbacks.
|
// parent element adopts a child element, it must set these callbacks. They are
|
||||||
|
// allowed to be nil.
|
||||||
type ParentHooks struct {
|
type ParentHooks struct {
|
||||||
// Draw is called when a part of the child element's surface is updated.
|
// Draw is called when a part of the child element's surface is updated.
|
||||||
// The updated region will be passed to the callback as a sub-image.
|
// The updated region will be passed to the callback as a sub-image.
|
||||||
|
@ -15,11 +16,19 @@ type ParentHooks struct {
|
||||||
// event.
|
// event.
|
||||||
MinimumSizeChange func (width, height int)
|
MinimumSizeChange func (width, height int)
|
||||||
|
|
||||||
|
// FlexibleHeightChange is called when the parameters affecting the
|
||||||
|
// element's expanding height have changed.
|
||||||
|
FlexibleHeightChange func ()
|
||||||
|
|
||||||
// SelectionRequest is called when the child element element wants
|
// SelectionRequest is called when the child element element wants
|
||||||
// itself to be selected. If the parent element chooses to grant the
|
// itself to be selected. If the parent element chooses to grant the
|
||||||
// request, it must send the child element a selection event and return
|
// request, it must send the child element a selection event and return
|
||||||
// true.
|
// true.
|
||||||
SelectionRequest func () (granted bool)
|
SelectionRequest func () (granted bool)
|
||||||
|
|
||||||
|
// SelectionMotionRequest is called when the child element wants the
|
||||||
|
// parent element to select the previous/next element in relation to it.
|
||||||
|
SelectionMotionRequest func (direction SelectionDirection) (granted bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunDraw runs the Draw hook if it is not nil. If it is nil, it does nothing.
|
// RunDraw runs the Draw hook if it is not nil. If it is nil, it does nothing.
|
||||||
|
@ -37,6 +46,14 @@ func (hooks ParentHooks) RunMinimumSizeChange (width, height int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunFlexibleHeightChange runs the ExpandingHeightChange hook if it is not
|
||||||
|
// nil. If it is nil, it does nothing.
|
||||||
|
func (hooks ParentHooks) RunFlexibleHeightChange () {
|
||||||
|
if hooks.FlexibleHeightChange != nil {
|
||||||
|
hooks.FlexibleHeightChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RunSelectionRequest runs the SelectionRequest hook if it is not nil. If it is
|
// RunSelectionRequest runs the SelectionRequest hook if it is not nil. If it is
|
||||||
// nil, it does nothing.
|
// nil, it does nothing.
|
||||||
func (hooks ParentHooks) RunSelectionRequest () (granted bool) {
|
func (hooks ParentHooks) RunSelectionRequest () (granted bool) {
|
||||||
|
@ -46,6 +63,19 @@ func (hooks ParentHooks) RunSelectionRequest () (granted bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunSelectionMotionRequest runs the SelectionMotionRequest hook if it is not
|
||||||
|
// nil. If it is nil, it does nothing.
|
||||||
|
func (hooks ParentHooks) RunSelectionMotionRequest (
|
||||||
|
direction SelectionDirection,
|
||||||
|
) (
|
||||||
|
granted bool,
|
||||||
|
) {
|
||||||
|
if hooks.SelectionMotionRequest != nil {
|
||||||
|
granted = hooks.SelectionMotionRequest(direction)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Element represents a basic on-screen object.
|
// Element represents a basic on-screen object.
|
||||||
type Element interface {
|
type Element interface {
|
||||||
// Element must implement the Canvas interface. Elements should start
|
// Element must implement the Canvas interface. Elements should start
|
||||||
|
@ -153,9 +183,9 @@ type MouseTarget interface {
|
||||||
HandleScroll (x, y int, deltaX, deltaY float64)
|
HandleScroll (x, y int, deltaX, deltaY float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expanding represents an element who's preferred minimum height can change in
|
// Flexible represents an element who's preferred minimum height can change in
|
||||||
// response to its width.
|
// response to its width.
|
||||||
type Expanding interface {
|
type Flexible interface {
|
||||||
Element
|
Element
|
||||||
|
|
||||||
// HeightForWidth returns what the element's minimum height would be if
|
// HeightForWidth returns what the element's minimum height would be if
|
||||||
|
@ -170,6 +200,6 @@ type Expanding interface {
|
||||||
// minimum size that the element may be resized to.
|
// minimum size that the element may be resized to.
|
||||||
//
|
//
|
||||||
// It is important to note that if a parent container checks for
|
// It is important to note that if a parent container checks for
|
||||||
// expanding chilren, it itself will likely need to be expanding.
|
// flexible chilren, it itself will likely need to be flexible.
|
||||||
MinimumHeightFor (width int) (height int)
|
MinimumHeightFor (width int) (height int)
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ func (element *Button) HandleSelection (
|
||||||
) (
|
) (
|
||||||
accepted bool,
|
accepted bool,
|
||||||
) {
|
) {
|
||||||
|
direction = direction.Canon()
|
||||||
if !element.enabled { return false }
|
if !element.enabled { return false }
|
||||||
if element.selected && direction != tomo.SelectionDirectionNeutral {
|
if element.selected && direction != tomo.SelectionDirectionNeutral {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -18,6 +18,7 @@ type Container struct {
|
||||||
warping bool
|
warping bool
|
||||||
selected bool
|
selected bool
|
||||||
selectable bool
|
selectable bool
|
||||||
|
flexible bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContainer creates a new container.
|
// NewContainer creates a new container.
|
||||||
|
@ -43,16 +44,24 @@ func (element *Container) SetLayout (layout tomo.Layout) {
|
||||||
// whatever way is defined by the current layout.
|
// whatever way is defined by the current layout.
|
||||||
func (element *Container) Adopt (child tomo.Element, expand bool) {
|
func (element *Container) Adopt (child tomo.Element, expand bool) {
|
||||||
child.SetParentHooks (tomo.ParentHooks {
|
child.SetParentHooks (tomo.ParentHooks {
|
||||||
|
Draw: func (region tomo.Canvas) {
|
||||||
|
element.drawChildRegion(child, region)
|
||||||
|
},
|
||||||
MinimumSizeChange: func (int, int) {
|
MinimumSizeChange: func (int, int) {
|
||||||
element.updateMinimumSize()
|
element.updateMinimumSize()
|
||||||
},
|
},
|
||||||
|
FlexibleHeightChange: element.updateMinimumSize,
|
||||||
SelectionRequest: func () (granted bool) {
|
SelectionRequest: func () (granted bool) {
|
||||||
child, selectable := child.(tomo.Selectable)
|
child, selectable := child.(tomo.Selectable)
|
||||||
if !selectable { return }
|
if !selectable { return }
|
||||||
return element.childSelectionRequestCallback(child)
|
return element.childSelectionRequestCallback(child)
|
||||||
},
|
},
|
||||||
Draw: func (region tomo.Canvas) {
|
SelectionMotionRequest: func (
|
||||||
element.drawChildRegion(child, region)
|
direction tomo.SelectionDirection,
|
||||||
|
) (
|
||||||
|
granted bool,
|
||||||
|
) {
|
||||||
|
return element.core.RequestSelectionMotion(direction)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
element.children = append (element.children, tomo.LayoutEntry {
|
element.children = append (element.children, tomo.LayoutEntry {
|
||||||
|
@ -61,7 +70,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) {
|
||||||
})
|
})
|
||||||
|
|
||||||
element.updateMinimumSize()
|
element.updateMinimumSize()
|
||||||
element.updateSelectable()
|
element.reflectChildProperties()
|
||||||
if element.core.HasImage() && !element.warping {
|
if element.core.HasImage() && !element.warping {
|
||||||
element.recalculate()
|
element.recalculate()
|
||||||
element.draw()
|
element.draw()
|
||||||
|
@ -106,7 +115,7 @@ func (element *Container) Disown (child tomo.Element) {
|
||||||
}
|
}
|
||||||
|
|
||||||
element.updateMinimumSize()
|
element.updateMinimumSize()
|
||||||
element.updateSelectable()
|
element.reflectChildProperties()
|
||||||
if element.core.HasImage() && !element.warping {
|
if element.core.HasImage() && !element.warping {
|
||||||
element.recalculate()
|
element.recalculate()
|
||||||
element.draw()
|
element.draw()
|
||||||
|
@ -119,7 +128,7 @@ func (element *Container) DisownAll () {
|
||||||
element.children = nil
|
element.children = nil
|
||||||
|
|
||||||
element.updateMinimumSize()
|
element.updateMinimumSize()
|
||||||
element.updateSelectable()
|
element.reflectChildProperties()
|
||||||
if element.core.HasImage() && !element.warping {
|
if element.core.HasImage() && !element.warping {
|
||||||
element.recalculate()
|
element.recalculate()
|
||||||
element.draw()
|
element.draw()
|
||||||
|
@ -176,8 +185,6 @@ func (element *Container) Resize (width, height int) {
|
||||||
element.draw()
|
element.draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement KeyboardTarget
|
|
||||||
|
|
||||||
func (element *Container) HandleMouseDown (x, y int, button tomo.Button) {
|
func (element *Container) HandleMouseDown (x, y int, button tomo.Button) {
|
||||||
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
|
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
|
||||||
if !handlesMouse { return }
|
if !handlesMouse { return }
|
||||||
|
@ -223,7 +230,7 @@ func (element *Container) HandleKeyDown (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Container) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) {
|
func (element *Container) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) {
|
||||||
element.forSelected (func (child tomo.Selectable) bool {
|
element.forSelected (func (child tomo.Selectable) bool {
|
||||||
child0, handlesKeyboard := child.(tomo.KeyboardTarget)
|
child0, handlesKeyboard := child.(tomo.KeyboardTarget)
|
||||||
if handlesKeyboard {
|
if handlesKeyboard {
|
||||||
|
@ -293,6 +300,10 @@ func (element *Container) HandleSelection (direction tomo.SelectionDirection) (o
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (element *Container) MinimumHeightFor (width int) (height int) {
|
||||||
|
return element.layout.MinimumHeightFor(element.children, width)
|
||||||
|
}
|
||||||
|
|
||||||
func (element *Container) HandleDeselection () {
|
func (element *Container) HandleDeselection () {
|
||||||
element.selected = false
|
element.selected = false
|
||||||
element.forSelected (func (child tomo.Selectable) bool {
|
element.forSelected (func (child tomo.Selectable) bool {
|
||||||
|
@ -319,6 +330,15 @@ func (element *Container) forSelectable (callback func (child tomo.Selectable) b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (element *Container) forFlexible (callback func (child tomo.Flexible) bool) {
|
||||||
|
for _, entry := range element.children {
|
||||||
|
child, selectable := entry.Element.(tomo.Flexible)
|
||||||
|
if selectable {
|
||||||
|
if !callback(child) { break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (element *Container) forSelectableBackward (callback func (child tomo.Selectable) bool) {
|
func (element *Container) forSelectableBackward (callback func (child tomo.Selectable) bool) {
|
||||||
for index := len(element.children) - 1; index >= 0; index -- {
|
for index := len(element.children) - 1; index >= 0; index -- {
|
||||||
child, selectable := element.children[index].Element.(tomo.Selectable)
|
child, selectable := element.children[index].Element.(tomo.Selectable)
|
||||||
|
@ -338,12 +358,17 @@ func (element *Container) firstSelected () (index int) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Container) updateSelectable () {
|
func (element *Container) reflectChildProperties () {
|
||||||
element.selectable = false
|
element.selectable = false
|
||||||
element.forSelectable (func (tomo.Selectable) bool {
|
element.forSelectable (func (tomo.Selectable) bool {
|
||||||
element.selectable = true
|
element.selectable = true
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
element.flexible = false
|
||||||
|
element.forFlexible (func (tomo.Flexible) bool {
|
||||||
|
element.flexible = true
|
||||||
|
return false
|
||||||
|
})
|
||||||
if !element.selectable {
|
if !element.selectable {
|
||||||
element.selected = false
|
element.selected = false
|
||||||
}
|
}
|
||||||
|
@ -367,8 +392,11 @@ func (element *Container) childSelectionRequestCallback (
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Container) updateMinimumSize () {
|
func (element *Container) updateMinimumSize () {
|
||||||
element.core.SetMinimumSize (
|
width, height := element.layout.MinimumSize(element.children)
|
||||||
element.layout.MinimumSize(element.children, 1e9))
|
if element.flexible {
|
||||||
|
height = element.layout.MinimumHeightFor(element.children, width)
|
||||||
|
}
|
||||||
|
element.core.SetMinimumSize(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Container) recalculate () {
|
func (element *Container) recalculate () {
|
||||||
|
|
|
@ -27,6 +27,7 @@ func NewLabel (text string, wrap bool) (element *Label) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resize resizes the label and re-wraps the text if wrapping is enabled.
|
||||||
func (element *Label) Resize (width, height int) {
|
func (element *Label) Resize (width, height int) {
|
||||||
element.core.AllocateCanvas(width, height)
|
element.core.AllocateCanvas(width, height)
|
||||||
if element.wrap {
|
if element.wrap {
|
||||||
|
@ -37,6 +38,17 @@ func (element *Label) Resize (width, height int) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MinimumHeightFor returns the reccomended height for this element based on the
|
||||||
|
// given width in order to allow the text to wrap properly.
|
||||||
|
func (element *Label) MinimumHeightFor (width int) (height int) {
|
||||||
|
if element.wrap {
|
||||||
|
return element.drawer.ReccomendedHeightFor(width)
|
||||||
|
} else {
|
||||||
|
_, height = element.MinimumSize()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetText sets the label's text.
|
// SetText sets the label's text.
|
||||||
func (element *Label) SetText (text string) {
|
func (element *Label) SetText (text string) {
|
||||||
if element.text == text { return }
|
if element.text == text { return }
|
||||||
|
@ -76,6 +88,7 @@ func (element *Label) updateMinimumSize () {
|
||||||
if em < 1 { em = theme.Padding() }
|
if em < 1 { em = theme.Padding() }
|
||||||
element.core.SetMinimumSize (
|
element.core.SetMinimumSize (
|
||||||
em, element.drawer.LineHeight().Round())
|
em, element.drawer.LineHeight().Round())
|
||||||
|
element.core.NotifyFlexibleHeightChange()
|
||||||
} else {
|
} else {
|
||||||
bounds := element.drawer.LayoutBounds()
|
bounds := element.drawer.LayoutBounds()
|
||||||
element.core.SetMinimumSize(bounds.Dx(), bounds.Dy())
|
element.core.SetMinimumSize(bounds.Dx(), bounds.Dy())
|
||||||
|
|
|
@ -80,6 +80,18 @@ func (control CoreControl) RequestSelection () (granted bool) {
|
||||||
return control.core.hooks.RunSelectionRequest()
|
return control.core.hooks.RunSelectionRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestSelectionMotion requests that the element's parent deselect this
|
||||||
|
// element and select the one to the left or right of it, depending on the
|
||||||
|
// direction. If the requests was granted, it returns true. If it was denied, it
|
||||||
|
// returns false.
|
||||||
|
func (control CoreControl) RequestSelectionMotion (
|
||||||
|
direction tomo.SelectionDirection,
|
||||||
|
) (
|
||||||
|
granted bool,
|
||||||
|
) {
|
||||||
|
return control.core.hooks.RunSelectionMotionRequest(direction)
|
||||||
|
}
|
||||||
|
|
||||||
// HasImage returns true if the core has an allocated image buffer, and false if
|
// HasImage returns true if the core has an allocated image buffer, and false if
|
||||||
// it doesn't.
|
// it doesn't.
|
||||||
func (control CoreControl) HasImage () (has bool) {
|
func (control CoreControl) HasImage () (has bool) {
|
||||||
|
@ -101,7 +113,6 @@ func (control CoreControl) PushAll () {
|
||||||
// AllocateCanvas resizes the canvas, constraining the width and height so that
|
// AllocateCanvas resizes the canvas, constraining the width and height so that
|
||||||
// they are not less than the specified minimum width and height.
|
// they are not less than the specified minimum width and height.
|
||||||
func (control *CoreControl) AllocateCanvas (width, height int) {
|
func (control *CoreControl) AllocateCanvas (width, height int) {
|
||||||
width, height, _ = control.ConstrainSize(width, height)
|
|
||||||
control.core.canvas = tomo.NewBasicCanvas(width, height)
|
control.core.canvas = tomo.NewBasicCanvas(width, height)
|
||||||
control.BasicCanvas = control.core.canvas
|
control.BasicCanvas = control.core.canvas
|
||||||
}
|
}
|
||||||
|
@ -132,6 +143,12 @@ func (control CoreControl) SetMinimumSize (width, height int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyFlexibleHeightChange notifies the parent element that this element's
|
||||||
|
// flexible height has changed.
|
||||||
|
func (control CoreControl) NotifyFlexibleHeightChange () {
|
||||||
|
control.core.hooks.RunFlexibleHeightChange()
|
||||||
|
}
|
||||||
|
|
||||||
// ConstrainSize contstrains the specified width and height to the minimum width
|
// ConstrainSize contstrains the specified width and height to the minimum width
|
||||||
// and height, and returns wether or not anything ended up being constrained.
|
// and height, and returns wether or not anything ended up being constrained.
|
||||||
func (control CoreControl) ConstrainSize (
|
func (control CoreControl) ConstrainSize (
|
||||||
|
|
|
@ -21,7 +21,7 @@ func run () {
|
||||||
world.Stages = map [string] func () {
|
world.Stages = map [string] func () {
|
||||||
"start": func () {
|
"start": func () {
|
||||||
label := basic.NewLabel (
|
label := basic.NewLabel (
|
||||||
"you are standing next to a river.", false)
|
"you are standing next to a river.", true)
|
||||||
|
|
||||||
button0 := basic.NewButton("go in the river")
|
button0 := basic.NewButton("go in the river")
|
||||||
button0.OnClick(world.SwitchFunc("wet"))
|
button0.OnClick(world.SwitchFunc("wet"))
|
||||||
|
@ -41,7 +41,7 @@ func run () {
|
||||||
"wet": func () {
|
"wet": func () {
|
||||||
label := basic.NewLabel (
|
label := basic.NewLabel (
|
||||||
"you get completely soaked.\n" +
|
"you get completely soaked.\n" +
|
||||||
"you die of hypothermia.", false)
|
"you die of hypothermia.", true)
|
||||||
|
|
||||||
button0 := basic.NewButton("try again")
|
button0 := basic.NewButton("try again")
|
||||||
button0.OnClick(world.SwitchFunc("start"))
|
button0.OnClick(world.SwitchFunc("start"))
|
||||||
|
@ -58,7 +58,7 @@ func run () {
|
||||||
"house": func () {
|
"house": func () {
|
||||||
label := basic.NewLabel (
|
label := basic.NewLabel (
|
||||||
"you are standing in front of a delapidated " +
|
"you are standing in front of a delapidated " +
|
||||||
"house.", false)
|
"house.", true)
|
||||||
|
|
||||||
button1 := basic.NewButton("go inside")
|
button1 := basic.NewButton("go inside")
|
||||||
button1.OnClick(world.SwitchFunc("inside"))
|
button1.OnClick(world.SwitchFunc("inside"))
|
||||||
|
@ -78,7 +78,7 @@ func run () {
|
||||||
"it is dark, but rays of light stream " +
|
"it is dark, but rays of light stream " +
|
||||||
"through the window.\n" +
|
"through the window.\n" +
|
||||||
"there is nothing particularly interesting " +
|
"there is nothing particularly interesting " +
|
||||||
"here.", false)
|
"here.", true)
|
||||||
|
|
||||||
button0 := basic.NewButton("go back outside")
|
button0 := basic.NewButton("go back outside")
|
||||||
button0.OnClick(world.SwitchFunc("house"))
|
button0.OnClick(world.SwitchFunc("house"))
|
||||||
|
@ -92,7 +92,7 @@ func run () {
|
||||||
"bear": func () {
|
"bear": func () {
|
||||||
label := basic.NewLabel (
|
label := basic.NewLabel (
|
||||||
"you come face to face with a bear.\n" +
|
"you come face to face with a bear.\n" +
|
||||||
"it eats you (it was hungry).", false)
|
"it eats you (it was hungry).", true)
|
||||||
|
|
||||||
button0 := basic.NewButton("try again")
|
button0 := basic.NewButton("try again")
|
||||||
button0.OnClick(world.SwitchFunc("start"))
|
button0.OnClick(world.SwitchFunc("start"))
|
||||||
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
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/layouts"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
|
import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/testing"
|
|
||||||
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
|
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
|
||||||
|
|
||||||
func main () {
|
func main () {
|
||||||
|
@ -11,15 +10,15 @@ func main () {
|
||||||
}
|
}
|
||||||
|
|
||||||
func run () {
|
func run () {
|
||||||
window, _ := tomo.NewWindow(2, 2)
|
window, _ := tomo.NewWindow(256, 2)
|
||||||
window.SetTitle("horizontal stack")
|
window.SetTitle("horizontal stack")
|
||||||
|
|
||||||
container := basic.NewContainer(layouts.Horizontal { true, true })
|
container := basic.NewContainer(layouts.Horizontal { true, true })
|
||||||
window.Adopt(container)
|
window.Adopt(container)
|
||||||
|
|
||||||
container.Adopt(testing.NewMouse(), true)
|
container.Adopt(basic.NewLabel("this is sample text", true), true)
|
||||||
container.Adopt(basic.NewLabel("<- left\nright ->", false), false)
|
container.Adopt(basic.NewLabel("this is sample text", true), true)
|
||||||
container.Adopt(testing.NewMouse(), true)
|
container.Adopt(basic.NewLabel("this is sample text", true), true)
|
||||||
|
|
||||||
window.OnClose(tomo.Stop)
|
window.OnClose(tomo.Stop)
|
||||||
window.Show()
|
window.Show()
|
||||||
|
|
|
@ -9,7 +9,7 @@ func main () {
|
||||||
}
|
}
|
||||||
|
|
||||||
func run () {
|
func run () {
|
||||||
window, _ := tomo.NewWindow(480, 360)
|
window, _ := tomo.NewWindow(480, 2)
|
||||||
window.SetTitle("example label")
|
window.SetTitle("example label")
|
||||||
window.Adopt(basic.NewLabel(text, true))
|
window.Adopt(basic.NewLabel(text, true))
|
||||||
window.OnClose(tomo.Stop)
|
window.OnClose(tomo.Stop)
|
||||||
|
|
|
@ -17,7 +17,7 @@ func run () {
|
||||||
container := basic.NewContainer(layouts.Vertical { true, true })
|
container := basic.NewContainer(layouts.Vertical { true, true })
|
||||||
window.Adopt(container)
|
window.Adopt(container)
|
||||||
|
|
||||||
label := basic.NewLabel("it is a label hehe", false)
|
label := basic.NewLabel("it is a label hehe", true)
|
||||||
button := basic.NewButton("drawing pad")
|
button := basic.NewButton("drawing pad")
|
||||||
okButton := basic.NewButton("OK")
|
okButton := basic.NewButton("OK")
|
||||||
button.OnClick (func () {
|
button.OnClick (func () {
|
||||||
|
|
11
layout.go
11
layout.go
|
@ -20,8 +20,11 @@ type Layout interface {
|
||||||
Arrange (entries []LayoutEntry, width, height int)
|
Arrange (entries []LayoutEntry, width, height int)
|
||||||
|
|
||||||
// MinimumSize returns the minimum width and height that the layout
|
// MinimumSize returns the minimum width and height that the layout
|
||||||
// needs to properly arrange the given slice of layout entries, given a
|
// needs to properly arrange the given slice of layout entries.
|
||||||
// "suqeeze" width so that the height can be determined for elements
|
MinimumSize (entries []LayoutEntry) (width, height int)
|
||||||
// fulfilling the Expanding interface.
|
|
||||||
MinimumSize (entries []LayoutEntry, squeeze int) (width, height int)
|
// MinimumHeightFor Returns the minimum height the layout needs to lay
|
||||||
|
// out the specified elements at the given width, taking into account
|
||||||
|
// flexible elements.
|
||||||
|
MinimumHeightFor (entries []LayoutEntry, squeeze int) (height int)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,6 @@ func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, width, height int)
|
||||||
// arrange the given list of entries.
|
// arrange the given list of entries.
|
||||||
func (layout Horizontal) MinimumSize (
|
func (layout Horizontal) MinimumSize (
|
||||||
entries []tomo.LayoutEntry,
|
entries []tomo.LayoutEntry,
|
||||||
squeeze int,
|
|
||||||
) (
|
) (
|
||||||
width, height int,
|
width, height int,
|
||||||
) {
|
) {
|
||||||
|
@ -93,3 +92,62 @@ func (layout Horizontal) MinimumSize (
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (layout Horizontal) MinimumHeightFor (
|
||||||
|
entries []tomo.LayoutEntry,
|
||||||
|
width int,
|
||||||
|
) (
|
||||||
|
height int,
|
||||||
|
) {
|
||||||
|
// TODO: maybe put calculating the expanding element width in a separate
|
||||||
|
// method
|
||||||
|
if layout.Pad {
|
||||||
|
width -= theme.Padding() * 2
|
||||||
|
}
|
||||||
|
freeSpace := width
|
||||||
|
expandingElements := 0
|
||||||
|
|
||||||
|
// count the number of expanding elements and the amount of free space
|
||||||
|
// for them to collectively occupy
|
||||||
|
for index, entry := range entries {
|
||||||
|
if entry.Expand {
|
||||||
|
expandingElements ++
|
||||||
|
} else {
|
||||||
|
entryMinWidth, _ := entry.MinimumSize()
|
||||||
|
freeSpace -= entryMinWidth
|
||||||
|
}
|
||||||
|
if index > 0 && layout.Gap {
|
||||||
|
freeSpace -= theme.Padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expandingElementWidth := 0
|
||||||
|
if expandingElements > 0 {
|
||||||
|
expandingElementWidth = freeSpace / expandingElements
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y := 0, 0
|
||||||
|
if layout.Pad {
|
||||||
|
x += theme.Padding()
|
||||||
|
y += theme.Padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the size and position of each element
|
||||||
|
for index, entry := range entries {
|
||||||
|
entryWidth, entryHeight := entry.MinimumSize()
|
||||||
|
if entry.Expand {
|
||||||
|
entryWidth = expandingElementWidth
|
||||||
|
}
|
||||||
|
if child, flexible := entry.Element.(tomo.Flexible); flexible {
|
||||||
|
entryHeight = child.MinimumHeightFor(entryWidth)
|
||||||
|
}
|
||||||
|
if entryHeight > height { height = entryHeight }
|
||||||
|
|
||||||
|
x += entryWidth
|
||||||
|
if index > 0 && layout.Gap { x += theme.Padding() }
|
||||||
|
}
|
||||||
|
|
||||||
|
if layout.Pad {
|
||||||
|
height += theme.Padding() * 2
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -26,12 +26,21 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) {
|
||||||
expandingElements := 0
|
expandingElements := 0
|
||||||
|
|
||||||
// count the number of expanding elements and the amount of free space
|
// count the number of expanding elements and the amount of free space
|
||||||
// for them to collectively occupy
|
// for them to collectively occupy, while gathering minimum heights.
|
||||||
|
minimumHeights := make([]int, len(entries))
|
||||||
for index, entry := range entries {
|
for index, entry := range entries {
|
||||||
|
var entryMinHeight int
|
||||||
|
|
||||||
|
if child, flexible := entry.Element.(tomo.Flexible); flexible {
|
||||||
|
entryMinHeight = child.MinimumHeightFor(width)
|
||||||
|
} else {
|
||||||
|
_, entryMinHeight = entry.MinimumSize()
|
||||||
|
}
|
||||||
|
minimumHeights[index] = entryMinHeight
|
||||||
|
|
||||||
if entry.Expand {
|
if entry.Expand {
|
||||||
expandingElements ++
|
expandingElements ++
|
||||||
} else {
|
} else {
|
||||||
_, entryMinHeight := entry.MinimumSize()
|
|
||||||
freeSpace -= entryMinHeight
|
freeSpace -= entryMinHeight
|
||||||
}
|
}
|
||||||
if index > 0 && layout.Gap {
|
if index > 0 && layout.Gap {
|
||||||
|
@ -58,7 +67,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) {
|
||||||
if entry.Expand {
|
if entry.Expand {
|
||||||
entryHeight = expandingElementHeight
|
entryHeight = expandingElementHeight
|
||||||
} else {
|
} else {
|
||||||
_, entryHeight = entry.MinimumSize()
|
entryHeight = minimumHeights[index]
|
||||||
}
|
}
|
||||||
y += entryHeight
|
y += entryHeight
|
||||||
entryBounds := entry.Bounds()
|
entryBounds := entry.Bounds()
|
||||||
|
@ -72,7 +81,6 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) {
|
||||||
// arrange the given list of entries.
|
// arrange the given list of entries.
|
||||||
func (layout Vertical) MinimumSize (
|
func (layout Vertical) MinimumSize (
|
||||||
entries []tomo.LayoutEntry,
|
entries []tomo.LayoutEntry,
|
||||||
squeeze int,
|
|
||||||
) (
|
) (
|
||||||
width, height int,
|
width, height int,
|
||||||
) {
|
) {
|
||||||
|
@ -93,3 +101,32 @@ func (layout Vertical) MinimumSize (
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MinimumHeightFor Returns the minimum height the layout needs to lay out the
|
||||||
|
// specified elements at the given width, taking into account flexible elements.
|
||||||
|
func (layout Vertical) MinimumHeightFor (
|
||||||
|
entries []tomo.LayoutEntry,
|
||||||
|
width int,
|
||||||
|
) (
|
||||||
|
height int,
|
||||||
|
) {
|
||||||
|
if layout.Pad {
|
||||||
|
width -= theme.Padding() * 2
|
||||||
|
height += theme.Padding() * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, entry := range entries {
|
||||||
|
child, flexible := entry.Element.(tomo.Flexible)
|
||||||
|
if flexible {
|
||||||
|
height += child.MinimumHeightFor(width)
|
||||||
|
} else {
|
||||||
|
_, entryHeight := entry.MinimumSize()
|
||||||
|
height += entryHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
if layout.Gap && index > 0 {
|
||||||
|
height += theme.Padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user