Oh my jod

This commit is contained in:
Sasha Koshka 2023-02-08 14:36:14 -05:00
parent 6936353516
commit a0e57921a4
18 changed files with 333 additions and 193 deletions

View File

@ -165,6 +165,7 @@ func (drawer *TextDrawer) LineHeight () (height fixed.Int26_6) {
// have its maximum width set to the given width. This does not alter the
// drawer's state.
func (drawer *TextDrawer) ReccomendedHeightFor (width int) (height int) {
if drawer.face == nil { return }
if !drawer.layoutClean { drawer.recalculate() }
metrics := drawer.face.Metrics()
dot := fixed.Point26_6 { 0, metrics.Height }

View File

@ -50,3 +50,44 @@ func (Default) ScrollVelocity () int {
func (Default) ThemePath () (string) {
return ""
}
// Wrapped wraps a configuration and uses Default if it is nil.
type Wrapped struct {
Config
}
// Padding returns the amount of internal padding elements should have.
// An element's inner content (such as text) should be inset by this
// amount, in addition to the inset returned by the pattern of its
// background.
func (wrapped Wrapped) Padding () int {
return wrapped.ensure().Padding()
}
// Margin returns how much space should be put in between elements.
func (wrapped Wrapped) Margin () int {
return wrapped.ensure().Margin()
}
// HandleWidth returns how large grab handles should typically be. This
// is important for accessibility reasons.
func (wrapped Wrapped) HandleWidth () int {
return wrapped.ensure().HandleWidth()
}
// ScrollVelocity returns how many pixels should be scrolled every time
// a scroll button is pressed.
func (wrapped Wrapped) ScrollVelocity () int {
return wrapped.ensure().ScrollVelocity()
}
// ThemePath returns the directory path to the theme.
func (wrapped Wrapped) ThemePath () string {
return wrapped.ensure().ThemePath()
}
func (wrapped Wrapped) ensure () (real Config) {
real = wrapped.Config
if real == nil { real = Default { } }
return
}

View File

@ -18,18 +18,16 @@ type Button struct {
pressed bool
text string
config config.Config
theme theme.Theme
c theme.Case
config config.Wrapped
theme theme.Wrapped
onClick func ()
}
// NewButton creates a new button with the specified label text.
func NewButton (text string) (element *Button) {
element = &Button {
c: theme.C("basic", "button"),
}
element = &Button { }
element.theme.Case = theme.C("basic", "button")
element.Core, element.core = core.NewCore(element.draw)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.redo)
@ -103,18 +101,19 @@ func (element *Button) SetText (text string) {
// SetTheme sets the element's theme.
func (element *Button) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c))
theme.FontSizeNormal))
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Button) SetConfig (new config.Config) {
element.config = new
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.redo()
}
@ -141,7 +140,7 @@ func (element *Button) draw () {
Pressed: element.pressed,
}
pattern := element.theme.Pattern(theme.PatternButton, element.c, state)
pattern := element.theme.Pattern(theme.PatternButton, state)
artist.FillRectangle(element, pattern, bounds)
@ -156,6 +155,10 @@ func (element *Button) draw () {
offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X
foreground := element.theme.Pattern(theme.PatternForeground, element.c, state)
if element.pressed {
offset = offset.Add(element.theme.Sink(theme.PatternButton))
}
foreground := element.theme.Pattern(theme.PatternForeground, state)
element.drawer.Draw(element, foreground, offset)
}

View File

@ -19,19 +19,16 @@ type Checkbox struct {
checked bool
text string
config config.Config
theme theme.Theme
c theme.Case
config config.Wrapped
theme theme.Wrapped
onToggle func ()
}
// NewCheckbox creates a new cbeckbox with the specified label text.
func NewCheckbox (text string, checked bool) (element *Checkbox) {
element = &Checkbox {
checked: checked,
c: theme.C("basic", "checkbox"),
}
element = &Checkbox { checked: checked }
element.theme.Case = theme.C("basic", "checkbox")
element.Core, element.core = core.NewCore(element.draw)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.redo)
@ -126,18 +123,19 @@ func (element *Checkbox) SetText (text string) {
// SetTheme sets the element's theme.
func (element *Checkbox) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c))
theme.FontSizeNormal))
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Checkbox) SetConfig (new config.Config) {
element.config = new
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.redo()
}
@ -172,10 +170,10 @@ func (element *Checkbox) draw () {
}
backgroundPattern := element.theme.Pattern (
theme.PatternBackground, element.c, state)
theme.PatternBackground, state)
artist.FillRectangle(element, backgroundPattern, bounds)
pattern := element.theme.Pattern(theme.PatternButton, element.c, state)
pattern := element.theme.Pattern(theme.PatternButton, state)
artist.FillRectangle(element, pattern, boxBounds)
textBounds := element.drawer.LayoutBounds()
@ -186,7 +184,6 @@ func (element *Checkbox) draw () {
offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X
foreground := element.theme.Pattern (
theme.PatternForeground, element.c, state)
foreground := element.theme.Pattern(theme.PatternForeground, state)
element.drawer.Draw(element, foreground, offset)
}

View File

@ -24,9 +24,8 @@ type Container struct {
focusable bool
flexible bool
config config.Config
theme theme.Theme
c theme.Case
config config.Wrapped
theme theme.Wrapped
onFocusRequest func () (granted bool)
onFocusMotionRequest func (input.KeynavDirection) (granted bool)
@ -35,9 +34,8 @@ type Container struct {
// NewContainer creates a new container.
func NewContainer (layout layouts.Layout) (element *Container) {
element = &Container {
c: theme.C("basic", "container"),
}
element = &Container { }
element.theme.Case = theme.C("basic", "container")
element.Core, element.core = core.NewCore(element.redoAll)
element.SetLayout(layout)
return
@ -57,6 +55,12 @@ func (element *Container) SetLayout (layout layouts.Layout) {
// whatever way is defined by the current layout.
func (element *Container) Adopt (child elements.Element, expand bool) {
// set event handlers
if child0, ok := child.(elements.Themeable); ok {
child0.SetTheme(element.theme.Theme)
}
if child0, ok := child.(elements.Configurable); ok {
child0.SetConfig(element.config.Config)
}
child.OnDamage (func (region canvas.Canvas) {
element.core.DamageRegion(region.Bounds())
})
@ -212,7 +216,6 @@ func (element *Container) redoAll () {
bounds := element.Bounds()
pattern := element.theme.Pattern (
theme.PatternBackground,
element.c,
theme.PatternState { })
artist.FillRectangle(element, pattern, bounds)
@ -225,10 +228,11 @@ func (element *Container) redoAll () {
// SetTheme sets the element's theme.
func (element *Container) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
for _, child := range element.children {
if child0, ok := child.Element.(elements.Themeable); ok {
child0.SetTheme(element.theme)
child0.SetTheme(element.theme.Theme)
}
}
element.updateMinimumSize()
@ -237,7 +241,8 @@ func (element *Container) SetTheme (new theme.Theme) {
// SetConfig sets the element's configuration.
func (element *Container) SetConfig (new config.Config) {
element.config = new
if new == element.config.Config { return }
element.config.Config = new
for _, child := range element.children {
if child0, ok := child.Element.(elements.Configurable); ok {
child0.SetConfig(element.config)

View File

@ -14,9 +14,8 @@ type Label struct {
text string
drawer artist.TextDrawer
config config.Config
theme theme.Theme
c theme.Case
config config.Wrapped
theme theme.Wrapped
onFlexibleHeightChange func ()
}
@ -24,7 +23,8 @@ type Label struct {
// NewLabel creates a new label. If wrap is set to true, the text inside will be
// wrapped.
func NewLabel (text string, wrap bool) (element *Label) {
element = &Label { c: theme.C("basic", "label") }
element = &Label { }
element.theme.Case = theme.C("basic", "label")
element.Core, element.core = core.NewCore(element.handleResize)
element.SetWrap(wrap)
element.SetText(text)
@ -34,8 +34,7 @@ func NewLabel (text string, wrap bool) (element *Label) {
func (element *Label) redo () {
face := element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c)
theme.FontSizeNormal)
element.drawer.SetFace(face)
element.updateMinimumSize()
bounds := element.Bounds()
@ -109,11 +108,11 @@ func (element *Label) SetWrap (wrap bool) {
// SetTheme sets the element's theme.
func (element *Label) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c))
theme.FontSizeNormal))
element.updateMinimumSize()
if element.core.HasImage () {
@ -124,7 +123,8 @@ func (element *Label) SetTheme (new theme.Theme) {
// SetConfig sets the element's configuration.
func (element *Label) SetConfig (new config.Config) {
element.config = new
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
if element.core.HasImage () {
@ -153,7 +153,6 @@ func (element *Label) draw () {
pattern := element.theme.Pattern (
theme.PatternBackground,
element.c,
theme.PatternState { })
artist.FillRectangle(element, pattern, bounds)
@ -161,7 +160,6 @@ func (element *Label) draw () {
foreground := element.theme.Pattern (
theme.PatternForeground,
element.c,
theme.PatternState { })
element.drawer.Draw(element, foreground, bounds.Min.Sub(textBounds.Min))
}

View File

@ -26,9 +26,8 @@ type List struct {
scroll int
entries []ListEntry
config config.Config
theme theme.Theme
c theme.Case
config config.Wrapped
theme theme.Wrapped
onScrollBoundsChange func ()
onNoEntrySelected func ()
@ -36,10 +35,8 @@ type List struct {
// NewList creates a new list element with the specified entries.
func NewList (entries ...ListEntry) (element *List) {
element = &List {
selectedEntry: -1,
c: theme.C("basic", "list"),
}
element = &List { selectedEntry: -1 }
element.theme.Case = theme.C("basic", "list")
element.Core, element.core = core.NewCore(element.handleResize)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () {
@ -71,9 +68,10 @@ func (element *List) handleResize () {
// SetTheme sets the element's theme.
func (element *List) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
for index, entry := range element.entries {
entry.SetConfig(element.config)
entry.SetTheme(element.theme.Theme)
element.entries[index] = entry
}
element.updateMinimumSize()
@ -82,7 +80,8 @@ func (element *List) SetTheme (new theme.Theme) {
// SetConfig sets the element's configuration.
func (element *List) SetConfig (new config.Config) {
element.config = new
if new == element.config.Config { return }
element.config.Config = new
for index, entry := range element.entries {
entry.SetConfig(element.config)
element.entries[index] = entry
@ -206,7 +205,7 @@ func (element *List) ScrollAxes () (horizontal, vertical bool) {
}
func (element *List) scrollViewportHeight () (height int) {
inset := element.theme.Inset(theme.PatternSunken, element.c)
inset := element.theme.Inset(theme.PatternSunken)
return element.Bounds().Dy() - inset[0] - inset[2]
}
@ -238,7 +237,7 @@ func (element *List) CountEntries () (count int) {
func (element *List) Append (entry ListEntry) {
// append
entry.Collapse(element.forcedMinimumWidth)
entry.SetTheme(element.theme)
entry.SetTheme(element.theme.Theme)
entry.SetConfig(element.config)
element.entries = append(element.entries, entry)
@ -332,7 +331,7 @@ func (element *List) Replace (index int, entry ListEntry) {
}
func (element *List) selectUnderMouse (x, y int) (updated bool) {
inset := element.theme.Inset(theme.PatternSunken, element.c)
inset := element.theme.Inset(theme.PatternSunken)
bounds := inset.Apply(element.Bounds())
mousePoint := image.Pt(x, y)
dot := image.Pt (
@ -374,7 +373,7 @@ func (element *List) changeSelectionBy (delta int) (updated bool) {
}
func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) {
inset := element.theme.Inset(theme.PatternSunken, element.c)
inset := element.theme.Inset(theme.PatternSunken)
entry.Collapse(element.forcedMinimumWidth - inset[3] - inset[1])
return entry
}
@ -401,7 +400,7 @@ func (element *List) updateMinimumSize () {
minimumHeight = element.contentHeight
}
inset := element.theme.Inset(theme.PatternSunken, element.c)
inset := element.theme.Inset(theme.PatternSunken)
minimumHeight += inset[0] + inset[2]
element.core.SetMinimumSize(minimumWidth, minimumHeight)
@ -410,8 +409,8 @@ func (element *List) updateMinimumSize () {
func (element *List) draw () {
bounds := element.Bounds()
inset := element.theme.Inset(theme.PatternSunken, element.c)
pattern := element.theme.Pattern (theme.PatternSunken, element.c, theme.PatternState {
inset := element.theme.Inset(theme.PatternSunken)
pattern := element.theme.Pattern (theme.PatternSunken, theme.PatternState {
Disabled: !element.Enabled(),
Focused: element.Focused(),
})

View File

@ -14,9 +14,8 @@ type ListEntry struct {
text string
forcedMinimumWidth int
theme theme.Theme
config config.Config
c theme.Case
config config.Wrapped
theme theme.Wrapped
onSelect func ()
}
@ -25,8 +24,8 @@ func NewListEntry (text string, onSelect func ()) (entry ListEntry) {
entry = ListEntry {
text: text,
onSelect: onSelect,
c: theme.C("basic", "listEntry"),
}
entry.theme.Case = theme.C("basic", "listEntry")
entry.drawer.SetText([]rune(text))
entry.updateBounds()
return
@ -39,16 +38,17 @@ func (entry *ListEntry) Collapse (width int) {
}
func (entry *ListEntry) SetTheme (new theme.Theme) {
entry.theme = new
if new == entry.theme.Theme { return }
entry.theme.Theme = new
entry.drawer.SetFace (entry.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
entry.c))
theme.FontSizeNormal))
entry.updateBounds()
}
func (entry *ListEntry) SetConfig (config config.Config) {
entry.config = config
func (entry *ListEntry) SetConfig (new config.Config) {
if new == entry.config.Config { return }
entry.config.Config = new
}
func (entry *ListEntry) updateBounds () {
@ -60,7 +60,7 @@ func (entry *ListEntry) updateBounds () {
entry.bounds.Max.X = entry.drawer.LayoutBounds().Dx()
}
inset := entry.theme.Inset(theme.PatternRaised, entry.c)
inset := entry.theme.Inset(theme.PatternRaised)
entry.bounds.Max.Y += inset[0] + inset[2]
entry.textPoint =
@ -80,12 +80,12 @@ func (entry *ListEntry) Draw (
Focused: focused,
On: on,
}
pattern := entry.theme.Pattern (theme.PatternRaised, entry.c, state)
pattern := entry.theme.Pattern (theme.PatternRaised, state)
artist.FillRectangle (
destination,
pattern,
entry.Bounds().Add(offset))
foreground := entry.theme.Pattern (theme.PatternForeground, entry.c, state)
foreground := entry.theme.Pattern (theme.PatternForeground, state)
return entry.drawer.Draw (
destination,
foreground,

View File

@ -12,18 +12,15 @@ type ProgressBar struct {
core core.CoreControl
progress float64
theme theme.Theme
config config.Config
c theme.Case
config config.Wrapped
theme theme.Wrapped
}
// NewProgressBar creates a new progress bar displaying the given progress
// level.
func NewProgressBar (progress float64) (element *ProgressBar) {
element = &ProgressBar {
progress: progress,
c: theme.C("basic", "progressBar"),
}
element = &ProgressBar { progress: progress }
element.theme.Case = theme.C("basic", "progressBar")
element.Core, element.core = core.NewCore(element.draw)
return
}
@ -40,14 +37,16 @@ func (element *ProgressBar) SetProgress (progress float64) {
// SetTheme sets the element's theme.
func (element *ProgressBar) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *ProgressBar) SetConfig (new config.Config) {
element.config = new
if new == nil || new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.redo()
}
@ -70,9 +69,8 @@ func (element *ProgressBar) draw () {
pattern := element.theme.Pattern (
theme.PatternSunken,
element.c,
theme.PatternState { })
inset := element.theme.Inset(theme.PatternSunken, element.c)
inset := element.theme.Inset(theme.PatternSunken)
artist.FillRectangle(element, pattern, bounds)
bounds = inset.Apply(bounds)
meterBounds := image.Rect (
@ -80,8 +78,7 @@ func (element *ProgressBar) draw () {
bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
bounds.Max.Y)
accent := element.theme.Pattern (
theme.PatternSunken,
element.c,
theme.PatternAccent,
theme.PatternState { })
artist.FillRectangle(element, accent, meterBounds)
}

View File

@ -20,7 +20,7 @@ type ScrollContainer struct {
childWidth, childHeight int
horizontal struct {
c theme.Case
theme theme.Wrapped
exists bool
enabled bool
dragging bool
@ -31,7 +31,7 @@ type ScrollContainer struct {
}
vertical struct {
c theme.Case
theme theme.Wrapped
exists bool
enabled bool
dragging bool
@ -41,9 +41,8 @@ type ScrollContainer struct {
bar image.Rectangle
}
theme theme.Theme
config config.Config
c theme.Case
config config.Wrapped
theme theme.Wrapped
onFocusRequest func () (granted bool)
onFocusMotionRequest func (input.KeynavDirection) (granted bool)
@ -52,12 +51,12 @@ type ScrollContainer struct {
// NewScrollContainer creates a new scroll container with the specified scroll
// bars.
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
element = &ScrollContainer { c: theme.C("basic", "scrollContainer") }
element.horizontal.c = theme.C("basic", "scrollBarHorizontal")
element.vertical.c = theme.C("basic", "scrollBarVertical")
element = &ScrollContainer { }
element.theme.Case = theme.C("basic", "scrollContainer")
element.horizontal.theme.Case = theme.C("basic", "scrollBarHorizontal")
element.vertical.theme.Case = theme.C("basic", "scrollBarVertical")
element.Core, element.core = core.NewCore(element.handleResize)
element.updateMinimumSize()
element.horizontal.exists = horizontal
element.vertical.exists = vertical
return
@ -81,6 +80,12 @@ func (element *ScrollContainer) Adopt (child elements.Scrollable) {
// adopt new child
element.child = child
if child != nil {
if child0, ok := child.(elements.Themeable); ok {
child0.SetTheme(element.theme.Theme)
}
if child0, ok := child.(elements.Configurable); ok {
child0.SetConfig(element.config.Config)
}
child.OnDamage(element.childDamageCallback)
child.OnMinimumSizeChange(element.updateMinimumSize)
child.OnScrollBoundsChange(element.childScrollBoundsChangeCallback)
@ -102,6 +107,34 @@ func (element *ScrollContainer) Adopt (child elements.Scrollable) {
}
}
// SetTheme sets the element's theme.
func (element *ScrollContainer) SetTheme (new theme.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
if child, ok := element.child.(elements.Themeable); ok {
child.SetTheme(element.theme.Theme)
}
if element.core.HasImage() {
element.recalculate()
element.resizeChildToFit()
element.draw()
}
}
// SetConfig sets the element's configuration.
func (element *ScrollContainer) SetConfig (new config.Config) {
if new == element.config.Config { return }
element.config.Config = new
if child, ok := element.child.(elements.Configurable); ok {
child.SetConfig(element.config.Config)
}
if element.core.HasImage() {
element.recalculate()
element.resizeChildToFit()
element.draw()
}
}
func (element *ScrollContainer) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if child, ok := element.child.(elements.KeyboardTarget); ok {
child.HandleKeyDown(key, modifiers)
@ -289,8 +322,8 @@ func (element *ScrollContainer) recalculate () {
horizontal := &element.horizontal
vertical := &element.vertical
gutterInsetHorizontal := element.theme.Inset(theme.PatternGutter, horizontal.c)
gutterInsetVertical := element.theme.Inset(theme.PatternGutter, vertical.c)
gutterInsetHorizontal := horizontal.theme.Inset(theme.PatternGutter)
gutterInsetVertical := vertical.theme.Inset(theme.PatternGutter)
bounds := element.Bounds()
thicknessHorizontal :=
@ -376,7 +409,7 @@ func (element *ScrollContainer) recalculate () {
func (element *ScrollContainer) draw () {
artist.Paste(element, element.child, image.Point { })
deadPattern := element.theme.Pattern (
theme.PatternDead, element.c, theme.PatternState { })
theme.PatternDead, theme.PatternState { })
artist.FillRectangle (
element, deadPattern,
image.Rect (
@ -393,12 +426,10 @@ func (element *ScrollContainer) drawHorizontalBar () {
Disabled: !element.horizontal.enabled,
Pressed: element.horizontal.dragging,
}
gutterPattern := element.theme.Pattern (
theme.PatternGutter, element.horizontal.c, state)
gutterPattern := element.horizontal.theme.Pattern(theme.PatternGutter, state)
artist.FillRectangle(element, gutterPattern, element.horizontal.gutter)
handlePattern := element.theme.Pattern (
theme.PatternHandle, element.horizontal.c, state)
handlePattern := element.horizontal.theme.Pattern(theme.PatternHandle, state)
artist.FillRectangle(element, handlePattern, element.horizontal.bar)
}
@ -407,12 +438,10 @@ func (element *ScrollContainer) drawVerticalBar () {
Disabled: !element.vertical.enabled,
Pressed: element.vertical.dragging,
}
gutterPattern := element.theme.Pattern (
theme.PatternGutter, element.vertical.c, state)
gutterPattern := element.vertical.theme.Pattern(theme.PatternGutter, state)
artist.FillRectangle(element, gutterPattern, element.vertical.gutter)
handlePattern := element.theme.Pattern (
theme.PatternHandle, element.vertical.c, state)
handlePattern := element.vertical.theme.Pattern(theme.PatternHandle, state)
artist.FillRectangle(element, handlePattern, element.vertical.bar)
}
@ -435,10 +464,8 @@ func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) {
}
func (element *ScrollContainer) updateMinimumSize () {
gutterInsetHorizontal := element.theme.Inset (
theme.PatternGutter, element.horizontal.c)
gutterInsetVertical := element.theme.Inset (
theme.PatternGutter, element.vertical.c)
gutterInsetHorizontal := element.horizontal.theme.Inset(theme.PatternGutter)
gutterInsetVertical := element.vertical.theme.Inset(theme.PatternGutter)
thicknessHorizontal :=
element.config.HandleWidth() +

View File

@ -11,16 +11,16 @@ type Spacer struct {
core core.CoreControl
line bool
theme theme.Theme
config config.Config
c theme.Case
config config.Wrapped
theme theme.Wrapped
}
// NewSpacer creates a new spacer. If line is set to true, the spacer will be
// filled with a line color, and if compressed to its minimum width or height,
// will appear as a line.
func NewSpacer (line bool) (element *Spacer) {
element = &Spacer { line: line, c: theme.C("basic", "spacer") }
element = &Spacer { line: line }
element.theme.Case = theme.C("basic", "spacer")
element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(1, 1)
return
@ -38,13 +38,15 @@ func (element *Spacer) SetLine (line bool) {
// SetTheme sets the element's theme.
func (element *Spacer) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Spacer) SetConfig (new config.Config) {
element.config = new
if new == element.config.Config { return }
element.config.Config = new
element.redo()
}
@ -61,13 +63,11 @@ func (element *Spacer) draw () {
if element.line {
pattern := element.theme.Pattern (
theme.PatternForeground,
element.c,
theme.PatternState { })
artist.FillRectangle(element, pattern, bounds)
} else {
pattern := element.theme.Pattern (
theme.PatternBackground,
element.c,
theme.PatternState { })
artist.FillRectangle(element, pattern, bounds)
}

View File

@ -20,9 +20,8 @@ type Switch struct {
checked bool
text string
theme theme.Theme
config config.Config
c theme.Case
config config.Wrapped
theme theme.Wrapped
onToggle func ()
}
@ -32,8 +31,8 @@ func NewSwitch (text string, on bool) (element *Switch) {
element = &Switch {
checked: on,
text: text,
c: theme.C("basic", "switch"),
}
element.theme.Case = theme.C("basic", "switch")
element.Core, element.core = core.NewCore(element.draw)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.redo)
@ -116,18 +115,19 @@ func (element *Switch) SetText (text string) {
// SetTheme sets the element's theme.
func (element *Switch) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
element.drawer.SetFace (element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c))
theme.FontSizeNormal))
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Switch) SetConfig (new config.Config) {
element.config = new
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.redo()
}
@ -165,7 +165,7 @@ func (element *Switch) draw () {
Pressed: element.pressed,
}
backgroundPattern := element.theme.Pattern (
theme.PatternBackground, element.c, state)
theme.PatternBackground, state)
artist.FillRectangle (element, backgroundPattern, bounds)
if element.checked {
@ -183,11 +183,11 @@ func (element *Switch) draw () {
}
gutterPattern := element.theme.Pattern (
theme.PatternGutter, element.c, state)
theme.PatternGutter, state)
artist.FillRectangle(element, gutterPattern, gutterBounds)
handlePattern := element.theme.Pattern (
theme.PatternHandle, element.c, state)
theme.PatternHandle, state)
artist.FillRectangle(element, handlePattern, handleBounds)
textBounds := element.drawer.LayoutBounds()
@ -199,6 +199,6 @@ func (element *Switch) draw () {
offset.X -= textBounds.Min.X
foreground := element.theme.Pattern (
theme.PatternForeground, element.c, state)
theme.PatternForeground, state)
element.drawer.Draw(element, foreground, offset)
}

View File

@ -23,9 +23,8 @@ type TextBox struct {
placeholderDrawer artist.TextDrawer
valueDrawer artist.TextDrawer
theme theme.Theme
config config.Config
c theme.Case
config config.Wrapped
theme theme.Wrapped
onKeyDown func (key input.Key, modifiers input.Modifiers) (handled bool)
onChange func ()
@ -36,7 +35,8 @@ type TextBox struct {
// a value. When the value is empty, the placeholder will be displayed in gray
// text.
func NewTextBox (placeholder, value string) (element *TextBox) {
element = &TextBox { c: theme.C("basic", "textBox") }
element = &TextBox { }
element.theme.Case = theme.C("basic", "textBox")
element.Core, element.core = core.NewCore(element.handleResize)
element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () {
@ -252,11 +252,11 @@ func (element *TextBox) scrollToCursor () {
// SetTheme sets the element's theme.
func (element *TextBox) SetTheme (new theme.Theme) {
element.theme = new
if new == element.theme.Theme { return }
element.theme.Theme = new
face := element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c)
theme.FontSizeNormal)
element.placeholderDrawer.SetFace(face)
element.valueDrawer.SetFace(face)
element.updateMinimumSize()
@ -265,19 +265,19 @@ func (element *TextBox) SetTheme (new theme.Theme) {
// SetConfig sets the element's configuration.
func (element *TextBox) SetConfig (new config.Config) {
element.config = new
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.redo()
}
func (element *TextBox) updateMinimumSize () {
textBounds := element.placeholderDrawer.LayoutBounds()
inset := element.theme.Inset(theme.PatternInput, element.c)
element.core.SetMinimumSize (
textBounds.Dx() +
element.config.Padding() * 2 + inset[3] + inset[1],
element.config.Padding() * 2,
element.placeholderDrawer.LineHeight().Round() +
element.config.Padding() * 2 + inset[0] + inset[2])
element.config.Padding() * 2)
}
func (element *TextBox) redo () {
@ -295,7 +295,7 @@ func (element *TextBox) draw () {
Disabled: !element.Enabled(),
Focused: element.Focused(),
}
pattern := element.theme.Pattern(theme.PatternSunken, element.c, state)
pattern := element.theme.Pattern(theme.PatternSunken, state)
artist.FillRectangle(element, pattern, bounds)
if len(element.text) == 0 && !element.Focused() {
@ -306,7 +306,7 @@ func (element *TextBox) draw () {
Y: element.config.Padding(),
})
foreground := element.theme.Pattern (
theme.PatternForeground, element.c,
theme.PatternForeground,
theme.PatternState { Disabled: true })
element.placeholderDrawer.Draw (
element,
@ -320,7 +320,7 @@ func (element *TextBox) draw () {
Y: element.config.Padding(),
})
foreground := element.theme.Pattern (
theme.PatternForeground, element.c, state)
theme.PatternForeground, state)
element.valueDrawer.Draw (
element,
foreground,

View File

@ -4,7 +4,6 @@ import "fmt"
import "time"
import "image"
import "image/color"
import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/defaultfont"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -20,8 +19,7 @@ type Artist struct {
// NewArtist creates a new artist test element.
func NewArtist () (element *Artist) {
element = &Artist { }
element.Core, element.core = core.NewCore (
element.draw, nil, nil, theme.C("testing", "artist"))
element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(480, 600)
return
}

View File

@ -3,6 +3,7 @@ package testing
import "image"
import "image/color"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -15,21 +16,33 @@ type Mouse struct {
drawing bool
color artist.Pattern
lastMousePos image.Point
config config.Config
theme theme.Theme
c theme.Case
}
// NewMouse creates a new mouse test element.
func NewMouse () (element *Mouse) {
element = &Mouse { }
element.Core, element.core = core.NewCore (
element.draw,
element.redo,
element.redo,
theme.C("testing", "mouse"))
element = &Mouse { c: theme.C("testing", "mouse") }
element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(32, 32)
element.color = artist.NewUniform(color.Black)
return
}
// SetTheme sets the element's theme.
func (element *Mouse) SetTheme (new theme.Theme) {
element.theme = new
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Mouse) SetConfig (new config.Config) {
element.config = new
element.redo()
}
func (element *Mouse) redo () {
if !element.core.HasImage() { return }
element.draw()
@ -38,7 +51,10 @@ func (element *Mouse) redo () {
func (element *Mouse) draw () {
bounds := element.Bounds()
pattern := element.core.Pattern(theme.PatternAccent, theme.PatternState { })
pattern := element.theme.Pattern (
theme.PatternAccent,
element.c,
theme.PatternState { })
artist.FillRectangle(element, pattern, bounds)
artist.StrokeRectangle (
element,

View File

@ -41,7 +41,7 @@ func (Default) Pattern (
case PatternBackground:
return backgroundPattern
case PatternForeground:
if state.Disabled {
if state.Disabled || c == C("basic", "spacer") {
return weakForegroundPattern
} else {
return foregroundPattern
@ -77,6 +77,16 @@ func (Default) Pattern (
} else {
return listPattern
}
} else if c == C("basic", "textBox") {
if state.Disabled {
return disabledInputPattern
} else {
if state.Focused {
return selectedInputPattern
} else {
return inputPattern
}
}
} else {
return sunkenPattern
}
@ -86,7 +96,7 @@ func (Default) Pattern (
if state.Disabled {
return disabledButtonPattern
} else {
if state.Pressed {
if state.Pressed || state.On && c == C("basic", "checkbox") {
if state.Focused {
return pressedSelectedButtonPattern
} else {
@ -144,13 +154,15 @@ func (Default) Inset (pattern Pattern, c Case) Inset {
switch pattern {
case PatternRaised:
if c == C("basic", "listEntry") {
return Inset { 2, 1, 2, 1 }
return Inset { 4, 6, 4, 6 }
} else {
return Inset { 1, 1, 1, 1 }
}
case PatternSunken:
if c == C("basic", "list") {
return Inset { 4, 6, 4, 6 }
return Inset { 2, 1, 2, 1 }
} else if c == C("basic", "progressBar") {
return Inset { 2, 1, 1, 2 }
} else {
return Inset { 1, 1, 1, 1 }
}

View File

@ -46,3 +46,33 @@ type PatternState struct {
// or outline.
Invalid bool
}
// FontStyle specifies stylistic alterations to a font face.
type FontStyle int; const (
FontStyleRegular FontStyle = 0
FontStyleBold FontStyle = 1
FontStyleItalic FontStyle = 2
FontStyleBoldItalic FontStyle = 1 | 2
)
// FontSize specifies the general size of a font face in a semantic way.
type FontSize int; const (
// FontSizeNormal is the default font size that should be used for most
// things.
FontSizeNormal FontSize = iota
// FontSizeLarge is a larger font size suitable for things like section
// headings.
FontSizeLarge
// FontSizeHuge is a very large font size suitable for things like
// titles, wizard step names, digital clocks, etc.
FontSizeHuge
// FontSizeSmall is a smaller font size. Try not to use this unless it
// makes a lot of sense to do so, because it can negatively impact
// accessibility. It is useful for things like copyright notices at the
// bottom of some window that the average user doesn't actually care
// about.
FontSizeSmall
)

View File

@ -4,36 +4,6 @@ import "image"
import "golang.org/x/image/font"
import "git.tebibyte.media/sashakoshka/tomo/artist"
// FontStyle specifies stylistic alterations to a font face.
type FontStyle int; const (
FontStyleRegular FontStyle = 0
FontStyleBold FontStyle = 1
FontStyleItalic FontStyle = 2
FontStyleBoldItalic FontStyle = 1 | 2
)
// FontSize specifies the general size of a font face in a semantic way.
type FontSize int; const (
// FontSizeNormal is the default font size that should be used for most
// things.
FontSizeNormal FontSize = iota
// FontSizeLarge is a larger font size suitable for things like section
// headings.
FontSizeLarge
// FontSizeHuge is a very large font size suitable for things like
// titles, wizard step names, digital clocks, etc.
FontSizeHuge
// FontSizeSmall is a smaller font size. Try not to use this unless it
// makes a lot of sense to do so, because it can negatively impact
// accessibility. It is useful for things like copyright notices at the
// bottom of some window that the average user doesn't actually care
// about.
FontSizeSmall
)
// Pattern lists a number of cannonical pattern types, each with its own ID.
// This allows custom elements to follow themes, even those that do not
// explicitly support them.
@ -98,3 +68,49 @@ type Theme interface {
// sinking effect.
Sink (Pattern, Case) image.Point
}
// Wrapped wraps any theme and injects a case into it automatically so that it
// doesn't need to be specified for each query. Additionally, if the underlying
// theme is nil, it just uses the default theme instead.
type Wrapped struct {
Theme
Case
}
// FontFace returns the proper font for a given style and size.
func (wrapped Wrapped) FontFace (style FontStyle, size FontSize) font.Face {
real := wrapped.ensure()
return real.FontFace(style, size, wrapped.Case)
}
// Icon returns an appropriate icon given an icon name.
func (wrapped Wrapped) Icon (name string) artist.Pattern {
real := wrapped.ensure()
return real.Icon(name, wrapped.Case)
}
// Pattern returns an appropriate pattern given a pattern name and state.
func (wrapped Wrapped) Pattern (id Pattern, state PatternState) artist.Pattern {
real := wrapped.ensure()
return real.Pattern(id, wrapped.Case, state)
}
// Inset returns the area on all sides of a given pattern that is not meant to
// be drawn on.
func (wrapped Wrapped) Inset (id Pattern) Inset {
real := wrapped.ensure()
return real.Inset(id, wrapped.Case)
}
// Sink returns a vector that should be added to an element's inner content when
// it is pressed down (if applicable) to simulate a 3D sinking effect.
func (wrapped Wrapped) Sink (id Pattern) image.Point {
real := wrapped.ensure()
return real.Sink(id, wrapped.Case)
}
func (wrapped Wrapped) ensure () (real Theme) {
real = wrapped.Theme
if real == nil { real = Default { } }
return
}