Merge pull request 'atomize-parent-hooks' (#4) from atomize-parent-hooks into main
Reviewed-on: sashakoshka/tomo#4
This commit is contained in:
		
						commit
						71d50cab4b
					
				@ -122,7 +122,7 @@ func (window *Window) handleButtonPress (
 | 
			
		||||
			sum := scrollSum { }
 | 
			
		||||
			sum.add(buttonEvent.Detail)
 | 
			
		||||
			window.compressScrollSum(buttonEvent, &sum)
 | 
			
		||||
			child.HandleScroll (
 | 
			
		||||
			child.HandleMouseScroll (
 | 
			
		||||
				int(buttonEvent.EventX),
 | 
			
		||||
				int(buttonEvent.EventY),
 | 
			
		||||
				float64(sum.x), float64(sum.y))
 | 
			
		||||
 | 
			
		||||
@ -76,26 +76,39 @@ func (backend *Backend) NewWindow (
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *Window) Adopt (child tomo.Element) {
 | 
			
		||||
	// disown previous child
 | 
			
		||||
	if window.child != nil {
 | 
			
		||||
		window.child.SetParentHooks (tomo.ParentHooks { })
 | 
			
		||||
		if previousChild, ok := window.child.(tomo.Selectable); ok {
 | 
			
		||||
			if previousChild.Selected() {
 | 
			
		||||
				previousChild.HandleDeselection()
 | 
			
		||||
			}
 | 
			
		||||
		window.child.OnDamage(nil)
 | 
			
		||||
		window.child.OnMinimumSizeChange(nil)
 | 
			
		||||
	}
 | 
			
		||||
	if previousChild, ok := window.child.(tomo.Flexible); ok {
 | 
			
		||||
		previousChild.OnFlexibleHeightChange(nil)
 | 
			
		||||
	}
 | 
			
		||||
	if previousChild, ok := window.child.(tomo.Selectable); ok {
 | 
			
		||||
		previousChild.OnSelectionRequest(nil)
 | 
			
		||||
		previousChild.OnSelectionMotionRequest(nil)
 | 
			
		||||
		if previousChild.Selected() {
 | 
			
		||||
			previousChild.HandleDeselection()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// adopt new child
 | 
			
		||||
	window.child = child
 | 
			
		||||
	if child != nil {
 | 
			
		||||
		child.SetParentHooks (tomo.ParentHooks {
 | 
			
		||||
			Draw: window.childDrawCallback,
 | 
			
		||||
			MinimumSizeChange: window.childMinimumSizeChangeCallback,
 | 
			
		||||
			FlexibleHeightChange: window.resizeChildToFit,
 | 
			
		||||
			SelectionRequest: window.childSelectionRequestCallback,
 | 
			
		||||
		})
 | 
			
		||||
		
 | 
			
		||||
		window.resizeChildToFit()
 | 
			
		||||
	if newChild, ok := child.(tomo.Flexible); ok {
 | 
			
		||||
		newChild.OnFlexibleHeightChange(window.resizeChildToFit)
 | 
			
		||||
	}
 | 
			
		||||
	if newChild, ok := child.(tomo.Selectable); ok {
 | 
			
		||||
		newChild.OnSelectionRequest(window.childSelectionRequestCallback)
 | 
			
		||||
	}
 | 
			
		||||
	if child != nil {
 | 
			
		||||
		child.OnDamage(window.childDrawCallback)
 | 
			
		||||
		child.OnMinimumSizeChange (func () {
 | 
			
		||||
			window.childMinimumSizeChangeCallback (
 | 
			
		||||
				child.MinimumSize())
 | 
			
		||||
		})
 | 
			
		||||
		window.resizeChildToFit()
 | 
			
		||||
		window.childMinimumSizeChangeCallback(child.MinimumSize())
 | 
			
		||||
	}
 | 
			
		||||
	window.childMinimumSizeChangeCallback(child.MinimumSize())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *Window) Child () (child tomo.Element) {
 | 
			
		||||
@ -204,7 +217,7 @@ func (window *Window) redrawChildEntirely () {
 | 
			
		||||
func (window *Window) resizeChildToFit () {
 | 
			
		||||
	window.skipChildDrawCallback = true
 | 
			
		||||
	if child, ok := window.child.(tomo.Flexible); ok {
 | 
			
		||||
		minimumHeight := child.MinimumHeightFor(window.metrics.width)
 | 
			
		||||
		minimumHeight := child.FlexibleHeightFor(window.metrics.width)
 | 
			
		||||
		minimumWidth, _ := child.MinimumSize()
 | 
			
		||||
		
 | 
			
		||||
		icccm.WmNormalHintsSet (
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										134
									
								
								element.go
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								element.go
									
									
									
									
									
								
							@ -2,96 +2,6 @@ package tomo
 | 
			
		||||
 | 
			
		||||
import "image"
 | 
			
		||||
 | 
			
		||||
// ParentHooks is a struct that contains callbacks that let child elements send
 | 
			
		||||
// information to their parent element without the child element knowing
 | 
			
		||||
// anything about the parent element or containing any reference to it. When a
 | 
			
		||||
// parent element adopts a child element, it must set these callbacks. They are
 | 
			
		||||
// allowed to be nil.
 | 
			
		||||
type ParentHooks struct {
 | 
			
		||||
	// 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.
 | 
			
		||||
	Draw func (region Canvas)
 | 
			
		||||
 | 
			
		||||
	// MinimumSizeChange is called when the child element's minimum width
 | 
			
		||||
	// and/or height changes. When this function is called, the element will
 | 
			
		||||
	// have already been resized and there is no need to send it a resize
 | 
			
		||||
	// event.
 | 
			
		||||
	MinimumSizeChange func (width, height int)
 | 
			
		||||
 | 
			
		||||
	// FlexibleHeightChange is called when the parameters affecting the
 | 
			
		||||
	// element's felxible height have changed.
 | 
			
		||||
	FlexibleHeightChange func ()
 | 
			
		||||
 | 
			
		||||
	// ContentBoundsChange is called by scrollable elements when the
 | 
			
		||||
	// element's content bounds have changed. When this function is called,
 | 
			
		||||
	// the element will have already done any drawing necessary. This is
 | 
			
		||||
	// only intended for updating things like scrollbar positions.
 | 
			
		||||
	ContentBoundsChange func ()
 | 
			
		||||
	
 | 
			
		||||
	// SelectionRequest is called when the child element element wants
 | 
			
		||||
	// itself to be selected. If the parent element chooses to grant the
 | 
			
		||||
	// request, it must send the child element a selection event and return
 | 
			
		||||
	// true.
 | 
			
		||||
	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.
 | 
			
		||||
func (hooks ParentHooks) RunDraw (region Canvas) {
 | 
			
		||||
	if hooks.Draw != nil {
 | 
			
		||||
		hooks.Draw(region)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RunMinimumSizeChange runs the MinimumSizeChange hook if it is not nil. If it
 | 
			
		||||
// is nil, it does nothing.
 | 
			
		||||
func (hooks ParentHooks) RunMinimumSizeChange (width, height int) {
 | 
			
		||||
	if hooks.MinimumSizeChange != nil {
 | 
			
		||||
		hooks.MinimumSizeChange(width, height)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
// nil, it does nothing.
 | 
			
		||||
func (hooks ParentHooks) RunSelectionRequest () (granted bool) {
 | 
			
		||||
	if hooks.SelectionRequest != nil {
 | 
			
		||||
		granted = hooks.SelectionRequest()
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RunContentBoundsChange runs the ContentBoundsChange hook if it is not nil. If
 | 
			
		||||
// it is nil, it does nothing.
 | 
			
		||||
func (hooks ParentHooks) RunContentBoundsChange () {
 | 
			
		||||
	if hooks.ContentBoundsChange != nil {
 | 
			
		||||
		hooks.ContentBoundsChange()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Element represents a basic on-screen object.
 | 
			
		||||
type Element interface {
 | 
			
		||||
	// Element must implement the Canvas interface. Elements should start
 | 
			
		||||
@ -109,11 +19,13 @@ type Element interface {
 | 
			
		||||
	// element's parent.
 | 
			
		||||
	Resize (width, height int)
 | 
			
		||||
 | 
			
		||||
	// SetParentHooks gives the element callbacks that let it send
 | 
			
		||||
	// information to its parent element without it knowing anything about
 | 
			
		||||
	// the parent element or containing any reference to it. When a parent
 | 
			
		||||
	// element adopts a child element, it must set these callbacks.
 | 
			
		||||
	SetParentHooks (callbacks ParentHooks)
 | 
			
		||||
	// OnDamage sets a function to be called when an area of the element is
 | 
			
		||||
	// drawn on and should be pushed to the screen.
 | 
			
		||||
	OnDamage (callback func (region Canvas))
 | 
			
		||||
 | 
			
		||||
	// OnMinimumSizeChange sets a function to be called when the element's
 | 
			
		||||
	// minimum size is changed.
 | 
			
		||||
	OnMinimumSizeChange (callback func ())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectionDirection represents a keyboard navigation direction.
 | 
			
		||||
@ -156,6 +68,18 @@ type Selectable interface {
 | 
			
		||||
	// HandleDeselection causes this element to mark itself and all of its
 | 
			
		||||
	// children as deselected.
 | 
			
		||||
	HandleDeselection ()
 | 
			
		||||
 | 
			
		||||
	// OnSelectionRequest sets a function to be called when this element
 | 
			
		||||
	// wants its parent element to select it. Parent elements should return
 | 
			
		||||
	// true if the request was granted, and false if it was not.
 | 
			
		||||
	OnSelectionRequest (func () (granted bool))
 | 
			
		||||
 | 
			
		||||
	// OnSelectionMotionRequest sets a function to be called when this
 | 
			
		||||
	// element wants its parent element to select the element behind or in
 | 
			
		||||
	// front of it, depending on the specified direction. Parent elements
 | 
			
		||||
	// should return true if the request was granted, and false if it was
 | 
			
		||||
	// not.
 | 
			
		||||
	OnSelectionMotionRequest (func (SelectionDirection) (granted bool))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// KeyboardTarget represents an element that can receive keyboard input.
 | 
			
		||||
@ -196,7 +120,7 @@ type MouseTarget interface {
 | 
			
		||||
 | 
			
		||||
	// HandleScroll is called when the mouse is scrolled. The X and Y
 | 
			
		||||
	// direction of the scroll event are passed as deltaX and deltaY.
 | 
			
		||||
	HandleScroll (x, y int, deltaX, deltaY float64)
 | 
			
		||||
	HandleMouseScroll (x, y int, deltaX, deltaY float64)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Flexible represents an element who's preferred minimum height can change in
 | 
			
		||||
@ -204,10 +128,10 @@ type MouseTarget interface {
 | 
			
		||||
type Flexible interface {
 | 
			
		||||
	Element
 | 
			
		||||
 | 
			
		||||
	// HeightForWidth returns what the element's minimum height would be if
 | 
			
		||||
	// resized to a specified width. This does not actually alter the state
 | 
			
		||||
	// of the element in any way, but it may perform significant work, so it
 | 
			
		||||
	// should be called sparingly.
 | 
			
		||||
	// FlexibleHeightFor returns what the element's minimum height would be
 | 
			
		||||
	// if resized to a specified width. This does not actually alter the
 | 
			
		||||
	// state of the element in any way, but it may perform significant work,
 | 
			
		||||
	// so it should be called sparingly.
 | 
			
		||||
	//
 | 
			
		||||
	// It is reccomended that parent containers check for this interface and
 | 
			
		||||
	// take this method's value into account in order to support things like
 | 
			
		||||
@ -217,7 +141,11 @@ type Flexible interface {
 | 
			
		||||
	//
 | 
			
		||||
	// It is important to note that if a parent container checks for
 | 
			
		||||
	// flexible chilren, it itself will likely need to be flexible.
 | 
			
		||||
	MinimumHeightFor (width int) (height int)
 | 
			
		||||
	FlexibleHeightFor (width int) (height int)
 | 
			
		||||
 | 
			
		||||
	// OnFlexibleHeightChange sets a function to be called when the
 | 
			
		||||
	// parameters affecting this element's flexible height are changed.
 | 
			
		||||
	OnFlexibleHeightChange (callback func ())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scrollable represents an element that can be scrolled. It acts as a viewport
 | 
			
		||||
@ -238,4 +166,8 @@ type Scrollable interface {
 | 
			
		||||
 | 
			
		||||
	// ScrollAxes returns the supported axes for scrolling.
 | 
			
		||||
	ScrollAxes () (horizontal, vertical bool)
 | 
			
		||||
 | 
			
		||||
	// OnScrollBoundsChange sets a function to be called when the element's
 | 
			
		||||
	// ScrollContentBounds or ScrollViewportBounds are changed.
 | 
			
		||||
	OnScrollBoundsChange (callback func ())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -14,10 +14,13 @@ type Button struct {
 | 
			
		||||
	pressed  bool
 | 
			
		||||
	enabled  bool
 | 
			
		||||
	selected bool
 | 
			
		||||
	onClick func ()
 | 
			
		||||
 | 
			
		||||
	text   string
 | 
			
		||||
	drawer artist.TextDrawer
 | 
			
		||||
	
 | 
			
		||||
	onClick func ()
 | 
			
		||||
	onSelectionRequest func () (granted bool)
 | 
			
		||||
	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewButton creates a new button with the specified label text.
 | 
			
		||||
@ -41,7 +44,7 @@ func (element *Button) HandleMouseDown (x, y int, button tomo.Button) {
 | 
			
		||||
	element.pressed = true
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -50,7 +53,7 @@ func (element *Button) HandleMouseUp (x, y int, button tomo.Button) {
 | 
			
		||||
	element.pressed = false
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	within := image.Point { x, y }.
 | 
			
		||||
@ -63,7 +66,7 @@ func (element *Button) HandleMouseUp (x, y int, button tomo.Button) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Button) HandleMouseMove (x, y int) { }
 | 
			
		||||
func (element *Button) HandleScroll (x, y int, deltaX, deltaY float64) { }
 | 
			
		||||
func (element *Button) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
 | 
			
		||||
 | 
			
		||||
func (element *Button) HandleKeyDown (
 | 
			
		||||
	key tomo.Key,
 | 
			
		||||
@ -75,7 +78,7 @@ func (element *Button) HandleKeyDown (
 | 
			
		||||
		element.pressed = true
 | 
			
		||||
		if element.core.HasImage() {
 | 
			
		||||
			element.draw()
 | 
			
		||||
			element.core.PushAll()
 | 
			
		||||
			element.core.DamageAll()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -85,7 +88,7 @@ func (element *Button) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) {
 | 
			
		||||
		element.pressed = false
 | 
			
		||||
		if element.core.HasImage() {
 | 
			
		||||
			element.draw()
 | 
			
		||||
			element.core.PushAll()
 | 
			
		||||
			element.core.DamageAll()
 | 
			
		||||
		}
 | 
			
		||||
		if !element.enabled { return }
 | 
			
		||||
		if element.onClick != nil {
 | 
			
		||||
@ -100,7 +103,9 @@ func (element *Button) Selected () (selected bool) {
 | 
			
		||||
 | 
			
		||||
func (element *Button) Select () {
 | 
			
		||||
	if !element.enabled { return }
 | 
			
		||||
	element.core.RequestSelection()
 | 
			
		||||
	if element.onSelectionRequest != nil {
 | 
			
		||||
		element.onSelectionRequest()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Button) HandleSelection (
 | 
			
		||||
@ -117,7 +122,7 @@ func (element *Button) HandleSelection (
 | 
			
		||||
	element.selected = true
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
@ -126,10 +131,20 @@ func (element *Button) HandleDeselection () {
 | 
			
		||||
	element.selected = false
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Button) OnSelectionRequest (callback func () (granted bool)) {
 | 
			
		||||
	element.onSelectionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Button) OnSelectionMotionRequest (
 | 
			
		||||
	callback func (direction tomo.SelectionDirection) (granted bool),
 | 
			
		||||
) {
 | 
			
		||||
	element.onSelectionMotionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnClick sets the function to be called when the button is clicked.
 | 
			
		||||
func (element *Button) OnClick (callback func ()) {
 | 
			
		||||
	element.onClick = callback
 | 
			
		||||
@ -141,7 +156,7 @@ func (element *Button) SetEnabled (enabled bool) {
 | 
			
		||||
	element.enabled = enabled
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -157,7 +172,7 @@ func (element *Button) SetText (text string) {
 | 
			
		||||
		theme.Padding() * 2 + textBounds.Dy())
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,10 +15,13 @@ type Checkbox struct {
 | 
			
		||||
	checked  bool
 | 
			
		||||
	enabled  bool
 | 
			
		||||
	selected bool
 | 
			
		||||
	onClick func ()
 | 
			
		||||
 | 
			
		||||
	text   string
 | 
			
		||||
	drawer artist.TextDrawer
 | 
			
		||||
	
 | 
			
		||||
	onClick func ()
 | 
			
		||||
	onSelectionRequest func () (granted bool)
 | 
			
		||||
	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCheckbox creates a new cbeckbox with the specified label text.
 | 
			
		||||
@ -41,7 +44,7 @@ func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) {
 | 
			
		||||
	element.pressed = true
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -57,7 +60,7 @@ func (element *Checkbox) HandleMouseUp (x, y int, button tomo.Button) {
 | 
			
		||||
	
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
	if within && element.onClick != nil {
 | 
			
		||||
		element.onClick()
 | 
			
		||||
@ -76,7 +79,7 @@ func (element *Checkbox) HandleKeyDown (
 | 
			
		||||
		element.pressed = true
 | 
			
		||||
		if element.core.HasImage() {
 | 
			
		||||
			element.draw()
 | 
			
		||||
			element.core.PushAll()
 | 
			
		||||
			element.core.DamageAll()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -87,7 +90,7 @@ func (element *Checkbox) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) {
 | 
			
		||||
		element.checked = !element.checked
 | 
			
		||||
		if element.core.HasImage() {
 | 
			
		||||
			element.draw()
 | 
			
		||||
			element.core.PushAll()
 | 
			
		||||
			element.core.DamageAll()
 | 
			
		||||
		}
 | 
			
		||||
		if element.onClick != nil {
 | 
			
		||||
			element.onClick()
 | 
			
		||||
@ -102,7 +105,10 @@ func (element *Checkbox) Selected () (selected bool) {
 | 
			
		||||
 | 
			
		||||
// Select requests that this element be selected.
 | 
			
		||||
func (element *Checkbox) Select () {
 | 
			
		||||
	element.core.RequestSelection()
 | 
			
		||||
	if !element.enabled { return }
 | 
			
		||||
	if element.onSelectionRequest != nil {
 | 
			
		||||
		element.onSelectionRequest()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Checkbox) HandleSelection (
 | 
			
		||||
@ -119,7 +125,7 @@ func (element *Checkbox) HandleSelection (
 | 
			
		||||
	element.selected = true
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
@ -128,10 +134,20 @@ func (element *Checkbox) HandleDeselection () {
 | 
			
		||||
	element.selected = false
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Checkbox) OnSelectionRequest (callback func () (granted bool)) {
 | 
			
		||||
	element.onSelectionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Checkbox) OnSelectionMotionRequest (
 | 
			
		||||
	callback func (direction tomo.SelectionDirection) (granted bool),
 | 
			
		||||
) {
 | 
			
		||||
	element.onSelectionMotionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnClick sets the function to be called when the checkbox is toggled.
 | 
			
		||||
func (element *Checkbox) OnClick (callback func ()) {
 | 
			
		||||
	element.onClick = callback
 | 
			
		||||
@ -148,7 +164,7 @@ func (element *Checkbox) SetEnabled (enabled bool) {
 | 
			
		||||
	element.enabled = enabled
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -164,7 +180,7 @@ func (element *Checkbox) SetText (text string) {
 | 
			
		||||
		textBounds.Dy())
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,10 @@ type Container struct {
 | 
			
		||||
	selected   bool
 | 
			
		||||
	selectable bool
 | 
			
		||||
	flexible   bool
 | 
			
		||||
	
 | 
			
		||||
	onSelectionRequest func () (granted bool)
 | 
			
		||||
	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool)
 | 
			
		||||
	onFlexibleHeightChange func ()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewContainer creates a new container.
 | 
			
		||||
@ -35,7 +39,7 @@ func (element *Container) SetLayout (layout tomo.Layout) {
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.recalculate()
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -43,38 +47,38 @@ func (element *Container) SetLayout (layout tomo.Layout) {
 | 
			
		||||
// the element will expand (instead of contract to its minimum size), in
 | 
			
		||||
// whatever way is defined by the current layout.
 | 
			
		||||
func (element *Container) Adopt (child tomo.Element, expand bool) {
 | 
			
		||||
	child.SetParentHooks (tomo.ParentHooks {
 | 
			
		||||
		Draw: func (region tomo.Canvas) {
 | 
			
		||||
			element.drawChildRegion(child, region)
 | 
			
		||||
		},
 | 
			
		||||
		MinimumSizeChange: func (int, int) {
 | 
			
		||||
			element.updateMinimumSize()
 | 
			
		||||
		},
 | 
			
		||||
		FlexibleHeightChange: element.updateMinimumSize,
 | 
			
		||||
		SelectionRequest: func () (granted bool) {
 | 
			
		||||
			child, selectable := child.(tomo.Selectable)
 | 
			
		||||
			if !selectable { return }
 | 
			
		||||
			return element.childSelectionRequestCallback(child)
 | 
			
		||||
		},
 | 
			
		||||
		SelectionMotionRequest: func (
 | 
			
		||||
			direction tomo.SelectionDirection,
 | 
			
		||||
		) (
 | 
			
		||||
			granted bool,
 | 
			
		||||
		) {
 | 
			
		||||
			return element.core.RequestSelectionMotion(direction)
 | 
			
		||||
		},
 | 
			
		||||
	// set event handlers
 | 
			
		||||
	child.OnDamage (func (region tomo.Canvas) {
 | 
			
		||||
		element.drawChildRegion(child, region)
 | 
			
		||||
	})
 | 
			
		||||
	child.OnMinimumSizeChange(element.updateMinimumSize)
 | 
			
		||||
	if child0, ok := child.(tomo.Flexible); ok {
 | 
			
		||||
		child0.OnFlexibleHeightChange(element.updateMinimumSize)
 | 
			
		||||
	}
 | 
			
		||||
	if child0, ok := child.(tomo.Selectable); ok {
 | 
			
		||||
		child0.OnSelectionRequest (func () (granted bool) {
 | 
			
		||||
			return element.childSelectionRequestCallback(child0)
 | 
			
		||||
		})
 | 
			
		||||
		child0.OnSelectionMotionRequest (
 | 
			
		||||
			func (direction tomo.SelectionDirection) (granted bool) {
 | 
			
		||||
				if element.onSelectionMotionRequest == nil { return }
 | 
			
		||||
				return element.onSelectionMotionRequest(direction)
 | 
			
		||||
			})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// add child
 | 
			
		||||
	element.children = append (element.children, tomo.LayoutEntry {
 | 
			
		||||
		Element: child,
 | 
			
		||||
		Expand:  expand,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// refresh stale data
 | 
			
		||||
	element.updateMinimumSize()
 | 
			
		||||
	element.reflectChildProperties()
 | 
			
		||||
	if element.core.HasImage() && !element.warping {
 | 
			
		||||
		element.recalculate()
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -97,7 +101,7 @@ func (element *Container) Warp (callback func ()) {
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.recalculate()
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -106,7 +110,7 @@ func (element *Container) Warp (callback func ()) {
 | 
			
		||||
func (element *Container) Disown (child tomo.Element) {
 | 
			
		||||
	for index, entry := range element.children {
 | 
			
		||||
		if entry.Element == child {
 | 
			
		||||
			entry.SetParentHooks(tomo.ParentHooks { })
 | 
			
		||||
			element.clearChildEventHandlers(entry.Element)
 | 
			
		||||
			element.children = append (
 | 
			
		||||
				element.children[:index],
 | 
			
		||||
				element.children[index + 1:]...)
 | 
			
		||||
@ -119,7 +123,22 @@ func (element *Container) Disown (child tomo.Element) {
 | 
			
		||||
	if element.core.HasImage() && !element.warping {
 | 
			
		||||
		element.recalculate()
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) clearChildEventHandlers (child tomo.Element) {
 | 
			
		||||
	child.OnDamage(nil)
 | 
			
		||||
	child.OnMinimumSizeChange(nil)
 | 
			
		||||
	if child0, ok := child.(tomo.Selectable); ok {
 | 
			
		||||
		child0.OnSelectionRequest(nil)
 | 
			
		||||
		child0.OnSelectionMotionRequest(nil)
 | 
			
		||||
		if child0.Selected() {
 | 
			
		||||
			child0.HandleDeselection()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if child0, ok := child.(tomo.Flexible); ok {
 | 
			
		||||
		child0.OnFlexibleHeightChange(nil)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -132,7 +151,7 @@ func (element *Container) DisownAll () {
 | 
			
		||||
	if element.core.HasImage() && !element.warping {
 | 
			
		||||
		element.recalculate()
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -209,11 +228,11 @@ func (element *Container) HandleMouseMove (x, y int) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) HandleScroll (x, y int, deltaX, deltaY float64) {
 | 
			
		||||
func (element *Container) HandleMouseScroll (x, y int, deltaX, deltaY float64) {
 | 
			
		||||
	child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
 | 
			
		||||
	if !handlesMouse { return }
 | 
			
		||||
	childPosition := element.childPosition(child)
 | 
			
		||||
	child.HandleScroll(x - childPosition.X, y - childPosition.Y, deltaX, deltaY)
 | 
			
		||||
	child.HandleMouseScroll(x - childPosition.X, y - childPosition.Y, deltaX, deltaY)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) HandleKeyDown (
 | 
			
		||||
@ -245,7 +264,9 @@ func (element *Container) Selected () (selected bool) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) Select () {
 | 
			
		||||
	element.core.RequestSelection()
 | 
			
		||||
	if element.onSelectionRequest != nil {
 | 
			
		||||
		element.onSelectionRequest()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) HandleSelection (direction tomo.SelectionDirection) (ok bool) {
 | 
			
		||||
@ -300,8 +321,12 @@ func (element *Container) HandleSelection (direction tomo.SelectionDirection) (o
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) MinimumHeightFor (width int) (height int) {
 | 
			
		||||
	return element.layout.MinimumHeightFor(element.children, width)
 | 
			
		||||
func (element *Container) FlexibleHeightFor (width int) (height int) {
 | 
			
		||||
	return element.layout.FlexibleHeightFor(element.children, width)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) OnFlexibleHeightChange (callback func ()) {
 | 
			
		||||
	element.onFlexibleHeightChange = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) HandleDeselection () {
 | 
			
		||||
@ -312,6 +337,16 @@ func (element *Container) HandleDeselection () {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) OnSelectionRequest (callback func () (granted bool)) {
 | 
			
		||||
	element.onSelectionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) OnSelectionMotionRequest (
 | 
			
		||||
	callback func (direction tomo.SelectionDirection) (granted bool),
 | 
			
		||||
) {
 | 
			
		||||
	element.onSelectionMotionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) forSelected (callback func (child tomo.Selectable) bool) {
 | 
			
		||||
	for _, entry := range element.children {
 | 
			
		||||
		child, selectable := entry.Element.(tomo.Selectable)
 | 
			
		||||
@ -379,7 +414,7 @@ func (element *Container) childSelectionRequestCallback (
 | 
			
		||||
) (
 | 
			
		||||
	granted bool,
 | 
			
		||||
) {
 | 
			
		||||
	if element.core.RequestSelection() {
 | 
			
		||||
	if element.onSelectionRequest != nil && element.onSelectionRequest() {
 | 
			
		||||
		element.forSelected (func (child tomo.Selectable) bool {
 | 
			
		||||
			child.HandleDeselection()
 | 
			
		||||
			return true
 | 
			
		||||
@ -394,7 +429,7 @@ func (element *Container) childSelectionRequestCallback (
 | 
			
		||||
func (element *Container) updateMinimumSize () {
 | 
			
		||||
	width, height := element.layout.MinimumSize(element.children)
 | 
			
		||||
	if element.flexible {
 | 
			
		||||
		height = element.layout.MinimumHeightFor(element.children, width)
 | 
			
		||||
		height = element.layout.FlexibleHeightFor(element.children, width)
 | 
			
		||||
	}
 | 
			
		||||
	element.core.SetMinimumSize(width, height)
 | 
			
		||||
}
 | 
			
		||||
@ -422,7 +457,7 @@ func (element *Container) drawChildRegion (child tomo.Element, region tomo.Canva
 | 
			
		||||
	for _, entry := range element.children {
 | 
			
		||||
		if entry.Element == child {
 | 
			
		||||
			artist.Paste(element.core, region, entry.Position)
 | 
			
		||||
			element.core.PushRegion (
 | 
			
		||||
			element.core.DamageRegion (
 | 
			
		||||
				region.Bounds().Add(entry.Position))
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,8 @@ type Label struct {
 | 
			
		||||
	wrap   bool
 | 
			
		||||
	text   string
 | 
			
		||||
	drawer artist.TextDrawer
 | 
			
		||||
	
 | 
			
		||||
	onFlexibleHeightChange func ()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLabel creates a new label. If wrap is set to true, the text inside will be
 | 
			
		||||
@ -38,9 +40,9 @@ func (element *Label) Resize (width, height int) {
 | 
			
		||||
	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) {
 | 
			
		||||
// FlexibleHeightFor returns the reccomended height for this element based on
 | 
			
		||||
// the given width in order to allow the text to wrap properly.
 | 
			
		||||
func (element *Label) FlexibleHeightFor (width int) (height int) {
 | 
			
		||||
	if element.wrap {
 | 
			
		||||
		return element.drawer.ReccomendedHeightFor(width)
 | 
			
		||||
	} else {
 | 
			
		||||
@ -49,6 +51,12 @@ func (element *Label) MinimumHeightFor (width int) (height int) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnFlexibleHeightChange sets a function to be called when the parameters
 | 
			
		||||
// affecting this element's flexible height are changed.
 | 
			
		||||
func (element *Label) OnFlexibleHeightChange (callback func ()) {
 | 
			
		||||
	element.onFlexibleHeightChange = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetText sets the label's text.
 | 
			
		||||
func (element *Label) SetText (text string) {
 | 
			
		||||
	if element.text == text { return }
 | 
			
		||||
@ -59,7 +67,7 @@ func (element *Label) SetText (text string) {
 | 
			
		||||
	
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -78,7 +86,7 @@ func (element *Label) SetWrap (wrap bool) {
 | 
			
		||||
	
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -88,7 +96,9 @@ func (element *Label) updateMinimumSize () {
 | 
			
		||||
		if em < 1 { em = theme.Padding() }
 | 
			
		||||
		element.core.SetMinimumSize (
 | 
			
		||||
			em, element.drawer.LineHeight().Round())
 | 
			
		||||
		element.core.NotifyFlexibleHeightChange()
 | 
			
		||||
		if element.onFlexibleHeightChange != nil {
 | 
			
		||||
			element.onFlexibleHeightChange()
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		bounds := element.drawer.LayoutBounds()
 | 
			
		||||
		element.core.SetMinimumSize(bounds.Dx(), bounds.Dy())
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ func (element *ProgressBar) SetProgress (progress float64) {
 | 
			
		||||
	element.progress = progress
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,9 @@ type ScrollContainer struct {
 | 
			
		||||
		enabled bool
 | 
			
		||||
		bounds image.Rectangle
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onSelectionRequest func () (granted bool)
 | 
			
		||||
	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewScrollContainer creates a new scroll container with the specified scroll
 | 
			
		||||
@ -52,23 +55,20 @@ func (element *ScrollContainer) Resize (width, height int) {
 | 
			
		||||
func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
 | 
			
		||||
	// disown previous child if it exists
 | 
			
		||||
	if element.child != nil {
 | 
			
		||||
		element.child.SetParentHooks (tomo.ParentHooks { })
 | 
			
		||||
		if previousChild, ok := element.child.(tomo.Selectable); ok {
 | 
			
		||||
			if previousChild.Selected() {
 | 
			
		||||
				previousChild.HandleDeselection()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		element.clearChildEventHandlers(child)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// adopt new child
 | 
			
		||||
	element.child = child
 | 
			
		||||
	if child != nil {
 | 
			
		||||
		child.SetParentHooks (tomo.ParentHooks {
 | 
			
		||||
			// Draw: window.childDrawCallback,
 | 
			
		||||
			// MinimumSizeChange: window.childMinimumSizeChangeCallback,
 | 
			
		||||
			// FlexibleHeightChange: window.resizeChildToFit,
 | 
			
		||||
			// SelectionRequest: window.childSelectionRequestCallback,
 | 
			
		||||
		})
 | 
			
		||||
		child.OnDamage(element.childDamageCallback)
 | 
			
		||||
		child.OnMinimumSizeChange(element.updateMinimumSize)
 | 
			
		||||
		if newChild, ok := child.(tomo.Selectable); ok {
 | 
			
		||||
			newChild.OnSelectionRequest (
 | 
			
		||||
				element.childSelectionRequestCallback)
 | 
			
		||||
			newChild.OnSelectionMotionRequest (
 | 
			
		||||
				element.childSelectionMotionRequestCallback)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO: somehow inform the core that we do not in fact want to
 | 
			
		||||
		// redraw the element.
 | 
			
		||||
@ -84,6 +84,104 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) HandleKeyDown (
 | 
			
		||||
	key tomo.Key,
 | 
			
		||||
	modifiers tomo.Modifiers,
 | 
			
		||||
	repeated bool,
 | 
			
		||||
) {
 | 
			
		||||
	if child, ok := element.child.(tomo.KeyboardTarget); ok {
 | 
			
		||||
		child.HandleKeyDown(key, modifiers, repeated)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) {
 | 
			
		||||
	if child, ok := element.child.(tomo.KeyboardTarget); ok {
 | 
			
		||||
		child.HandleKeyUp(key, modifiers)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) Selected () (selected bool) {
 | 
			
		||||
	return element.selected
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) Select () {
 | 
			
		||||
	if element.onSelectionRequest != nil {
 | 
			
		||||
		element.onSelectionRequest()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) HandleSelection (
 | 
			
		||||
	direction tomo.SelectionDirection,
 | 
			
		||||
) (
 | 
			
		||||
	accepted bool,
 | 
			
		||||
) {
 | 
			
		||||
	if child, ok := element.child.(tomo.Selectable); ok {
 | 
			
		||||
		element.selected = true
 | 
			
		||||
		return child.HandleSelection(direction)
 | 
			
		||||
	} else {
 | 
			
		||||
		element.selected = false
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) HandleDeselection () {
 | 
			
		||||
	if child, ok := element.child.(tomo.Selectable); ok {
 | 
			
		||||
		child.HandleDeselection()
 | 
			
		||||
	}
 | 
			
		||||
	element.selected = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) OnSelectionRequest (callback func () (granted bool)) {
 | 
			
		||||
	element.onSelectionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) OnSelectionMotionRequest (
 | 
			
		||||
	callback func (direction tomo.SelectionDirection) (granted bool),
 | 
			
		||||
) {
 | 
			
		||||
	element.onSelectionMotionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) childDamageCallback (region tomo.Canvas) {
 | 
			
		||||
	element.core.DamageRegion(artist.Paste(element, region, image.Point { }))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) childSelectionRequestCallback () (granted bool) {
 | 
			
		||||
	child, ok := element.child.(tomo.Selectable)
 | 
			
		||||
	if !ok { return false }
 | 
			
		||||
	if element.onSelectionRequest != nil && element.onSelectionRequest() {
 | 
			
		||||
		child.HandleSelection(tomo.SelectionDirectionNeutral)
 | 
			
		||||
		return true
 | 
			
		||||
	} else {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) childSelectionMotionRequestCallback (
 | 
			
		||||
	direction tomo.SelectionDirection,
 | 
			
		||||
) (
 | 
			
		||||
	granted bool,
 | 
			
		||||
) {
 | 
			
		||||
	if element.onSelectionMotionRequest == nil {
 | 
			
		||||
		 return
 | 
			
		||||
	}
 | 
			
		||||
	return element.onSelectionMotionRequest(direction)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) clearChildEventHandlers (child tomo.Element) {
 | 
			
		||||
	child.OnDamage(nil)
 | 
			
		||||
	child.OnMinimumSizeChange(nil)
 | 
			
		||||
	if child0, ok := child.(tomo.Selectable); ok {
 | 
			
		||||
		child0.OnSelectionRequest(nil)
 | 
			
		||||
		child0.OnSelectionMotionRequest(nil)
 | 
			
		||||
		if child0.Selected() {
 | 
			
		||||
			child0.HandleDeselection()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if child0, ok := child.(tomo.Flexible); ok {
 | 
			
		||||
		child0.OnFlexibleHeightChange(nil)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) recalculate () {
 | 
			
		||||
	horizontal := &element.horizontal
 | 
			
		||||
	vertical   := &element.vertical
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ func (element *Spacer) SetLine (line bool) {
 | 
			
		||||
	element.line = line
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,9 @@ type TextBox struct {
 | 
			
		||||
	
 | 
			
		||||
	onKeyDown func (tomo.Key, tomo.Modifiers, bool) (bool)
 | 
			
		||||
	onChange  func ()
 | 
			
		||||
	onSelectionRequest func () (granted bool)
 | 
			
		||||
	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool)
 | 
			
		||||
	onScrollBoundsChange func ()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewTextBox creates a new text box with the specified placeholder text, and
 | 
			
		||||
@ -119,7 +122,7 @@ func (element *TextBox) HandleKeyDown (
 | 
			
		||||
	
 | 
			
		||||
	if altered && element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -130,7 +133,9 @@ func (element *TextBox) Selected () (selected bool) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *TextBox) Select () {
 | 
			
		||||
	element.core.RequestSelection()
 | 
			
		||||
	if element.onSelectionRequest != nil {
 | 
			
		||||
		element.onSelectionRequest()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *TextBox) HandleSelection (
 | 
			
		||||
@ -147,7 +152,7 @@ func (element *TextBox) HandleSelection (
 | 
			
		||||
	element.selected = true
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
@ -156,16 +161,26 @@ func (element *TextBox) HandleDeselection () {
 | 
			
		||||
	element.selected = false
 | 
			
		||||
	if element.core.HasImage() {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *TextBox) OnSelectionRequest (callback func () (granted bool)) {
 | 
			
		||||
	element.onSelectionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *TextBox) OnSelectionMotionRequest (
 | 
			
		||||
	callback func (direction tomo.SelectionDirection) (granted bool),
 | 
			
		||||
) {
 | 
			
		||||
	element.onSelectionMotionRequest = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *TextBox) SetEnabled (enabled bool) {
 | 
			
		||||
	if element.enabled == enabled { return }
 | 
			
		||||
	element.enabled = enabled
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -178,7 +193,7 @@ func (element *TextBox) SetPlaceholder (placeholder string) {
 | 
			
		||||
	element.updateMinimumSize()
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -195,7 +210,7 @@ func (element *TextBox) SetValue (text string) {
 | 
			
		||||
	
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -246,9 +261,11 @@ func (element *TextBox) ScrollTo (position image.Point) {
 | 
			
		||||
 | 
			
		||||
	if element.core.HasImage () {
 | 
			
		||||
		element.draw()
 | 
			
		||||
		element.core.PushAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	}
 | 
			
		||||
	if element.onScrollBoundsChange != nil {
 | 
			
		||||
		element.onScrollBoundsChange()
 | 
			
		||||
	}
 | 
			
		||||
	element.core.NotifyContentBoundsChange()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScrollAxes returns the supported axes for scrolling.
 | 
			
		||||
@ -256,6 +273,10 @@ func (element *TextBox) ScrollAxes () (horizontal, vertical bool) {
 | 
			
		||||
	return true, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *TextBox) OnScrollBoundsChange (callback func ()) {
 | 
			
		||||
	element.onScrollBoundsChange = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *TextBox) updateMinimumSize () {
 | 
			
		||||
	textBounds := element.placeholderDrawer.LayoutBounds()
 | 
			
		||||
	element.core.SetMinimumSize (
 | 
			
		||||
@ -286,7 +307,9 @@ func (element *TextBox) scrollToCursor () {
 | 
			
		||||
		element.scroll -= minX - cursorPosition.X
 | 
			
		||||
		if element.scroll < 0 { element.scroll = 0 }
 | 
			
		||||
	}
 | 
			
		||||
	element.core.NotifyContentBoundsChange()
 | 
			
		||||
	if element.onScrollBoundsChange != nil {
 | 
			
		||||
		element.onScrollBoundsChange()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *TextBox) draw () {
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,9 @@ type Core struct {
 | 
			
		||||
 | 
			
		||||
	selectable bool
 | 
			
		||||
	selected   bool
 | 
			
		||||
	hooks tomo.ParentHooks
 | 
			
		||||
 | 
			
		||||
	onMinimumSizeChange func ()
 | 
			
		||||
	onDamage func (region tomo.Canvas)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCore creates a new element core and its corresponding control.
 | 
			
		||||
@ -58,10 +60,16 @@ func (core *Core) MinimumSize () (width, height int) {
 | 
			
		||||
	return core.metrics.minimumWidth, core.metrics.minimumHeight
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetParentHooks fulfils the tomo.Element interface. This should not need to be
 | 
			
		||||
// OnDamage fulfils the tomo.Element interface. This should not need to be
 | 
			
		||||
// overridden.
 | 
			
		||||
func (core *Core) SetParentHooks (hooks tomo.ParentHooks) {
 | 
			
		||||
	core.hooks = hooks
 | 
			
		||||
func (core *Core) OnDamage (callback func (region tomo.Canvas)) {
 | 
			
		||||
	core.onDamage = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnMinimumSizeChange fulfils the tomo.Element interface. This should not need
 | 
			
		||||
// to be overridden.
 | 
			
		||||
func (core *Core) OnMinimumSizeChange (callback func ()) {
 | 
			
		||||
	core.onMinimumSizeChange = callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CoreControl is a struct that can exert control over a Core struct. It can be
 | 
			
		||||
@ -73,41 +81,24 @@ type CoreControl struct {
 | 
			
		||||
	core *Core
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RequestSelection requests that the element's parent send it a selection
 | 
			
		||||
// event. If the request was granted, it returns true. If it was denied, it
 | 
			
		||||
// returns false.
 | 
			
		||||
func (control CoreControl) RequestSelection () (granted bool) {
 | 
			
		||||
	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
 | 
			
		||||
// it doesn't.
 | 
			
		||||
func (control CoreControl) HasImage () (has bool) {
 | 
			
		||||
	return !control.Bounds().Empty()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PushRegion pushes the selected region of pixels to the parent element. This
 | 
			
		||||
// DamageRegion pushes the selected region of pixels to the parent element. This
 | 
			
		||||
// does not need to be called when responding to a resize event.
 | 
			
		||||
func (control CoreControl) PushRegion (bounds image.Rectangle) {
 | 
			
		||||
	control.core.hooks.RunDraw(tomo.Cut(control, bounds))
 | 
			
		||||
func (control CoreControl) DamageRegion (bounds image.Rectangle) {
 | 
			
		||||
	if control.core.onDamage != nil {
 | 
			
		||||
		control.core.onDamage(tomo.Cut(control, bounds))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PushAll pushes all pixels to the parent element. This does not need to be
 | 
			
		||||
// DamageAll pushes all pixels to the parent element. This does not need to be
 | 
			
		||||
// called when responding to a resize event.
 | 
			
		||||
func (control CoreControl) PushAll () {
 | 
			
		||||
	control.PushRegion(control.Bounds())
 | 
			
		||||
func (control CoreControl) DamageAll () {
 | 
			
		||||
	control.DamageRegion(control.Bounds())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AllocateCanvas resizes the canvas, constraining the width and height so that
 | 
			
		||||
@ -128,7 +119,9 @@ func (control CoreControl) SetMinimumSize (width, height int) {
 | 
			
		||||
 | 
			
		||||
	core.metrics.minimumWidth  = width
 | 
			
		||||
	core.metrics.minimumHeight = height
 | 
			
		||||
	core.hooks.RunMinimumSizeChange(width, height)
 | 
			
		||||
	if control.core.onMinimumSizeChange != nil {
 | 
			
		||||
		control.core.onMinimumSizeChange()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if there is an image buffer, and the current size is less
 | 
			
		||||
	// than this new minimum size, send core.parent a resize event.
 | 
			
		||||
@ -143,18 +136,6 @@ 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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NotifyContentBoundsChange notifies the parent element that this element's
 | 
			
		||||
// inner content bounds or scroll position have changed.
 | 
			
		||||
func (control CoreControl) NotifyContentBoundsChange () {
 | 
			
		||||
	control.core.hooks.RunContentBoundsChange()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConstrainSize contstrains the specified width and height to the minimum width
 | 
			
		||||
// and height, and returns wether or not anything ended up being constrained.
 | 
			
		||||
func (control CoreControl) ConstrainSize (
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@ func (element *Mouse) HandleMouseDown (x, y int, button tomo.Button) {
 | 
			
		||||
func (element *Mouse) HandleMouseUp (x, y int, button tomo.Button) {
 | 
			
		||||
	element.drawing = false
 | 
			
		||||
	mousePos := image.Pt(x, y)
 | 
			
		||||
	element.core.PushRegion (artist.Line (
 | 
			
		||||
	element.core.DamageRegion (artist.Line (
 | 
			
		||||
		element.core, element.color, 1,
 | 
			
		||||
		element.lastMousePos, mousePos))
 | 
			
		||||
	element.lastMousePos = mousePos
 | 
			
		||||
@ -63,10 +63,10 @@ func (element *Mouse) HandleMouseUp (x, y int, button tomo.Button) {
 | 
			
		||||
func (element *Mouse) HandleMouseMove (x, y int) {
 | 
			
		||||
	if !element.drawing { return }
 | 
			
		||||
	mousePos := image.Pt(x, y)
 | 
			
		||||
	element.core.PushRegion (artist.Line (
 | 
			
		||||
	element.core.DamageRegion (artist.Line (
 | 
			
		||||
		element.core, element.color, 1,
 | 
			
		||||
		element.lastMousePos, mousePos))
 | 
			
		||||
	element.lastMousePos = mousePos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Mouse) HandleScroll (x, y int, deltaX, deltaY float64) { }
 | 
			
		||||
func (element *Mouse) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
 | 
			
		||||
 | 
			
		||||
@ -23,8 +23,8 @@ type Layout interface {
 | 
			
		||||
	// needs to properly arrange the given slice of layout entries.
 | 
			
		||||
	MinimumSize (entries []LayoutEntry) (width, height int)
 | 
			
		||||
 | 
			
		||||
	// MinimumHeightFor Returns the minimum height the layout needs to lay
 | 
			
		||||
	// FlexibleHeightFor 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)
 | 
			
		||||
	FlexibleHeightFor (entries []LayoutEntry, squeeze int) (height int)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -134,7 +134,9 @@ func (layout Dialog) MinimumSize (
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (layout Dialog) MinimumHeightFor (
 | 
			
		||||
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the
 | 
			
		||||
// specified elements at the given width, taking into account flexible elements.
 | 
			
		||||
func (layout Dialog) FlexibleHeightFor (
 | 
			
		||||
	entries []tomo.LayoutEntry,
 | 
			
		||||
	width int,
 | 
			
		||||
) (
 | 
			
		||||
@ -147,7 +149,7 @@ func (layout Dialog) MinimumHeightFor (
 | 
			
		||||
	if len(entries) > 0 {
 | 
			
		||||
		mainChildHeight := 0
 | 
			
		||||
		if child, flexible := entries[0].Element.(tomo.Flexible); flexible {
 | 
			
		||||
			mainChildHeight = child.MinimumHeightFor(width)
 | 
			
		||||
			mainChildHeight = child.FlexibleHeightFor(width)
 | 
			
		||||
		} else {
 | 
			
		||||
			_, mainChildHeight = entries[0].MinimumSize()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -75,7 +75,9 @@ func (layout Horizontal) MinimumSize (
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (layout Horizontal) MinimumHeightFor (
 | 
			
		||||
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the
 | 
			
		||||
// specified elements at the given width, taking into account flexible elements.
 | 
			
		||||
func (layout Horizontal) FlexibleHeightFor (
 | 
			
		||||
	entries []tomo.LayoutEntry,
 | 
			
		||||
	width int,
 | 
			
		||||
) (
 | 
			
		||||
@ -100,7 +102,7 @@ func (layout Horizontal) MinimumHeightFor (
 | 
			
		||||
			entryWidth = expandingElementWidth
 | 
			
		||||
		}
 | 
			
		||||
		if child, flexible := entry.Element.(tomo.Flexible); flexible {
 | 
			
		||||
			entryHeight = child.MinimumHeightFor(entryWidth)
 | 
			
		||||
			entryHeight = child.FlexibleHeightFor(entryWidth)
 | 
			
		||||
		}
 | 
			
		||||
		if entryHeight > height { height = entryHeight }
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) {
 | 
			
		||||
		var entryMinHeight int
 | 
			
		||||
 | 
			
		||||
		if child, flexible := entry.Element.(tomo.Flexible); flexible {
 | 
			
		||||
			entryMinHeight = child.MinimumHeightFor(width)
 | 
			
		||||
			entryMinHeight = child.FlexibleHeightFor(width)
 | 
			
		||||
		} else {
 | 
			
		||||
			_, entryMinHeight = entry.MinimumSize()
 | 
			
		||||
		}
 | 
			
		||||
@ -102,9 +102,9 @@ func (layout Vertical) MinimumSize (
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MinimumHeightFor Returns the minimum height the layout needs to lay out the
 | 
			
		||||
// FlexibleHeightFor 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 (
 | 
			
		||||
func (layout Vertical) FlexibleHeightFor (
 | 
			
		||||
	entries []tomo.LayoutEntry,
 | 
			
		||||
	width int,
 | 
			
		||||
) (
 | 
			
		||||
@ -118,7 +118,7 @@ func (layout Vertical) MinimumHeightFor (
 | 
			
		||||
	for index, entry := range entries {
 | 
			
		||||
		child, flexible := entry.Element.(tomo.Flexible)
 | 
			
		||||
		if flexible {
 | 
			
		||||
			height += child.MinimumHeightFor(width)
 | 
			
		||||
			height += child.FlexibleHeightFor(width)
 | 
			
		||||
		} else {
 | 
			
		||||
			_, entryHeight := entry.MinimumSize()
 | 
			
		||||
			height += entryHeight
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user