restructure-config #8
@ -3,6 +3,7 @@ package basicElements
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
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/elements/core"
|
||||
|
||||
@ -17,51 +18,37 @@ type Button struct {
|
||||
pressed bool
|
||||
text string
|
||||
|
||||
config config.Config
|
||||
theme theme.Theme
|
||||
c theme.Case
|
||||
|
||||
onClick func ()
|
||||
}
|
||||
|
||||
// NewButton creates a new button with the specified label text.
|
||||
func NewButton (text string) (element *Button) {
|
||||
element = &Button { }
|
||||
element.Core, element.core = core.NewCore (
|
||||
element.draw,
|
||||
element.redo,
|
||||
element.redo,
|
||||
theme.C("basic", "button"))
|
||||
element = &Button {
|
||||
c: theme.C("basic", "button"),
|
||||
}
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore(element.redo)
|
||||
element.SetText(text)
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Button) redo () {
|
||||
element.drawer.SetFace (
|
||||
element.core.FontFace(theme.FontStyleRegular,
|
||||
theme.FontSizeNormal))
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Button) HandleMouseDown (x, y int, button input.Button) {
|
||||
if !element.Enabled() { return }
|
||||
if !element.Focused() { element.Focus() }
|
||||
if button != input.ButtonLeft { return }
|
||||
element.pressed = true
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
|
||||
func (element *Button) HandleMouseUp (x, y int, button input.Button) {
|
||||
if button != input.ButtonLeft { return }
|
||||
element.pressed = false
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
|
||||
within := image.Point { x, y }.
|
||||
In(element.Bounds())
|
||||
@ -79,10 +66,7 @@ func (element *Button) HandleKeyDown (key input.Key, modifiers input.Modifiers)
|
||||
if !element.Enabled() { return }
|
||||
if key == input.KeyEnter {
|
||||
element.pressed = true
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,12 +97,41 @@ func (element *Button) SetText (text string) {
|
||||
|
||||
element.text = text
|
||||
element.drawer.SetText([]rune(text))
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
minimumSize := textBounds.Inset(-element.core.Config().Padding())
|
||||
element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
|
||||
element.updateMinimumSize()
|
||||
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 () {
|
||||
bounds := element.Bounds()
|
||||
|
||||
@ -128,7 +141,7 @@ func (element *Button) draw () {
|
||||
Pressed: element.pressed,
|
||||
}
|
||||
|
||||
pattern := element.core.Pattern(theme.PatternButton, state)
|
||||
pattern := element.theme.Pattern(theme.PatternButton, element.c, state)
|
||||
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
|
||||
@ -143,6 +156,6 @@ func (element *Button) draw () {
|
||||
offset.Y -= textBounds.Min.Y
|
||||
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)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package basicElements
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
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/elements/core"
|
||||
|
||||
@ -18,33 +19,26 @@ type Checkbox struct {
|
||||
checked bool
|
||||
text string
|
||||
|
||||
config config.Config
|
||||
theme theme.Theme
|
||||
c theme.Case
|
||||
|
||||
onToggle func ()
|
||||
}
|
||||
|
||||
// NewCheckbox creates a new cbeckbox with the specified label text.
|
||||
func NewCheckbox (text string, checked bool) (element *Checkbox) {
|
||||
element = &Checkbox { checked: checked }
|
||||
element.Core, element.core = core.NewCore (
|
||||
element.draw,
|
||||
element.redo,
|
||||
element.redo,
|
||||
theme.C("basic", "checkbox"))
|
||||
element = &Checkbox {
|
||||
checked: checked,
|
||||
c: theme.C("basic", "checkbox"),
|
||||
}
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore(element.redo)
|
||||
element.SetText(text)
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Checkbox) redo () {
|
||||
element.drawer.SetFace (
|
||||
element.core.FontFace(theme.FontStyleRegular,
|
||||
theme.FontSizeNormal))
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Checkbox) HandleMouseDown (x, y int, button input.Button) {
|
||||
if !element.Enabled() { return }
|
||||
element.Focus()
|
||||
@ -122,16 +116,44 @@ func (element *Checkbox) SetText (text string) {
|
||||
|
||||
element.text = 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())
|
||||
} else {
|
||||
element.core.SetMinimumSize (
|
||||
textBounds.Dy() + element.core.Config().Padding() + textBounds.Dx(),
|
||||
textBounds.Dy() + element.config.Padding() + textBounds.Dx(),
|
||||
textBounds.Dy())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (element *Checkbox) redo () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
@ -149,20 +171,22 @@ func (element *Checkbox) draw () {
|
||||
On: element.checked,
|
||||
}
|
||||
|
||||
backgroundPattern := element.core.Pattern(theme.PatternBackground, state)
|
||||
backgroundPattern := element.theme.Pattern (
|
||||
theme.PatternBackground, element.c, state)
|
||||
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)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
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.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)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package basicElements
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/config"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/layouts"
|
||||
@ -23,6 +24,10 @@ type Container struct {
|
||||
focusable bool
|
||||
flexible bool
|
||||
|
||||
config config.Config
|
||||
theme theme.Theme
|
||||
c theme.Case
|
||||
|
||||
onFocusRequest func () (granted bool)
|
||||
onFocusMotionRequest func (input.KeynavDirection) (granted bool)
|
||||
onFlexibleHeightChange func ()
|
||||
@ -30,12 +35,10 @@ type Container struct {
|
||||
|
||||
// NewContainer creates a new container.
|
||||
func NewContainer (layout layouts.Layout) (element *Container) {
|
||||
element = &Container { }
|
||||
element.Core, element.core = core.NewCore (
|
||||
element.redoAll,
|
||||
element.handleConfigChange,
|
||||
element.handleThemeChange,
|
||||
theme.C("basic", "container"))
|
||||
element = &Container {
|
||||
c: theme.C("basic", "container"),
|
||||
}
|
||||
element.Core, element.core = core.NewCore(element.redoAll)
|
||||
element.SetLayout(layout)
|
||||
return
|
||||
}
|
||||
@ -207,7 +210,10 @@ func (element *Container) redoAll () {
|
||||
|
||||
// draw a background
|
||||
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)
|
||||
|
||||
// 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 {
|
||||
if child0, ok := child.Element.(elements.Configurable); ok {
|
||||
child0.SetConfig(element.core.Config())
|
||||
if child0, ok := child.Element.(elements.Themeable); ok {
|
||||
child0.SetTheme(element.theme)
|
||||
}
|
||||
}
|
||||
element.updateMinimumSize()
|
||||
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 {
|
||||
if child0, ok := child.Element.(elements.Themeable); ok {
|
||||
child0.SetTheme(element.core.Theme())
|
||||
if child0, ok := child.Element.(elements.Configurable); ok {
|
||||
child0.SetConfig(element.config)
|
||||
}
|
||||
}
|
||||
element.updateMinimumSize()
|
||||
element.redoAll()
|
||||
}
|
||||
|
||||
@ -284,7 +297,7 @@ func (element *Container) HandleKeyUp (key input.Key, modifiers input.Modifiers)
|
||||
func (element *Container) FlexibleHeightFor (width int) (height int) {
|
||||
return element.layout.FlexibleHeightFor (
|
||||
element.children,
|
||||
element.core.Config().Margin(), width)
|
||||
element.config.Margin(), width)
|
||||
}
|
||||
|
||||
func (element *Container) OnFlexibleHeightChange (callback func ()) {
|
||||
@ -487,15 +500,15 @@ func (element *Container) childFocusRequestCallback (
|
||||
|
||||
func (element *Container) updateMinimumSize () {
|
||||
width, height := element.layout.MinimumSize (
|
||||
element.children, element.core.Config().Margin())
|
||||
element.children, element.config.Margin())
|
||||
if element.flexible {
|
||||
height = element.layout.FlexibleHeightFor (
|
||||
element.children, element.core.Config().Margin(), width)
|
||||
element.children, element.config.Margin(), width)
|
||||
}
|
||||
element.core.SetMinimumSize(width, height)
|
||||
}
|
||||
|
||||
func (element *Container) recalculate () {
|
||||
element.layout.Arrange (
|
||||
element.children, element.core.Config().Margin(), element.Bounds())
|
||||
element.children, element.config.Margin(), element.Bounds())
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package basicElements
|
||||
|
||||
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/elements/core"
|
||||
|
||||
@ -13,31 +14,28 @@ type Label struct {
|
||||
text string
|
||||
drawer artist.TextDrawer
|
||||
|
||||
config config.Config
|
||||
theme theme.Theme
|
||||
c theme.Case
|
||||
|
||||
onFlexibleHeightChange func ()
|
||||
}
|
||||
|
||||
// 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 { }
|
||||
element.Core, element.core = core.NewCore (
|
||||
element.handleResize,
|
||||
element.redo,
|
||||
element.redo,
|
||||
theme.C("basic", "label"))
|
||||
face := element.core.FontFace (
|
||||
theme.FontStyleRegular,
|
||||
theme.FontSizeNormal)
|
||||
element.drawer.SetFace(face)
|
||||
element = &Label { c: theme.C("basic", "label") }
|
||||
element.Core, element.core = core.NewCore(element.handleResize)
|
||||
element.SetWrap(wrap)
|
||||
element.SetText(text)
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Label) redo () {
|
||||
face := element.core.FontFace (
|
||||
face := element.theme.FontFace (
|
||||
theme.FontStyleRegular,
|
||||
theme.FontSizeNormal)
|
||||
theme.FontSizeNormal,
|
||||
element.c)
|
||||
element.drawer.SetFace(face)
|
||||
element.updateMinimumSize()
|
||||
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 () {
|
||||
if element.wrap {
|
||||
em := element.drawer.Em().Round()
|
||||
if em < 1 { em = element.core.Config().Padding() }
|
||||
if em < 1 { em = element.config.Padding() }
|
||||
element.core.SetMinimumSize (
|
||||
em, element.drawer.LineHeight().Round())
|
||||
if element.onFlexibleHeightChange != nil {
|
||||
@ -127,15 +151,17 @@ func (element *Label) updateMinimumSize () {
|
||||
func (element *Label) draw () {
|
||||
bounds := element.Bounds()
|
||||
|
||||
pattern := element.core.Pattern (
|
||||
pattern := element.theme.Pattern (
|
||||
theme.PatternBackground,
|
||||
element.c,
|
||||
theme.PatternState { })
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
|
||||
foreground := element.core.Pattern (
|
||||
foreground := element.theme.Pattern (
|
||||
theme.PatternForeground,
|
||||
element.c,
|
||||
theme.PatternState { })
|
||||
element.drawer.Draw (element, foreground, bounds.Min.Sub(textBounds.Min))
|
||||
element.drawer.Draw(element, foreground, bounds.Min.Sub(textBounds.Min))
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import "fmt"
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/config"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
||||
@ -25,18 +26,21 @@ type List struct {
|
||||
scroll int
|
||||
entries []ListEntry
|
||||
|
||||
config config.Config
|
||||
theme theme.Theme
|
||||
c theme.Case
|
||||
|
||||
onScrollBoundsChange func ()
|
||||
onNoEntrySelected func ()
|
||||
}
|
||||
|
||||
// NewList creates a new list element with the specified entries.
|
||||
func NewList (entries ...ListEntry) (element *List) {
|
||||
element = &List { selectedEntry: -1 }
|
||||
element.Core, element.core = core.NewCore (
|
||||
element.handleResize,
|
||||
element.redo,
|
||||
element.redo,
|
||||
theme.C("basic", "list"))
|
||||
element = &List {
|
||||
selectedEntry: -1,
|
||||
c: theme.C("basic", "list"),
|
||||
}
|
||||
element.Core, element.core = core.NewCore(element.handleResize)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
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 {
|
||||
entry.SetConfig(element.core.Config())
|
||||
entry.SetConfig(element.config)
|
||||
element.entries[index] = entry
|
||||
}
|
||||
element.updateMinimumSize()
|
||||
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 {
|
||||
entry.SetConfig(element.core.Config())
|
||||
entry.SetConfig(element.config)
|
||||
element.entries[index] = entry
|
||||
}
|
||||
element.updateMinimumSize()
|
||||
element.redo()
|
||||
}
|
||||
|
||||
@ -196,7 +206,7 @@ func (element *List) ScrollAxes () (horizontal, vertical bool) {
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
@ -228,8 +238,8 @@ func (element *List) CountEntries () (count int) {
|
||||
func (element *List) Append (entry ListEntry) {
|
||||
// append
|
||||
entry.Collapse(element.forcedMinimumWidth)
|
||||
entry.SetTheme(element.core.Theme())
|
||||
entry.SetConfig(element.core.Config())
|
||||
entry.SetTheme(element.theme)
|
||||
entry.SetConfig(element.config)
|
||||
element.entries = append(element.entries, entry)
|
||||
|
||||
// recalculate, redraw, notify
|
||||
@ -322,7 +332,7 @@ func (element *List) Replace (index int, entry ListEntry) {
|
||||
}
|
||||
|
||||
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())
|
||||
mousePoint := image.Pt(x, y)
|
||||
dot := image.Pt (
|
||||
@ -364,7 +374,7 @@ func (element *List) changeSelectionBy (delta int) (updated bool) {
|
||||
}
|
||||
|
||||
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])
|
||||
return entry
|
||||
}
|
||||
@ -391,7 +401,7 @@ func (element *List) updateMinimumSize () {
|
||||
minimumHeight = element.contentHeight
|
||||
}
|
||||
|
||||
inset := element.core.Inset(theme.PatternSunken)
|
||||
inset := element.theme.Inset(theme.PatternSunken, element.c)
|
||||
minimumHeight += inset[0] + inset[2]
|
||||
|
||||
element.core.SetMinimumSize(minimumWidth, minimumHeight)
|
||||
@ -400,8 +410,8 @@ func (element *List) updateMinimumSize () {
|
||||
func (element *List) draw () {
|
||||
bounds := element.Bounds()
|
||||
|
||||
inset := element.core.Inset(theme.PatternSunken)
|
||||
pattern := element.core.Pattern (theme.PatternSunken, theme.PatternState {
|
||||
inset := element.theme.Inset(theme.PatternSunken, element.c)
|
||||
pattern := element.theme.Pattern (theme.PatternSunken, element.c, theme.PatternState {
|
||||
Disabled: !element.Enabled(),
|
||||
Focused: element.Focused(),
|
||||
})
|
||||
|
@ -6,8 +6,6 @@ import "git.tebibyte.media/sashakoshka/tomo/config"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||
|
||||
var listEntryCase = theme.C("basic", "listEntry")
|
||||
|
||||
// ListEntry is an item that can be added to a list.
|
||||
type ListEntry struct {
|
||||
drawer artist.TextDrawer
|
||||
@ -15,15 +13,19 @@ type ListEntry struct {
|
||||
textPoint image.Point
|
||||
text string
|
||||
forcedMinimumWidth int
|
||||
onSelect func ()
|
||||
|
||||
theme theme.Theme
|
||||
config config.Config
|
||||
c theme.Case
|
||||
|
||||
onSelect func ()
|
||||
}
|
||||
|
||||
func NewListEntry (text string, onSelect func ()) (entry ListEntry) {
|
||||
entry = ListEntry {
|
||||
text: text,
|
||||
onSelect: onSelect,
|
||||
c: theme.C("basic", "listEntry"),
|
||||
}
|
||||
entry.drawer.SetText([]rune(text))
|
||||
entry.updateBounds()
|
||||
@ -41,7 +43,7 @@ func (entry *ListEntry) SetTheme (new theme.Theme) {
|
||||
entry.drawer.SetFace (entry.theme.FontFace (
|
||||
theme.FontStyleRegular,
|
||||
theme.FontSizeNormal,
|
||||
listEntryCase))
|
||||
entry.c))
|
||||
entry.updateBounds()
|
||||
}
|
||||
|
||||
@ -58,7 +60,7 @@ func (entry *ListEntry) updateBounds () {
|
||||
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.textPoint =
|
||||
@ -78,12 +80,12 @@ func (entry *ListEntry) Draw (
|
||||
Focused: focused,
|
||||
On: on,
|
||||
}
|
||||
pattern := entry.theme.Pattern (theme.PatternRaised, listEntryCase, state)
|
||||
pattern := entry.theme.Pattern (theme.PatternRaised, entry.c, state)
|
||||
artist.FillRectangle (
|
||||
destination,
|
||||
pattern,
|
||||
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 (
|
||||
destination,
|
||||
foreground,
|
||||
|
@ -2,6 +2,7 @@ package basicElements
|
||||
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/config"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
||||
|
||||
@ -10,28 +11,21 @@ type ProgressBar struct {
|
||||
*core.Core
|
||||
core core.CoreControl
|
||||
progress float64
|
||||
|
||||
theme theme.Theme
|
||||
config config.Config
|
||||
c theme.Case
|
||||
}
|
||||
|
||||
// NewProgressBar creates a new progress bar displaying the given progress
|
||||
// level.
|
||||
func NewProgressBar (progress float64) (element *ProgressBar) {
|
||||
element = &ProgressBar { progress: progress }
|
||||
element.Core, element.core = core.NewCore (
|
||||
element.draw,
|
||||
element.redo,
|
||||
element.redo,
|
||||
theme.C("basic", "progressBar"))
|
||||
return
|
||||
}
|
||||
|
||||
func (element *ProgressBar) redo () {
|
||||
element.core.SetMinimumSize (
|
||||
element.core.Config().Padding() * 2,
|
||||
element.core.Config().Padding() * 2)
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
element = &ProgressBar {
|
||||
progress: progress,
|
||||
c: theme.C("basic", "progressBar"),
|
||||
}
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
return
|
||||
}
|
||||
|
||||
// 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 () {
|
||||
bounds := element.Bounds()
|
||||
|
||||
pattern := element.core.Pattern (
|
||||
pattern := element.theme.Pattern (
|
||||
theme.PatternSunken,
|
||||
element.c,
|
||||
theme.PatternState { })
|
||||
inset := element.core.Inset(theme.PatternSunken)
|
||||
inset := element.theme.Inset(theme.PatternSunken, element.c)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
bounds = inset.Apply(bounds)
|
||||
meterBounds := image.Rect (
|
||||
bounds.Min.X, bounds.Min.Y,
|
||||
bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
|
||||
bounds.Max.Y)
|
||||
accent := element.core.Pattern (
|
||||
accent := element.theme.Pattern (
|
||||
theme.PatternSunken,
|
||||
element.c,
|
||||
theme.PatternState { })
|
||||
artist.FillRectangle(element, accent, meterBounds)
|
||||
}
|
||||
|
@ -3,15 +3,12 @@ package basicElements
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/config"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
||||
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
|
||||
// element.
|
||||
type ScrollContainer struct {
|
||||
@ -23,6 +20,7 @@ type ScrollContainer struct {
|
||||
childWidth, childHeight int
|
||||
|
||||
horizontal struct {
|
||||
c theme.Case
|
||||
exists bool
|
||||
enabled bool
|
||||
dragging bool
|
||||
@ -33,6 +31,7 @@ type ScrollContainer struct {
|
||||
}
|
||||
|
||||
vertical struct {
|
||||
c theme.Case
|
||||
exists bool
|
||||
enabled bool
|
||||
dragging bool
|
||||
@ -41,6 +40,10 @@ type ScrollContainer struct {
|
||||
track image.Rectangle
|
||||
bar image.Rectangle
|
||||
}
|
||||
|
||||
theme theme.Theme
|
||||
config config.Config
|
||||
c theme.Case
|
||||
|
||||
onFocusRequest func () (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
|
||||
// bars.
|
||||
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.updateMinimumSize()
|
||||
element.horizontal.exists = horizontal
|
||||
@ -85,8 +91,6 @@ func (element *ScrollContainer) Adopt (child elements.Scrollable) {
|
||||
element.childFocusMotionRequestCallback)
|
||||
}
|
||||
|
||||
// TODO: somehow inform the core that we do not in fact want to
|
||||
// redraw the element.
|
||||
element.updateMinimumSize()
|
||||
|
||||
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) {
|
||||
velocity := element.config.ScrollVelocity()
|
||||
point := image.Pt(x, y)
|
||||
if point.In(element.horizontal.bar) {
|
||||
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
|
||||
// values from the same place
|
||||
if x > element.horizontal.bar.Min.X {
|
||||
element.scrollChildBy(16, 0)
|
||||
element.scrollChildBy(velocity, 0)
|
||||
} else {
|
||||
element.scrollChildBy(-16, 0)
|
||||
element.scrollChildBy(-velocity, 0)
|
||||
}
|
||||
|
||||
} 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) {
|
||||
if y > element.vertical.bar.Min.Y {
|
||||
element.scrollChildBy(0, 16)
|
||||
element.scrollChildBy(0, velocity)
|
||||
} else {
|
||||
element.scrollChildBy(0, -16)
|
||||
element.scrollChildBy(0, -velocity)
|
||||
}
|
||||
|
||||
} else if child, ok := element.child.(elements.MouseTarget); ok {
|
||||
@ -281,22 +286,19 @@ func (element *ScrollContainer) resizeChildToFit () {
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) recalculate () {
|
||||
_, gutterInsetHorizontal := theme.GutterPattern(theme.PatternState {
|
||||
Case: scrollBarHorizontalCase,
|
||||
})
|
||||
_, gutterInsetVertical := theme.GutterPattern(theme.PatternState {
|
||||
Case: scrollBarHorizontalCase,
|
||||
})
|
||||
|
||||
horizontal := &element.horizontal
|
||||
vertical := &element.vertical
|
||||
|
||||
gutterInsetHorizontal := element.theme.Inset(theme.PatternGutter, horizontal.c)
|
||||
gutterInsetVertical := element.theme.Inset(theme.PatternGutter, vertical.c)
|
||||
|
||||
bounds := element.Bounds()
|
||||
thicknessHorizontal :=
|
||||
theme.HandleWidth() +
|
||||
element.config.HandleWidth() +
|
||||
gutterInsetHorizontal[3] +
|
||||
gutterInsetHorizontal[1]
|
||||
thicknessVertical :=
|
||||
theme.HandleWidth() +
|
||||
element.config.HandleWidth() +
|
||||
gutterInsetVertical[3] +
|
||||
gutterInsetVertical[1]
|
||||
|
||||
@ -373,9 +375,8 @@ func (element *ScrollContainer) recalculate () {
|
||||
|
||||
func (element *ScrollContainer) draw () {
|
||||
artist.Paste(element, element.child, image.Point { })
|
||||
deadPattern, _ := theme.DeadPattern(theme.PatternState {
|
||||
Case: scrollContainerCase,
|
||||
})
|
||||
deadPattern := element.theme.Pattern (
|
||||
theme.PatternDead, element.c, theme.PatternState { })
|
||||
artist.FillRectangle (
|
||||
element, deadPattern,
|
||||
image.Rect (
|
||||
@ -388,32 +389,30 @@ func (element *ScrollContainer) draw () {
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) drawHorizontalBar () {
|
||||
gutterPattern, _ := theme.GutterPattern (theme.PatternState {
|
||||
Case: scrollBarHorizontalCase,
|
||||
Disabled: !element.horizontal.enabled,
|
||||
})
|
||||
artist.FillRectangle(element, gutterPattern, element.horizontal.gutter)
|
||||
|
||||
handlePattern, _ := theme.HandlePattern (theme.PatternState {
|
||||
Case: scrollBarHorizontalCase,
|
||||
state := theme.PatternState {
|
||||
Disabled: !element.horizontal.enabled,
|
||||
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)
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) drawVerticalBar () {
|
||||
gutterPattern, _ := theme.GutterPattern (theme.PatternState {
|
||||
Case: scrollBarVerticalCase,
|
||||
Disabled: !element.vertical.enabled,
|
||||
})
|
||||
artist.FillRectangle(element, gutterPattern, element.vertical.gutter)
|
||||
|
||||
handlePattern, _ := theme.HandlePattern (theme.PatternState {
|
||||
Case: scrollBarVerticalCase,
|
||||
state := theme.PatternState {
|
||||
Disabled: !element.vertical.enabled,
|
||||
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)
|
||||
}
|
||||
|
||||
@ -436,19 +435,17 @@ func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) {
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) updateMinimumSize () {
|
||||
_, gutterInsetHorizontal := theme.GutterPattern(theme.PatternState {
|
||||
Case: scrollBarHorizontalCase,
|
||||
})
|
||||
_, gutterInsetVertical := theme.GutterPattern(theme.PatternState {
|
||||
Case: scrollBarHorizontalCase,
|
||||
})
|
||||
gutterInsetHorizontal := element.theme.Inset (
|
||||
theme.PatternGutter, element.horizontal.c)
|
||||
gutterInsetVertical := element.theme.Inset (
|
||||
theme.PatternGutter, element.vertical.c)
|
||||
|
||||
thicknessHorizontal :=
|
||||
theme.HandleWidth() +
|
||||
element.config.HandleWidth() +
|
||||
gutterInsetHorizontal[3] +
|
||||
gutterInsetHorizontal[1]
|
||||
thicknessVertical :=
|
||||
theme.HandleWidth() +
|
||||
element.config.HandleWidth() +
|
||||
gutterInsetVertical[3] +
|
||||
gutterInsetVertical[1]
|
||||
|
||||
|
@ -1,23 +1,26 @@
|
||||
package basicElements
|
||||
|
||||
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/elements/core"
|
||||
|
||||
var spacerCase = theme.C("basic", "spacer")
|
||||
|
||||
// Spacer can be used to put space between two elements..
|
||||
type Spacer struct {
|
||||
*core.Core
|
||||
core core.CoreControl
|
||||
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
|
||||
// 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 }
|
||||
element = &Spacer { line: line, c: theme.C("basic", "spacer") }
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.core.SetMinimumSize(1, 1)
|
||||
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 () {
|
||||
bounds := element.Bounds()
|
||||
|
||||
if element.line {
|
||||
pattern, _ := theme.ForegroundPattern(theme.PatternState {
|
||||
Case: spacerCase,
|
||||
Disabled: true,
|
||||
})
|
||||
pattern := element.theme.Pattern (
|
||||
theme.PatternForeground,
|
||||
element.c,
|
||||
theme.PatternState { })
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
} else {
|
||||
pattern, _ := theme.BackgroundPattern(theme.PatternState {
|
||||
Case: spacerCase,
|
||||
Disabled: true,
|
||||
})
|
||||
pattern := element.theme.Pattern (
|
||||
theme.PatternBackground,
|
||||
element.c,
|
||||
theme.PatternState { })
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,10 @@ package basicElements
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
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/elements/core"
|
||||
|
||||
var switchCase = theme.C("basic", "switch")
|
||||
|
||||
// Switch is a toggle-able on/off switch with an optional label. It is
|
||||
// functionally identical to Checkbox, but plays a different semantic role.
|
||||
type Switch struct {
|
||||
@ -21,23 +20,25 @@ type Switch struct {
|
||||
checked bool
|
||||
text string
|
||||
|
||||
theme theme.Theme
|
||||
config config.Config
|
||||
c theme.Case
|
||||
|
||||
onToggle func ()
|
||||
}
|
||||
|
||||
// NewSwitch creates a new switch with the specified label text.
|
||||
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.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
})
|
||||
element.drawer.SetFace(theme.FontFaceRegular())
|
||||
element.focusableControl = core.NewFocusableCore(element.redo)
|
||||
element.drawer.SetText([]rune(text))
|
||||
element.calculateMinimumSize()
|
||||
element.updateMinimumSize()
|
||||
return
|
||||
}
|
||||
|
||||
@ -45,10 +46,7 @@ func (element *Switch) HandleMouseDown (x, y int, button input.Button) {
|
||||
if !element.Enabled() { return }
|
||||
element.Focus()
|
||||
element.pressed = true
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
|
||||
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) {
|
||||
if key == input.KeyEnter {
|
||||
element.pressed = true
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,10 +82,7 @@ func (element *Switch) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
|
||||
if key == input.KeyEnter && element.pressed {
|
||||
element.pressed = false
|
||||
element.checked = !element.checked
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
if element.onToggle != nil {
|
||||
element.onToggle()
|
||||
}
|
||||
@ -118,15 +110,36 @@ func (element *Switch) SetText (text string) {
|
||||
|
||||
element.text = 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 () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Switch) calculateMinimumSize () {
|
||||
func (element *Switch) updateMinimumSize () {
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
lineHeight := element.drawer.LineHeight().Round()
|
||||
|
||||
@ -134,7 +147,9 @@ func (element *Switch) calculateMinimumSize () {
|
||||
element.core.SetMinimumSize(lineHeight * 2, lineHeight)
|
||||
} else {
|
||||
element.core.SetMinimumSize (
|
||||
lineHeight * 2 + theme.Padding() + textBounds.Dx(),
|
||||
lineHeight * 2 +
|
||||
element.config.Padding() +
|
||||
textBounds.Dx(),
|
||||
lineHeight)
|
||||
}
|
||||
}
|
||||
@ -143,9 +158,14 @@ func (element *Switch) draw () {
|
||||
bounds := element.Bounds()
|
||||
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)
|
||||
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)
|
||||
|
||||
if element.checked {
|
||||
@ -162,33 +182,23 @@ func (element *Switch) draw () {
|
||||
}
|
||||
}
|
||||
|
||||
gutterPattern, _ := theme.GutterPattern(theme.PatternState {
|
||||
Case: switchCase,
|
||||
Disabled: !element.Enabled(),
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
gutterPattern := element.theme.Pattern (
|
||||
theme.PatternGutter, element.c, state)
|
||||
artist.FillRectangle(element, gutterPattern, gutterBounds)
|
||||
|
||||
handlePattern, _ := theme.HandlePattern(theme.PatternState {
|
||||
Case: switchCase,
|
||||
Disabled: !element.Enabled(),
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
handlePattern := element.theme.Pattern (
|
||||
theme.PatternHandle, element.c, state)
|
||||
artist.FillRectangle(element, handlePattern, handleBounds)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
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.X -= textBounds.Min.X
|
||||
|
||||
foreground, _ := theme.ForegroundPattern (theme.PatternState {
|
||||
Case: switchCase,
|
||||
Disabled: !element.Enabled(),
|
||||
})
|
||||
foreground := element.theme.Pattern (
|
||||
theme.PatternForeground, element.c, state)
|
||||
element.drawer.Draw(element, foreground, offset)
|
||||
}
|
||||
|
@ -3,12 +3,11 @@ package basicElements
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
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/textmanip"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
||||
|
||||
var textBoxCase = theme.C("basic", "textBox")
|
||||
|
||||
// TextBox is a single-line text input.
|
||||
type TextBox struct {
|
||||
*core.Core
|
||||
@ -24,6 +23,10 @@ type TextBox struct {
|
||||
placeholderDrawer artist.TextDrawer
|
||||
valueDrawer artist.TextDrawer
|
||||
|
||||
theme theme.Theme
|
||||
config config.Config
|
||||
c theme.Case
|
||||
|
||||
onKeyDown func (key input.Key, modifiers input.Modifiers) (handled bool)
|
||||
onChange func ()
|
||||
onScrollBoundsChange func ()
|
||||
@ -33,7 +36,7 @@ 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 { }
|
||||
element = &TextBox { c: theme.C("basic", "textBox") }
|
||||
element.Core, element.core = core.NewCore(element.handleResize)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
@ -42,8 +45,6 @@ func NewTextBox (placeholder, value string) (element *TextBox) {
|
||||
element.core.DamageAll()
|
||||
}
|
||||
})
|
||||
element.placeholderDrawer.SetFace(theme.FontFaceRegular())
|
||||
element.valueDrawer.SetFace(theme.FontFaceRegular())
|
||||
element.placeholder = placeholder
|
||||
element.placeholderDrawer.SetText([]rune(placeholder))
|
||||
element.updateMinimumSize()
|
||||
@ -130,9 +131,8 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
|
||||
element.onScrollBoundsChange()
|
||||
}
|
||||
|
||||
if altered && element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
if altered {
|
||||
element.redo()
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,10 +145,7 @@ func (element *TextBox) SetPlaceholder (placeholder string) {
|
||||
element.placeholderDrawer.SetText([]rune(placeholder))
|
||||
|
||||
element.updateMinimumSize()
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
|
||||
func (element *TextBox) SetValue (text string) {
|
||||
@ -161,11 +158,7 @@ func (element *TextBox) SetValue (text string) {
|
||||
element.cursor = element.valueDrawer.Length()
|
||||
}
|
||||
element.scrollToCursor()
|
||||
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
|
||||
func (element *TextBox) Value () (value string) {
|
||||
@ -203,7 +196,7 @@ func (element *TextBox) ScrollViewportBounds () (bounds image.Rectangle) {
|
||||
}
|
||||
|
||||
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
|
||||
@ -218,10 +211,7 @@ func (element *TextBox) ScrollTo (position image.Point) {
|
||||
maxPosition := contentBounds.Max.X - element.scrollViewportWidth()
|
||||
if element.scroll > maxPosition { element.scroll = maxPosition }
|
||||
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
element.redo()
|
||||
if element.onScrollBoundsChange != nil {
|
||||
element.onScrollBoundsChange()
|
||||
}
|
||||
@ -236,18 +226,6 @@ func (element *TextBox) OnScrollBoundsChange (callback func ()) {
|
||||
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 () {
|
||||
if element.onChange != nil {
|
||||
element.onChange()
|
||||
@ -257,7 +235,7 @@ func (element *TextBox) runOnChange () {
|
||||
func (element *TextBox) scrollToCursor () {
|
||||
if !element.core.HasImage() { return }
|
||||
|
||||
bounds := element.Bounds().Inset(theme.Padding())
|
||||
bounds := element.Bounds().Inset(element.config.Padding())
|
||||
bounds = bounds.Sub(bounds.Min)
|
||||
bounds.Max.X -= element.valueDrawer.Em().Round()
|
||||
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 () {
|
||||
bounds := element.Bounds()
|
||||
|
||||
// FIXME: take index into account
|
||||
pattern, inset := theme.InputPattern(theme.PatternState {
|
||||
Case: textBoxCase,
|
||||
state := theme.PatternState {
|
||||
Disabled: !element.Enabled(),
|
||||
Focused: element.Focused(),
|
||||
})
|
||||
}
|
||||
pattern := element.theme.Pattern(theme.PatternSunken, element.c, state)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
|
||||
if len(element.text) == 0 && !element.Focused() {
|
||||
// draw placeholder
|
||||
textBounds := element.placeholderDrawer.LayoutBounds()
|
||||
offset := bounds.Min.Add (image.Point {
|
||||
X: theme.Padding() + inset[3],
|
||||
Y: theme.Padding() + inset[0],
|
||||
})
|
||||
foreground, _ := theme.ForegroundPattern(theme.PatternState {
|
||||
Case: textBoxCase,
|
||||
Disabled: true,
|
||||
X: element.config.Padding(),
|
||||
Y: element.config.Padding(),
|
||||
})
|
||||
foreground := element.theme.Pattern (
|
||||
theme.PatternForeground, element.c,
|
||||
theme.PatternState { Disabled: true })
|
||||
element.placeholderDrawer.Draw (
|
||||
element,
|
||||
foreground,
|
||||
@ -302,13 +316,11 @@ func (element *TextBox) draw () {
|
||||
// draw input value
|
||||
textBounds := element.valueDrawer.LayoutBounds()
|
||||
offset := bounds.Min.Add (image.Point {
|
||||
X: theme.Padding() + inset[3] - element.scroll,
|
||||
Y: theme.Padding() + inset[0],
|
||||
})
|
||||
foreground, _ := theme.ForegroundPattern(theme.PatternState {
|
||||
Case: textBoxCase,
|
||||
Disabled: !element.Enabled(),
|
||||
X: element.config.Padding() - element.scroll,
|
||||
Y: element.config.Padding(),
|
||||
})
|
||||
foreground := element.theme.Pattern (
|
||||
theme.PatternForeground, element.c, state)
|
||||
element.valueDrawer.Draw (
|
||||
element,
|
||||
foreground,
|
||||
@ -318,9 +330,6 @@ func (element *TextBox) draw () {
|
||||
// cursor
|
||||
cursorPosition := element.valueDrawer.PositionOf (
|
||||
element.cursor)
|
||||
foreground, _ := theme.ForegroundPattern(theme.PatternState {
|
||||
Case: textBoxCase,
|
||||
})
|
||||
artist.Line (
|
||||
element,
|
||||
foreground, 1,
|
||||
|
@ -2,11 +2,7 @@ package core
|
||||
|
||||
import "image"
|
||||
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/artist"
|
||||
|
||||
// Core is a struct that implements some core functionality common to most
|
||||
// widgets. It is meant to be embedded directly into a struct.
|
||||
@ -18,32 +14,20 @@ type Core struct {
|
||||
minimumHeight int
|
||||
}
|
||||
|
||||
config config.Config
|
||||
theme theme.Theme
|
||||
c theme.Case
|
||||
|
||||
handleSizeChange func ()
|
||||
handleConfigChange func ()
|
||||
handleThemeChange func ()
|
||||
drawSizeChange func ()
|
||||
onMinimumSizeChange func ()
|
||||
onDamage func (region canvas.Canvas)
|
||||
}
|
||||
|
||||
// NewCore creates a new element core and its corresponding control.
|
||||
func NewCore (
|
||||
handleSizeChange func (),
|
||||
handleConfigChange func (),
|
||||
handleThemeChange func (),
|
||||
c theme.Case,
|
||||
drawSizeChange func (),
|
||||
) (
|
||||
core *Core,
|
||||
control CoreControl,
|
||||
) {
|
||||
core = &Core {
|
||||
handleSizeChange: handleSizeChange,
|
||||
handleConfigChange: handleConfigChange,
|
||||
handleThemeChange: handleThemeChange,
|
||||
c: c,
|
||||
drawSizeChange: drawSizeChange,
|
||||
}
|
||||
control = CoreControl { core: core }
|
||||
return
|
||||
@ -88,8 +72,8 @@ func (core *Core) MinimumSize () (width, height int) {
|
||||
// overridden.
|
||||
func (core *Core) DrawTo (canvas canvas.Canvas) {
|
||||
core.canvas = canvas
|
||||
if core.handleSizeChange != nil {
|
||||
core.handleSizeChange()
|
||||
if core.drawSizeChange != nil {
|
||||
core.drawSizeChange()
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,24 +89,6 @@ func (core *Core) OnMinimumSizeChange (callback func ()) {
|
||||
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
|
||||
// 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
|
||||
@ -188,49 +154,3 @@ func (control CoreControl) ConstrainSize (
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user