This commit is contained in:
Sasha Koshka 2023-05-03 01:07:44 -04:00
parent 9e754cdb59
commit 72fc28e223
27 changed files with 293 additions and 411 deletions

View File

@ -4,7 +4,6 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/shatter"
import "git.tebibyte.media/sashakoshka/tomo/ability"
var boxCase = tomo.C("tomo", "box")

View File

@ -4,9 +4,10 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
var buttonCase = tomo.C("tomo", "button")
// Button is a clickable button.
type Button struct {
entity tomo.Entity
@ -26,11 +27,11 @@ type Button struct {
// NewButton creates a new button with the specified label text.
func NewButton (text string) (element *Button) {
element = &Button { showText: true, enabled: true }
element.entity = tomo.NewEntity(element).(buttonEntity)
element.theme.Case = tomo.C("tomo", "button")
element.drawer.SetFace (element.theme.FontFace (
element.entity = tomo.GetBackend().NewEntity(element)
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
tomo.FontSizeNormal,
buttonCase))
element.SetText(text)
return
}
@ -44,13 +45,13 @@ func (element *Button) Entity () tomo.Entity {
func (element *Button) Draw (destination artist.Canvas) {
state := element.state()
bounds := element.entity.Bounds()
pattern := element.theme.Pattern(tomo.PatternButton, state)
pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, buttonCase)
pattern.Draw(destination, bounds)
foreground := element.theme.Color(tomo.ColorForeground, state)
sink := element.theme.Sink(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
foreground := element.entity.Theme().Color(tomo.ColorForeground, state, buttonCase)
sink := element.entity.Theme().Sink(tomo.PatternButton, buttonCase)
margin := element.entity.Theme().Margin(tomo.PatternButton, buttonCase)
offset := image.Pt (
bounds.Dx() / 2,
@ -65,7 +66,7 @@ func (element *Button) Draw (destination artist.Canvas) {
}
if element.hasIcon {
icon := element.theme.Icon(element.iconId, tomo.IconSizeSmall)
icon := element.entity.Theme().Icon(element.iconId, tomo.IconSizeSmall, buttonCase)
if icon != nil {
iconBounds := icon.Bounds()
addedWidth := iconBounds.Dx()
@ -150,21 +151,11 @@ func (element *Button) ShowText (showText bool) {
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *Button) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
func (element *Button) HandleThemeChange () {
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
element.updateMinimumSize()
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
tomo.FontSizeNormal,
buttonCase))
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -219,14 +210,14 @@ func (element *Button) HandleKeyUp(key input.Key, modifiers input.Modifiers) {
}
func (element *Button) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
padding := element.entity.Theme().Padding(tomo.PatternButton, buttonCase)
margin := element.entity.Theme().Margin(tomo.PatternButton, buttonCase)
textBounds := element.drawer.LayoutBounds()
minimumSize := textBounds.Sub(textBounds.Min)
if element.hasIcon {
icon := element.theme.Icon(element.iconId, tomo.IconSizeSmall)
icon := element.entity.Theme().Icon(element.iconId, tomo.IconSizeSmall, buttonCase)
if icon != nil {
bounds := icon.Bounds()
if element.showText {

View File

@ -2,8 +2,9 @@ package elements
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil"
var cellCase = tomo.C("tomo", "cell")
// Cell is a single-element container that satisfies tomo.Selectable. It
// provides styling based on whether or not it is selected.
@ -20,8 +21,7 @@ type Cell struct {
// method.
func NewCell (child tomo.Element) (element *Cell) {
element = &Cell { enabled: true }
element.theme.Case = tomo.C("tomo", "cell")
element.entity = tomo.NewEntity(element).(cellEntity)
element.entity = tomo.GetBackend().NewEntity(element)
element.Adopt(child)
return
}
@ -34,11 +34,11 @@ func (element *Cell) Entity () tomo.Entity {
// Draw causes the element to draw to the specified destination canvas.
func (element *Cell) Draw (destination artist.Canvas) {
bounds := element.entity.Bounds()
pattern := element.theme.Pattern(tomo.PatternTableCell, element.state())
pattern := element.entity.Theme().Pattern(tomo.PatternTableCell, element.state(), cellCase)
if element.child == nil {
pattern.Draw(destination, bounds)
} else {
artist.DrawShatter (
artutil.DrawShatter (
destination, pattern, bounds,
element.child.Entity().Bounds())
}
@ -49,7 +49,7 @@ func (element *Cell) Layout () {
if element.child == nil { return }
bounds := element.entity.Bounds()
bounds = element.theme.Padding(tomo.PatternTableCell).Apply(bounds)
bounds = element.entity.Theme().Padding(tomo.PatternTableCell, cellCase).Apply(bounds)
element.entity.PlaceChild(0, bounds)
}
@ -57,7 +57,7 @@ func (element *Cell) Layout () {
// DrawBackground draws this element's background pattern to the specified
// destination canvas.
func (element *Cell) DrawBackground (destination artist.Canvas) {
element.theme.Pattern(tomo.PatternTableCell, element.state()).
element.entity.Theme().Pattern(tomo.PatternTableCell, element.state(), cellCase).
Draw(destination, element.entity.Bounds())
}
@ -96,16 +96,6 @@ func (element *Cell) SetEnabled (enabled bool) {
element.invalidateChild()
}
// SetTheme sets this element's theme.
func (element *Cell) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
element.updateMinimumSize()
element.entity.Invalidate()
element.invalidateChild()
element.entity.InvalidateLayout()
}
// OnSelectionChange sets a function to be called when this element is selected
// or unselected.
func (element *Cell) OnSelectionChange (callback func ()) {
@ -116,6 +106,13 @@ func (element *Cell) Selected () bool {
return element.entity.Selected()
}
func (element *Cell) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
element.invalidateChild()
element.entity.InvalidateLayout()
}
func (element *Cell) HandleSelectionChange () {
element.entity.Invalidate()
element.invalidateChild()
@ -145,7 +142,7 @@ func (element *Cell) updateMinimumSize () {
width += childWidth
height += childHeight
}
padding := element.theme.Padding(tomo.PatternTableCell)
padding := element.entity.Theme().Padding(tomo.PatternTableCell, cellCase)
width += padding.Horizontal()
height += padding.Vertical()

View File

@ -4,9 +4,10 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
var checkboxCase = tomo.C("tomo", "checkbox")
// Checkbox is a toggle-able checkbox with a label.
type Checkbox struct {
entity tomo.Entity
@ -23,11 +24,11 @@ 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, enabled: true }
element.entity = tomo.NewEntity(element).(checkboxEntity)
element.theme.Case = tomo.C("tomo", "checkbox")
element.drawer.SetFace (element.theme.FontFace (
element.entity = tomo.GetBackend().NewEntity(element)
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
tomo.FontSizeNormal,
checkboxCase))
element.SetText(text)
return
}
@ -51,11 +52,11 @@ func (element *Checkbox) Draw (destination artist.Canvas) {
element.entity.DrawBackground(destination)
pattern := element.theme.Pattern(tomo.PatternButton, state)
pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, checkboxCase)
pattern.Draw(destination, boxBounds)
textBounds := element.drawer.LayoutBounds()
margin := element.theme.Margin(tomo.PatternBackground)
margin := element.entity.Theme().Margin(tomo.PatternBackground, checkboxCase)
offset := bounds.Min.Add(image.Point {
X: bounds.Dy() + margin.X,
})
@ -63,7 +64,7 @@ func (element *Checkbox) Draw (destination artist.Canvas) {
offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X
foreground := element.theme.Color(tomo.ColorForeground, state)
foreground := element.entity.Theme().Color(tomo.ColorForeground, state, checkboxCase)
element.drawer.Draw(destination, foreground, offset)
}
@ -103,21 +104,11 @@ func (element *Checkbox) SetText (text string) {
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *Checkbox) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
func (element *Checkbox) HandleThemeChange () {
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
element.updateMinimumSize()
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
tomo.FontSizeNormal,
checkboxCase))
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -179,7 +170,7 @@ func (element *Checkbox) updateMinimumSize () {
if element.text == "" {
element.entity.SetMinimumSize(textBounds.Dy(), textBounds.Dy())
} else {
margin := element.theme.Margin(tomo.PatternBackground)
margin := element.entity.Theme().Margin(tomo.PatternBackground, checkboxCase)
element.entity.SetMinimumSize (
textBounds.Dy() + margin.X + textBounds.Dx(),
textBounds.Dy())

View File

@ -7,6 +7,8 @@ import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
var comboBoxCase = tomo.C("tomo", "comboBox")
// Option specifies a ComboBox option. A blank option will display as "(None)".
type Option string
@ -36,11 +38,11 @@ type ComboBox struct {
func NewComboBox (options ...Option) (element *ComboBox) {
if len(options) == 0 { options = []Option { "" } }
element = &ComboBox { enabled: true, options: options }
element.entity = tomo.NewEntity(element).(tomo.FocusableEntity)
element.theme.Case = tomo.C("tomo", "comboBox")
element.drawer.SetFace (element.theme.FontFace (
element.entity = tomo.GetBackend().NewEntity(element)
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
tomo.FontSizeNormal,
comboBoxCase))
element.Select(options[0])
return
}
@ -54,14 +56,14 @@ func (element *ComboBox) Entity () tomo.Entity {
func (element *ComboBox) Draw (destination artist.Canvas) {
state := element.state()
bounds := element.entity.Bounds()
pattern := element.theme.Pattern(tomo.PatternButton, state)
pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, comboBoxCase)
pattern.Draw(destination, bounds)
foreground := element.theme.Color(tomo.ColorForeground, state)
sink := element.theme.Sink(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
padding := element.theme.Padding(tomo.PatternButton)
foreground := element.entity.Theme().Color(tomo.ColorForeground, state, comboBoxCase)
sink := element.entity.Theme().Sink(tomo.PatternButton, comboBoxCase)
margin := element.entity.Theme().Margin(tomo.PatternButton, comboBoxCase)
padding := element.entity.Theme().Padding(tomo.PatternButton, comboBoxCase)
offset := image.Pt(0, bounds.Dy() / 2).Add(bounds.Min)
@ -70,7 +72,7 @@ func (element *ComboBox) Draw (destination artist.Canvas) {
offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X
icon := element.theme.Icon(tomo.IconExpand, tomo.IconSizeSmall)
icon := element.entity.Theme().Icon(tomo.IconExpand, tomo.IconSizeSmall, comboBoxCase)
if icon != nil {
iconBounds := icon.Bounds()
addedWidth := iconBounds.Dx() + margin.X
@ -138,21 +140,11 @@ func (element *ComboBox) SetEnabled (enabled bool) {
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *ComboBox) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
func (element *ComboBox) HandleThemeChange () {
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
element.updateMinimumSize()
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *ComboBox) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
tomo.FontSizeNormal,
comboBoxCase))
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -224,7 +216,7 @@ func (element *ComboBox) dropDown () {
menu, err := window.NewMenu(element.entity.Bounds())
if err != nil { return }
cellToOption := make(map[tomo.Selectable] Option)
cellToOption := make(map[ability.Selectable] Option)
list := NewList()
for _, option := range element.options {
@ -250,13 +242,13 @@ func (element *ComboBox) dropDown () {
}
func (element *ComboBox) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
padding := element.entity.Theme().Padding(tomo.PatternButton, comboBoxCase)
margin := element.entity.Theme().Margin(tomo.PatternButton, comboBoxCase)
textBounds := element.drawer.LayoutBounds()
minimumSize := textBounds.Sub(textBounds.Min)
icon := element.theme.Icon(tomo.IconExpand, tomo.IconSizeSmall)
icon := element.entity.Theme().Icon(tomo.IconExpand, tomo.IconSizeSmall, comboBoxCase)
if icon != nil {
bounds := icon.Bounds()
minimumSize.Max.X += bounds.Dx()

View File

@ -1,7 +1,6 @@
package elements
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/ability"
type scratchEntry struct {
expand bool

View File

@ -11,6 +11,8 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter"
// TODO: base on flow implementation of list. also be able to switch to a table
// variant for a more information dense view.
var directoryCase = tomo.C("tomo", "list")
type historyEntry struct {
location string
filesystem ReadDirStatFS
@ -42,8 +44,7 @@ func NewDirectory (
err error,
) {
element = &Directory { }
element.theme.Case = tomo.C("tomo", "list")
element.entity = tomo.NewEntity(element).(directoryEntity)
element.entity = tomo.GetBackend().NewEntity(element)
element.container.entity = element.entity
element.minimumSize = element.updateMinimumSize
element.init()
@ -59,7 +60,7 @@ func (element *Directory) Draw (destination artist.Canvas) {
tiles := shatter.Shatter(element.entity.Bounds(), rocks...)
for _, tile := range tiles {
element.DrawBackground(canvas.Cut(destination, tile))
element.DrawBackground(artist.Cut(destination, tile))
}
}
@ -68,8 +69,8 @@ func (element *Directory) Layout () {
element.scroll.Y = element.maxScrollHeight()
}
margin := element.theme.Margin(tomo.PatternPinboard)
padding := element.theme.Padding(tomo.PatternPinboard)
margin := element.entity.Theme().Margin(tomo.PatternPinboard, directoryCase)
padding := element.entity.Theme().Padding(tomo.PatternPinboard, directoryCase)
bounds := padding.Apply(element.entity.Bounds())
element.contentBounds = image.Rectangle { }
@ -93,7 +94,7 @@ func (element *Directory) Layout () {
if width + dot.X > bounds.Max.X {
nextLine()
}
if typedChild, ok := child.(tomo.Flexible); ok {
if typedChild, ok := child.(ability.Flexible); ok {
height = typedChild.FlexibleHeightFor(width)
}
if rowHeight < height {
@ -139,7 +140,7 @@ func (element *Directory) HandleChildMouseDown (
child tomo.Element,
) {
element.selectNone()
if child, ok := child.(tomo.Selectable); ok {
if child, ok := child.(ability.Selectable); ok {
index := element.entity.IndexOf(child)
element.entity.SelectChild(index, true)
}
@ -166,7 +167,7 @@ func (element *Directory) ScrollContentBounds () image.Rectangle {
// ScrollViewportBounds returns the size and position of the element's
// viewport relative to ScrollBounds.
func (element *Directory) ScrollViewportBounds () image.Rectangle {
padding := element.theme.Padding(tomo.PatternPinboard)
padding := element.entity.Theme().Padding(tomo.PatternPinboard, directoryCase)
bounds := padding.Apply(element.entity.Bounds())
bounds = bounds.Sub(bounds.Min).Add(element.scroll)
return bounds
@ -199,14 +200,11 @@ func (element *Directory) ScrollAxes () (horizontal, vertical bool) {
}
func (element *Directory) DrawBackground (destination artist.Canvas) {
element.theme.Pattern(tomo.PatternPinboard, tomo.State { }).
element.entity.Theme().Pattern(tomo.PatternPinboard, tomo.State { }, directoryCase).
Draw(destination, element.entity.Bounds())
}
// SetTheme sets the element's theme.
func (element *Directory) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
func (element *Directory) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
@ -295,7 +293,7 @@ func (element *Directory) selectNone () {
}
func (element *Directory) maxScrollHeight () (height int) {
padding := element.theme.Padding(tomo.PatternSunken)
padding := element.entity.Theme().Padding(tomo.PatternSunken, directoryCase)
viewportHeight := element.entity.Bounds().Dy() - padding.Vertical()
height = element.contentBounds.Dy() - viewportHeight
if height < 0 { height = 0 }
@ -304,7 +302,7 @@ func (element *Directory) maxScrollHeight () (height int) {
func (element *Directory) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternPinboard)
padding := element.entity.Theme().Padding(tomo.PatternPinboard, directoryCase)
minimumWidth := 0
for index := 0; index < element.entity.CountChildren(); index ++ {
width, height := element.entity.ChildMinimumSize(index)

View File

@ -6,6 +6,8 @@ import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/shatter"
var documentCase = tomo.C("tomo", "document")
// Document is a scrollable container capcable of laying out flexible child
// elements. Children can be added either inline (similar to an HTML/CSS inline
// element), or expanding (similar to an HTML/CSS block element).
@ -22,8 +24,7 @@ type Document struct {
// NewDocument creates a new document container.
func NewDocument (children ...tomo.Element) (element *Document) {
element = &Document { }
element.theme.Case = tomo.C("tomo", "document")
element.entity = tomo.NewEntity(element)
element.entity = tomo.GetBackend().NewEntity(element)
element.container.entity = element.entity
element.minimumSize = element.updateMinimumSize
element.init()
@ -40,7 +41,7 @@ func (element *Document) Draw (destination artist.Canvas) {
tiles := shatter.Shatter(element.entity.Bounds(), rocks...)
for _, tile := range tiles {
element.entity.DrawBackground(canvas.Cut(destination, tile))
element.entity.DrawBackground(artist.Cut(destination, tile))
}
}
@ -50,8 +51,8 @@ func (element *Document) Layout () {
element.scroll.Y = element.maxScrollHeight()
}
margin := element.theme.Margin(tomo.PatternBackground)
padding := element.theme.Padding(tomo.PatternBackground)
margin := element.entity.Theme().Margin(tomo.PatternBackground, documentCase)
padding := element.entity.Theme().Padding(tomo.PatternBackground, documentCase)
bounds := padding.Apply(element.entity.Bounds())
element.contentBounds = image.Rectangle { }
@ -82,7 +83,7 @@ func (element *Document) Layout () {
if width < bounds.Dx() && entry.expand {
width = bounds.Dx()
}
if typedChild, ok := child.(tomo.Flexible); ok {
if typedChild, ok := child.(ability.Flexible); ok {
height = typedChild.FlexibleHeightFor(width)
}
if rowHeight < height {
@ -135,10 +136,7 @@ func (element *Document) DrawBackground (destination artist.Canvas) {
element.entity.DrawBackground(destination)
}
// SetTheme sets the element's theme.
func (element *Document) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
func (element *Document) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
@ -152,7 +150,7 @@ func (element *Document) ScrollContentBounds () image.Rectangle {
// ScrollViewportBounds returns the size and position of the element's
// viewport relative to ScrollBounds.
func (element *Document) ScrollViewportBounds () image.Rectangle {
padding := element.theme.Padding(tomo.PatternBackground)
padding := element.entity.Theme().Padding(tomo.PatternBackground, documentCase)
bounds := padding.Apply(element.entity.Bounds())
bounds = bounds.Sub(bounds.Min).Add(element.scroll)
return bounds
@ -185,7 +183,7 @@ func (element *Document) ScrollAxes () (horizontal, vertical bool) {
}
func (element *Document) maxScrollHeight () (height int) {
padding := element.theme.Padding(tomo.PatternSunken)
padding := element.entity.Theme().Padding(tomo.PatternBackground, documentCase)
viewportHeight := element.entity.Bounds().Dy() - padding.Vertical()
height = element.contentBounds.Dy() - viewportHeight
if height < 0 { height = 0 }
@ -193,7 +191,7 @@ func (element *Document) maxScrollHeight () (height int) {
}
func (element *Document) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternBackground)
padding := element.entity.Theme().Padding(tomo.PatternBackground, documentCase)
minimumWidth := 0
for index := 0; index < element.entity.CountChildren(); index ++ {
width, height := element.entity.ChildMinimumSize(index)

View File

@ -7,6 +7,8 @@ import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
var fileCase = tomo.C("files", "file")
// File displays an interactive visual representation of a file within any
// file system.
type File struct {
@ -32,8 +34,7 @@ func NewFile (
err error,
) {
element = &File { enabled: true }
element.theme.Case = tomo.C("files", "file")
element.entity = tomo.NewEntity(element).(fileEntity)
element.entity = tomo.GetBackend().NewEntity(element)
err = element.SetLocation(location, within)
return
}
@ -48,9 +49,9 @@ func (element *File) Draw (destination artist.Canvas) {
// background
state := element.state()
bounds := element.entity.Bounds()
sink := element.theme.Sink(tomo.PatternButton)
element.theme.
Pattern(tomo.PatternButton, state).
sink := element.entity.Theme().Sink(tomo.PatternButton, fileCase)
element.entity.Theme().
Pattern(tomo.PatternButton, state, fileCase).
Draw(destination, bounds)
// icon
@ -65,7 +66,7 @@ func (element *File) Draw (destination artist.Canvas) {
}
icon.Draw (
destination,
element.theme.Color(tomo.ColorForeground, state),
element.entity.Theme().Color(tomo.ColorForeground, state, fileCase),
bounds.Min.Add(offset))
}
}
@ -174,7 +175,7 @@ func (element *File) HandleMouseUp (
if button != input.ButtonLeft { return }
element.pressed = false
within := position.In(element.entity.Bounds())
if time.Since(element.lastClick) < element.config.DoubleClickDelay() {
if time.Since(element.lastClick) < element.entity.Config().DoubleClickDelay() {
if element.Enabled() && within && element.onChoose != nil {
element.onChoose()
}
@ -184,17 +185,8 @@ func (element *File) HandleMouseUp (
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *File) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *File) SetConfig (config tomo.Config) {
if config == element.config.Config { return }
element.config.Config = config
func (element *File) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -208,11 +200,11 @@ func (element *File) state () tomo.State {
}
func (element *File) icon () artist.Icon {
return element.theme.Icon(element.iconID, tomo.IconSizeLarge)
return element.entity.Theme().Icon(element.iconID, tomo.IconSizeLarge, fileCase)
}
func (element *File) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternButton)
padding := element.entity.Theme().Padding(tomo.PatternButton, fileCase)
icon := element.icon()
if icon == nil {
element.entity.SetMinimumSize (

View File

@ -4,6 +4,8 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist"
var iconCase = tomo.C("tomo", "icon")
// Icon is an element capable of displaying a singular icon.
type Icon struct {
entity tomo.Entity
@ -17,8 +19,7 @@ func NewIcon (id tomo.Icon, size tomo.IconSize) (element *Icon) {
id: id,
size: size,
}
element.entity = tomo.NewEntity(element).(ability.ThemeableEntity)
element.theme.Case = tomo.C("tomo", "icon")
element.entity = tomo.GetBackend().NewEntity(element)
element.updateMinimumSize()
return
}
@ -37,11 +38,7 @@ func (element *Icon) SetIcon (id tomo.Icon, size tomo.IconSize) {
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 }
func (element *Icon) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -52,8 +49,8 @@ func (element *Icon) Draw (destination artist.Canvas) {
bounds := element.entity.Bounds()
state := tomo.State { }
element.theme.
Pattern(tomo.PatternBackground, state).
element.entity.Theme().
Pattern(tomo.PatternBackground, state, iconCase).
Draw(destination, bounds)
icon := element.icon()
if icon != nil {
@ -63,13 +60,13 @@ func (element *Icon) Draw (destination artist.Canvas) {
(bounds.Dy() - iconBounds.Dy()) / 2)
icon.Draw (
destination,
element.theme.Color(tomo.ColorForeground, state),
element.entity.Theme().Color(tomo.ColorForeground, state, iconCase),
bounds.Min.Add(offset))
}
}
func (element *Icon) icon () artist.Icon {
return element.theme.Icon(element.id, element.size)
return element.entity.Theme().Icon(element.id, element.size, iconCase)
}
func (element *Icon) updateMinimumSize () {

View File

@ -15,8 +15,8 @@ type Image struct {
// NewImage creates a new image element.
func NewImage (image image.Image) (element *Image) {
element = &Image { buffer: canvas.FromImage(image) }
element.entity = tomo.NewEntity(element)
element = &Image { buffer: artist.FromImage(image) }
element.entity = tomo.GetBackend().NewEntity(element)
bounds := element.buffer.Bounds()
element.entity.SetMinimumSize(bounds.Dx(), bounds.Dy())
return

View File

@ -8,6 +8,8 @@ import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
var labelCase = tomo.C("tomo", "label")
// Label is a simple text box.
type Label struct {
entity tomo.Entity
@ -25,11 +27,10 @@ type Label struct {
// NewLabel creates a new label.
func NewLabel (text string) (element *Label) {
element = &Label { }
element.theme.Case = tomo.C("tomo", "label")
element.entity = tomo.NewEntity(element)
element.drawer.SetFace (element.theme.FontFace (
element.entity = tomo.GetBackend().NewEntity(element)
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
tomo.FontSizeNormal, labelCase))
element.SetText(text)
return
}
@ -58,9 +59,9 @@ func (element *Label) Draw (destination artist.Canvas) {
element.entity.DrawBackground(destination)
textBounds := element.drawer.LayoutBounds()
foreground := element.theme.Color (
foreground := element.entity.Theme().Color (
tomo.ColorForeground,
tomo.State { })
tomo.State { }, labelCase)
element.drawer.Draw(destination, foreground, bounds.Min.Sub(textBounds.Min))
}
@ -127,21 +128,10 @@ func (element *Label) SetAlign (align textdraw.Align) {
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 (
func (element *Label) HandleThemeChange () {
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
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
tomo.FontSizeNormal, labelCase))
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -190,7 +180,7 @@ func (element *Label) updateMinimumSize () {
if element.wrap {
em := element.drawer.Em().Round()
if em < 1 {
em = element.theme.Padding(tomo.PatternBackground)[0]
em = element.entity.Theme().Padding(tomo.PatternBackground, labelCase)[0]
}
width, height = em, element.drawer.LineHeight().Round()
element.entity.NotifyFlexibleHeightChange()

View File

@ -33,7 +33,7 @@ func NewHLerpSlider[T Numeric] (min, max T, value T) (element *LerpSlider[T]) {
min: min,
max: max,
}
element.entity = tomo.NewEntity(element).(tomo.FocusableEntity)
element.entity = tomo.GetBackend().NewEntity(element)
element.construct()
element.SetValue(value)
return

View File

@ -4,11 +4,15 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil"
type list struct {
container
entity tomo.Entity
c tomo.Case
enabled bool
scroll image.Point
contentBounds image.Rectangle
@ -32,8 +36,8 @@ type FlowList struct {
func NewList (children ...tomo.Element) (element *List) {
element = &List { }
element.theme.Case = tomo.C("tomo", "list")
element.entity = tomo.NewEntity(element)
element.c = tomo.C("tomo", "list")
element.entity = tomo.GetBackend().NewEntity(element)
element.container.entity = element.entity
element.minimumSize = element.updateMinimumSize
element.init(children...)
@ -42,8 +46,8 @@ func NewList (children ...tomo.Element) (element *List) {
func NewFlowList (children ...tomo.Element) (element *FlowList) {
element = &FlowList { }
element.theme.Case = tomo.C("tomo", "flowList")
element.entity = tomo.NewEntity(element).(listEntity)
element.c = tomo.C("tomo", "flowList")
element.entity = tomo.GetBackend().NewEntity(element)
element.container.entity = element.entity
element.minimumSize = element.updateMinimumSize
element.init(children...)
@ -63,8 +67,8 @@ func (element *list) Draw (destination artist.Canvas) {
rocks[index] = element.entity.Child(index).Entity().Bounds()
}
pattern := element.theme.Pattern(tomo.PatternSunken, element.state())
artist.DrawShatter(destination, pattern, element.entity.Bounds(), rocks...)
pattern := element.entity.Theme().Pattern(tomo.PatternSunken, element.state(), element.c)
artutil.DrawShatter(destination, pattern, element.entity.Bounds(), rocks...)
}
func (element *List) Layout () {
@ -72,8 +76,8 @@ func (element *List) Layout () {
element.scroll.Y = element.maxScrollHeight()
}
margin := element.theme.Margin(tomo.PatternSunken)
padding := element.theme.Padding(tomo.PatternSunken)
margin := element.entity.Theme().Margin(tomo.PatternSunken, element.c)
padding := element.entity.Theme().Padding(tomo.PatternSunken, element.c)
bounds := padding.Apply(element.entity.Bounds())
element.contentBounds = image.Rectangle { }
@ -110,8 +114,8 @@ func (element *FlowList) Layout () {
element.scroll.Y = element.maxScrollHeight()
}
margin := element.theme.Margin(tomo.PatternSunken)
padding := element.theme.Padding(tomo.PatternSunken)
margin := element.entity.Theme().Margin(tomo.PatternSunken, element.c)
padding := element.entity.Theme().Padding(tomo.PatternSunken, element.c)
bounds := padding.Apply(element.entity.Bounds())
element.contentBounds = image.Rectangle { }
@ -135,7 +139,7 @@ func (element *FlowList) Layout () {
if width + dot.X > bounds.Max.X {
nextLine()
}
if typedChild, ok := child.(tomo.Flexible); ok {
if typedChild, ok := child.(ability.Flexible); ok {
height = typedChild.FlexibleHeightFor(width)
}
if rowHeight < height {
@ -162,7 +166,7 @@ func (element *FlowList) Layout () {
func (element *list) Selected () ability.Selectable {
if element.selected == -1 { return nil }
child, ok := element.entity.Child(element.selected).(tomo.Selectable)
child, ok := element.entity.Child(element.selected).(ability.Selectable)
if !ok { return nil }
return child
}
@ -221,7 +225,7 @@ func (element *list) HandleChildMouseDown (
) {
if !element.enabled { return }
element.Focus()
if child, ok := child.(tomo.Selectable); ok {
if child, ok := child.(ability.Selectable); ok {
element.Select(child)
}
}
@ -274,10 +278,7 @@ func (element *list) DrawBackground (destination artist.Canvas) {
element.entity.DrawBackground(destination)
}
// SetTheme sets the element's theme.
func (element *list) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
func (element *list) HandleThemeChange () {
element.minimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
@ -312,7 +313,7 @@ func (element *list) ScrollContentBounds () image.Rectangle {
// ScrollViewportBounds returns the size and position of the element's
// viewport relative to ScrollBounds.
func (element *list) ScrollViewportBounds () image.Rectangle {
padding := element.theme.Padding(tomo.PatternSunken)
padding := element.entity.Theme().Padding(tomo.PatternSunken, element.c)
bounds := padding.Apply(element.entity.Bounds())
bounds = bounds.Sub(bounds.Min).Add(element.scroll)
return bounds
@ -364,7 +365,7 @@ func (element *list) selectNone () {
func (element *list) scrollToSelected () {
if element.selected < 0 { return }
target := element.entity.Child(element.selected).Entity().Bounds()
padding := element.theme.Padding(tomo.PatternSunken)
padding := element.entity.Theme().Padding(tomo.PatternSunken, element.c)
bounds := padding.Apply(element.entity.Bounds())
if target.Min.Y < bounds.Min.Y {
element.scroll.Y -= bounds.Min.Y - target.Min.Y
@ -385,7 +386,7 @@ func (element *list) state () tomo.State {
}
func (element *list) maxScrollHeight () (height int) {
padding := element.theme.Padding(tomo.PatternSunken)
padding := element.entity.Theme().Padding(tomo.PatternSunken, element.c)
viewportHeight := element.entity.Bounds().Dy() - padding.Vertical()
height = element.contentBounds.Dy() - viewportHeight
if height < 0 { height = 0 }
@ -393,8 +394,8 @@ func (element *list) maxScrollHeight () (height int) {
}
func (element *List) updateMinimumSize () {
margin := element.theme.Margin(tomo.PatternSunken)
padding := element.theme.Padding(tomo.PatternSunken)
margin := element.entity.Theme().Margin(tomo.PatternSunken, element.c)
padding := element.entity.Theme().Padding(tomo.PatternSunken, element.c)
width := 0
height := 0
@ -427,7 +428,7 @@ func (element *List) updateMinimumSize () {
}
func (element *FlowList) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternSunken)
padding := element.entity.Theme().Padding(tomo.PatternSunken, element.c)
minimumWidth := 0
for index := 0; index < element.entity.CountChildren(); index ++ {
width, height := element.entity.ChildMinimumSize(index)

View File

@ -4,6 +4,8 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist"
var progressBarCase = tomo.C("tomo", "progressBar")
// ProgressBar displays a visual indication of how far along a task is.
type ProgressBar struct {
entity tomo.Entity
@ -16,8 +18,7 @@ func NewProgressBar (progress float64) (element *ProgressBar) {
if progress < 0 { progress = 0 }
if progress > 1 { progress = 1 }
element = &ProgressBar { progress: progress }
element.entity = tomo.NewEntity(element)
element.theme.Case = tomo.C("tomo", "progressBar")
element.entity = tomo.GetBackend().NewEntity(element)
element.updateMinimumSize()
return
}
@ -31,15 +32,15 @@ func (element *ProgressBar) Entity () tomo.Entity {
func (element *ProgressBar) Draw (destination artist.Canvas) {
bounds := element.entity.Bounds()
pattern := element.theme.Pattern(tomo.PatternSunken, tomo.State { })
padding := element.theme.Padding(tomo.PatternSunken)
pattern := element.entity.Theme().Pattern(tomo.PatternSunken, tomo.State { }, progressBarCase)
padding := element.entity.Theme().Padding(tomo.PatternSunken, progressBarCase)
pattern.Draw(destination, bounds)
bounds = padding.Apply(bounds)
meterBounds := image.Rect (
bounds.Min.X, bounds.Min.Y,
bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
bounds.Max.Y)
mercury := element.theme.Pattern(tomo.PatternMercury, tomo.State { })
mercury := element.entity.Theme().Pattern(tomo.PatternMercury, tomo.State { }, progressBarCase)
mercury.Draw(destination, meterBounds)
}
@ -52,17 +53,14 @@ func (element *ProgressBar) SetProgress (progress float64) {
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *ProgressBar) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
func (element *ProgressBar) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
}
func (element *ProgressBar) updateMinimumSize() {
padding := element.theme.Padding(tomo.PatternSunken)
innerPadding := element.theme.Padding(tomo.PatternMercury)
padding := element.entity.Theme().Padding(tomo.PatternSunken, progressBarCase)
innerPadding := element.entity.Theme().Padding(tomo.PatternMercury, progressBarCase)
element.entity.SetMinimumSize (
padding.Horizontal() + innerPadding.Horizontal(),
padding.Vertical() + innerPadding.Vertical())

View File

@ -6,6 +6,8 @@ import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
var scrollCase = tomo.C("tomo", "scroll")
// ScrollMode specifies which sides of a Scroll have scroll bars.
type ScrollMode int; const (
ScrollNeither ScrollMode = 0
@ -33,8 +35,7 @@ type Scroll struct {
// NewScroll creates a new scroll element.
func NewScroll (mode ScrollMode, child ability.Scrollable) (element *Scroll) {
element = &Scroll { }
element.theme.Case = tomo.C("tomo", "scroll")
element.entity = tomo.NewEntity(element).(scrollEntity)
element.entity = tomo.GetBackend().NewEntity(element)
if mode.Includes(ScrollHorizontal) {
element.horizontal = NewHScrollBar()
@ -82,8 +83,8 @@ func (element *Scroll) Draw (destination artist.Canvas) {
bounds.Max.X - element.vertical.Entity().Bounds().Dx(),
bounds.Max.Y - element.horizontal.Entity().Bounds().Dy())
state := tomo.State { }
deadArea := element.theme.Pattern(tomo.PatternDead, state)
deadArea.Draw(canvas.Cut(destination, bounds), bounds)
deadArea := element.entity.Theme().Pattern(tomo.PatternDead, state, scrollCase)
deadArea.Draw(artist.Cut(destination, bounds), bounds)
}
}
@ -185,20 +186,12 @@ func (element *Scroll) HandleScroll (
element.scrollChildBy(int(deltaX), int(deltaY))
}
// SetTheme sets the element's theme.
func (element *Scroll) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
func (element *Scroll) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
// SetConfig sets the element's configuration.
func (element *Scroll) SetConfig (config tomo.Config) {
element.config.Config = config
}
func (element *Scroll) updateMinimumSize () {
var width, height int

View File

@ -4,7 +4,6 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
// ScrollBar is an element similar to Slider, but it has special behavior that
// makes it well suited for controlling the viewport position on one axis of a
@ -20,6 +19,8 @@ import "git.tebibyte.media/sashakoshka/tomo/ability"
type ScrollBar struct {
entity tomo.Entity
c tomo.Case
vertical bool
enabled bool
dragging bool
@ -39,8 +40,8 @@ func NewVScrollBar () (element *ScrollBar) {
vertical: true,
enabled: true,
}
element.theme.Case = tomo.C("tomo", "scrollBarVertical")
element.entity = tomo.NewEntity(element).(scrollBarEntity)
element.c = tomo.C("tomo", "scrollBarVertical")
element.entity = tomo.GetBackend().NewEntity(element)
element.updateMinimumSize()
return
}
@ -50,8 +51,8 @@ func NewHScrollBar () (element *ScrollBar) {
element = &ScrollBar {
enabled: true,
}
element.theme.Case = tomo.C("tomo", "scrollBarHorizontal")
element.entity = tomo.NewEntity(element).(tomo.Entity)
element.c = tomo.C("tomo", "scrollBarHorizontal")
element.entity = tomo.GetBackend().NewEntity(element)
element.updateMinimumSize()
return
}
@ -70,10 +71,10 @@ func (element *ScrollBar) Draw (destination artist.Canvas) {
Disabled: !element.Enabled(),
Pressed: element.dragging,
}
element.theme.Pattern(tomo.PatternGutter, state).Draw (
element.entity.Theme().Pattern(tomo.PatternGutter, state, element.c).Draw (
destination,
bounds)
element.theme.Pattern(tomo.PatternHandle, state).Draw (
element.entity.Theme().Pattern(tomo.PatternHandle, state, element.c).Draw (
destination,
element.bar)
}
@ -83,7 +84,7 @@ func (element *ScrollBar) HandleMouseDown (
button input.Button,
modifiers input.Modifiers,
) {
velocity := element.config.ScrollVelocity()
velocity := element.entity.Config().ScrollVelocity()
if position.In(element.bar) {
// the mouse is pressed down within the bar's handle
@ -185,17 +186,7 @@ func (element *ScrollBar) OnScroll (callback func (viewport image.Point)) {
element.onScroll = callback
}
// SetTheme sets the element's theme.
func (element *ScrollBar) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *ScrollBar) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
func (element *ScrollBar) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -263,7 +254,7 @@ func (element *ScrollBar) recalculate () {
func (element *ScrollBar) recalculateVertical () {
bounds := element.entity.Bounds()
padding := element.theme.Padding(tomo.PatternGutter)
padding := element.entity.Theme().Padding(tomo.PatternGutter, element.c)
element.track = padding.Apply(bounds)
contentBounds := element.contentBounds
@ -290,7 +281,7 @@ func (element *ScrollBar) recalculateVertical () {
func (element *ScrollBar) recalculateHorizontal () {
bounds := element.entity.Bounds()
padding := element.theme.Padding(tomo.PatternGutter)
padding := element.entity.Theme().Padding(tomo.PatternGutter, element.c)
element.track = padding.Apply(bounds)
contentBounds := element.contentBounds
@ -316,8 +307,8 @@ func (element *ScrollBar) recalculateHorizontal () {
}
func (element *ScrollBar) updateMinimumSize () {
gutterPadding := element.theme.Padding(tomo.PatternGutter)
handlePadding := element.theme.Padding(tomo.PatternHandle)
gutterPadding := element.entity.Theme().Padding(tomo.PatternGutter, element.c)
handlePadding := element.entity.Theme().Padding(tomo.PatternHandle, element.c)
if element.vertical {
element.entity.SetMinimumSize (
gutterPadding.Horizontal() + handlePadding.Horizontal(),

View File

@ -4,7 +4,6 @@ import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
// Slider is a slider control with a floating point value between zero and one.
type Slider struct {
@ -22,12 +21,16 @@ func NewVSlider (value float64) (element *Slider) {
func NewHSlider (value float64) (element *Slider) {
element = &Slider { }
element.value = value
element.entity = tomo.NewEntity(element)
element.entity = tomo.GetBackend().NewEntity(element)
element.construct()
return
}
type slider struct {
entity tomo.Entity
c tomo.Case
value float64
vertical bool
dragging bool
@ -43,9 +46,9 @@ type slider struct {
func (element *slider) construct () {
element.enabled = true
if element.vertical {
element.theme.Case = tomo.C("tomo", "sliderVertical")
element.c = tomo.C("tomo", "sliderVertical")
} else {
element.theme.Case = tomo.C("tomo", "sliderHorizontal")
element.c = tomo.C("tomo", "sliderHorizontal")
}
element.updateMinimumSize()
}
@ -58,7 +61,7 @@ func (element *slider) Entity () tomo.Entity {
// Draw causes the element to draw to the specified destination canvas.
func (element *slider) Draw (destination artist.Canvas) {
bounds := element.entity.Bounds()
element.track = element.theme.Padding(tomo.PatternGutter).Apply(bounds)
element.track = element.entity.Theme().Padding(tomo.PatternGutter, element.c).Apply(bounds)
if element.vertical {
barSize := element.track.Dx()
element.bar = image.Rect(0, 0, barSize, barSize).Add(element.track.Min)
@ -80,8 +83,8 @@ func (element *slider) Draw (destination artist.Canvas) {
Focused: element.entity.Focused(),
Pressed: element.dragging,
}
element.theme.Pattern(tomo.PatternGutter, state).Draw(destination, bounds)
element.theme.Pattern(tomo.PatternHandle, state).Draw(destination, element.bar)
element.entity.Theme().Pattern(tomo.PatternGutter, state, element.c).Draw(destination, bounds)
element.entity.Theme().Pattern(tomo.PatternHandle, state, element.c).Draw(destination, element.bar)
}
// Focus gives this element input focus.
@ -205,21 +208,12 @@ func (element *slider) OnRelease (callback func ()) {
element.onRelease = callback
}
// SetTheme sets the element's theme.
func (element *slider) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *slider) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
func (element *slider) HandleThemeChange () {
element.updateMinimumSize()
element.entity.Invalidate()
}
func (element *slider) changeValue (delta float64) {
element.value += delta
if element.value < 0 {
@ -252,8 +246,8 @@ func (element *slider) valueFor (x, y int) (value float64) {
}
func (element *slider) updateMinimumSize () {
gutterPadding := element.theme.Padding(tomo.PatternGutter)
handlePadding := element.theme.Padding(tomo.PatternHandle)
gutterPadding := element.entity.Theme().Padding(tomo.PatternGutter, element.c)
handlePadding := element.entity.Theme().Padding(tomo.PatternHandle, element.c)
if element.vertical {
element.entity.SetMinimumSize (
gutterPadding.Horizontal() + handlePadding.Horizontal(),

View File

@ -3,6 +3,8 @@ package elements
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist"
var spacerCase = tomo.C("tomo", "spacer")
// Spacer can be used to put space between two elements..
type Spacer struct {
entity tomo.Entity
@ -12,8 +14,7 @@ type Spacer struct {
// NewSpacer creates a new spacer.
func NewSpacer () (element *Spacer) {
element = &Spacer { }
element.entity = tomo.NewEntity(element).(spacerEntity)
element.theme.Case = tomo.C("tomo", "spacer")
element.entity = tomo.GetBackend().NewEntity(element)
element.updateMinimumSize()
return
}
@ -35,14 +36,14 @@ func (element *Spacer) Draw (destination artist.Canvas) {
bounds := element.entity.Bounds()
if element.line {
pattern := element.theme.Pattern (
pattern := element.entity.Theme().Pattern (
tomo.PatternLine,
tomo.State { })
tomo.State { }, spacerCase)
pattern.Draw(destination, bounds)
} else {
pattern := element.theme.Pattern (
pattern := element.entity.Theme().Pattern (
tomo.PatternBackground,
tomo.State { })
tomo.State { }, spacerCase)
pattern.Draw(destination, bounds)
}
}
@ -55,23 +56,13 @@ func (element *Spacer) SetLine (line bool) {
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *Spacer) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *Spacer) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
func (element *Spacer) HandleThemeChange () {
element.entity.Invalidate()
}
func (element *Spacer) updateMinimumSize () {
if element.line {
padding := element.theme.Padding(tomo.PatternLine)
padding := element.entity.Theme().Padding(tomo.PatternLine, spacerCase)
element.entity.SetMinimumSize (
padding.Horizontal(),
padding.Vertical())

View File

@ -6,6 +6,8 @@ import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
var switchCase = tomo.C("tomo", "switch")
// 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 {
@ -27,11 +29,10 @@ func NewSwitch (text string, on bool) (element *Switch) {
text: text,
enabled: true,
}
element.entity = tomo.NewEntity(element).(checkboxEntity)
element.theme.Case = tomo.C("tomo", "switch")
element.drawer.SetFace (element.theme.FontFace (
element.entity = tomo.GetBackend().NewEntity(element)
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
tomo.FontSizeNormal, switchCase))
element.drawer.SetText([]rune(text))
element.updateMinimumSize()
return
@ -71,24 +72,24 @@ func (element *Switch) Draw (destination artist.Canvas) {
}
}
gutterPattern := element.theme.Pattern (
tomo.PatternGutter, state)
gutterPattern := element.entity.Theme().Pattern (
tomo.PatternGutter, state, switchCase)
gutterPattern.Draw(destination, gutterBounds)
handlePattern := element.theme.Pattern (
tomo.PatternHandle, state)
handlePattern := element.entity.Theme().Pattern (
tomo.PatternHandle, state, switchCase)
handlePattern.Draw(destination, handleBounds)
textBounds := element.drawer.LayoutBounds()
offset := bounds.Min.Add(image.Point {
X: bounds.Dy() * 2 +
element.theme.Margin(tomo.PatternBackground).X,
element.entity.Theme().Margin(tomo.PatternBackground, switchCase).X,
})
offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X
foreground := element.theme.Color(tomo.ColorForeground, state)
foreground := element.entity.Theme().Color(tomo.ColorForeground, state, switchCase)
element.drawer.Draw(destination, foreground, offset)
}
@ -181,21 +182,10 @@ func (element *Switch) SetText (text string) {
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *Switch) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
func (element *Switch) HandleThemeChange () {
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
element.updateMinimumSize()
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *Switch) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
tomo.FontSizeNormal, switchCase))
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -209,7 +199,7 @@ func (element *Switch) updateMinimumSize () {
} else {
element.entity.SetMinimumSize (
lineHeight * 2 +
element.theme.Margin(tomo.PatternBackground).X +
element.entity.Theme().Margin(tomo.PatternBackground, switchCase).X +
textBounds.Dx(),
lineHeight)
}

View File

@ -12,6 +12,8 @@ import "git.tebibyte.media/sashakoshka/tomo/textmanip"
import "git.tebibyte.media/sashakoshka/tomo/fixedutil"
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
var textBoxCase = tomo.C("tomo", "textBox")
// TextBox is a single-line text input.
type TextBox struct {
entity tomo.Entity
@ -38,15 +40,14 @@ type TextBox struct {
// text.
func NewTextBox (placeholder, value string) (element *TextBox) {
element = &TextBox { enabled: true }
element.theme.Case = tomo.C("tomo", "textBox")
element.entity = tomo.NewEntity(element).(textBoxEntity)
element.entity = tomo.GetBackend().NewEntity(element)
element.placeholder = placeholder
element.placeholderDrawer.SetFace (element.theme.FontFace (
element.placeholderDrawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
element.valueDrawer.SetFace (element.theme.FontFace (
tomo.FontSizeNormal, textBoxCase))
element.valueDrawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
tomo.FontSizeNormal, textBoxCase))
element.placeholderDrawer.SetText([]rune(placeholder))
element.updateMinimumSize()
element.SetValue(value)
@ -63,15 +64,15 @@ func (element *TextBox) Draw (destination artist.Canvas) {
bounds := element.entity.Bounds()
state := element.state()
pattern := element.theme.Pattern(tomo.PatternInput, state)
padding := element.theme.Padding(tomo.PatternInput)
innerCanvas := canvas.Cut(destination, padding.Apply(bounds))
pattern := element.entity.Theme().Pattern(tomo.PatternInput, state, textBoxCase)
padding := element.entity.Theme().Padding(tomo.PatternInput, textBoxCase)
innerCanvas := artist.Cut(destination, padding.Apply(bounds))
pattern.Draw(destination, bounds)
offset := element.textOffset()
if element.entity.Focused() && !element.dot.Empty() {
// draw selection bounds
accent := element.theme.Color(tomo.ColorAccent, state)
accent := element.entity.Theme().Color(tomo.ColorAccent, state, textBoxCase)
canon := element.dot.Canon()
foff := fixedutil.Pt(offset)
start := element.valueDrawer.PositionAt(canon.Start).Add(foff)
@ -89,9 +90,9 @@ func (element *TextBox) Draw (destination artist.Canvas) {
if len(element.text) == 0 {
// draw placeholder
textBounds := element.placeholderDrawer.LayoutBounds()
foreground := element.theme.Color (
foreground := element.entity.Theme().Color (
tomo.ColorForeground,
tomo.State { Disabled: true })
tomo.State { Disabled: true }, textBoxCase)
element.placeholderDrawer.Draw (
innerCanvas,
foreground,
@ -99,7 +100,7 @@ func (element *TextBox) Draw (destination artist.Canvas) {
} else {
// draw input value
textBounds := element.valueDrawer.LayoutBounds()
foreground := element.theme.Color(tomo.ColorForeground, state)
foreground := element.entity.Theme().Color(tomo.ColorForeground, state, textBoxCase)
element.valueDrawer.Draw (
innerCanvas,
foreground,
@ -108,7 +109,7 @@ func (element *TextBox) Draw (destination artist.Canvas) {
if element.entity.Focused() && element.dot.Empty() {
// draw cursor
foreground := element.theme.Color(tomo.ColorForeground, state)
foreground := element.entity.Theme().Color(tomo.ColorForeground, state, textBoxCase)
cursorPosition := fixedutil.RoundPt (
element.valueDrawer.PositionAt(element.dot.End))
shapes.ColorLine (
@ -144,7 +145,7 @@ func (element *TextBox) HandleMouseDown (
runeIndex := element.atPosition(position)
if runeIndex == -1 { return }
if time.Since(element.lastClick) < element.config.DoubleClickDelay() {
if time.Since(element.lastClick) < element.entity.Config().DoubleClickDelay() {
element.dragging = 2
element.dot = textmanip.WordAround(element.text, runeIndex)
} else {
@ -202,7 +203,7 @@ func (element *TextBox) HandleMotion (position image.Point) {
}
func (element *TextBox) textOffset () image.Point {
padding := element.theme.Padding(tomo.PatternInput)
padding := element.entity.Theme().Padding(tomo.PatternInput, textBoxCase)
bounds := element.entity.Bounds()
innerBounds := padding.Apply(bounds)
textHeight := element.valueDrawer.LineHeight().Round()
@ -464,27 +465,17 @@ func (element *TextBox) ScrollAxes () (horizontal, vertical bool) {
return true, false
}
// SetTheme sets the element's theme.
func (element *TextBox) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
face := element.theme.FontFace (
func (element *TextBox) HandleThemeChange () {
face := element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal)
tomo.FontSizeNormal,
textBoxCase)
element.placeholderDrawer.SetFace(face)
element.valueDrawer.SetFace(face)
element.updateMinimumSize()
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *TextBox) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.entity.Invalidate()
}
func (element *TextBox) contextMenu (position image.Point) {
window := element.entity.Window()
menu, err := window.NewMenu(image.Rectangle { position, position })
@ -528,12 +519,12 @@ func (element *TextBox) runOnChange () {
}
func (element *TextBox) scrollViewportWidth () (width int) {
padding := element.theme.Padding(tomo.PatternInput)
padding := element.entity.Theme().Padding(tomo.PatternInput, textBoxCase)
return padding.Apply(element.entity.Bounds()).Dx()
}
func (element *TextBox) scrollToCursor () {
padding := element.theme.Padding(tomo.PatternInput)
padding := element.entity.Theme().Padding(tomo.PatternInput, textBoxCase)
bounds := padding.Apply(element.entity.Bounds())
bounds = bounds.Sub(bounds.Min)
bounds.Max.X -= element.valueDrawer.Em().Round()
@ -556,7 +547,7 @@ func (element *TextBox) scrollToCursor () {
func (element *TextBox) updateMinimumSize () {
textBounds := element.placeholderDrawer.LayoutBounds()
padding := element.theme.Padding(tomo.PatternInput)
padding := element.entity.Theme().Padding(tomo.PatternInput, textBoxCase)
element.entity.SetMinimumSize (
padding.Horizontal() + textBounds.Dx(),
padding.Vertical() +

View File

@ -6,6 +6,8 @@ import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
var toggleButtonCase = tomo.C("tomo", "toggleButton")
// ToggleButton is a togglable button.
type ToggleButton struct {
entity tomo.Entity
@ -30,11 +32,11 @@ func NewToggleButton (text string, on bool) (element *ToggleButton) {
enabled: true,
on: on,
}
element.entity = tomo.NewEntity(element)
element.theme.Case = tomo.C("tomo", "toggleButton")
element.drawer.SetFace (element.theme.FontFace (
element.entity = tomo.GetBackend().NewEntity(element)
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
tomo.FontSizeNormal,
toggleButtonCase))
element.SetText(text)
return
}
@ -48,10 +50,10 @@ func (element *ToggleButton) Entity () tomo.Entity {
func (element *ToggleButton) Draw (destination artist.Canvas) {
state := element.state()
bounds := element.entity.Bounds()
pattern := element.theme.Pattern(tomo.PatternButton, state)
pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, toggleButtonCase)
lampPattern := element.theme.Pattern(tomo.PatternLamp, state)
lampPadding := element.theme.Padding(tomo.PatternLamp).Horizontal()
lampPattern := element.entity.Theme().Pattern(tomo.PatternLamp, state, toggleButtonCase)
lampPadding := element.entity.Theme().Padding(tomo.PatternLamp, toggleButtonCase).Horizontal()
lampBounds := bounds
lampBounds.Max.X = lampBounds.Min.X + lampPadding
bounds.Min.X += lampPadding
@ -59,9 +61,9 @@ func (element *ToggleButton) Draw (destination artist.Canvas) {
pattern.Draw(destination, bounds)
lampPattern.Draw(destination, lampBounds)
foreground := element.theme.Color(tomo.ColorForeground, state)
sink := element.theme.Sink(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
foreground := element.entity.Theme().Color(tomo.ColorForeground, state, toggleButtonCase)
sink := element.entity.Theme().Sink(tomo.PatternButton, toggleButtonCase)
margin := element.entity.Theme().Margin(tomo.PatternButton, toggleButtonCase)
offset := image.Pt (
bounds.Dx() / 2,
@ -76,7 +78,7 @@ func (element *ToggleButton) Draw (destination artist.Canvas) {
}
if element.hasIcon {
icon := element.theme.Icon(element.iconId, tomo.IconSizeSmall)
icon := element.entity.Theme().Icon(element.iconId, tomo.IconSizeSmall, toggleButtonCase)
if icon != nil {
iconBounds := icon.Bounds()
addedWidth := iconBounds.Dx()
@ -166,21 +168,10 @@ func (element *ToggleButton) ShowText (showText bool) {
element.entity.Invalidate()
}
// SetTheme sets the element's theme.
func (element *ToggleButton) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
func (element *ToggleButton) HandleThemeChange () {
element.drawer.SetFace (element.entity.Theme().FontFace (
tomo.FontStyleRegular,
tomo.FontSizeNormal))
element.updateMinimumSize()
element.entity.Invalidate()
}
// SetConfig sets the element's configuration.
func (element *ToggleButton) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.config.Config = new
tomo.FontSizeNormal, toggleButtonCase))
element.updateMinimumSize()
element.entity.Invalidate()
}
@ -239,15 +230,15 @@ func (element *ToggleButton) HandleKeyUp(key input.Key, modifiers input.Modifier
}
func (element *ToggleButton) updateMinimumSize () {
padding := element.theme.Padding(tomo.PatternButton)
margin := element.theme.Margin(tomo.PatternButton)
lampPadding := element.theme.Padding(tomo.PatternLamp)
padding := element.entity.Theme().Padding(tomo.PatternButton, toggleButtonCase)
margin := element.entity.Theme().Margin(tomo.PatternButton, toggleButtonCase)
lampPadding := element.entity.Theme().Padding(tomo.PatternLamp, toggleButtonCase)
textBounds := element.drawer.LayoutBounds()
minimumSize := textBounds.Sub(textBounds.Min)
if element.hasIcon {
icon := element.theme.Icon(element.iconId, tomo.IconSizeSmall)
icon := element.entity.Theme().Icon(element.iconId, tomo.IconSizeSmall, toggleButtonCase)
if icon != nil {
bounds := icon.Bounds()
if element.showText {

View File

@ -1,16 +1,19 @@
package main
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/testing"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
func main () {
tomo.Run(run)
nasin.Run(Application { })
}
func run () {
window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 128, 128))
type Application struct { }
func (Application) Init () error {
window, err := nasin.NewWindow(tomo.Bounds(0, 0, 128, 128))
if err != nil { return err }
window.SetTitle("vertical stack")
container := elements.NewVBox(elements.SpaceBoth)
@ -25,13 +28,15 @@ func run () {
container.Adopt(okButton)
okButton.Focus()
})
okButton.OnClick(tomo.Stop)
okButton.OnClick(nasin.Stop)
container.AdoptExpand(label)
container.Adopt(button, okButton)
window.Adopt(container)
okButton.Focus()
window.OnClose(tomo.Stop)
window.OnClose(nasin.Stop)
window.Show()
return nil
}

View File

@ -6,6 +6,7 @@ import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
type entity struct {
backend *backend
window *window
parent *entity
children []*entity
@ -21,7 +22,7 @@ type entity struct {
}
func (backend *backend) NewEntity (owner tomo.Element) tomo.Entity {
entity := &entity { element: owner }
entity := &entity { element: owner, backend: backend }
entity.InvalidateLayout()
return entity
}
@ -162,7 +163,7 @@ func (entity *entity) DrawBackground (destination artist.Canvas) {
if entity.parent != nil {
entity.parent.element.(ability.Container).DrawBackground(destination)
} else if entity.window != nil {
entity.window.system.theme.Pattern (
entity.backend.theme.Pattern (
tomo.PatternBackground,
tomo.State { },
tomo.C("tomo", "window")).Draw (
@ -292,11 +293,11 @@ func (entity *entity) NotifyScrollBoundsChange () {
// ----------- ThemeableEntity ----------- //
func (entity *entity) Theme () tomo.Theme {
return entity.window.theme
return entity.backend.theme
}
// ----------- ConfigurableEntity ----------- //
func (entity *entity) Config () tomo.Config {
return entity.window.config
return entity.backend.config
}

View File

@ -1,11 +1,8 @@
package x
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
import defaultTheme "git.tebibyte.media/sashakoshka/tomo/default/theme"
import defaultConfig "git.tebibyte.media/sashakoshka/tomo/default/config"
type entitySet map[*entity] struct { }
@ -27,9 +24,6 @@ type system struct {
focused *entity
canvas artist.BasicCanvas
theme tomo.Theme
config tomo.Config
invalidateIgnore bool
drawingInvalid entitySet
anyLayoutInvalid bool
@ -43,12 +37,7 @@ func (system *system) initialize () {
system.drawingInvalid = make(entitySet)
}
func (system *system) setTheme (theme tomo.Theme) {
if theme == nil {
system.theme = defaultTheme.Default { }
} else {
system.theme = theme
}
func (system *system) handleThemeChange () {
system.propagate (func (entity *entity) bool {
if child, ok := system.child.element.(ability.Themeable); ok {
child.HandleThemeChange()
@ -57,12 +46,7 @@ func (system *system) setTheme (theme tomo.Theme) {
})
}
func (system *system) setConfig (config tomo.Config) {
if config == nil {
system.config = defaultConfig.Default { }
} else {
system.config = config
}
func (system *system) handleConfigChange () {
system.propagate (func (entity *entity) bool {
if child, ok := system.child.element.(ability.Configurable); ok {
child.HandleConfigChange()

View File

@ -121,9 +121,6 @@ func (backend *backend) newWindow (
xevent.SelectionRequestFun(window.handleSelectionRequest).
Connect(backend.connection, window.xWindow.Id)
window.setTheme(backend.theme)
window.setConfig(backend.config)
window.metrics.bounds = bounds
window.setMinimumSize(8, 8)

View File

@ -1,6 +1,8 @@
package x
import "git.tebibyte.media/sashakoshka/tomo"
import defaultTheme "git.tebibyte.media/sashakoshka/tomo/default/theme"
import defaultConfig "git.tebibyte.media/sashakoshka/tomo/default/config"
import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto"
@ -96,17 +98,26 @@ func (backend *backend) Do (callback func ()) {
func (backend *backend) SetTheme (theme tomo.Theme) {
backend.assert()
backend.theme = theme
if theme == nil {
backend.theme = defaultTheme.Default { }
} else {
backend.theme = theme
}
for _, window := range backend.windows {
window.setTheme(theme)
window.handleThemeChange()
}
}
func (backend *backend) SetConfig (config tomo.Config) {
backend.assert()
if config == nil {
backend.config = defaultConfig.Default { }
} else {
backend.config = config
}
backend.config = config
for _, window := range backend.windows {
window.setConfig(config)
window.handleConfigChange()
}
}