Added a selectability core to reduce complexity of selectables
This commit is contained in:
		
							parent
							
								
									b2b2a80a06
								
							
						
					
					
						commit
						9422ff6198
					
				| @ -9,24 +9,28 @@ import "git.tebibyte.media/sashakoshka/tomo/elements/core" | |||||||
| // Button is a clickable button. | // Button is a clickable button. | ||||||
| type Button struct { | type Button struct { | ||||||
| 	*core.Core | 	*core.Core | ||||||
|  | 	*core.SelectableCore | ||||||
| 	core core.CoreControl | 	core core.CoreControl | ||||||
| 	 | 	selectableControl core.SelectableCoreControl | ||||||
| 	pressed  bool |  | ||||||
| 	enabled  bool |  | ||||||
| 	selected bool |  | ||||||
| 
 |  | ||||||
| 	text   string |  | ||||||
| 	drawer artist.TextDrawer | 	drawer artist.TextDrawer | ||||||
| 
 | 
 | ||||||
|  | 	pressed bool | ||||||
|  | 	text    string | ||||||
|  | 	 | ||||||
| 	onClick func () | 	onClick func () | ||||||
| 	onSelectionRequest func () (granted bool) |  | ||||||
| 	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewButton creates a new button with the specified label text. | // NewButton creates a new button with the specified label text. | ||||||
| func NewButton (text string) (element *Button) { | func NewButton (text string) (element *Button) { | ||||||
| 	element = &Button { enabled: true } | 	element = &Button { } | ||||||
| 	element.Core, element.core = core.NewCore(element) | 	element.Core, element.core = core.NewCore(element) | ||||||
|  | 	element.SelectableCore, | ||||||
|  | 	element.selectableControl = core.NewSelectableCore (func () { | ||||||
|  | 		if element.core.HasImage () { | ||||||
|  | 			element.draw() | ||||||
|  | 			element.core.DamageAll() | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
| 	element.drawer.SetFace(theme.FontFaceRegular()) | 	element.drawer.SetFace(theme.FontFaceRegular()) | ||||||
| 	element.SetText(text) | 	element.SetText(text) | ||||||
| 	return | 	return | ||||||
| @ -38,8 +42,8 @@ func (element *Button) Resize (width, height int) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (element *Button) HandleMouseDown (x, y int, button tomo.Button) { | func (element *Button) HandleMouseDown (x, y int, button tomo.Button) { | ||||||
| 	if !element.enabled { return } | 	if !element.Enabled()  { return } | ||||||
| 	if !element.selected { element.Select() } | 	if !element.Selected() { element.Select() } | ||||||
| 	if button != tomo.ButtonLeft { return } | 	if button != tomo.ButtonLeft { return } | ||||||
| 	element.pressed = true | 	element.pressed = true | ||||||
| 	if element.core.HasImage() { | 	if element.core.HasImage() { | ||||||
| @ -59,7 +63,7 @@ func (element *Button) HandleMouseUp (x, y int, button tomo.Button) { | |||||||
| 	within := image.Point { x, y }. | 	within := image.Point { x, y }. | ||||||
| 		In(element.Bounds()) | 		In(element.Bounds()) | ||||||
| 		 | 		 | ||||||
| 	if !element.enabled { return } | 	if !element.Enabled() { return } | ||||||
| 	if within && element.onClick != nil { | 	if within && element.onClick != nil { | ||||||
| 		element.onClick() | 		element.onClick() | ||||||
| 	} | 	} | ||||||
| @ -69,7 +73,7 @@ func (element *Button) HandleMouseMove (x, y int) { } | |||||||
| func (element *Button) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } | func (element *Button) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } | ||||||
| 
 | 
 | ||||||
| func (element *Button) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { | func (element *Button) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { | ||||||
| 	if !element.enabled { return } | 	if !element.Enabled() { return } | ||||||
| 	if key == tomo.KeyEnter { | 	if key == tomo.KeyEnter { | ||||||
| 		element.pressed = true | 		element.pressed = true | ||||||
| 		if element.core.HasImage() { | 		if element.core.HasImage() { | ||||||
| @ -86,61 +90,13 @@ func (element *Button) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { | |||||||
| 			element.draw() | 			element.draw() | ||||||
| 			element.core.DamageAll() | 			element.core.DamageAll() | ||||||
| 		} | 		} | ||||||
| 		if !element.enabled { return } | 		if !element.Enabled() { return } | ||||||
| 		if element.onClick != nil { | 		if element.onClick != nil { | ||||||
| 			element.onClick() | 			element.onClick() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (element *Button) Selected () (selected bool) { |  | ||||||
| 	return element.selected |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *Button) Select () { |  | ||||||
| 	if !element.enabled { return } |  | ||||||
| 	if element.onSelectionRequest != nil { |  | ||||||
| 		element.onSelectionRequest() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *Button) HandleSelection ( |  | ||||||
| 	direction tomo.SelectionDirection, |  | ||||||
| ) ( |  | ||||||
| 	accepted bool, |  | ||||||
| ) { |  | ||||||
| 	direction = direction.Canon() |  | ||||||
| 	if !element.enabled { return false } |  | ||||||
| 	if element.selected && direction != tomo.SelectionDirectionNeutral { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	element.selected = true |  | ||||||
| 	if element.core.HasImage() { |  | ||||||
| 		element.draw() |  | ||||||
| 		element.core.DamageAll() |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *Button) HandleDeselection () { |  | ||||||
| 	element.selected = false |  | ||||||
| 	if element.core.HasImage() { |  | ||||||
| 		element.draw() |  | ||||||
| 		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. | // OnClick sets the function to be called when the button is clicked. | ||||||
| func (element *Button) OnClick (callback func ()) { | func (element *Button) OnClick (callback func ()) { | ||||||
| 	element.onClick = callback | 	element.onClick = callback | ||||||
| @ -148,12 +104,7 @@ func (element *Button) OnClick (callback func ()) { | |||||||
| 
 | 
 | ||||||
| // SetEnabled sets whether this button can be clicked or not. | // SetEnabled sets whether this button can be clicked or not. | ||||||
| func (element *Button) SetEnabled (enabled bool) { | func (element *Button) SetEnabled (enabled bool) { | ||||||
| 	if element.enabled == enabled { return } | 	element.selectableControl.SetEnabled(enabled) | ||||||
| 	element.enabled = enabled |  | ||||||
| 	if element.core.HasImage () { |  | ||||||
| 		element.draw() |  | ||||||
| 		element.core.DamageAll() |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetText sets the button's label text. | // SetText sets the button's label text. | ||||||
| @ -178,7 +129,7 @@ func (element *Button) draw () { | |||||||
| 	artist.FillRectangle ( | 	artist.FillRectangle ( | ||||||
| 		element.core, | 		element.core, | ||||||
| 		theme.ButtonPattern ( | 		theme.ButtonPattern ( | ||||||
| 			element.enabled, | 			element.Enabled(), | ||||||
| 			element.Selected(), | 			element.Selected(), | ||||||
| 			element.pressed), | 			element.pressed), | ||||||
| 		bounds) | 		bounds) | ||||||
| @ -204,6 +155,6 @@ func (element *Button) draw () { | |||||||
| 		offset = offset.Add(theme.SinkOffsetVector()) | 		offset = offset.Add(theme.SinkOffsetVector()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	foreground := theme.ForegroundPattern(element.enabled) | 	foreground := theme.ForegroundPattern(element.Enabled()) | ||||||
| 	element.drawer.Draw(element.core, foreground, offset) | 	element.drawer.Draw(element.core, foreground, offset) | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,25 +9,29 @@ import "git.tebibyte.media/sashakoshka/tomo/elements/core" | |||||||
| // Checkbox is a toggle-able checkbox with a label. | // Checkbox is a toggle-able checkbox with a label. | ||||||
| type Checkbox struct { | type Checkbox struct { | ||||||
| 	*core.Core | 	*core.Core | ||||||
|  | 	*core.SelectableCore | ||||||
| 	core core.CoreControl | 	core core.CoreControl | ||||||
| 
 | 	selectableControl core.SelectableCoreControl | ||||||
| 	pressed  bool |  | ||||||
| 	checked  bool |  | ||||||
| 	enabled  bool |  | ||||||
| 	selected bool |  | ||||||
| 
 |  | ||||||
| 	text   string |  | ||||||
| 	drawer artist.TextDrawer | 	drawer artist.TextDrawer | ||||||
| 
 | 
 | ||||||
| 	onClick func () | 	pressed bool | ||||||
| 	onSelectionRequest func () (granted bool) | 	checked bool | ||||||
| 	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool) | 	text    string | ||||||
|  | 	 | ||||||
|  | 	onToggle func () | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewCheckbox creates a new cbeckbox with the specified label text. | // NewCheckbox creates a new cbeckbox with the specified label text. | ||||||
| func NewCheckbox (text string, checked bool) (element *Checkbox) { | func NewCheckbox (text string, checked bool) (element *Checkbox) { | ||||||
| 	element = &Checkbox { enabled: true, checked: checked } | 	element = &Checkbox { checked: checked } | ||||||
| 	element.Core, element.core = core.NewCore(element) | 	element.Core, element.core = core.NewCore(element) | ||||||
|  | 	element.SelectableCore, | ||||||
|  | 	element.selectableControl = core.NewSelectableCore (func () { | ||||||
|  | 		if element.core.HasImage () { | ||||||
|  | 			element.draw() | ||||||
|  | 			element.core.DamageAll() | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
| 	element.drawer.SetFace(theme.FontFaceRegular()) | 	element.drawer.SetFace(theme.FontFaceRegular()) | ||||||
| 	element.SetText(text) | 	element.SetText(text) | ||||||
| 	return | 	return | ||||||
| @ -40,6 +44,7 @@ func (element *Checkbox) Resize (width, height int) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) { | func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) { | ||||||
|  | 	if !element.Enabled() { return } | ||||||
| 	element.Select() | 	element.Select() | ||||||
| 	element.pressed = true | 	element.pressed = true | ||||||
| 	if element.core.HasImage() { | 	if element.core.HasImage() { | ||||||
| @ -49,7 +54,7 @@ func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (element *Checkbox) HandleMouseUp (x, y int, button tomo.Button) { | func (element *Checkbox) HandleMouseUp (x, y int, button tomo.Button) { | ||||||
| 	if button != tomo.ButtonLeft { return } | 	if button != tomo.ButtonLeft || !element.pressed { return } | ||||||
| 
 | 
 | ||||||
| 	element.pressed = false | 	element.pressed = false | ||||||
| 	within := image.Point { x, y }. | 	within := image.Point { x, y }. | ||||||
| @ -62,8 +67,8 @@ func (element *Checkbox) HandleMouseUp (x, y int, button tomo.Button) { | |||||||
| 		element.draw() | 		element.draw() | ||||||
| 		element.core.DamageAll() | 		element.core.DamageAll() | ||||||
| 	} | 	} | ||||||
| 	if within && element.onClick != nil { | 	if within && element.onToggle != nil { | ||||||
| 		element.onClick() | 		element.onToggle() | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -88,65 +93,15 @@ func (element *Checkbox) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) { | |||||||
| 			element.draw() | 			element.draw() | ||||||
| 			element.core.DamageAll() | 			element.core.DamageAll() | ||||||
| 		} | 		} | ||||||
| 		if element.onClick != nil { | 		if element.onToggle != nil { | ||||||
| 			element.onClick() | 			element.onToggle() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Selected returns whether or not this element is selected. | // OnToggle sets the function to be called when the checkbox is toggled. | ||||||
| func (element *Checkbox) Selected () (selected bool) { | func (element *Checkbox) OnToggle (callback func ()) { | ||||||
| 	return element.selected | 	element.onToggle = callback | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Select requests that this element be selected. |  | ||||||
| func (element *Checkbox) Select () { |  | ||||||
| 	if !element.enabled { return } |  | ||||||
| 	if element.onSelectionRequest != nil { |  | ||||||
| 		element.onSelectionRequest() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *Checkbox) HandleSelection ( |  | ||||||
| 	direction tomo.SelectionDirection, |  | ||||||
| ) ( |  | ||||||
| 	accepted bool, |  | ||||||
| ) { |  | ||||||
| 	direction = direction.Canon() |  | ||||||
| 	if !element.enabled { return false } |  | ||||||
| 	if element.selected && direction != tomo.SelectionDirectionNeutral { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	element.selected = true |  | ||||||
| 	if element.core.HasImage() { |  | ||||||
| 		element.draw() |  | ||||||
| 		element.core.DamageAll() |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *Checkbox) HandleDeselection () { |  | ||||||
| 	element.selected = false |  | ||||||
| 	if element.core.HasImage() { |  | ||||||
| 		element.draw() |  | ||||||
| 		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 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Value reports whether or not the checkbox is currently checked. | // Value reports whether or not the checkbox is currently checked. | ||||||
| @ -156,12 +111,7 @@ func (element *Checkbox) Value () (checked bool) { | |||||||
| 
 | 
 | ||||||
| // SetEnabled sets whether this checkbox can be toggled or not. | // SetEnabled sets whether this checkbox can be toggled or not. | ||||||
| func (element *Checkbox) SetEnabled (enabled bool) { | func (element *Checkbox) SetEnabled (enabled bool) { | ||||||
| 	if element.enabled == enabled { return } | 	element.selectableControl.SetEnabled(enabled) | ||||||
| 	element.enabled = enabled |  | ||||||
| 	if element.core.HasImage () { |  | ||||||
| 		element.draw() |  | ||||||
| 		element.core.DamageAll() |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetText sets the checkbox's label text. | // SetText sets the checkbox's label text. | ||||||
| @ -171,9 +121,15 @@ func (element *Checkbox) SetText (text string) { | |||||||
| 	element.text = text | 	element.text = text | ||||||
| 	element.drawer.SetText([]rune(text)) | 	element.drawer.SetText([]rune(text)) | ||||||
| 	textBounds := element.drawer.LayoutBounds() | 	textBounds := element.drawer.LayoutBounds() | ||||||
| 	element.core.SetMinimumSize ( | 	 | ||||||
| 		textBounds.Dy() + theme.Padding() + textBounds.Dx(), | 	if text == "" { | ||||||
| 		textBounds.Dy()) | 		element.core.SetMinimumSize(textBounds.Dy(), textBounds.Dy()) | ||||||
|  | 	} else { | ||||||
|  | 		element.core.SetMinimumSize ( | ||||||
|  | 			textBounds.Dy() + theme.Padding() + textBounds.Dx(), | ||||||
|  | 			textBounds.Dy()) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
| 	if element.core.HasImage () { | 	if element.core.HasImage () { | ||||||
| 		element.draw() | 		element.draw() | ||||||
| 		element.core.DamageAll() | 		element.core.DamageAll() | ||||||
| @ -188,17 +144,11 @@ func (element *Checkbox) draw () { | |||||||
| 	artist.FillRectangle ( | 	artist.FillRectangle ( | ||||||
| 		element.core, | 		element.core, | ||||||
| 		theme.ButtonPattern ( | 		theme.ButtonPattern ( | ||||||
| 			element.enabled, | 			element.Enabled(), | ||||||
| 			element.Selected(), | 			element.Selected(), | ||||||
| 			element.pressed), | 			element.pressed), | ||||||
| 		boxBounds) | 		boxBounds) | ||||||
| 
 | 
 | ||||||
| 	innerBounds := bounds |  | ||||||
| 	innerBounds.Min.X += theme.Padding() |  | ||||||
| 	innerBounds.Min.Y += theme.Padding() |  | ||||||
| 	innerBounds.Max.X -= theme.Padding() |  | ||||||
| 	innerBounds.Max.Y -= theme.Padding() |  | ||||||
| 
 |  | ||||||
| 	textBounds := element.drawer.LayoutBounds() | 	textBounds := element.drawer.LayoutBounds() | ||||||
| 	offset := image.Point { | 	offset := image.Point { | ||||||
| 		X: bounds.Dy() + theme.Padding(), | 		X: bounds.Dy() + theme.Padding(), | ||||||
| @ -207,7 +157,7 @@ func (element *Checkbox) draw () { | |||||||
| 	offset.Y -= textBounds.Min.Y | 	offset.Y -= textBounds.Min.Y | ||||||
| 	offset.X -= textBounds.Min.X | 	offset.X -= textBounds.Min.X | ||||||
| 
 | 
 | ||||||
| 	foreground := theme.ForegroundPattern(element.enabled) | 	foreground := theme.ForegroundPattern(element.Enabled()) | ||||||
| 	element.drawer.Draw(element.core, foreground, offset) | 	element.drawer.Draw(element.core, foreground, offset) | ||||||
| 	 | 	 | ||||||
| 	if element.checked { | 	if element.checked { | ||||||
| @ -217,7 +167,7 @@ func (element *Checkbox) draw () { | |||||||
| 		} | 		} | ||||||
| 		artist.FillRectangle ( | 		artist.FillRectangle ( | ||||||
| 			element.core, | 			element.core, | ||||||
| 			theme.ForegroundPattern(element.enabled), | 			theme.ForegroundPattern(element.Enabled()), | ||||||
| 			checkBounds) | 			checkBounds) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,10 +10,10 @@ import "git.tebibyte.media/sashakoshka/tomo/elements/core" | |||||||
| // List is an element that contains several objects that a user can select. | // List is an element that contains several objects that a user can select. | ||||||
| type List struct { | type List struct { | ||||||
| 	*core.Core | 	*core.Core | ||||||
|  | 	*core.SelectableCore | ||||||
| 	core core.CoreControl | 	core core.CoreControl | ||||||
|  | 	selectableControl core.SelectableCoreControl | ||||||
| 
 | 
 | ||||||
| 	enabled bool |  | ||||||
| 	selected bool |  | ||||||
| 	pressed bool | 	pressed bool | ||||||
| 	 | 	 | ||||||
| 	contentHeight int | 	contentHeight int | ||||||
| @ -24,16 +24,21 @@ type List struct { | |||||||
| 	scroll int | 	scroll int | ||||||
| 	entries []ListEntry | 	entries []ListEntry | ||||||
| 	 | 	 | ||||||
| 	onSelectionRequest func () (granted bool) |  | ||||||
| 	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool) |  | ||||||
| 	onScrollBoundsChange func () | 	onScrollBoundsChange func () | ||||||
| 	onNoEntrySelected func () | 	onNoEntrySelected func () | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewList creates a new list element with the specified entries. | // NewList creates a new list element with the specified entries. | ||||||
| func NewList (entries ...ListEntry) (element *List) { | func NewList (entries ...ListEntry) (element *List) { | ||||||
| 	element = &List { enabled: true, selectedEntry: -1 } | 	element = &List { selectedEntry: -1 } | ||||||
| 	element.Core, element.core = core.NewCore(element) | 	element.Core, element.core = core.NewCore(element) | ||||||
|  | 	element.SelectableCore, | ||||||
|  | 	element.selectableControl = core.NewSelectableCore (func () { | ||||||
|  | 		if element.core.HasImage () { | ||||||
|  | 			element.draw() | ||||||
|  | 			element.core.DamageAll() | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
| 	 | 	 | ||||||
| 	element.entries = make([]ListEntry, len(entries)) | 	element.entries = make([]ListEntry, len(entries)) | ||||||
| 	for index, entry := range entries { | 	for index, entry := range entries { | ||||||
| @ -70,8 +75,8 @@ func (element *List) Collapse (width, height int) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (element *List) HandleMouseDown (x, y int, button tomo.Button) { | func (element *List) HandleMouseDown (x, y int, button tomo.Button) { | ||||||
| 	if !element.enabled  { return } | 	if !element.Enabled()  { return } | ||||||
| 	if !element.selected { element.Select() } | 	if !element.Selected() { element.Select() } | ||||||
| 	if button != tomo.ButtonLeft { return } | 	if button != tomo.ButtonLeft { return } | ||||||
| 	element.pressed = true | 	element.pressed = true | ||||||
| 	if element.selectUnderMouse(x, y) && element.core.HasImage() { | 	if element.selectUnderMouse(x, y) && element.core.HasImage() { | ||||||
| @ -97,7 +102,7 @@ func (element *List) HandleMouseMove (x, y int) { | |||||||
| func (element *List) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } | func (element *List) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } | ||||||
| 
 | 
 | ||||||
| func (element *List) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { | func (element *List) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { | ||||||
| 	if !element.enabled { return } | 	if !element.Enabled() { return } | ||||||
| 
 | 
 | ||||||
| 	altered := false | 	altered := false | ||||||
| 	switch key { | 	switch key { | ||||||
| @ -119,54 +124,6 @@ func (element *List) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { | |||||||
| 
 | 
 | ||||||
| func (element *List) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { } | func (element *List) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { } | ||||||
| 
 | 
 | ||||||
| func (element *List) Selected () (selected bool) { |  | ||||||
| 	return element.selected |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *List) Select () { |  | ||||||
| 	if !element.enabled { return } |  | ||||||
| 	if element.onSelectionRequest != nil { |  | ||||||
| 		element.onSelectionRequest() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *List) HandleSelection ( |  | ||||||
| 	direction tomo.SelectionDirection, |  | ||||||
| ) ( |  | ||||||
| 	accepted bool, |  | ||||||
| ) { |  | ||||||
| 	direction = direction.Canon() |  | ||||||
| 	if !element.enabled { return false } |  | ||||||
| 	if element.selected && direction != tomo.SelectionDirectionNeutral { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	element.selected = true |  | ||||||
| 	if element.core.HasImage() { |  | ||||||
| 		element.draw() |  | ||||||
| 		element.core.DamageAll() |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *List) HandleDeselection () { |  | ||||||
| 	element.selected = false |  | ||||||
| 	if element.core.HasImage() { |  | ||||||
| 		element.draw() |  | ||||||
| 		element.core.DamageAll() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *List) OnSelectionRequest (callback func () (granted bool)) { |  | ||||||
| 	element.onSelectionRequest = callback |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *List) OnSelectionMotionRequest ( |  | ||||||
| 	callback func (direction tomo.SelectionDirection) (granted bool), |  | ||||||
| ) { |  | ||||||
| 	element.onSelectionMotionRequest = callback |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ScrollContentBounds returns the full content size of the element. | // ScrollContentBounds returns the full content size of the element. | ||||||
| func (element *List) ScrollContentBounds () (bounds image.Rectangle) { | func (element *List) ScrollContentBounds () (bounds image.Rectangle) { | ||||||
| 	return image.Rect ( | 	return image.Rect ( | ||||||
| @ -222,16 +179,6 @@ func (element *List) OnScrollBoundsChange (callback func ()) { | |||||||
| 	element.onScrollBoundsChange = callback | 	element.onScrollBoundsChange = callback | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetEnabled sets whether this list can be interacted with or not. |  | ||||||
| func (element *List) SetEnabled (enabled bool) { |  | ||||||
| 	if element.enabled == enabled { return } |  | ||||||
| 	element.enabled = enabled |  | ||||||
| 	if element.core.HasImage () { |  | ||||||
| 		element.draw() |  | ||||||
| 		element.core.DamageAll() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // OnNoEntrySelected sets a function to be called when the user chooses to | // OnNoEntrySelected sets a function to be called when the user chooses to | ||||||
| // deselect the current selected entry by clicking on empty space within the | // deselect the current selected entry by clicking on empty space within the | ||||||
| // list or by pressing the escape key. | // list or by pressing the escape key. | ||||||
| @ -416,7 +363,7 @@ func (element *List) draw () { | |||||||
| 
 | 
 | ||||||
| 	artist.FillRectangle ( | 	artist.FillRectangle ( | ||||||
| 		element, | 		element, | ||||||
| 		theme.ListPattern(element.selected), | 		theme.ListPattern(element.Selected()), | ||||||
| 		bounds) | 		bounds) | ||||||
| 
 | 
 | ||||||
| 	dot := image.Point { | 	dot := image.Point { | ||||||
| @ -437,6 +384,6 @@ func (element *List) draw () { | |||||||
| 		} | 		} | ||||||
| 		entry.Draw ( | 		entry.Draw ( | ||||||
| 			element, entryPosition, | 			element, entryPosition, | ||||||
| 			element.selectedEntry == index && element.selected) | 			element.selectedEntry == index && element.Selected()) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								elements/basic/switch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								elements/basic/switch.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | package basic | ||||||
| @ -10,10 +10,9 @@ import "git.tebibyte.media/sashakoshka/tomo/elements/core" | |||||||
| // TextBox is a single-line text input. | // TextBox is a single-line text input. | ||||||
| type TextBox struct { | type TextBox struct { | ||||||
| 	*core.Core | 	*core.Core | ||||||
|  | 	*core.SelectableCore | ||||||
| 	core core.CoreControl | 	core core.CoreControl | ||||||
| 	 | 	selectableControl core.SelectableCoreControl | ||||||
| 	enabled  bool |  | ||||||
| 	selected bool |  | ||||||
| 	 | 	 | ||||||
| 	cursor int | 	cursor int | ||||||
| 	scroll int | 	scroll int | ||||||
| @ -25,8 +24,6 @@ type TextBox struct { | |||||||
| 	 | 	 | ||||||
| 	onKeyDown func (key tomo.Key, modifiers tomo.Modifiers) (handled bool) | 	onKeyDown func (key tomo.Key, modifiers tomo.Modifiers) (handled bool) | ||||||
| 	onChange  func () | 	onChange  func () | ||||||
| 	onSelectionRequest func () (granted bool) |  | ||||||
| 	onSelectionMotionRequest func (tomo.SelectionDirection) (granted bool) |  | ||||||
| 	onScrollBoundsChange func () | 	onScrollBoundsChange func () | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -34,8 +31,15 @@ type TextBox struct { | |||||||
| // a value. When the value is empty, the placeholder will be displayed in gray | // a value. When the value is empty, the placeholder will be displayed in gray | ||||||
| // text. | // text. | ||||||
| func NewTextBox (placeholder, value string) (element *TextBox) { | func NewTextBox (placeholder, value string) (element *TextBox) { | ||||||
| 	element = &TextBox { enabled: true } | 	element = &TextBox { } | ||||||
| 	element.Core, element.core = core.NewCore(element) | 	element.Core, element.core = core.NewCore(element) | ||||||
|  | 	element.SelectableCore, | ||||||
|  | 	element.selectableControl = core.NewSelectableCore (func () { | ||||||
|  | 		if element.core.HasImage () { | ||||||
|  | 			element.draw() | ||||||
|  | 			element.core.DamageAll() | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
| 	element.placeholderDrawer.SetFace(theme.FontFaceRegular()) | 	element.placeholderDrawer.SetFace(theme.FontFaceRegular()) | ||||||
| 	element.valueDrawer.SetFace(theme.FontFaceRegular()) | 	element.valueDrawer.SetFace(theme.FontFaceRegular()) | ||||||
| 	element.placeholder = placeholder | 	element.placeholder = placeholder | ||||||
| @ -55,8 +59,8 @@ func (element *TextBox) Resize (width, height int) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (element *TextBox) HandleMouseDown (x, y int, button tomo.Button) { | func (element *TextBox) HandleMouseDown (x, y int, button tomo.Button) { | ||||||
| 	if !element.enabled { return } | 	if !element.Enabled()  { return } | ||||||
| 	if !element.selected { element.Select() } | 	if !element.Selected() { element.Select() } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (element *TextBox) HandleMouseUp (x, y int, button tomo.Button) { } | func (element *TextBox) HandleMouseUp (x, y int, button tomo.Button) { } | ||||||
| @ -133,62 +137,6 @@ func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) { | |||||||
| 
 | 
 | ||||||
| func (element *TextBox) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { } | func (element *TextBox) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { } | ||||||
| 
 | 
 | ||||||
| func (element *TextBox) Selected () (selected bool) { |  | ||||||
| 	return element.selected |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *TextBox) Select () { |  | ||||||
| 	if element.onSelectionRequest != nil { |  | ||||||
| 		element.onSelectionRequest() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *TextBox) HandleSelection ( |  | ||||||
| 	direction tomo.SelectionDirection, |  | ||||||
| ) ( |  | ||||||
| 	accepted bool, |  | ||||||
| ) { |  | ||||||
| 	direction = direction.Canon() |  | ||||||
| 	if !element.enabled { return false } |  | ||||||
| 	if element.selected && direction != tomo.SelectionDirectionNeutral { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	element.selected = true |  | ||||||
| 	if element.core.HasImage() { |  | ||||||
| 		element.draw() |  | ||||||
| 		element.core.DamageAll() |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *TextBox) HandleDeselection () { |  | ||||||
| 	element.selected = false |  | ||||||
| 	if element.core.HasImage() { |  | ||||||
| 		element.draw() |  | ||||||
| 		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.DamageAll() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (element *TextBox) SetPlaceholder (placeholder string) { | func (element *TextBox) SetPlaceholder (placeholder string) { | ||||||
| 	if element.placeholder == placeholder { return } | 	if element.placeholder == placeholder { return } | ||||||
| 	 | 	 | ||||||
| @ -325,11 +273,11 @@ func (element *TextBox) draw () { | |||||||
| 	artist.FillRectangle ( | 	artist.FillRectangle ( | ||||||
| 		element.core, | 		element.core, | ||||||
| 		theme.InputPattern ( | 		theme.InputPattern ( | ||||||
| 			element.enabled, | 			element.Enabled(), | ||||||
| 			element.Selected()), | 			element.Selected()), | ||||||
| 		bounds) | 		bounds) | ||||||
| 
 | 
 | ||||||
| 	if len(element.text) == 0 && !element.selected { | 	if len(element.text) == 0 && !element.Selected() { | ||||||
| 		// draw placeholder | 		// draw placeholder | ||||||
| 		textBounds := element.placeholderDrawer.LayoutBounds() | 		textBounds := element.placeholderDrawer.LayoutBounds() | ||||||
| 		offset := image.Point { | 		offset := image.Point { | ||||||
| @ -348,13 +296,13 @@ func (element *TextBox) draw () { | |||||||
| 			X: theme.Padding() - element.scroll, | 			X: theme.Padding() - element.scroll, | ||||||
| 			Y: theme.Padding(), | 			Y: theme.Padding(), | ||||||
| 		} | 		} | ||||||
| 		foreground := theme.ForegroundPattern(element.enabled) | 		foreground := theme.ForegroundPattern(element.Enabled()) | ||||||
| 		element.valueDrawer.Draw ( | 		element.valueDrawer.Draw ( | ||||||
| 			element.core, | 			element.core, | ||||||
| 			foreground, | 			foreground, | ||||||
| 			offset.Sub(textBounds.Min)) | 			offset.Sub(textBounds.Min)) | ||||||
| 
 | 
 | ||||||
| 		if element.selected { | 		if element.Selected() { | ||||||
| 			// cursor | 			// cursor | ||||||
| 			cursorPosition := element.valueDrawer.PositionOf ( | 			cursorPosition := element.valueDrawer.PositionOf ( | ||||||
| 				element.cursor) | 				element.cursor) | ||||||
|  | |||||||
							
								
								
									
										111
									
								
								elements/core/selectable.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								elements/core/selectable.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | |||||||
|  | package core | ||||||
|  | 
 | ||||||
|  | import "git.tebibyte.media/sashakoshka/tomo" | ||||||
|  | 
 | ||||||
|  | // SelectableCore is a struct that can be embedded into objects to make them | ||||||
|  | // selectable, giving them the default selectability behavior. | ||||||
|  | type SelectableCore struct { | ||||||
|  | 	selected bool | ||||||
|  | 	enabled  bool | ||||||
|  | 	drawSelectionChange func () | ||||||
|  | 	onSelectionRequest func () (granted bool) | ||||||
|  | 	onSelectionMotionRequest func(tomo.SelectionDirection) (granted bool) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewSelectableCore creates a new selectability core and its corresponding | ||||||
|  | // control. If your element needs to visually update itself when it's selection | ||||||
|  | // state changes (which it should), a callback to draw and push the update can | ||||||
|  | // be specified.  | ||||||
|  | func NewSelectableCore ( | ||||||
|  | 	drawSelectionChange func (), | ||||||
|  | ) (	 | ||||||
|  | 	core *SelectableCore, | ||||||
|  | 	control SelectableCoreControl, | ||||||
|  | ) { | ||||||
|  | 	core = &SelectableCore { | ||||||
|  | 		drawSelectionChange: drawSelectionChange, | ||||||
|  | 		enabled: true, | ||||||
|  | 	} | ||||||
|  | 	control = SelectableCoreControl { core: core } | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Selected returns whether or not this element is currently selected. | ||||||
|  | func (core *SelectableCore) Selected () (selected bool) { | ||||||
|  | 	return core.selected | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Select selects this element, if its parent element grants the request. | ||||||
|  | func (core *SelectableCore) Select () { | ||||||
|  | 	if !core.enabled { return } | ||||||
|  | 	if core.onSelectionRequest != nil { | ||||||
|  | 		core.onSelectionRequest() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // HandleSelection causes this element to mark itself as selected, if it can | ||||||
|  | // currently be. Otherwise, it will return false and do nothing. | ||||||
|  | func (core *SelectableCore) HandleSelection ( | ||||||
|  | 	direction tomo.SelectionDirection, | ||||||
|  | ) ( | ||||||
|  | 	accepted bool, | ||||||
|  | ) { | ||||||
|  | 	direction = direction.Canon() | ||||||
|  | 	if !core.enabled { return false } | ||||||
|  | 	if core.selected && direction != tomo.SelectionDirectionNeutral { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	core.selected = true | ||||||
|  | 	if core.drawSelectionChange != nil { core.drawSelectionChange() } | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // HandleDeselection causes this element to mark itself as deselected. | ||||||
|  | func (core *SelectableCore) HandleDeselection () { | ||||||
|  | 	core.selected = false | ||||||
|  | 	if core.drawSelectionChange != nil { core.drawSelectionChange() } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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. | ||||||
|  | func (core *SelectableCore) OnSelectionRequest (callback func () (granted bool)) { | ||||||
|  | 	core.onSelectionRequest = callback | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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. | ||||||
|  | func (core *SelectableCore) OnSelectionMotionRequest ( | ||||||
|  | 	callback func (direction tomo.SelectionDirection) (granted bool), | ||||||
|  | ) { | ||||||
|  | 	core.onSelectionMotionRequest = callback | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Enabled returns whether or not the element is enabled. | ||||||
|  | func (core *SelectableCore) Enabled () (enabled bool) { | ||||||
|  | 	return core.enabled | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SelectableCoreControl is a struct that can be used to exert control over a | ||||||
|  | // selectability core. It must not be directly embedded into an element, but | ||||||
|  | // instead kept as a private member. When a SelectableCore struct is created, a | ||||||
|  | // corresponding SelectableCoreControl struct is linked to it and returned | ||||||
|  | // alongside it. | ||||||
|  | type SelectableCoreControl struct { | ||||||
|  | 	core *SelectableCore | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetEnabled sets whether the selectability core is enabled. If the state | ||||||
|  | // changes, this will call drawSelectionChange. | ||||||
|  | func (control SelectableCoreControl) SetEnabled (enabled bool) { | ||||||
|  | 	if control.core.enabled == enabled { return } | ||||||
|  | 	control.core.enabled = enabled | ||||||
|  | 	if !enabled { control.core.selected = false } | ||||||
|  | 	if control.core.drawSelectionChange != nil { | ||||||
|  | 		control.core.drawSelectionChange() | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -30,7 +30,7 @@ func run () { | |||||||
| 	disabledCheckbox.SetEnabled(false) | 	disabledCheckbox.SetEnabled(false) | ||||||
| 	container.Adopt(disabledCheckbox, false) | 	container.Adopt(disabledCheckbox, false) | ||||||
| 	vsync := basic.NewCheckbox("Enable vsync", false) | 	vsync := basic.NewCheckbox("Enable vsync", false) | ||||||
| 	vsync.OnClick (func () { | 	vsync.OnToggle (func () { | ||||||
| 		if vsync.Value() { | 		if vsync.Value() { | ||||||
| 			popups.NewDialog ( | 			popups.NewDialog ( | ||||||
| 				popups.DialogKindInfo, | 				popups.DialogKindInfo, | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user