asuhfdjkshlk
This commit is contained in:
parent
3998d842b1
commit
6936353516
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
})
|
})
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user