Half-done migration of basic elements

This commit is contained in:
Sasha Koshka 2023-02-07 11:27:59 -05:00
parent 0bdbaa39ca
commit 3998d842b1
8 changed files with 214 additions and 128 deletions

View File

@ -6,8 +6,6 @@ import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var buttonCase = theme.C("basic", "button")
// Button is a clickable button.
type Button struct {
*core.Core
@ -25,19 +23,27 @@ type Button struct {
// NewButton creates a new button with the specified label text.
func NewButton (text string) (element *Button) {
element = &Button { }
element.Core, element.core = core.NewCore(element.draw)
element.Core, element.core = core.NewCore (
element.draw,
element.redo,
element.redo,
theme.C("basic", "button"))
element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
})
element.drawer.SetFace(theme.FontFaceRegular())
element.focusableControl = core.NewFocusableCore(element.redo)
element.SetText(text)
return
}
func (element *Button) redo () {
element.drawer.SetFace (
element.core.FontFace(theme.FontStyleRegular,
theme.FontSizeNormal))
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
func (element *Button) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return }
if !element.Focused() { element.Focus() }
@ -83,10 +89,7 @@ func (element *Button) HandleKeyDown (key input.Key, modifiers input.Modifiers)
func (element *Button) HandleKeyUp(key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter && element.pressed {
element.pressed = false
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
element.redo()
if !element.Enabled() { return }
if element.onClick != nil {
element.onClick()
@ -111,33 +114,28 @@ func (element *Button) SetText (text string) {
element.text = text
element.drawer.SetText([]rune(text))
textBounds := element.drawer.LayoutBounds()
_, inset := theme.ButtonPattern(theme.PatternState { Case: buttonCase })
minimumSize := inset.Inverse().Apply(textBounds).Inset(-theme.Padding())
minimumSize := textBounds.Inset(-element.core.Config().Padding())
element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
element.redo()
}
func (element *Button) draw () {
bounds := element.Bounds()
pattern, inset := theme.ButtonPattern(theme.PatternState {
Case: buttonCase,
state := theme.PatternState {
Disabled: !element.Enabled(),
Focused: element.Focused(),
Focused: element.Focused(),
Pressed: element.pressed,
})
}
pattern := element.core.Pattern(theme.PatternButton, state)
artist.FillRectangle(element, pattern, bounds)
innerBounds := inset.Apply(bounds)
textBounds := element.drawer.LayoutBounds()
offset := image.Point {
X: innerBounds.Min.X + (innerBounds.Dx() - textBounds.Dx()) / 2,
Y: innerBounds.Min.Y + (innerBounds.Dy() - textBounds.Dy()) / 2,
X: bounds.Min.X + (bounds.Dx() - textBounds.Dx()) / 2,
Y: bounds.Min.Y + (bounds.Dy() - textBounds.Dy()) / 2,
}
// account for the fact that the bounding rectangle will be shifted over
@ -145,9 +143,6 @@ func (element *Button) draw () {
offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X
foreground, _ := theme.ForegroundPattern (theme.PatternState {
Case: buttonCase,
Disabled: !element.Enabled(),
})
foreground := element.core.Pattern(theme.PatternForeground, state)
element.drawer.Draw(element, foreground, offset)
}

View File

@ -6,8 +6,6 @@ import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var checkboxCase = theme.C("basic", "checkbox")
// Checkbox is a toggle-able checkbox with a label.
type Checkbox struct {
*core.Core
@ -26,19 +24,27 @@ type Checkbox struct {
// NewCheckbox creates a new cbeckbox with the specified label text.
func NewCheckbox (text string, checked bool) (element *Checkbox) {
element = &Checkbox { checked: checked }
element.Core, element.core = core.NewCore(element.draw)
element.Core, element.core = core.NewCore (
element.draw,
element.redo,
element.redo,
theme.C("basic", "checkbox"))
element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
})
element.drawer.SetFace(theme.FontFaceRegular())
element.focusableControl = core.NewFocusableCore(element.redo)
element.SetText(text)
return
}
func (element *Checkbox) redo () {
element.drawer.SetFace (
element.core.FontFace(theme.FontStyleRegular,
theme.FontSizeNormal))
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
func (element *Checkbox) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return }
element.Focus()
@ -122,7 +128,7 @@ func (element *Checkbox) SetText (text string) {
element.core.SetMinimumSize(textBounds.Dy(), textBounds.Dy())
} else {
element.core.SetMinimumSize (
textBounds.Dy() + theme.Padding() + textBounds.Dx(),
textBounds.Dy() + element.core.Config().Padding() + textBounds.Dx(),
textBounds.Dy())
}
@ -136,35 +142,27 @@ func (element *Checkbox) draw () {
bounds := element.Bounds()
boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState {
Case: checkboxCase,
})
artist.FillRectangle(element, backgroundPattern, bounds)
pattern, inset := theme.ButtonPattern(theme.PatternState {
Case: checkboxCase,
state := theme.PatternState {
Disabled: !element.Enabled(),
Focused: element.Focused(),
Pressed: element.pressed,
})
On: element.checked,
}
backgroundPattern := element.core.Pattern(theme.PatternBackground, state)
artist.FillRectangle(element, backgroundPattern, bounds)
pattern := element.core.Pattern (theme.PatternButton, state)
artist.FillRectangle(element, pattern, boxBounds)
textBounds := element.drawer.LayoutBounds()
offset := bounds.Min.Add(image.Point {
X: bounds.Dy() + theme.Padding(),
X: bounds.Dy() + element.core.Config().Padding(),
})
offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X
foreground, _ := theme.ForegroundPattern (theme.PatternState {
Case: checkboxCase,
Disabled: !element.Enabled(),
})
foreground := element.core.Pattern(theme.PatternForeground, state)
element.drawer.Draw(element, foreground, offset)
if element.checked {
checkBounds := inset.Apply(boxBounds).Inset(2)
artist.FillRectangle(element, foreground, checkBounds)
}
}

View File

@ -9,8 +9,6 @@ import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var containerCase = theme.C("basic", "container")
// Container is an element capable of containg other elements, and arranging
// them in a layout.
type Container struct {
@ -33,7 +31,11 @@ type Container struct {
// NewContainer creates a new container.
func NewContainer (layout layouts.Layout) (element *Container) {
element = &Container { }
element.Core, element.core = core.NewCore(element.redoAll)
element.Core, element.core = core.NewCore (
element.redoAll,
element.handleConfigChange,
element.handleThemeChange,
theme.C("basic", "container"))
element.SetLayout(layout)
return
}
@ -205,9 +207,7 @@ func (element *Container) redoAll () {
// draw a background
bounds := element.Bounds()
pattern, _ := theme.BackgroundPattern (theme.PatternState {
Case: containerCase,
})
pattern := element.core.Pattern (theme.PatternBackground, theme.PatternState { })
artist.FillRectangle(element, pattern, bounds)
// cut our canvas up and give peices to child elements
@ -216,6 +216,24 @@ func (element *Container) redoAll () {
}
}
func (element *Container) handleConfigChange () {
for _, child := range element.children {
if child0, ok := child.Element.(elements.Configurable); ok {
child0.SetConfig(element.core.Config())
}
}
element.redoAll()
}
func (element *Container) handleThemeChange () {
for _, child := range element.children {
if child0, ok := child.Element.(elements.Themeable); ok {
child0.SetTheme(element.core.Theme())
}
}
element.redoAll()
}
func (element *Container) HandleMouseDown (x, y int, button input.Button) {
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(elements.MouseTarget)
if !handlesMouse { return }
@ -266,7 +284,7 @@ func (element *Container) HandleKeyUp (key input.Key, modifiers input.Modifiers)
func (element *Container) FlexibleHeightFor (width int) (height int) {
return element.layout.FlexibleHeightFor (
element.children,
theme.Margin(), width)
element.core.Config().Margin(), width)
}
func (element *Container) OnFlexibleHeightChange (callback func ()) {
@ -469,15 +487,15 @@ func (element *Container) childFocusRequestCallback (
func (element *Container) updateMinimumSize () {
width, height := element.layout.MinimumSize (
element.children, theme.Margin())
element.children, element.core.Config().Margin())
if element.flexible {
height = element.layout.FlexibleHeightFor (
element.children, theme.Margin(), width)
element.children, element.core.Config().Margin(), width)
}
element.core.SetMinimumSize(width, height)
}
func (element *Container) recalculate () {
element.layout.Arrange (
element.children, theme.Margin(), element.Bounds())
element.children, element.core.Config().Margin(), element.Bounds())
}

View File

@ -4,8 +4,6 @@ import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var labelCase = theme.C("basic", "label")
// Label is a simple text box.
type Label struct {
*core.Core
@ -22,14 +20,35 @@ type Label struct {
// wrapped.
func NewLabel (text string, wrap bool) (element *Label) {
element = &Label { }
element.Core, element.core = core.NewCore(element.handleResize)
face := theme.FontFaceRegular()
element.Core, element.core = core.NewCore (
element.handleResize,
element.redo,
element.redo,
theme.C("basic", "label"))
face := element.core.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal)
element.drawer.SetFace(face)
element.SetWrap(wrap)
element.SetText(text)
return
}
func (element *Label) redo () {
face := element.core.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal)
element.drawer.SetFace(face)
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 {
@ -93,7 +112,7 @@ func (element *Label) SetWrap (wrap bool) {
func (element *Label) updateMinimumSize () {
if element.wrap {
em := element.drawer.Em().Round()
if em < 1 { em = theme.Padding() }
if em < 1 { em = element.core.Config().Padding() }
element.core.SetMinimumSize (
em, element.drawer.LineHeight().Round())
if element.onFlexibleHeightChange != nil {
@ -108,15 +127,15 @@ func (element *Label) updateMinimumSize () {
func (element *Label) draw () {
bounds := element.Bounds()
pattern, _ := theme.BackgroundPattern(theme.PatternState {
Case: labelCase,
})
pattern := element.core.Pattern (
theme.PatternBackground,
theme.PatternState { })
artist.FillRectangle(element, pattern, bounds)
textBounds := element.drawer.LayoutBounds()
foreground, _ := theme.ForegroundPattern (theme.PatternState {
Case: labelCase,
})
foreground := element.core.Pattern (
theme.PatternForeground,
theme.PatternState { })
element.drawer.Draw (element, foreground, bounds.Min.Sub(textBounds.Min))
}

View File

@ -8,8 +8,6 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var listCase = theme.C("basic", "list")
// List is an element that contains several objects that a user can select.
type List struct {
*core.Core
@ -34,7 +32,11 @@ type List struct {
// NewList creates a new list element with the specified entries.
func NewList (entries ...ListEntry) (element *List) {
element = &List { selectedEntry: -1 }
element.Core, element.core = core.NewCore(element.handleResize)
element.Core, element.core = core.NewCore (
element.handleResize,
element.redo,
element.redo,
theme.C("basic", "list"))
element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () {
@ -63,6 +65,36 @@ func (element *List) handleResize () {
}
}
func (element *List) handleConfigChange () {
for index, entry := range element.entries {
entry.SetConfig(element.core.Config())
element.entries[index] = entry
}
element.redo()
}
func (element *List) handleThemeChange () {
for index, entry := range element.entries {
entry.SetConfig(element.core.Config())
element.entries[index] = entry
}
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()
}
if element.onScrollBoundsChange != nil {
element.onScrollBoundsChange()
}
}
// 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
@ -164,9 +196,7 @@ func (element *List) ScrollAxes () (horizontal, vertical bool) {
}
func (element *List) scrollViewportHeight () (height int) {
_, inset := theme.ListPattern(theme.PatternState {
Case: listCase,
})
inset := element.core.Inset(theme.PatternSunken)
return element.Bounds().Dy() - inset[0] - inset[2]
}
@ -198,6 +228,8 @@ func (element *List) CountEntries () (count int) {
func (element *List) Append (entry ListEntry) {
// append
entry.Collapse(element.forcedMinimumWidth)
entry.SetTheme(element.core.Theme())
entry.SetConfig(element.core.Config())
element.entries = append(element.entries, entry)
// recalculate, redraw, notify
@ -290,7 +322,7 @@ func (element *List) Replace (index int, entry ListEntry) {
}
func (element *List) selectUnderMouse (x, y int) (updated bool) {
_, inset := theme.ListPattern(theme.PatternState { })
inset := element.core.Inset(theme.PatternSunken)
bounds := inset.Apply(element.Bounds())
mousePoint := image.Pt(x, y)
dot := image.Pt (
@ -332,9 +364,7 @@ func (element *List) changeSelectionBy (delta int) (updated bool) {
}
func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) {
_, inset := theme.ListPattern(theme.PatternState {
Case: listCase,
})
inset := element.core.Inset(theme.PatternSunken)
entry.Collapse(element.forcedMinimumWidth - inset[3] - inset[1])
return entry
}
@ -361,9 +391,7 @@ func (element *List) updateMinimumSize () {
minimumHeight = element.contentHeight
}
_, inset := theme.ListPattern(theme.PatternState {
Case: listCase,
})
inset := element.core.Inset(theme.PatternSunken)
minimumHeight += inset[0] + inset[2]
element.core.SetMinimumSize(minimumWidth, minimumHeight)
@ -372,8 +400,8 @@ func (element *List) updateMinimumSize () {
func (element *List) draw () {
bounds := element.Bounds()
pattern, inset := theme.ListPattern(theme.PatternState {
Case: listCase,
inset := element.core.Inset(theme.PatternSunken)
pattern := element.core.Pattern (theme.PatternSunken, theme.PatternState {
Disabled: !element.Enabled(),
Focused: element.Focused(),
})

View File

@ -2,6 +2,7 @@ package basicElements
import "image"
import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist"
@ -15,6 +16,8 @@ type ListEntry struct {
text string
forcedMinimumWidth int
onSelect func ()
theme theme.Theme
config config.Config
}
func NewListEntry (text string, onSelect func ()) (entry ListEntry) {
@ -23,7 +26,6 @@ func NewListEntry (text string, onSelect func ()) (entry ListEntry) {
onSelect: onSelect,
}
entry.drawer.SetText([]rune(text))
entry.drawer.SetFace(theme.FontFaceRegular())
entry.updateBounds()
return
}
@ -34,6 +36,19 @@ func (entry *ListEntry) Collapse (width int) {
entry.updateBounds()
}
func (entry *ListEntry) SetTheme (new theme.Theme) {
entry.theme = new
entry.drawer.SetFace (entry.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
listEntryCase))
entry.updateBounds()
}
func (entry *ListEntry) SetConfig (config config.Config) {
entry.config = config
}
func (entry *ListEntry) updateBounds () {
entry.bounds = image.Rectangle { }
entry.bounds.Max.Y = entry.drawer.LineHeight().Round()
@ -43,8 +58,7 @@ func (entry *ListEntry) updateBounds () {
entry.bounds.Max.X = entry.drawer.LayoutBounds().Dx()
}
_, inset := theme.ItemPattern(theme.PatternState {
})
inset := entry.theme.Inset(theme.PatternRaised, listEntryCase)
entry.bounds.Max.Y += inset[0] + inset[2]
entry.textPoint =
@ -60,20 +74,16 @@ func (entry *ListEntry) Draw (
) (
updatedRegion image.Rectangle,
) {
pattern, _ := theme.ItemPattern(theme.PatternState {
Case: listEntryCase,
state := theme.PatternState {
Focused: focused,
On: on,
})
}
pattern := entry.theme.Pattern (theme.PatternRaised, listEntryCase, state)
artist.FillRectangle (
destination,
pattern,
entry.Bounds().Add(offset))
foreground, _ := theme.ForegroundPattern (theme.PatternState {
Case: listEntryCase,
Focused: focused,
On: on,
})
foreground := entry.theme.Pattern (theme.PatternForeground, listEntryCase, state)
return entry.drawer.Draw (
destination,
foreground,

View File

@ -16,11 +16,24 @@ type ProgressBar struct {
// level.
func NewProgressBar (progress float64) (element *ProgressBar) {
element = &ProgressBar { progress: progress }
element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(theme.Padding() * 2, theme.Padding() * 2)
element.Core, element.core = core.NewCore (
element.draw,
element.redo,
element.redo,
theme.C("basic", "progressBar"))
return
}
func (element *ProgressBar) redo () {
element.core.SetMinimumSize (
element.core.Config().Padding() * 2,
element.core.Config().Padding() * 2)
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
}
// SetProgress sets the progress level of the bar.
func (element *ProgressBar) SetProgress (progress float64) {
if progress == element.progress { return }
@ -34,13 +47,18 @@ func (element *ProgressBar) SetProgress (progress float64) {
func (element *ProgressBar) draw () {
bounds := element.Bounds()
pattern, inset := theme.SunkenPattern(theme.PatternState { })
pattern := element.core.Pattern (
theme.PatternSunken,
theme.PatternState { })
inset := element.core.Inset(theme.PatternSunken)
artist.FillRectangle(element, pattern, bounds)
bounds = inset.Apply(bounds)
meterBounds := image.Rect (
bounds.Min.X, bounds.Min.Y,
bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
bounds.Max.Y)
accent, _ := theme.AccentPattern(theme.PatternState { })
accent := element.core.Pattern (
theme.PatternSunken,
theme.PatternState { })
artist.FillRectangle(element, accent, meterBounds)
}

View File

@ -22,27 +22,27 @@ type Core struct {
theme theme.Theme
c theme.Case
drawSizeChange func ()
onConfigChange func ()
onThemeChange func ()
handleSizeChange func ()
handleConfigChange func ()
handleThemeChange func ()
onMinimumSizeChange func ()
onDamage func (region canvas.Canvas)
}
// NewCore creates a new element core and its corresponding control.
func NewCore (
drawSizeChange func (),
onConfigChange func (),
onThemeChange func (),
handleSizeChange func (),
handleConfigChange func (),
handleThemeChange func (),
c theme.Case,
) (
core *Core,
control CoreControl,
) {
core = &Core {
drawSizeChange: drawSizeChange,
onConfigChange: onConfigChange,
onThemeChange: onThemeChange,
core = &Core {
handleSizeChange: handleSizeChange,
handleConfigChange: handleConfigChange,
handleThemeChange: handleThemeChange,
c: c,
}
control = CoreControl { core: core }
@ -88,8 +88,8 @@ func (core *Core) MinimumSize () (width, height int) {
// overridden.
func (core *Core) DrawTo (canvas canvas.Canvas) {
core.canvas = canvas
if core.drawSizeChange != nil {
core.drawSizeChange()
if core.handleSizeChange != nil {
core.handleSizeChange()
}
}
@ -109,8 +109,8 @@ func (core *Core) OnMinimumSizeChange (callback func ()) {
// to be overridden.
func (core *Core) SetConfig (config config.Config) {
core.config = config
if core.onConfigChange != nil {
core.onConfigChange()
if core.handleConfigChange != nil {
core.handleConfigChange()
}
}
@ -118,8 +118,8 @@ func (core *Core) SetConfig (config config.Config) {
// to be overridden.
func (core *Core) SetTheme (theme theme.Theme) {
core.theme = theme
if core.onThemeChange != nil {
core.onThemeChange()
if core.handleThemeChange != nil {
core.handleThemeChange()
}
}