Migrated over some elements

This commit is contained in:
Sasha Koshka 2023-04-14 22:03:22 -04:00
parent 4c6f1f80e7
commit 68128c94d8
16 changed files with 526 additions and 410 deletions

View File

@ -1,24 +1,19 @@
package elements
import "image"
// import "runtime/debug"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// import "git.tebibyte.media/sashakoshka/tomo/artist"
// import "git.tebibyte.media/sashakoshka/tomo/shatter"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
// Button is a clickable button.
type Button struct {
*core.Core
*core.FocusableCore
core core.CoreControl
focusableControl core.FocusableCoreControl
entity tomo.FocusableEntity
drawer textdraw.Drawer
enabled bool
pressed bool
text string
@ -36,9 +31,6 @@ type Button struct {
func NewButton (text string) (element *Button) {
element = &Button { showText: true }
element.theme.Case = tomo.C("tomo", "button")
element.Core, element.core = core.NewCore(element, element.drawAll)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.core, element.drawAndPush)
element.drawer.SetFace (element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
@ -46,42 +38,11 @@ func NewButton (text string) (element *Button) {
return
}
func (element *Button) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return }
if !element.Focused() { element.Focus() }
if button != input.ButtonLeft { return }
element.pressed = true
element.drawAndPush()
}
func (element *Button) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft { return }
element.pressed = false
within := image.Point { x, y }.
In(element.Bounds())
if element.Enabled() && within && element.onClick != nil {
element.onClick()
}
element.drawAndPush()
}
func (element *Button) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if !element.Enabled() { return }
if key == input.KeyEnter {
element.pressed = true
element.drawAndPush()
}
}
func (element *Button) HandleKeyUp(key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter && element.pressed {
element.pressed = false
element.drawAndPush()
if !element.Enabled() { return }
if element.onClick != nil {
element.onClick()
}
}
// Bind binds this element to an entity.
func (element *Button) Bind (entity tomo.Entity) {
if entity == nil { element.entity = nil; return }
element.entity = entity.(tomo.FocusableEntity)
element.updateMinimumSize()
}
// OnClick sets the function to be called when the button is clicked.
@ -89,19 +50,33 @@ func (element *Button) OnClick (callback func ()) {
element.onClick = callback
}
// Focus gives this element input focus.
func (element *Button) Focus () {
if element.entity == nil { return }
if !element.entity.Focused() { element.entity.Focus() }
}
// Enabled returns whether this button is enabled or not.
func (element *Button) Enabled () bool {
return element.enabled
}
// SetEnabled sets whether this button can be clicked or not.
func (element *Button) SetEnabled (enabled bool) {
element.focusableControl.SetEnabled(enabled)
if element.enabled == enabled { return }
element.enabled = enabled
if element.entity == nil { return }
element.entity.Invalidate()
}
// SetText sets the button's label text.
func (element *Button) SetText (text string) {
if element.text == text { return }
element.text = text
element.drawer.SetText([]rune(text))
if element.entity == nil { return }
element.updateMinimumSize()
element.drawAndPush()
element.entity.Invalidate()
}
// SetIcon sets the icon of the button. Passing theme.IconNone removes the
@ -109,23 +84,22 @@ func (element *Button) SetText (text string) {
func (element *Button) SetIcon (id tomo.Icon) {
if id == tomo.IconNone {
element.hasIcon = false
element.updateMinimumSize()
element.drawAndPush()
} else {
if element.hasIcon && element.iconId == id { return }
element.hasIcon = true
element.iconId = id
}
element.updateMinimumSize()
element.drawAndPush()
element.entity.Invalidate()
}
// ShowText sets whether or not the button's text will be displayed.
func (element *Button) ShowText (showText bool) {
if element.showText == showText { return }
element.showText = showText
if element.entity == nil { return }
element.updateMinimumSize()
element.drawAndPush()
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
@ -135,74 +109,30 @@ func (element *Button) SetTheme (new tomo.Theme) {
element.drawer.SetFace (element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
if element.entity == nil { return }
element.updateMinimumSize()
element.drawAndPush()
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *Button) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
if element.entity == nil { return }
element.updateMinimumSize()
element.drawAndPush()
element.entity.Invalidate()
}
func (element *Button) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
textBounds := element.drawer.LayoutBounds()
minimumSize := textBounds.Sub(textBounds.Min)
// Draw causes the element to draw to the specified destination canvas.
func (element *Button) Draw (destination canvas.Canvas) {
if element.entity == nil { return }
if element.hasIcon {
icon := element.theme.Icon(element.iconId, tomo.IconSizeSmall)
if icon != nil {
bounds := icon.Bounds()
if element.showText {
minimumSize.Max.X += bounds.Dx()
minimumSize.Max.X += margin.X
} else {
minimumSize.Max.X = bounds.Dx()
}
}
}
minimumSize = padding.Inverse().Apply(minimumSize)
element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
}
func (element *Button) state () tomo.State {
return tomo.State {
Disabled: !element.Enabled(),
Focused: element.Focused(),
Pressed: element.pressed,
}
}
func (element *Button) drawAndPush () {
if element.core.HasImage () {
element.drawAll()
element.core.DamageAll()
}
}
func (element *Button) drawAll () {
element.drawBackground()
element.drawText()
}
func (element *Button) drawBackground () []image.Rectangle {
state := element.state()
bounds := element.Bounds()
bounds := element.entity.Bounds()
pattern := element.theme.Pattern(tomo.PatternButton, state)
pattern.Draw(element.core, bounds)
return []image.Rectangle { bounds }
}
func (element *Button) drawText () {
state := element.state()
bounds := element.Bounds()
pattern.Draw(destination, bounds)
foreground := element.theme.Color(tomo.ColorForeground, state)
sink := element.theme.Sink(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
@ -240,7 +170,7 @@ func (element *Button) drawText () {
}
offset.X += addedWidth / 2
icon.Draw(element.core, foreground, iconOffset)
icon.Draw(destination, foreground, iconOffset)
}
}
@ -248,6 +178,84 @@ func (element *Button) drawText () {
if element.pressed {
offset = offset.Add(sink)
}
element.drawer.Draw(element.core, foreground, offset)
element.drawer.Draw(destination, foreground, offset)
}
}
func (element *Button) HandleFocusChange () {
if element.entity == nil { return }
element.entity.Invalidate()
}
func (element *Button) HandleMouseDown (x, y int, button input.Button) {
if element.entity == nil { return }
if !element.Enabled() { return }
element.Focus()
if button != input.ButtonLeft { return }
element.pressed = true
element.entity.Invalidate()
}
func (element *Button) HandleMouseUp (x, y int, button input.Button) {
if element.entity == nil { return }
if button != input.ButtonLeft { return }
element.pressed = false
within := image.Point { x, y }.In(element.entity.Bounds())
if element.Enabled() && within && element.onClick != nil {
element.onClick()
}
element.entity.Invalidate()
}
func (element *Button) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if element.entity == nil { return }
if !element.Enabled() { return }
if key == input.KeyEnter {
element.pressed = true
element.entity.Invalidate()
}
}
func (element *Button) HandleKeyUp(key input.Key, modifiers input.Modifiers) {
if element.entity == nil { return }
if key == input.KeyEnter && element.pressed {
element.pressed = false
element.entity.Invalidate()
if !element.Enabled() { return }
if element.onClick != nil {
element.onClick()
}
}
}
func (element *Button) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
textBounds := element.drawer.LayoutBounds()
minimumSize := textBounds.Sub(textBounds.Min)
if element.hasIcon {
icon := element.theme.Icon(element.iconId, tomo.IconSizeSmall)
if icon != nil {
bounds := icon.Bounds()
if element.showText {
minimumSize.Max.X += bounds.Dx()
minimumSize.Max.X += margin.X
} else {
minimumSize.Max.X = bounds.Dx()
}
}
}
minimumSize = padding.Inverse().Apply(minimumSize)
element.entity.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
}
func (element *Button) state () tomo.State {
return tomo.State {
Disabled: !element.Enabled(),
Focused: element.entity.Focused(),
Pressed: element.pressed,
}
}

View File

@ -3,19 +3,17 @@ package elements
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// Checkbox is a toggle-able checkbox with a label.
type Checkbox struct {
*core.Core
*core.FocusableCore
core core.CoreControl
focusableControl core.FocusableCoreControl
entity tomo.FocusableEntity
drawer textdraw.Drawer
enabled bool
pressed bool
checked bool
text string
@ -30,9 +28,6 @@ type Checkbox struct {
func NewCheckbox (text string, checked bool) (element *Checkbox) {
element = &Checkbox { checked: checked }
element.theme.Case = tomo.C("tomo", "checkbox")
element.Core, element.core = core.NewCore(element, element.draw)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.core, element.redo)
element.drawer.SetFace (element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
@ -40,57 +35,11 @@ func NewCheckbox (text string, checked bool) (element *Checkbox) {
return
}
func (element *Checkbox) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return }
element.Focus()
element.pressed = true
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
}
func (element *Checkbox) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft || !element.pressed { return }
element.pressed = false
within := image.Point { x, y }.
In(element.Bounds())
if within {
element.checked = !element.checked
}
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
if within && element.onToggle != nil {
element.onToggle()
}
}
func (element *Checkbox) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter {
element.pressed = true
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
}
}
func (element *Checkbox) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter && element.pressed {
element.pressed = false
element.checked = !element.checked
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
if element.onToggle != nil {
element.onToggle()
}
}
// Bind binds this element to an entity.
func (element *Checkbox) Bind (entity tomo.Entity) {
if entity == nil { element.entity = nil; return }
element.entity = entity.(tomo.FocusableEntity)
element.updateMinimumSize()
}
// OnToggle sets the function to be called when the checkbox is toggled.
@ -103,23 +52,33 @@ func (element *Checkbox) Value () (checked bool) {
return element.checked
}
// Focus gives this element input focus.
func (element *Checkbox) Focus () {
if element.entity == nil { return }
if !element.entity.Focused() { element.entity.Focus() }
}
// Enabled returns whether this checkbox is enabled or not.
func (element *Checkbox) Enabled () bool {
return element.enabled
}
// SetEnabled sets whether this checkbox can be toggled or not.
func (element *Checkbox) SetEnabled (enabled bool) {
element.focusableControl.SetEnabled(enabled)
if element.enabled == enabled { return }
element.enabled = enabled
if element.entity == nil { return }
element.entity.Invalidate()
}
// SetText sets the checkbox's label text.
func (element *Checkbox) SetText (text string) {
if element.text == text { return }
element.text = text
element.drawer.SetText([]rune(text))
if element.entity == nil { return }
element.updateMinimumSize()
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
@ -129,53 +88,38 @@ func (element *Checkbox) SetTheme (new tomo.Theme) {
element.drawer.SetFace (element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
if element.entity == nil { return }
element.updateMinimumSize()
element.redo()
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *Checkbox) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
if element.entity == nil { return }
element.updateMinimumSize()
element.redo()
element.entity.Invalidate()
}
func (element *Checkbox) updateMinimumSize () {
textBounds := element.drawer.LayoutBounds()
if element.text == "" {
element.core.SetMinimumSize(textBounds.Dy(), textBounds.Dy())
} else {
margin := element.theme.Margin(tomo.PatternBackground)
element.core.SetMinimumSize (
textBounds.Dy() + margin.X + textBounds.Dx(),
textBounds.Dy())
}
}
func (element *Checkbox) redo () {
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
func (element *Checkbox) draw () {
bounds := element.Bounds()
// Draw causes the element to draw to the specified destination canvas.
func (element *Checkbox) Draw (destination canvas.Canvas) {
if element.entity == nil { return }
bounds := element.entity.Bounds()
boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
state := tomo.State {
Disabled: !element.Enabled(),
Focused: element.Focused(),
Focused: element.entity.Focused(),
Pressed: element.pressed,
On: element.checked,
}
element.core.DrawBackground (
element.theme.Pattern(tomo.PatternBackground, state))
element.entity.DrawBackground(destination, bounds)
pattern := element.theme.Pattern(tomo.PatternButton, state)
pattern.Draw(element.core, boxBounds)
pattern.Draw(destination, boxBounds)
textBounds := element.drawer.LayoutBounds()
margin := element.theme.Margin(tomo.PatternBackground)
@ -187,5 +131,61 @@ func (element *Checkbox) draw () {
offset.X -= textBounds.Min.X
foreground := element.theme.Color(tomo.ColorForeground, state)
element.drawer.Draw(element.core, foreground, offset)
element.drawer.Draw(destination, foreground, offset)
}
func (element *Checkbox) HandleMouseDown (x, y int, button input.Button) {
if element.entity == nil { return }
if !element.Enabled() { return }
element.Focus()
element.pressed = true
element.entity.Invalidate()
}
func (element *Checkbox) HandleMouseUp (x, y int, button input.Button) {
if element.entity == nil { return }
if button != input.ButtonLeft || !element.pressed { return }
element.pressed = false
within := image.Point { x, y }.In(element.entity.Bounds())
if within {
element.checked = !element.checked
}
element.entity.Invalidate()
if within && element.onToggle != nil {
element.onToggle()
}
}
func (element *Checkbox) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if element.entity == nil { return }
if key == input.KeyEnter {
element.pressed = true
element.entity.Invalidate()
}
}
func (element *Checkbox) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if element.entity == nil { return }
if key == input.KeyEnter && element.pressed {
element.pressed = false
element.checked = !element.checked
element.entity.Invalidate()
if element.onToggle != nil {
element.onToggle()
}
}
}
func (element *Checkbox) updateMinimumSize () {
textBounds := element.drawer.LayoutBounds()
if element.text == "" {
element.entity.SetMinimumSize(textBounds.Dy(), textBounds.Dy())
} else {
margin := element.theme.Margin(tomo.PatternBackground)
element.entity.SetMinimumSize (
textBounds.Dy() + margin.X + textBounds.Dx(),
textBounds.Dy())
}
}

View File

@ -2,47 +2,72 @@ package elements
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
// Icon is an element capable of displaying a singular icon.
type Icon struct {
*core.Core
core core.CoreControl
theme theme.Wrapped
id tomo.Icon
size tomo.IconSize
entity tomo.Entity
theme theme.Wrapped
id tomo.Icon
size tomo.IconSize
}
// Icon creates a new icon element.
func NewIcon (id tomo.Icon, size tomo.IconSize) (element *Icon) {
element = &Icon {
id: id,
size: size,
}
element.theme.Case = tomo.C("tomo", "icon")
element.Core, element.core = core.NewCore(element, element.draw)
element.updateMinimumSize()
return
}
// Bind binds this element to an entity.
func (element *Icon) Bind (entity tomo.Entity) {
if entity == nil { element.entity = nil; return }
element.entity = entity
element.updateMinimumSize()
}
// SetIcon sets the element's icon.
func (element *Icon) SetIcon (id tomo.Icon, size tomo.IconSize) {
element.id = id
element.size = size
if element.entity == nil { return }
element.updateMinimumSize()
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *Icon) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
if element.entity == nil { return }
element.updateMinimumSize()
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
element.entity.Invalidate()
}
// Draw causes the element to draw to the specified destination canvas.
func (element *Icon) Draw (destination canvas.Canvas) {
if element.entity == nil { return }
bounds := element.entity.Bounds()
state := tomo.State { }
element.theme.
Pattern(tomo.PatternBackground, state).
Draw(destination, bounds)
icon := element.icon()
if icon != nil {
iconBounds := icon.Bounds()
offset := image.Pt (
(bounds.Dx() - iconBounds.Dx()) / 2,
(bounds.Dy() - iconBounds.Dy()) / 2)
icon.Draw (
destination,
element.theme.Color(tomo.ColorForeground, state),
bounds.Min.Add(offset))
}
}
@ -53,28 +78,9 @@ func (element *Icon) icon () artist.Icon {
func (element *Icon) updateMinimumSize () {
icon := element.icon()
if icon == nil {
element.core.SetMinimumSize(0, 0)
element.entity.SetMinimumSize(0, 0)
} else {
bounds := icon.Bounds()
element.core.SetMinimumSize(bounds.Dx(), bounds.Dy())
}
}
func (element *Icon) draw () {
bounds := element.Bounds()
state := tomo.State { }
element.theme.
Pattern(tomo.PatternBackground, state).
Draw(element.core, bounds)
icon := element.icon()
if icon != nil {
iconBounds := icon.Bounds()
offset := image.Pt (
(bounds.Dx() - iconBounds.Dx()) / 2,
(bounds.Dy() - iconBounds.Dy()) / 2)
icon.Draw (
element.core,
element.theme.Color(tomo.ColorForeground, state),
bounds.Min.Add(offset))
element.entity.SetMinimumSize(bounds.Dx(), bounds.Dy())
}
}

View File

@ -1,25 +1,35 @@
package elements
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/artist/patterns"
// TODO: this element is lame need to make it better
// Image is an element capable of displaying an image.
type Image struct {
*core.Core
core core.CoreControl
entity tomo.Entity
buffer canvas.Canvas
}
// NewImage creates a new image element.
func NewImage (image image.Image) (element *Image) {
element = &Image { buffer: canvas.FromImage(image) }
element.Core, element.core = core.NewCore(element, element.draw)
bounds := image.Bounds()
element.core.SetMinimumSize(bounds.Dx(), bounds.Dy())
return
}
func (element *Image) draw () {
(patterns.Texture { Canvas: element.buffer }).
Draw(element.core, element.Bounds())
// Bind binds this element to an entity.
func (element *Image) Bind (entity tomo.Entity) {
if entity == nil { element.entity = nil; return }
element.entity = entity
bounds := element.buffer.Bounds()
element.entity.SetMinimumSize(bounds.Dx(), bounds.Dy())
}
// Draw causes the element to draw to the specified destination canvas.
func (element *Image) Draw (destination canvas.Canvas) {
if element.entity == nil { return }
(patterns.Texture { Canvas: element.buffer }).
Draw(destination, element.entity.Bounds())
}

View File

@ -2,16 +2,15 @@ package elements
import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// Label is a simple text box.
type Label struct {
*core.Core
core core.CoreControl
entity tomo.FlexibleEntity
align textdraw.Align
wrap bool
text string
@ -19,11 +18,10 @@ type Label struct {
forcedColumns int
forcedRows int
minHeight int
config config.Wrapped
theme theme.Wrapped
onFlexibleHeightChange func ()
}
// NewLabel creates a new label. If wrap is set to true, the text inside will be
@ -31,7 +29,6 @@ type Label struct {
func NewLabel (text string, wrap bool) (element *Label) {
element = &Label { }
element.theme.Case = tomo.C("tomo", "label")
element.Core, element.core = core.NewCore(element, element.handleResize)
element.drawer.SetFace (element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
@ -40,29 +37,11 @@ func NewLabel (text string, wrap bool) (element *Label) {
return
}
func (element *Label) redo () {
face := element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal)
element.drawer.SetFace(face)
// Bind binds this element to an entity.
func (element *Label) Bind (entity tomo.Entity) {
if entity == nil { element.entity = nil; return }
element.entity = entity.(tomo.FlexibleEntity)
element.updateMinimumSize()
bounds := element.Bounds()
if element.wrap {
element.drawer.SetMaxWidth(bounds.Dx())
element.drawer.SetMaxHeight(bounds.Dy())
}
element.draw()
element.core.DamageAll()
}
func (element *Label) handleResize () {
bounds := element.Bounds()
if element.wrap {
element.drawer.SetMaxWidth(bounds.Dx())
element.drawer.SetMaxHeight(bounds.Dy())
}
element.draw()
return
}
// EmCollapse forces a minimum width and height upon the label. The width is
@ -73,6 +52,7 @@ func (element *Label) handleResize () {
func (element *Label) EmCollapse (columns int, rows int) {
element.forcedColumns = columns
element.forcedRows = rows
if element.entity == nil { return }
element.updateMinimumSize()
}
@ -82,29 +62,19 @@ func (element *Label) FlexibleHeightFor (width int) (height int) {
if element.wrap {
return element.drawer.ReccomendedHeightFor(width)
} else {
_, height = element.MinimumSize()
return
return element.minHeight
}
}
// 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 }
element.text = text
element.drawer.SetText([]rune(text))
if element.entity == nil { return }
element.updateMinimumSize()
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
element.entity.Invalidate()
}
// SetWrap sets wether or not the label's text wraps. If the text is set to
@ -118,26 +88,19 @@ func (element *Label) SetWrap (wrap bool) {
element.drawer.SetMaxHeight(0)
}
element.wrap = wrap
if element.entity == nil { return }
element.updateMinimumSize()
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
element.entity.Invalidate()
}
// SetAlign sets the alignment method of the label.
func (element *Label) SetAlign (align textdraw.Align) {
if align == element.align { return }
element.align = align
element.drawer.SetAlign(align)
if element.entity == nil { return }
element.updateMinimumSize()
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
@ -147,24 +110,38 @@ func (element *Label) SetTheme (new tomo.Theme) {
element.drawer.SetFace (element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
if element.entity == nil { return }
element.updateMinimumSize()
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *Label) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
if element.entity == nil { return }
element.updateMinimumSize()
element.entity.Invalidate()
}
// Draw causes the element to draw to the specified destination canvas.
func (element *Label) Draw (destination canvas.Canvas) {
if element.entity == nil { return }
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
bounds := element.entity. Bounds()
if element.wrap {
element.drawer.SetMaxWidth(bounds.Dx())
element.drawer.SetMaxHeight(bounds.Dy())
}
element.entity.DrawBackground(destination, bounds)
textBounds := element.drawer.LayoutBounds()
foreground := element.theme.Color (
tomo.ColorForeground,
tomo.State { })
element.drawer.Draw(destination, foreground, bounds.Min.Sub(textBounds.Min))
}
func (element *Label) updateMinimumSize () {
@ -176,9 +153,8 @@ func (element *Label) updateMinimumSize () {
em = element.theme.Padding(tomo.PatternBackground)[0]
}
width, height = em, element.drawer.LineHeight().Round()
if element.onFlexibleHeightChange != nil {
element.onFlexibleHeightChange()
}
// FIXME we shoudl not have to pass in the element here
element.entity.NotifyFlexibleHeightChange(element)
} else {
bounds := element.drawer.LayoutBounds()
width, height = bounds.Dx(), bounds.Dy()
@ -196,18 +172,6 @@ func (element *Label) updateMinimumSize () {
Mul(fixed.I(element.forcedRows)).Floor()
}
element.core.SetMinimumSize(width, height)
}
func (element *Label) draw () {
element.core.DrawBackground (
element.theme.Pattern(tomo.PatternBackground, tomo.State { }))
bounds := element.Bounds()
textBounds := element.drawer.LayoutBounds()
foreground := element.theme.Color (
tomo.ColorForeground,
tomo.State { })
element.drawer.Draw(element.core, foreground, bounds.Min.Sub(textBounds.Min))
element.minHeight = height
element.entity.SetMinimumSize(width, height)
}

177
elements/notdone/label.go Normal file
View File

@ -0,0 +1,177 @@
package elements
import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// Label is a simple text box.
type Label struct {
entity tomo.FlexibleEntity
align textdraw.Align
wrap bool
text string
drawer textdraw.Drawer
forcedColumns int
forcedRows int
minHeight int
config config.Wrapped
theme theme.Wrapped
}
// NewLabel creates a new label. If wrap is set to true, the text inside will be
// wrapped.
func NewLabel (text string, wrap bool) (element *Label) {
element = &Label { }
element.theme.Case = tomo.C("tomo", "label")
element.drawer.SetFace (element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
element.SetWrap(wrap)
element.SetText(text)
return
}
// Bind binds this element to an entity.
func (element *Label) Bind (entity tomo.Entity) {
element.entity = entity.(tomo.FlexibleEntity)
if element.entity == nil { return }
element.updateMinimumSize()
}
// EmCollapse forces a minimum width and height upon the label. The width is
// measured in emspaces, and the height is measured in lines. If a zero value is
// given for a dimension, its minimum will be determined by the label's content.
// If the label's content is greater than these dimensions, it will be truncated
// to fit.
func (element *Label) EmCollapse (columns int, rows int) {
element.forcedColumns = columns
element.forcedRows = rows
if element.entity == nil { return }
element.updateMinimumSize()
}
// 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 {
return element.minHeight
}
}
// SetText sets the label's text.
func (element *Label) SetText (text string) {
if element.text == text { return }
element.text = text
element.drawer.SetText([]rune(text))
if element.entity == nil { return }
element.updateMinimumSize()
element.entity.Invalidate()
}
// SetWrap sets wether or not the label's text wraps. If the text is set to
// wrap, the element will have a minimum size of a single character and
// automatically wrap its text. If the text is set to not wrap, the element will
// have a minimum size that fits its text.
func (element *Label) SetWrap (wrap bool) {
if wrap == element.wrap { return }
if !wrap {
element.drawer.SetMaxWidth(0)
element.drawer.SetMaxHeight(0)
}
element.wrap = wrap
if element.entity == nil { return }
element.updateMinimumSize()
element.entity.Invalidate()
}
// SetAlign sets the alignment method of the label.
func (element *Label) SetAlign (align textdraw.Align) {
if align == element.align { return }
element.align = align
element.drawer.SetAlign(align)
if element.entity == nil { return }
element.updateMinimumSize()
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *Label) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
if element.entity == nil { return }
element.updateMinimumSize()
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *Label) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
if element.entity == nil { return }
element.updateMinimumSize()
element.entity.Invalidate()
}
// Draw causes the element to draw to the specified destination canvas.
func (element *Label) Draw (destination canvas.Canvas) {
if element.entity == nil { return }
bounds := element.entity. Bounds()
if element.wrap {
element.drawer.SetMaxWidth(bounds.Dx())
element.drawer.SetMaxHeight(bounds.Dy())
}
element.entity.DrawBackground(destination, bounds)
textBounds := element.drawer.LayoutBounds()
foreground := element.theme.Color (
tomo.ColorForeground,
tomo.State { })
element.drawer.Draw(destination, foreground, bounds.Min.Sub(textBounds.Min))
}
func (element *Label) updateMinimumSize () {
var width, height int
if element.wrap {
em := element.drawer.Em().Round()
if em < 1 {
em = element.theme.Padding(tomo.PatternBackground)[0]
}
width, height = em, element.drawer.LineHeight().Round()
// FIXME we shoudl not have to pass in the element here
element.entity.NotifyFlexibleHeightChange(element)
} else {
bounds := element.drawer.LayoutBounds()
width, height = bounds.Dx(), bounds.Dy()
}
if element.forcedColumns > 0 {
width =
element.drawer.Em().
Mul(fixed.I(element.forcedColumns)).Floor()
}
if element.forcedRows > 0 {
height =
element.drawer.LineHeight().
Mul(fixed.I(element.forcedRows)).Floor()
}
element.minHeight = height
element.entity.SetMinimumSize(width, height)
}

View File

@ -6,17 +6,18 @@ import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
type listEntity interface {
tomo.FlexibleEntity
tomo.ContainerEntity
tomo.ScrollableEntity
}
// List is an element that contains several objects that a user can select.
type List struct {
*core.Core
*core.FocusableCore
core core.CoreControl
focusableControl core.FocusableCoreControl
entity listEntity
pressed bool
contentHeight int
@ -25,7 +26,6 @@ type List struct {
selectedEntry int
scroll int
entries []ListEntry
config config.Wrapped
theme theme.Wrapped
@ -38,36 +38,14 @@ type List struct {
func NewList (entries ...ListEntry) (element *List) {
element = &List { selectedEntry: -1 }
element.theme.Case = tomo.C("tomo", "list")
element.Core, element.core = core.NewCore(element, element.handleResize)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore (element.core, func () {
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
})
element.entries = make([]ListEntry, len(entries))
for index, entry := range entries {
element.entries[index] = entry
}
element.updateMinimumSize()
return
}
func (element *List) handleResize () {
for index, entry := range element.entries {
element.entries[index] = element.resizeEntryToFit(entry)
}
if element.scroll > element.maxScrollHeight() {
element.scroll = element.maxScrollHeight()
}
element.draw()
element.scrollBoundsChange()
}
// SetTheme sets the element's theme.
func (element *List) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
@ -77,7 +55,7 @@ func (element *List) SetTheme (new tomo.Theme) {
element.entries[index] = entry
}
element.updateMinimumSize()
element.redo()
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
@ -92,18 +70,6 @@ func (element *List) SetConfig (new tomo.Config) {
element.redo()
}
func (element *List) redo () {
for index, entry := range element.entries {
element.entries[index] = element.resizeEntryToFit(entry)
}
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
element.scrollBoundsChange()
}
// Collapse forces a minimum width and height upon the list. If a zero value is
// given for a dimension, its minimum will be determined by the list's content.
// If the list's height goes beyond the forced size, it will need to be accessed
@ -213,19 +179,6 @@ func (element *List) ScrollAxes () (horizontal, vertical bool) {
return false, true
}
func (element *List) scrollViewportHeight () (height int) {
padding := element.theme.Padding(tomo.PatternSunken)
return element.Bounds().Dy() - padding[0] - padding[2]
}
func (element *List) maxScrollHeight () (height int) {
height =
element.contentHeight -
element.scrollViewportHeight()
if height < 0 { height = 0 }
return
}
// OnNoEntrySelected sets a function to be called when the user chooses to
// deselect the current selected entry by clicking on empty space within the
// list or by pressing the escape key.
@ -443,7 +396,32 @@ func (element *List) scrollBoundsChange () {
}
}
func (element *List) draw () {
func (element *List) scrollViewportHeight () (height int) {
padding := element.theme.Padding(tomo.PatternSunken)
return element.Bounds().Dy() - padding[0] - padding[2]
}
func (element *List) maxScrollHeight () (height int) {
height =
element.contentHeight -
element.scrollViewportHeight()
if height < 0 { height = 0 }
return
}
func (element *List) Layout () {
for index, entry := range element.entries {
element.entries[index] = element.resizeEntryToFit(entry)
}
if element.scroll > element.maxScrollHeight() {
element.scroll = element.maxScrollHeight()
}
element.draw()
element.scrollBoundsChange()
}
func (element *List) Draw (destination canvas.Canvas) {
bounds := element.Bounds()
padding := element.theme.Padding(tomo.PatternSunken)
innerBounds := padding.Apply(bounds)

View File

@ -2,14 +2,11 @@ package elements
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// ProgressBar displays a visual indication of how far along a task is.
type ProgressBar struct {
*core.Core
core core.CoreControl
progress float64
config config.Wrapped

View File

@ -3,7 +3,6 @@ package elements
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
@ -19,9 +18,6 @@ import "git.tebibyte.media/sashakoshka/tomo/default/config"
// Typically, you wont't want to use a ScrollBar by itself. A ScrollContainer is
// better for most cases.
type ScrollBar struct {
*core.Core
core core.CoreControl
vertical bool
enabled bool
dragging bool

View File

@ -3,17 +3,11 @@ package elements
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// Slider is a slider control with a floating point value between zero and one.
type Slider struct {
*core.Core
*core.FocusableCore
core core.CoreControl
focusableControl core.FocusableCoreControl
value float64
vertical bool
dragging bool

View File

@ -1,14 +1,11 @@
package elements
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// Spacer can be used to put space between two elements..
type Spacer struct {
*core.Core
core core.CoreControl
line bool
config config.Wrapped

View File

@ -4,17 +4,12 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// Switch is a toggle-able on/off switch with an optional label. It is
// functionally identical to Checkbox, but plays a different semantic role.
type Switch struct {
*core.Core
*core.FocusableCore
core core.CoreControl
focusableControl core.FocusableCoreControl
drawer textdraw.Drawer
pressed bool

View File

@ -12,17 +12,11 @@ import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/textmanip"
import "git.tebibyte.media/sashakoshka/tomo/fixedutil"
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// TextBox is a single-line text input.
type TextBox struct {
*core.Core
*core.FocusableCore
core core.CoreControl
focusableControl core.FocusableCoreControl
lastClick time.Time
dragging int
dot textmanip.Dot