asuhfdjkshlk

This commit is contained in:
Sasha Koshka 2023-02-08 00:22:40 -05:00
parent 3998d842b1
commit 6936353516
12 changed files with 448 additions and 379 deletions

View File

@ -3,6 +3,7 @@ package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -17,51 +18,37 @@ type Button struct {
pressed bool pressed bool
text string text string
config config.Config
theme theme.Theme
c theme.Case
onClick func () onClick func ()
} }
// NewButton creates a new button with the specified label text. // NewButton creates a new button with the specified label text.
func NewButton (text string) (element *Button) { func NewButton (text string) (element *Button) {
element = &Button { } element = &Button {
element.Core, element.core = core.NewCore ( c: theme.C("basic", "button"),
element.draw, }
element.redo, element.Core, element.core = core.NewCore(element.draw)
element.redo,
theme.C("basic", "button"))
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.redo) element.focusableControl = core.NewFocusableCore(element.redo)
element.SetText(text) element.SetText(text)
return return
} }
func (element *Button) redo () {
element.drawer.SetFace (
element.core.FontFace(theme.FontStyleRegular,
theme.FontSizeNormal))
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
func (element *Button) HandleMouseDown (x, y int, button input.Button) { func (element *Button) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
if !element.Focused() { element.Focus() } if !element.Focused() { element.Focus() }
if button != input.ButtonLeft { return } if button != input.ButtonLeft { return }
element.pressed = true element.pressed = true
if element.core.HasImage() { element.redo()
element.draw()
element.core.DamageAll()
}
} }
func (element *Button) HandleMouseUp (x, y int, button input.Button) { func (element *Button) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft { return } if button != input.ButtonLeft { return }
element.pressed = false element.pressed = false
if element.core.HasImage() { element.redo()
element.draw()
element.core.DamageAll()
}
within := image.Point { x, y }. within := image.Point { x, y }.
In(element.Bounds()) In(element.Bounds())
@ -79,10 +66,7 @@ func (element *Button) HandleKeyDown (key input.Key, modifiers input.Modifiers)
if !element.Enabled() { return } if !element.Enabled() { return }
if key == input.KeyEnter { if key == input.KeyEnter {
element.pressed = true element.pressed = true
if element.core.HasImage() { element.redo()
element.draw()
element.core.DamageAll()
}
} }
} }
@ -113,12 +97,41 @@ func (element *Button) SetText (text string) {
element.text = text element.text = text
element.drawer.SetText([]rune(text)) element.drawer.SetText([]rune(text))
textBounds := element.drawer.LayoutBounds() element.updateMinimumSize()
minimumSize := textBounds.Inset(-element.core.Config().Padding())
element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
element.redo() element.redo()
} }
// SetTheme sets the element's theme.
func (element *Button) SetTheme (new theme.Theme) {
element.theme = new
element.drawer.SetFace (element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c))
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Button) SetConfig (new config.Config) {
element.config = new
element.updateMinimumSize()
element.redo()
}
func (element *Button) updateMinimumSize () {
textBounds := element.drawer.LayoutBounds()
minimumSize := textBounds.Inset(-element.config.Padding())
element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
}
func (element *Button) redo () {
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
func (element *Button) draw () { func (element *Button) draw () {
bounds := element.Bounds() bounds := element.Bounds()
@ -128,7 +141,7 @@ func (element *Button) draw () {
Pressed: element.pressed, Pressed: element.pressed,
} }
pattern := element.core.Pattern(theme.PatternButton, state) pattern := element.theme.Pattern(theme.PatternButton, element.c, state)
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
@ -143,6 +156,6 @@ func (element *Button) draw () {
offset.Y -= textBounds.Min.Y offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X offset.X -= textBounds.Min.X
foreground := element.core.Pattern(theme.PatternForeground, state) foreground := element.theme.Pattern(theme.PatternForeground, element.c, state)
element.drawer.Draw(element, foreground, offset) element.drawer.Draw(element, foreground, offset)
} }

View File

@ -3,6 +3,7 @@ package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -18,33 +19,26 @@ type Checkbox struct {
checked bool checked bool
text string text string
config config.Config
theme theme.Theme
c theme.Case
onToggle func () onToggle func ()
} }
// NewCheckbox creates a new cbeckbox with the specified label text. // NewCheckbox creates a new cbeckbox with the specified label text.
func NewCheckbox (text string, checked bool) (element *Checkbox) { func NewCheckbox (text string, checked bool) (element *Checkbox) {
element = &Checkbox { checked: checked } element = &Checkbox {
element.Core, element.core = core.NewCore ( checked: checked,
element.draw, c: theme.C("basic", "checkbox"),
element.redo, }
element.redo, element.Core, element.core = core.NewCore(element.draw)
theme.C("basic", "checkbox"))
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore(element.redo) element.focusableControl = core.NewFocusableCore(element.redo)
element.SetText(text) element.SetText(text)
return return
} }
func (element *Checkbox) redo () {
element.drawer.SetFace (
element.core.FontFace(theme.FontStyleRegular,
theme.FontSizeNormal))
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
func (element *Checkbox) HandleMouseDown (x, y int, button input.Button) { func (element *Checkbox) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
element.Focus() element.Focus()
@ -122,16 +116,44 @@ func (element *Checkbox) SetText (text string) {
element.text = text element.text = text
element.drawer.SetText([]rune(text)) element.drawer.SetText([]rune(text))
textBounds := element.drawer.LayoutBounds() element.updateMinimumSize()
if text == "" { if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
// SetTheme sets the element's theme.
func (element *Checkbox) SetTheme (new theme.Theme) {
element.theme = new
element.drawer.SetFace (element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c))
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Checkbox) SetConfig (new config.Config) {
element.config = new
element.updateMinimumSize()
element.redo()
}
func (element *Checkbox) updateMinimumSize () {
textBounds := element.drawer.LayoutBounds()
if element.text == "" {
element.core.SetMinimumSize(textBounds.Dy(), textBounds.Dy()) element.core.SetMinimumSize(textBounds.Dy(), textBounds.Dy())
} else { } else {
element.core.SetMinimumSize ( element.core.SetMinimumSize (
textBounds.Dy() + element.core.Config().Padding() + textBounds.Dx(), textBounds.Dy() + element.config.Padding() + textBounds.Dx(),
textBounds.Dy()) textBounds.Dy())
} }
}
func (element *Checkbox) redo () {
if element.core.HasImage () { if element.core.HasImage () {
element.draw() element.draw()
element.core.DamageAll() element.core.DamageAll()
@ -149,20 +171,22 @@ func (element *Checkbox) draw () {
On: element.checked, On: element.checked,
} }
backgroundPattern := element.core.Pattern(theme.PatternBackground, state) backgroundPattern := element.theme.Pattern (
theme.PatternBackground, element.c, state)
artist.FillRectangle(element, backgroundPattern, bounds) artist.FillRectangle(element, backgroundPattern, bounds)
pattern := element.core.Pattern (theme.PatternButton, state) pattern := element.theme.Pattern(theme.PatternButton, element.c, state)
artist.FillRectangle(element, pattern, boxBounds) artist.FillRectangle(element, pattern, boxBounds)
textBounds := element.drawer.LayoutBounds() textBounds := element.drawer.LayoutBounds()
offset := bounds.Min.Add(image.Point { offset := bounds.Min.Add(image.Point {
X: bounds.Dy() + element.core.Config().Padding(), X: bounds.Dy() + element.config.Padding(),
}) })
offset.Y -= textBounds.Min.Y offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X offset.X -= textBounds.Min.X
foreground := element.core.Pattern(theme.PatternForeground, state) foreground := element.theme.Pattern (
theme.PatternForeground, element.c, state)
element.drawer.Draw(element, foreground, offset) element.drawer.Draw(element, foreground, offset)
} }

View File

@ -3,6 +3,7 @@ package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts"
@ -23,6 +24,10 @@ type Container struct {
focusable bool focusable bool
flexible bool flexible bool
config config.Config
theme theme.Theme
c theme.Case
onFocusRequest func () (granted bool) onFocusRequest func () (granted bool)
onFocusMotionRequest func (input.KeynavDirection) (granted bool) onFocusMotionRequest func (input.KeynavDirection) (granted bool)
onFlexibleHeightChange func () onFlexibleHeightChange func ()
@ -30,12 +35,10 @@ type Container struct {
// NewContainer creates a new container. // NewContainer creates a new container.
func NewContainer (layout layouts.Layout) (element *Container) { func NewContainer (layout layouts.Layout) (element *Container) {
element = &Container { } element = &Container {
element.Core, element.core = core.NewCore ( c: theme.C("basic", "container"),
element.redoAll, }
element.handleConfigChange, element.Core, element.core = core.NewCore(element.redoAll)
element.handleThemeChange,
theme.C("basic", "container"))
element.SetLayout(layout) element.SetLayout(layout)
return return
} }
@ -207,7 +210,10 @@ func (element *Container) redoAll () {
// draw a background // draw a background
bounds := element.Bounds() bounds := element.Bounds()
pattern := element.core.Pattern (theme.PatternBackground, theme.PatternState { }) pattern := element.theme.Pattern (
theme.PatternBackground,
element.c,
theme.PatternState { })
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
// cut our canvas up and give peices to child elements // cut our canvas up and give peices to child elements
@ -216,21 +222,28 @@ func (element *Container) redoAll () {
} }
} }
func (element *Container) handleConfigChange () {
// SetTheme sets the element's theme.
func (element *Container) SetTheme (new theme.Theme) {
element.theme = new
for _, child := range element.children { for _, child := range element.children {
if child0, ok := child.Element.(elements.Configurable); ok { if child0, ok := child.Element.(elements.Themeable); ok {
child0.SetConfig(element.core.Config()) child0.SetTheme(element.theme)
} }
} }
element.updateMinimumSize()
element.redoAll() element.redoAll()
} }
func (element *Container) handleThemeChange () { // SetConfig sets the element's configuration.
func (element *Container) SetConfig (new config.Config) {
element.config = new
for _, child := range element.children { for _, child := range element.children {
if child0, ok := child.Element.(elements.Themeable); ok { if child0, ok := child.Element.(elements.Configurable); ok {
child0.SetTheme(element.core.Theme()) child0.SetConfig(element.config)
} }
} }
element.updateMinimumSize()
element.redoAll() element.redoAll()
} }
@ -284,7 +297,7 @@ func (element *Container) HandleKeyUp (key input.Key, modifiers input.Modifiers)
func (element *Container) FlexibleHeightFor (width int) (height int) { func (element *Container) FlexibleHeightFor (width int) (height int) {
return element.layout.FlexibleHeightFor ( return element.layout.FlexibleHeightFor (
element.children, element.children,
element.core.Config().Margin(), width) element.config.Margin(), width)
} }
func (element *Container) OnFlexibleHeightChange (callback func ()) { func (element *Container) OnFlexibleHeightChange (callback func ()) {
@ -487,15 +500,15 @@ func (element *Container) childFocusRequestCallback (
func (element *Container) updateMinimumSize () { func (element *Container) updateMinimumSize () {
width, height := element.layout.MinimumSize ( width, height := element.layout.MinimumSize (
element.children, element.core.Config().Margin()) element.children, element.config.Margin())
if element.flexible { if element.flexible {
height = element.layout.FlexibleHeightFor ( height = element.layout.FlexibleHeightFor (
element.children, element.core.Config().Margin(), width) element.children, element.config.Margin(), width)
} }
element.core.SetMinimumSize(width, height) element.core.SetMinimumSize(width, height)
} }
func (element *Container) recalculate () { func (element *Container) recalculate () {
element.layout.Arrange ( element.layout.Arrange (
element.children, element.core.Config().Margin(), element.Bounds()) element.children, element.config.Margin(), element.Bounds())
} }

View File

@ -1,6 +1,7 @@
package basicElements package basicElements
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -13,31 +14,28 @@ type Label struct {
text string text string
drawer artist.TextDrawer drawer artist.TextDrawer
config config.Config
theme theme.Theme
c theme.Case
onFlexibleHeightChange func () onFlexibleHeightChange func ()
} }
// NewLabel creates a new label. If wrap is set to true, the text inside will be // NewLabel creates a new label. If wrap is set to true, the text inside will be
// wrapped. // wrapped.
func NewLabel (text string, wrap bool) (element *Label) { func NewLabel (text string, wrap bool) (element *Label) {
element = &Label { } element = &Label { c: theme.C("basic", "label") }
element.Core, element.core = core.NewCore ( element.Core, element.core = core.NewCore(element.handleResize)
element.handleResize,
element.redo,
element.redo,
theme.C("basic", "label"))
face := element.core.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal)
element.drawer.SetFace(face)
element.SetWrap(wrap) element.SetWrap(wrap)
element.SetText(text) element.SetText(text)
return return
} }
func (element *Label) redo () { func (element *Label) redo () {
face := element.core.FontFace ( face := element.theme.FontFace (
theme.FontStyleRegular, theme.FontStyleRegular,
theme.FontSizeNormal) theme.FontSizeNormal,
element.c)
element.drawer.SetFace(face) element.drawer.SetFace(face)
element.updateMinimumSize() element.updateMinimumSize()
bounds := element.Bounds() bounds := element.Bounds()
@ -109,10 +107,36 @@ func (element *Label) SetWrap (wrap bool) {
} }
} }
// SetTheme sets the element's theme.
func (element *Label) SetTheme (new theme.Theme) {
element.theme = new
element.drawer.SetFace (element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c))
element.updateMinimumSize()
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
// SetConfig sets the element's configuration.
func (element *Label) SetConfig (new config.Config) {
element.config = new
element.updateMinimumSize()
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
func (element *Label) updateMinimumSize () { func (element *Label) updateMinimumSize () {
if element.wrap { if element.wrap {
em := element.drawer.Em().Round() em := element.drawer.Em().Round()
if em < 1 { em = element.core.Config().Padding() } if em < 1 { em = element.config.Padding() }
element.core.SetMinimumSize ( element.core.SetMinimumSize (
em, element.drawer.LineHeight().Round()) em, element.drawer.LineHeight().Round())
if element.onFlexibleHeightChange != nil { if element.onFlexibleHeightChange != nil {
@ -127,15 +151,17 @@ func (element *Label) updateMinimumSize () {
func (element *Label) draw () { func (element *Label) draw () {
bounds := element.Bounds() bounds := element.Bounds()
pattern := element.core.Pattern ( pattern := element.theme.Pattern (
theme.PatternBackground, theme.PatternBackground,
element.c,
theme.PatternState { }) theme.PatternState { })
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
textBounds := element.drawer.LayoutBounds() textBounds := element.drawer.LayoutBounds()
foreground := element.core.Pattern ( foreground := element.theme.Pattern (
theme.PatternForeground, theme.PatternForeground,
element.c,
theme.PatternState { }) theme.PatternState { })
element.drawer.Draw (element, foreground, bounds.Min.Sub(textBounds.Min)) element.drawer.Draw(element, foreground, bounds.Min.Sub(textBounds.Min))
} }

View File

@ -4,6 +4,7 @@ import "fmt"
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -25,18 +26,21 @@ type List struct {
scroll int scroll int
entries []ListEntry entries []ListEntry
config config.Config
theme theme.Theme
c theme.Case
onScrollBoundsChange func () onScrollBoundsChange func ()
onNoEntrySelected func () onNoEntrySelected func ()
} }
// NewList creates a new list element with the specified entries. // NewList creates a new list element with the specified entries.
func NewList (entries ...ListEntry) (element *List) { func NewList (entries ...ListEntry) (element *List) {
element = &List { selectedEntry: -1 } element = &List {
element.Core, element.core = core.NewCore ( selectedEntry: -1,
element.handleResize, c: theme.C("basic", "list"),
element.redo, }
element.redo, element.Core, element.core = core.NewCore(element.handleResize)
theme.C("basic", "list"))
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () { element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () { if element.core.HasImage () {
@ -65,19 +69,25 @@ func (element *List) handleResize () {
} }
} }
func (element *List) handleConfigChange () { // SetTheme sets the element's theme.
func (element *List) SetTheme (new theme.Theme) {
element.theme = new
for index, entry := range element.entries { for index, entry := range element.entries {
entry.SetConfig(element.core.Config()) entry.SetConfig(element.config)
element.entries[index] = entry element.entries[index] = entry
} }
element.updateMinimumSize()
element.redo() element.redo()
} }
func (element *List) handleThemeChange () { // SetConfig sets the element's configuration.
func (element *List) SetConfig (new config.Config) {
element.config = new
for index, entry := range element.entries { for index, entry := range element.entries {
entry.SetConfig(element.core.Config()) entry.SetConfig(element.config)
element.entries[index] = entry element.entries[index] = entry
} }
element.updateMinimumSize()
element.redo() element.redo()
} }
@ -196,7 +206,7 @@ func (element *List) ScrollAxes () (horizontal, vertical bool) {
} }
func (element *List) scrollViewportHeight () (height int) { func (element *List) scrollViewportHeight () (height int) {
inset := element.core.Inset(theme.PatternSunken) inset := element.theme.Inset(theme.PatternSunken, element.c)
return element.Bounds().Dy() - inset[0] - inset[2] return element.Bounds().Dy() - inset[0] - inset[2]
} }
@ -228,8 +238,8 @@ func (element *List) CountEntries () (count int) {
func (element *List) Append (entry ListEntry) { func (element *List) Append (entry ListEntry) {
// append // append
entry.Collapse(element.forcedMinimumWidth) entry.Collapse(element.forcedMinimumWidth)
entry.SetTheme(element.core.Theme()) entry.SetTheme(element.theme)
entry.SetConfig(element.core.Config()) entry.SetConfig(element.config)
element.entries = append(element.entries, entry) element.entries = append(element.entries, entry)
// recalculate, redraw, notify // recalculate, redraw, notify
@ -322,7 +332,7 @@ func (element *List) Replace (index int, entry ListEntry) {
} }
func (element *List) selectUnderMouse (x, y int) (updated bool) { func (element *List) selectUnderMouse (x, y int) (updated bool) {
inset := element.core.Inset(theme.PatternSunken) inset := element.theme.Inset(theme.PatternSunken, element.c)
bounds := inset.Apply(element.Bounds()) bounds := inset.Apply(element.Bounds())
mousePoint := image.Pt(x, y) mousePoint := image.Pt(x, y)
dot := image.Pt ( dot := image.Pt (
@ -364,7 +374,7 @@ func (element *List) changeSelectionBy (delta int) (updated bool) {
} }
func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) { func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) {
inset := element.core.Inset(theme.PatternSunken) inset := element.theme.Inset(theme.PatternSunken, element.c)
entry.Collapse(element.forcedMinimumWidth - inset[3] - inset[1]) entry.Collapse(element.forcedMinimumWidth - inset[3] - inset[1])
return entry return entry
} }
@ -391,7 +401,7 @@ func (element *List) updateMinimumSize () {
minimumHeight = element.contentHeight minimumHeight = element.contentHeight
} }
inset := element.core.Inset(theme.PatternSunken) inset := element.theme.Inset(theme.PatternSunken, element.c)
minimumHeight += inset[0] + inset[2] minimumHeight += inset[0] + inset[2]
element.core.SetMinimumSize(minimumWidth, minimumHeight) element.core.SetMinimumSize(minimumWidth, minimumHeight)
@ -400,8 +410,8 @@ func (element *List) updateMinimumSize () {
func (element *List) draw () { func (element *List) draw () {
bounds := element.Bounds() bounds := element.Bounds()
inset := element.core.Inset(theme.PatternSunken) inset := element.theme.Inset(theme.PatternSunken, element.c)
pattern := element.core.Pattern (theme.PatternSunken, theme.PatternState { pattern := element.theme.Pattern (theme.PatternSunken, element.c, theme.PatternState {
Disabled: !element.Enabled(), Disabled: !element.Enabled(),
Focused: element.Focused(), Focused: element.Focused(),
}) })

View File

@ -6,8 +6,6 @@ import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
var listEntryCase = theme.C("basic", "listEntry")
// ListEntry is an item that can be added to a list. // ListEntry is an item that can be added to a list.
type ListEntry struct { type ListEntry struct {
drawer artist.TextDrawer drawer artist.TextDrawer
@ -15,15 +13,19 @@ type ListEntry struct {
textPoint image.Point textPoint image.Point
text string text string
forcedMinimumWidth int forcedMinimumWidth int
onSelect func ()
theme theme.Theme theme theme.Theme
config config.Config config config.Config
c theme.Case
onSelect func ()
} }
func NewListEntry (text string, onSelect func ()) (entry ListEntry) { func NewListEntry (text string, onSelect func ()) (entry ListEntry) {
entry = ListEntry { entry = ListEntry {
text: text, text: text,
onSelect: onSelect, onSelect: onSelect,
c: theme.C("basic", "listEntry"),
} }
entry.drawer.SetText([]rune(text)) entry.drawer.SetText([]rune(text))
entry.updateBounds() entry.updateBounds()
@ -41,7 +43,7 @@ func (entry *ListEntry) SetTheme (new theme.Theme) {
entry.drawer.SetFace (entry.theme.FontFace ( entry.drawer.SetFace (entry.theme.FontFace (
theme.FontStyleRegular, theme.FontStyleRegular,
theme.FontSizeNormal, theme.FontSizeNormal,
listEntryCase)) entry.c))
entry.updateBounds() entry.updateBounds()
} }
@ -58,7 +60,7 @@ func (entry *ListEntry) updateBounds () {
entry.bounds.Max.X = entry.drawer.LayoutBounds().Dx() entry.bounds.Max.X = entry.drawer.LayoutBounds().Dx()
} }
inset := entry.theme.Inset(theme.PatternRaised, listEntryCase) inset := entry.theme.Inset(theme.PatternRaised, entry.c)
entry.bounds.Max.Y += inset[0] + inset[2] entry.bounds.Max.Y += inset[0] + inset[2]
entry.textPoint = entry.textPoint =
@ -78,12 +80,12 @@ func (entry *ListEntry) Draw (
Focused: focused, Focused: focused,
On: on, On: on,
} }
pattern := entry.theme.Pattern (theme.PatternRaised, listEntryCase, state) pattern := entry.theme.Pattern (theme.PatternRaised, entry.c, state)
artist.FillRectangle ( artist.FillRectangle (
destination, destination,
pattern, pattern,
entry.Bounds().Add(offset)) entry.Bounds().Add(offset))
foreground := entry.theme.Pattern (theme.PatternForeground, listEntryCase, state) foreground := entry.theme.Pattern (theme.PatternForeground, entry.c, state)
return entry.drawer.Draw ( return entry.drawer.Draw (
destination, destination,
foreground, foreground,

View File

@ -2,6 +2,7 @@ package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -10,28 +11,21 @@ type ProgressBar struct {
*core.Core *core.Core
core core.CoreControl core core.CoreControl
progress float64 progress float64
theme theme.Theme
config config.Config
c theme.Case
} }
// NewProgressBar creates a new progress bar displaying the given progress // NewProgressBar creates a new progress bar displaying the given progress
// level. // level.
func NewProgressBar (progress float64) (element *ProgressBar) { func NewProgressBar (progress float64) (element *ProgressBar) {
element = &ProgressBar { progress: progress } element = &ProgressBar {
element.Core, element.core = core.NewCore ( progress: progress,
element.draw, c: theme.C("basic", "progressBar"),
element.redo,
element.redo,
theme.C("basic", "progressBar"))
return
}
func (element *ProgressBar) redo () {
element.core.SetMinimumSize (
element.core.Config().Padding() * 2,
element.core.Config().Padding() * 2)
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
} }
element.Core, element.core = core.NewCore(element.draw)
return
} }
// SetProgress sets the progress level of the bar. // SetProgress sets the progress level of the bar.
@ -44,21 +38,50 @@ func (element *ProgressBar) SetProgress (progress float64) {
} }
} }
// SetTheme sets the element's theme.
func (element *ProgressBar) SetTheme (new theme.Theme) {
element.theme = new
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *ProgressBar) SetConfig (new config.Config) {
element.config = new
element.updateMinimumSize()
element.redo()
}
func (element (ProgressBar)) updateMinimumSize() {
element.core.SetMinimumSize (
element.config.Padding() * 2,
element.config.Padding() * 2)
}
func (element *ProgressBar) redo () {
if element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
}
func (element *ProgressBar) draw () { func (element *ProgressBar) draw () {
bounds := element.Bounds() bounds := element.Bounds()
pattern := element.core.Pattern ( pattern := element.theme.Pattern (
theme.PatternSunken, theme.PatternSunken,
element.c,
theme.PatternState { }) theme.PatternState { })
inset := element.core.Inset(theme.PatternSunken) inset := element.theme.Inset(theme.PatternSunken, element.c)
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
bounds = inset.Apply(bounds) bounds = inset.Apply(bounds)
meterBounds := image.Rect ( meterBounds := image.Rect (
bounds.Min.X, bounds.Min.Y, bounds.Min.X, bounds.Min.Y,
bounds.Min.X + int(float64(bounds.Dx()) * element.progress), bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
bounds.Max.Y) bounds.Max.Y)
accent := element.core.Pattern ( accent := element.theme.Pattern (
theme.PatternSunken, theme.PatternSunken,
element.c,
theme.PatternState { }) theme.PatternState { })
artist.FillRectangle(element, accent, meterBounds) artist.FillRectangle(element, accent, meterBounds)
} }

View File

@ -3,15 +3,12 @@ package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var scrollContainerCase = theme.C("basic", "scrollContainer")
var scrollBarHorizontalCase = theme.C("basic", "scrollBarHorizontal")
var scrollBarVerticalCase = theme.C("basic", "scrollBarVertical")
// ScrollContainer is a container that is capable of holding a scrollable // ScrollContainer is a container that is capable of holding a scrollable
// element. // element.
type ScrollContainer struct { type ScrollContainer struct {
@ -23,6 +20,7 @@ type ScrollContainer struct {
childWidth, childHeight int childWidth, childHeight int
horizontal struct { horizontal struct {
c theme.Case
exists bool exists bool
enabled bool enabled bool
dragging bool dragging bool
@ -33,6 +31,7 @@ type ScrollContainer struct {
} }
vertical struct { vertical struct {
c theme.Case
exists bool exists bool
enabled bool enabled bool
dragging bool dragging bool
@ -41,6 +40,10 @@ type ScrollContainer struct {
track image.Rectangle track image.Rectangle
bar image.Rectangle bar image.Rectangle
} }
theme theme.Theme
config config.Config
c theme.Case
onFocusRequest func () (granted bool) onFocusRequest func () (granted bool)
onFocusMotionRequest func (input.KeynavDirection) (granted bool) onFocusMotionRequest func (input.KeynavDirection) (granted bool)
@ -49,7 +52,10 @@ type ScrollContainer struct {
// NewScrollContainer creates a new scroll container with the specified scroll // NewScrollContainer creates a new scroll container with the specified scroll
// bars. // bars.
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) { func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
element = &ScrollContainer { } element = &ScrollContainer { c: theme.C("basic", "scrollContainer") }
element.horizontal.c = theme.C("basic", "scrollBarHorizontal")
element.vertical.c = theme.C("basic", "scrollBarVertical")
element.Core, element.core = core.NewCore(element.handleResize) element.Core, element.core = core.NewCore(element.handleResize)
element.updateMinimumSize() element.updateMinimumSize()
element.horizontal.exists = horizontal element.horizontal.exists = horizontal
@ -85,8 +91,6 @@ func (element *ScrollContainer) Adopt (child elements.Scrollable) {
element.childFocusMotionRequestCallback) element.childFocusMotionRequestCallback)
} }
// TODO: somehow inform the core that we do not in fact want to
// redraw the element.
element.updateMinimumSize() element.updateMinimumSize()
element.horizontal.enabled, element.horizontal.enabled,
@ -111,6 +115,7 @@ func (element *ScrollContainer) HandleKeyUp (key input.Key, modifiers input.Modi
} }
func (element *ScrollContainer) HandleMouseDown (x, y int, button input.Button) { func (element *ScrollContainer) HandleMouseDown (x, y int, button input.Button) {
velocity := element.config.ScrollVelocity()
point := image.Pt(x, y) point := image.Pt(x, y)
if point.In(element.horizontal.bar) { if point.In(element.horizontal.bar) {
element.horizontal.dragging = true element.horizontal.dragging = true
@ -123,9 +128,9 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button input.Button)
// FIXME: x backend and scroll container should pull these // FIXME: x backend and scroll container should pull these
// values from the same place // values from the same place
if x > element.horizontal.bar.Min.X { if x > element.horizontal.bar.Min.X {
element.scrollChildBy(16, 0) element.scrollChildBy(velocity, 0)
} else { } else {
element.scrollChildBy(-16, 0) element.scrollChildBy(-velocity, 0)
} }
} else if point.In(element.vertical.bar) { } else if point.In(element.vertical.bar) {
@ -137,9 +142,9 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button input.Button)
} else if point.In(element.vertical.gutter) { } else if point.In(element.vertical.gutter) {
if y > element.vertical.bar.Min.Y { if y > element.vertical.bar.Min.Y {
element.scrollChildBy(0, 16) element.scrollChildBy(0, velocity)
} else { } else {
element.scrollChildBy(0, -16) element.scrollChildBy(0, -velocity)
} }
} else if child, ok := element.child.(elements.MouseTarget); ok { } else if child, ok := element.child.(elements.MouseTarget); ok {
@ -281,22 +286,19 @@ func (element *ScrollContainer) resizeChildToFit () {
} }
func (element *ScrollContainer) recalculate () { func (element *ScrollContainer) recalculate () {
_, gutterInsetHorizontal := theme.GutterPattern(theme.PatternState {
Case: scrollBarHorizontalCase,
})
_, gutterInsetVertical := theme.GutterPattern(theme.PatternState {
Case: scrollBarHorizontalCase,
})
horizontal := &element.horizontal horizontal := &element.horizontal
vertical := &element.vertical vertical := &element.vertical
gutterInsetHorizontal := element.theme.Inset(theme.PatternGutter, horizontal.c)
gutterInsetVertical := element.theme.Inset(theme.PatternGutter, vertical.c)
bounds := element.Bounds() bounds := element.Bounds()
thicknessHorizontal := thicknessHorizontal :=
theme.HandleWidth() + element.config.HandleWidth() +
gutterInsetHorizontal[3] + gutterInsetHorizontal[3] +
gutterInsetHorizontal[1] gutterInsetHorizontal[1]
thicknessVertical := thicknessVertical :=
theme.HandleWidth() + element.config.HandleWidth() +
gutterInsetVertical[3] + gutterInsetVertical[3] +
gutterInsetVertical[1] gutterInsetVertical[1]
@ -373,9 +375,8 @@ func (element *ScrollContainer) recalculate () {
func (element *ScrollContainer) draw () { func (element *ScrollContainer) draw () {
artist.Paste(element, element.child, image.Point { }) artist.Paste(element, element.child, image.Point { })
deadPattern, _ := theme.DeadPattern(theme.PatternState { deadPattern := element.theme.Pattern (
Case: scrollContainerCase, theme.PatternDead, element.c, theme.PatternState { })
})
artist.FillRectangle ( artist.FillRectangle (
element, deadPattern, element, deadPattern,
image.Rect ( image.Rect (
@ -388,32 +389,30 @@ func (element *ScrollContainer) draw () {
} }
func (element *ScrollContainer) drawHorizontalBar () { func (element *ScrollContainer) drawHorizontalBar () {
gutterPattern, _ := theme.GutterPattern (theme.PatternState { state := theme.PatternState {
Case: scrollBarHorizontalCase,
Disabled: !element.horizontal.enabled,
})
artist.FillRectangle(element, gutterPattern, element.horizontal.gutter)
handlePattern, _ := theme.HandlePattern (theme.PatternState {
Case: scrollBarHorizontalCase,
Disabled: !element.horizontal.enabled, Disabled: !element.horizontal.enabled,
Pressed: element.horizontal.dragging, Pressed: element.horizontal.dragging,
}) }
gutterPattern := element.theme.Pattern (
theme.PatternGutter, element.horizontal.c, state)
artist.FillRectangle(element, gutterPattern, element.horizontal.gutter)
handlePattern := element.theme.Pattern (
theme.PatternHandle, element.horizontal.c, state)
artist.FillRectangle(element, handlePattern, element.horizontal.bar) artist.FillRectangle(element, handlePattern, element.horizontal.bar)
} }
func (element *ScrollContainer) drawVerticalBar () { func (element *ScrollContainer) drawVerticalBar () {
gutterPattern, _ := theme.GutterPattern (theme.PatternState { state := theme.PatternState {
Case: scrollBarVerticalCase,
Disabled: !element.vertical.enabled,
})
artist.FillRectangle(element, gutterPattern, element.vertical.gutter)
handlePattern, _ := theme.HandlePattern (theme.PatternState {
Case: scrollBarVerticalCase,
Disabled: !element.vertical.enabled, Disabled: !element.vertical.enabled,
Pressed: element.vertical.dragging, Pressed: element.vertical.dragging,
}) }
gutterPattern := element.theme.Pattern (
theme.PatternGutter, element.vertical.c, state)
artist.FillRectangle(element, gutterPattern, element.vertical.gutter)
handlePattern := element.theme.Pattern (
theme.PatternHandle, element.vertical.c, state)
artist.FillRectangle(element, handlePattern, element.vertical.bar) artist.FillRectangle(element, handlePattern, element.vertical.bar)
} }
@ -436,19 +435,17 @@ func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) {
} }
func (element *ScrollContainer) updateMinimumSize () { func (element *ScrollContainer) updateMinimumSize () {
_, gutterInsetHorizontal := theme.GutterPattern(theme.PatternState { gutterInsetHorizontal := element.theme.Inset (
Case: scrollBarHorizontalCase, theme.PatternGutter, element.horizontal.c)
}) gutterInsetVertical := element.theme.Inset (
_, gutterInsetVertical := theme.GutterPattern(theme.PatternState { theme.PatternGutter, element.vertical.c)
Case: scrollBarHorizontalCase,
})
thicknessHorizontal := thicknessHorizontal :=
theme.HandleWidth() + element.config.HandleWidth() +
gutterInsetHorizontal[3] + gutterInsetHorizontal[3] +
gutterInsetHorizontal[1] gutterInsetHorizontal[1]
thicknessVertical := thicknessVertical :=
theme.HandleWidth() + element.config.HandleWidth() +
gutterInsetVertical[3] + gutterInsetVertical[3] +
gutterInsetVertical[1] gutterInsetVertical[1]

View File

@ -1,23 +1,26 @@
package basicElements package basicElements
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var spacerCase = theme.C("basic", "spacer")
// Spacer can be used to put space between two elements.. // Spacer can be used to put space between two elements..
type Spacer struct { type Spacer struct {
*core.Core *core.Core
core core.CoreControl core core.CoreControl
line bool line bool
theme theme.Theme
config config.Config
c theme.Case
} }
// NewSpacer creates a new spacer. If line is set to true, the spacer will be // 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, // filled with a line color, and if compressed to its minimum width or height,
// will appear as a line. // will appear as a line.
func NewSpacer (line bool) (element *Spacer) { func NewSpacer (line bool) (element *Spacer) {
element = &Spacer { line: line } element = &Spacer { line: line, c: theme.C("basic", "spacer") }
element.Core, element.core = core.NewCore(element.draw) element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(1, 1) element.core.SetMinimumSize(1, 1)
return return
@ -33,20 +36,39 @@ func (element *Spacer) SetLine (line bool) {
} }
} }
// SetTheme sets the element's theme.
func (element *Spacer) SetTheme (new theme.Theme) {
element.theme = new
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Spacer) SetConfig (new config.Config) {
element.config = new
element.redo()
}
func (element *Spacer) redo () {
if !element.core.HasImage() {
element.draw()
element.core.DamageAll()
}
}
func (element *Spacer) draw () { func (element *Spacer) draw () {
bounds := element.Bounds() bounds := element.Bounds()
if element.line { if element.line {
pattern, _ := theme.ForegroundPattern(theme.PatternState { pattern := element.theme.Pattern (
Case: spacerCase, theme.PatternForeground,
Disabled: true, element.c,
}) theme.PatternState { })
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
} else { } else {
pattern, _ := theme.BackgroundPattern(theme.PatternState { pattern := element.theme.Pattern (
Case: spacerCase, theme.PatternBackground,
Disabled: true, element.c,
}) theme.PatternState { })
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
} }
} }

View File

@ -3,11 +3,10 @@ package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var switchCase = theme.C("basic", "switch")
// Switch is a toggle-able on/off switch with an optional label. It is // Switch is a toggle-able on/off switch with an optional label. It is
// functionally identical to Checkbox, but plays a different semantic role. // functionally identical to Checkbox, but plays a different semantic role.
type Switch struct { type Switch struct {
@ -21,23 +20,25 @@ type Switch struct {
checked bool checked bool
text string text string
theme theme.Theme
config config.Config
c theme.Case
onToggle func () onToggle func ()
} }
// NewSwitch creates a new switch with the specified label text. // NewSwitch creates a new switch with the specified label text.
func NewSwitch (text string, on bool) (element *Switch) { func NewSwitch (text string, on bool) (element *Switch) {
element = &Switch { checked: on, text: text } element = &Switch {
checked: on,
text: text,
c: theme.C("basic", "switch"),
}
element.Core, element.core = core.NewCore(element.draw) element.Core, element.core = core.NewCore(element.draw)
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () { element.focusableControl = core.NewFocusableCore(element.redo)
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
})
element.drawer.SetFace(theme.FontFaceRegular())
element.drawer.SetText([]rune(text)) element.drawer.SetText([]rune(text))
element.calculateMinimumSize() element.updateMinimumSize()
return return
} }
@ -45,10 +46,7 @@ func (element *Switch) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
element.Focus() element.Focus()
element.pressed = true element.pressed = true
if element.core.HasImage() { element.redo()
element.draw()
element.core.DamageAll()
}
} }
func (element *Switch) HandleMouseUp (x, y int, button input.Button) { func (element *Switch) HandleMouseUp (x, y int, button input.Button) {
@ -76,10 +74,7 @@ func (element *Switch) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *Switch) HandleKeyDown (key input.Key, modifiers input.Modifiers) { func (element *Switch) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter { if key == input.KeyEnter {
element.pressed = true element.pressed = true
if element.core.HasImage() { element.redo()
element.draw()
element.core.DamageAll()
}
} }
} }
@ -87,10 +82,7 @@ func (element *Switch) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter && element.pressed { if key == input.KeyEnter && element.pressed {
element.pressed = false element.pressed = false
element.checked = !element.checked element.checked = !element.checked
if element.core.HasImage() { element.redo()
element.draw()
element.core.DamageAll()
}
if element.onToggle != nil { if element.onToggle != nil {
element.onToggle() element.onToggle()
} }
@ -118,15 +110,36 @@ func (element *Switch) SetText (text string) {
element.text = text element.text = text
element.drawer.SetText([]rune(text)) element.drawer.SetText([]rune(text))
element.calculateMinimumSize() element.updateMinimumSize()
element.redo()
}
// SetTheme sets the element's theme.
func (element *Switch) SetTheme (new theme.Theme) {
element.theme = new
element.drawer.SetFace (element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c))
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Switch) SetConfig (new config.Config) {
element.config = new
element.updateMinimumSize()
element.redo()
}
func (element *Switch) redo () {
if element.core.HasImage () { if element.core.HasImage () {
element.draw() element.draw()
element.core.DamageAll() element.core.DamageAll()
} }
} }
func (element *Switch) calculateMinimumSize () { func (element *Switch) updateMinimumSize () {
textBounds := element.drawer.LayoutBounds() textBounds := element.drawer.LayoutBounds()
lineHeight := element.drawer.LineHeight().Round() lineHeight := element.drawer.LineHeight().Round()
@ -134,7 +147,9 @@ func (element *Switch) calculateMinimumSize () {
element.core.SetMinimumSize(lineHeight * 2, lineHeight) element.core.SetMinimumSize(lineHeight * 2, lineHeight)
} else { } else {
element.core.SetMinimumSize ( element.core.SetMinimumSize (
lineHeight * 2 + theme.Padding() + textBounds.Dx(), lineHeight * 2 +
element.config.Padding() +
textBounds.Dx(),
lineHeight) lineHeight)
} }
} }
@ -143,9 +158,14 @@ func (element *Switch) draw () {
bounds := element.Bounds() bounds := element.Bounds()
handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min) handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min) gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min)
backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState {
Case: switchCase, state := theme.PatternState {
}) Disabled: !element.Enabled(),
Focused: element.Focused(),
Pressed: element.pressed,
}
backgroundPattern := element.theme.Pattern (
theme.PatternBackground, element.c, state)
artist.FillRectangle (element, backgroundPattern, bounds) artist.FillRectangle (element, backgroundPattern, bounds)
if element.checked { if element.checked {
@ -162,33 +182,23 @@ func (element *Switch) draw () {
} }
} }
gutterPattern, _ := theme.GutterPattern(theme.PatternState { gutterPattern := element.theme.Pattern (
Case: switchCase, theme.PatternGutter, element.c, state)
Disabled: !element.Enabled(),
Focused: element.Focused(),
Pressed: element.pressed,
})
artist.FillRectangle(element, gutterPattern, gutterBounds) artist.FillRectangle(element, gutterPattern, gutterBounds)
handlePattern, _ := theme.HandlePattern(theme.PatternState { handlePattern := element.theme.Pattern (
Case: switchCase, theme.PatternHandle, element.c, state)
Disabled: !element.Enabled(),
Focused: element.Focused(),
Pressed: element.pressed,
})
artist.FillRectangle(element, handlePattern, handleBounds) artist.FillRectangle(element, handlePattern, handleBounds)
textBounds := element.drawer.LayoutBounds() textBounds := element.drawer.LayoutBounds()
offset := bounds.Min.Add(image.Point { offset := bounds.Min.Add(image.Point {
X: bounds.Dy() * 2 + theme.Padding(), X: bounds.Dy() * 2 + element.config.Padding(),
}) })
offset.Y -= textBounds.Min.Y offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X offset.X -= textBounds.Min.X
foreground, _ := theme.ForegroundPattern (theme.PatternState { foreground := element.theme.Pattern (
Case: switchCase, theme.PatternForeground, element.c, state)
Disabled: !element.Enabled(),
})
element.drawer.Draw(element, foreground, offset) element.drawer.Draw(element, foreground, offset)
} }

View File

@ -3,12 +3,11 @@ package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/textmanip" import "git.tebibyte.media/sashakoshka/tomo/textmanip"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var textBoxCase = theme.C("basic", "textBox")
// TextBox is a single-line text input. // TextBox is a single-line text input.
type TextBox struct { type TextBox struct {
*core.Core *core.Core
@ -24,6 +23,10 @@ type TextBox struct {
placeholderDrawer artist.TextDrawer placeholderDrawer artist.TextDrawer
valueDrawer artist.TextDrawer valueDrawer artist.TextDrawer
theme theme.Theme
config config.Config
c theme.Case
onKeyDown func (key input.Key, modifiers input.Modifiers) (handled bool) onKeyDown func (key input.Key, modifiers input.Modifiers) (handled bool)
onChange func () onChange func ()
onScrollBoundsChange func () onScrollBoundsChange func ()
@ -33,7 +36,7 @@ type TextBox struct {
// a value. When the value is empty, the placeholder will be displayed in gray // a value. When the value is empty, the placeholder will be displayed in gray
// text. // text.
func NewTextBox (placeholder, value string) (element *TextBox) { func NewTextBox (placeholder, value string) (element *TextBox) {
element = &TextBox { } element = &TextBox { c: theme.C("basic", "textBox") }
element.Core, element.core = core.NewCore(element.handleResize) element.Core, element.core = core.NewCore(element.handleResize)
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () { element.focusableControl = core.NewFocusableCore (func () {
@ -42,8 +45,6 @@ func NewTextBox (placeholder, value string) (element *TextBox) {
element.core.DamageAll() element.core.DamageAll()
} }
}) })
element.placeholderDrawer.SetFace(theme.FontFaceRegular())
element.valueDrawer.SetFace(theme.FontFaceRegular())
element.placeholder = placeholder element.placeholder = placeholder
element.placeholderDrawer.SetText([]rune(placeholder)) element.placeholderDrawer.SetText([]rune(placeholder))
element.updateMinimumSize() element.updateMinimumSize()
@ -130,9 +131,8 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
element.onScrollBoundsChange() element.onScrollBoundsChange()
} }
if altered && element.core.HasImage () { if altered {
element.draw() element.redo()
element.core.DamageAll()
} }
} }
@ -145,10 +145,7 @@ func (element *TextBox) SetPlaceholder (placeholder string) {
element.placeholderDrawer.SetText([]rune(placeholder)) element.placeholderDrawer.SetText([]rune(placeholder))
element.updateMinimumSize() element.updateMinimumSize()
if element.core.HasImage () { element.redo()
element.draw()
element.core.DamageAll()
}
} }
func (element *TextBox) SetValue (text string) { func (element *TextBox) SetValue (text string) {
@ -161,11 +158,7 @@ func (element *TextBox) SetValue (text string) {
element.cursor = element.valueDrawer.Length() element.cursor = element.valueDrawer.Length()
} }
element.scrollToCursor() element.scrollToCursor()
element.redo()
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
} }
func (element *TextBox) Value () (value string) { func (element *TextBox) Value () (value string) {
@ -203,7 +196,7 @@ func (element *TextBox) ScrollViewportBounds () (bounds image.Rectangle) {
} }
func (element *TextBox) scrollViewportWidth () (width int) { func (element *TextBox) scrollViewportWidth () (width int) {
return element.Bounds().Inset(theme.Padding()).Dx() return element.Bounds().Inset(element.config.Padding()).Dx()
} }
// ScrollTo scrolls the viewport to the specified point relative to // ScrollTo scrolls the viewport to the specified point relative to
@ -218,10 +211,7 @@ func (element *TextBox) ScrollTo (position image.Point) {
maxPosition := contentBounds.Max.X - element.scrollViewportWidth() maxPosition := contentBounds.Max.X - element.scrollViewportWidth()
if element.scroll > maxPosition { element.scroll = maxPosition } if element.scroll > maxPosition { element.scroll = maxPosition }
if element.core.HasImage () { element.redo()
element.draw()
element.core.DamageAll()
}
if element.onScrollBoundsChange != nil { if element.onScrollBoundsChange != nil {
element.onScrollBoundsChange() element.onScrollBoundsChange()
} }
@ -236,18 +226,6 @@ func (element *TextBox) OnScrollBoundsChange (callback func ()) {
element.onScrollBoundsChange = callback element.onScrollBoundsChange = callback
} }
func (element *TextBox) updateMinimumSize () {
textBounds := element.placeholderDrawer.LayoutBounds()
_, inset := theme.InputPattern(theme.PatternState {
Case: textBoxCase,
})
element.core.SetMinimumSize (
textBounds.Dx() +
theme.Padding() * 2 + inset[3] + inset[1],
element.placeholderDrawer.LineHeight().Round() +
theme.Padding() * 2 + inset[0] + inset[2])
}
func (element *TextBox) runOnChange () { func (element *TextBox) runOnChange () {
if element.onChange != nil { if element.onChange != nil {
element.onChange() element.onChange()
@ -257,7 +235,7 @@ func (element *TextBox) runOnChange () {
func (element *TextBox) scrollToCursor () { func (element *TextBox) scrollToCursor () {
if !element.core.HasImage() { return } if !element.core.HasImage() { return }
bounds := element.Bounds().Inset(theme.Padding()) bounds := element.Bounds().Inset(element.config.Padding())
bounds = bounds.Sub(bounds.Min) bounds = bounds.Sub(bounds.Min)
bounds.Max.X -= element.valueDrawer.Em().Round() bounds.Max.X -= element.valueDrawer.Em().Round()
cursorPosition := element.valueDrawer.PositionOf(element.cursor) cursorPosition := element.valueDrawer.PositionOf(element.cursor)
@ -272,28 +250,64 @@ func (element *TextBox) scrollToCursor () {
} }
} }
// SetTheme sets the element's theme.
func (element *TextBox) SetTheme (new theme.Theme) {
element.theme = new
face := element.theme.FontFace (
theme.FontStyleRegular,
theme.FontSizeNormal,
element.c)
element.placeholderDrawer.SetFace(face)
element.valueDrawer.SetFace(face)
element.updateMinimumSize()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *TextBox) SetConfig (new config.Config) {
element.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.placeholderDrawer.LineHeight().Round() +
element.config.Padding() * 2 + inset[0] + inset[2])
}
func (element *TextBox) redo () {
if element.core.HasImage () {
element.draw()
element.core.DamageAll()
}
}
func (element *TextBox) draw () { func (element *TextBox) draw () {
bounds := element.Bounds() bounds := element.Bounds()
// FIXME: take index into account // FIXME: take index into account
pattern, inset := theme.InputPattern(theme.PatternState { state := theme.PatternState {
Case: textBoxCase,
Disabled: !element.Enabled(), Disabled: !element.Enabled(),
Focused: element.Focused(), Focused: element.Focused(),
}) }
pattern := element.theme.Pattern(theme.PatternSunken, element.c, state)
artist.FillRectangle(element, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
if len(element.text) == 0 && !element.Focused() { if len(element.text) == 0 && !element.Focused() {
// draw placeholder // draw placeholder
textBounds := element.placeholderDrawer.LayoutBounds() textBounds := element.placeholderDrawer.LayoutBounds()
offset := bounds.Min.Add (image.Point { offset := bounds.Min.Add (image.Point {
X: theme.Padding() + inset[3], X: element.config.Padding(),
Y: theme.Padding() + inset[0], Y: element.config.Padding(),
})
foreground, _ := theme.ForegroundPattern(theme.PatternState {
Case: textBoxCase,
Disabled: true,
}) })
foreground := element.theme.Pattern (
theme.PatternForeground, element.c,
theme.PatternState { Disabled: true })
element.placeholderDrawer.Draw ( element.placeholderDrawer.Draw (
element, element,
foreground, foreground,
@ -302,13 +316,11 @@ func (element *TextBox) draw () {
// draw input value // draw input value
textBounds := element.valueDrawer.LayoutBounds() textBounds := element.valueDrawer.LayoutBounds()
offset := bounds.Min.Add (image.Point { offset := bounds.Min.Add (image.Point {
X: theme.Padding() + inset[3] - element.scroll, X: element.config.Padding() - element.scroll,
Y: theme.Padding() + inset[0], Y: element.config.Padding(),
})
foreground, _ := theme.ForegroundPattern(theme.PatternState {
Case: textBoxCase,
Disabled: !element.Enabled(),
}) })
foreground := element.theme.Pattern (
theme.PatternForeground, element.c, state)
element.valueDrawer.Draw ( element.valueDrawer.Draw (
element, element,
foreground, foreground,
@ -318,9 +330,6 @@ func (element *TextBox) draw () {
// cursor // cursor
cursorPosition := element.valueDrawer.PositionOf ( cursorPosition := element.valueDrawer.PositionOf (
element.cursor) element.cursor)
foreground, _ := theme.ForegroundPattern(theme.PatternState {
Case: textBoxCase,
})
artist.Line ( artist.Line (
element, element,
foreground, 1, foreground, 1,

View File

@ -2,11 +2,7 @@ package core
import "image" import "image"
import "image/color" import "image/color"
import "golang.org/x/image/font"
import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist"
// Core is a struct that implements some core functionality common to most // Core is a struct that implements some core functionality common to most
// widgets. It is meant to be embedded directly into a struct. // widgets. It is meant to be embedded directly into a struct.
@ -18,32 +14,20 @@ type Core struct {
minimumHeight int minimumHeight int
} }
config config.Config drawSizeChange func ()
theme theme.Theme
c theme.Case
handleSizeChange func ()
handleConfigChange func ()
handleThemeChange func ()
onMinimumSizeChange func () onMinimumSizeChange func ()
onDamage func (region canvas.Canvas) onDamage func (region canvas.Canvas)
} }
// NewCore creates a new element core and its corresponding control. // NewCore creates a new element core and its corresponding control.
func NewCore ( func NewCore (
handleSizeChange func (), drawSizeChange func (),
handleConfigChange func (),
handleThemeChange func (),
c theme.Case,
) ( ) (
core *Core, core *Core,
control CoreControl, control CoreControl,
) { ) {
core = &Core { core = &Core {
handleSizeChange: handleSizeChange, drawSizeChange: drawSizeChange,
handleConfigChange: handleConfigChange,
handleThemeChange: handleThemeChange,
c: c,
} }
control = CoreControl { core: core } control = CoreControl { core: core }
return return
@ -88,8 +72,8 @@ func (core *Core) MinimumSize () (width, height int) {
// overridden. // overridden.
func (core *Core) DrawTo (canvas canvas.Canvas) { func (core *Core) DrawTo (canvas canvas.Canvas) {
core.canvas = canvas core.canvas = canvas
if core.handleSizeChange != nil { if core.drawSizeChange != nil {
core.handleSizeChange() core.drawSizeChange()
} }
} }
@ -105,24 +89,6 @@ func (core *Core) OnMinimumSizeChange (callback func ()) {
core.onMinimumSizeChange = callback core.onMinimumSizeChange = callback
} }
// SetConfig fulfills the elements.Configurable interface. This should not need
// to be overridden.
func (core *Core) SetConfig (config config.Config) {
core.config = config
if core.handleConfigChange != nil {
core.handleConfigChange()
}
}
// SetTheme fulfills the elements.Themeable interface. This should not need
// to be overridden.
func (core *Core) SetTheme (theme theme.Theme) {
core.theme = theme
if core.handleThemeChange != nil {
core.handleThemeChange()
}
}
// CoreControl is a struct that can exert control over a Core struct. It can be // CoreControl is a struct that can exert control over a Core struct. It can be
// used as a canvas. It must not be directly embedded into an element, but // used as a canvas. It must not be directly embedded into an element, but
// instead kept as a private member. When a Core struct is created, a // instead kept as a private member. When a Core struct is created, a
@ -188,49 +154,3 @@ func (control CoreControl) ConstrainSize (
} }
return return
} }
// Config returns the current configuration.
func (control CoreControl) Config () (config.Config) {
return control.core.config
}
// Theme returns the current theme.
func (control CoreControl) Theme () (theme.Theme) {
return control.core.theme
}
// FontFace is like Theme.FontFace, but it automatically applies the correct
// case.
func (control CoreControl) FontFace (
style theme.FontStyle,
size theme.FontSize,
) (
face font.Face,
) {
return control.core.theme.FontFace(style, size, control.core.c)
}
// Icon is like Theme.Icon, but it automatically applies the correct case.
func (control CoreControl) Icon (name string) (artist.Pattern) {
return control.core.theme.Icon(name, control.core.c)
}
// Pattern is like Theme.Pattern, but it automatically applies the correct case.
func (control CoreControl) Pattern (
id theme.Pattern,
state theme.PatternState,
) (
pattern artist.Pattern,
) {
return control.core.theme.Pattern(id, control.core.c, state)
}
// Inset is like Theme.Inset, but it automatically applies the correct case.
func (control CoreControl) Inset (id theme.Pattern) (inset theme.Inset) {
return control.core.theme.Inset(id, control.core.c)
}
// Sink is like Theme.Sink, but it automatically applies the correct case.
func (control CoreControl) Sink (id theme.Pattern) (offset image.Point) {
return control.core.theme.Sink(id, control.core.c)
}