From 3998d842b108faa169332554ea2be2701acc7eed Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 7 Feb 2023 11:27:59 -0500 Subject: [PATCH] Half-done migration of basic elements --- elements/basic/button.go | 59 ++++++++++++++++------------------- elements/basic/checkbox.go | 56 ++++++++++++++++----------------- elements/basic/container.go | 38 ++++++++++++++++------ elements/basic/label.go | 41 +++++++++++++++++------- elements/basic/list.go | 58 +++++++++++++++++++++++++--------- elements/basic/listentry.go | 32 ++++++++++++------- elements/basic/progressbar.go | 26 ++++++++++++--- elements/core/core.go | 32 +++++++++---------- 8 files changed, 214 insertions(+), 128 deletions(-) diff --git a/elements/basic/button.go b/elements/basic/button.go index c768d23..d2c5899 100644 --- a/elements/basic/button.go +++ b/elements/basic/button.go @@ -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) } diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index ba0a869..a7ba73e 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -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) - } } diff --git a/elements/basic/container.go b/elements/basic/container.go index 328e18a..2719208 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -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()) } diff --git a/elements/basic/label.go b/elements/basic/label.go index b105580..2803798 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -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)) } diff --git a/elements/basic/list.go b/elements/basic/list.go index e8a60b9..d76ff0b 100644 --- a/elements/basic/list.go +++ b/elements/basic/list.go @@ -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(), }) diff --git a/elements/basic/listentry.go b/elements/basic/listentry.go index fb45ba2..60b9618 100644 --- a/elements/basic/listentry.go +++ b/elements/basic/listentry.go @@ -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, diff --git a/elements/basic/progressbar.go b/elements/basic/progressbar.go index e97665c..3545e60 100644 --- a/elements/basic/progressbar.go +++ b/elements/basic/progressbar.go @@ -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) } diff --git a/elements/core/core.go b/elements/core/core.go index 85999e0..3635c08 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -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() } }