Migrated some more elements
This commit is contained in:
@@ -1,177 +0,0 @@
|
||||
package elements
|
||||
|
||||
import "golang.org/x/image/math/fixed"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||
|
||||
// Label is a simple text box.
|
||||
type Label struct {
|
||||
entity tomo.FlexibleEntity
|
||||
|
||||
align textdraw.Align
|
||||
wrap bool
|
||||
text string
|
||||
drawer textdraw.Drawer
|
||||
|
||||
forcedColumns int
|
||||
forcedRows int
|
||||
minHeight int
|
||||
|
||||
config config.Wrapped
|
||||
theme theme.Wrapped
|
||||
}
|
||||
|
||||
// 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.theme.Case = tomo.C("tomo", "label")
|
||||
element.drawer.SetFace (element.theme.FontFace (
|
||||
tomo.FontStyleRegular,
|
||||
tomo.FontSizeNormal))
|
||||
element.SetWrap(wrap)
|
||||
element.SetText(text)
|
||||
return
|
||||
}
|
||||
|
||||
// Bind binds this element to an entity.
|
||||
func (element *Label) Bind (entity tomo.Entity) {
|
||||
element.entity = entity.(tomo.FlexibleEntity)
|
||||
if element.entity == nil { return }
|
||||
element.updateMinimumSize()
|
||||
}
|
||||
|
||||
// EmCollapse forces a minimum width and height upon the label. The width is
|
||||
// measured in emspaces, and the height is measured in lines. If a zero value is
|
||||
// given for a dimension, its minimum will be determined by the label's content.
|
||||
// If the label's content is greater than these dimensions, it will be truncated
|
||||
// to fit.
|
||||
func (element *Label) EmCollapse (columns int, rows int) {
|
||||
element.forcedColumns = columns
|
||||
element.forcedRows = rows
|
||||
if element.entity == nil { return }
|
||||
element.updateMinimumSize()
|
||||
}
|
||||
|
||||
// FlexibleHeightFor returns the reccomended height for this element based on
|
||||
// the given width in order to allow the text to wrap properly.
|
||||
func (element *Label) FlexibleHeightFor (width int) (height int) {
|
||||
if element.wrap {
|
||||
return element.drawer.ReccomendedHeightFor(width)
|
||||
} else {
|
||||
return element.minHeight
|
||||
}
|
||||
}
|
||||
|
||||
// SetText sets the label's text.
|
||||
func (element *Label) SetText (text string) {
|
||||
if element.text == text { return }
|
||||
|
||||
element.text = text
|
||||
element.drawer.SetText([]rune(text))
|
||||
if element.entity == nil { return }
|
||||
element.updateMinimumSize()
|
||||
element.entity.Invalidate()
|
||||
}
|
||||
|
||||
// SetWrap sets wether or not the label's text wraps. If the text is set to
|
||||
// wrap, the element will have a minimum size of a single character and
|
||||
// automatically wrap its text. If the text is set to not wrap, the element will
|
||||
// have a minimum size that fits its text.
|
||||
func (element *Label) SetWrap (wrap bool) {
|
||||
if wrap == element.wrap { return }
|
||||
if !wrap {
|
||||
element.drawer.SetMaxWidth(0)
|
||||
element.drawer.SetMaxHeight(0)
|
||||
}
|
||||
element.wrap = wrap
|
||||
if element.entity == nil { return }
|
||||
element.updateMinimumSize()
|
||||
element.entity.Invalidate()
|
||||
}
|
||||
|
||||
// SetAlign sets the alignment method of the label.
|
||||
func (element *Label) SetAlign (align textdraw.Align) {
|
||||
if align == element.align { return }
|
||||
element.align = align
|
||||
element.drawer.SetAlign(align)
|
||||
if element.entity == nil { return }
|
||||
element.updateMinimumSize()
|
||||
element.entity.Invalidate()
|
||||
}
|
||||
|
||||
// SetTheme sets the element's theme.
|
||||
func (element *Label) SetTheme (new tomo.Theme) {
|
||||
if new == element.theme.Theme { return }
|
||||
element.theme.Theme = new
|
||||
element.drawer.SetFace (element.theme.FontFace (
|
||||
tomo.FontStyleRegular,
|
||||
tomo.FontSizeNormal))
|
||||
if element.entity == nil { return }
|
||||
element.updateMinimumSize()
|
||||
element.entity.Invalidate()
|
||||
}
|
||||
|
||||
// SetConfig sets the element's configuration.
|
||||
func (element *Label) SetConfig (new tomo.Config) {
|
||||
if new == element.config.Config { return }
|
||||
element.config.Config = new
|
||||
if element.entity == nil { return }
|
||||
element.updateMinimumSize()
|
||||
element.entity.Invalidate()
|
||||
}
|
||||
|
||||
// Draw causes the element to draw to the specified destination canvas.
|
||||
func (element *Label) Draw (destination canvas.Canvas) {
|
||||
if element.entity == nil { return }
|
||||
|
||||
bounds := element.entity. Bounds()
|
||||
|
||||
if element.wrap {
|
||||
element.drawer.SetMaxWidth(bounds.Dx())
|
||||
element.drawer.SetMaxHeight(bounds.Dy())
|
||||
}
|
||||
|
||||
element.entity.DrawBackground(destination, bounds)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
foreground := element.theme.Color (
|
||||
tomo.ColorForeground,
|
||||
tomo.State { })
|
||||
element.drawer.Draw(destination, foreground, bounds.Min.Sub(textBounds.Min))
|
||||
}
|
||||
|
||||
func (element *Label) updateMinimumSize () {
|
||||
var width, height int
|
||||
|
||||
if element.wrap {
|
||||
em := element.drawer.Em().Round()
|
||||
if em < 1 {
|
||||
em = element.theme.Padding(tomo.PatternBackground)[0]
|
||||
}
|
||||
width, height = em, element.drawer.LineHeight().Round()
|
||||
// FIXME we shoudl not have to pass in the element here
|
||||
element.entity.NotifyFlexibleHeightChange(element)
|
||||
} else {
|
||||
bounds := element.drawer.LayoutBounds()
|
||||
width, height = bounds.Dx(), bounds.Dy()
|
||||
}
|
||||
|
||||
if element.forcedColumns > 0 {
|
||||
width =
|
||||
element.drawer.Em().
|
||||
Mul(fixed.I(element.forcedColumns)).Floor()
|
||||
}
|
||||
|
||||
if element.forcedRows > 0 {
|
||||
height =
|
||||
element.drawer.LineHeight().
|
||||
Mul(fixed.I(element.forcedRows)).Floor()
|
||||
}
|
||||
|
||||
element.minHeight = height
|
||||
element.entity.SetMinimumSize(width, height)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package elements
|
||||
|
||||
// Numeric is a type constraint representing a number.
|
||||
type Numeric interface {
|
||||
~float32 | ~float64 |
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||
}
|
||||
|
||||
// LerpSlider is a slider that has a minimum and maximum value, and who's value
|
||||
// can be any numeric type.
|
||||
type LerpSlider[T Numeric] struct {
|
||||
*Slider
|
||||
min T
|
||||
max T
|
||||
}
|
||||
|
||||
// NewLerpSlider creates a new LerpSlider with a minimum and maximum value. If
|
||||
// vertical is set to true, the slider will be vertical instead of horizontal.
|
||||
func NewLerpSlider[T Numeric] (min, max T, value T, vertical bool) (element *LerpSlider[T]) {
|
||||
if min > max {
|
||||
temp := max
|
||||
max = min
|
||||
min = temp
|
||||
}
|
||||
element = &LerpSlider[T] {
|
||||
Slider: NewSlider(0, vertical),
|
||||
min: min,
|
||||
max: max,
|
||||
}
|
||||
element.SetValue(value)
|
||||
return
|
||||
}
|
||||
|
||||
// SetValue sets the slider's value.
|
||||
func (element *LerpSlider[T]) SetValue (value T) {
|
||||
value -= element.min
|
||||
element.Slider.SetValue(float64(value) / float64(element.Range()))
|
||||
}
|
||||
|
||||
// Value returns the slider's value.
|
||||
func (element *LerpSlider[T]) Value () (value T) {
|
||||
return T (
|
||||
float64(element.Slider.Value()) * float64(element.Range())) +
|
||||
element.min
|
||||
}
|
||||
|
||||
// Range returns the difference between the slider's maximum and minimum values.
|
||||
func (element *LerpSlider[T]) Range () T {
|
||||
return element.max - element.min
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package elements
|
||||
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||
|
||||
// ProgressBar displays a visual indication of how far along a task is.
|
||||
type ProgressBar struct {
|
||||
progress float64
|
||||
|
||||
config config.Wrapped
|
||||
theme theme.Wrapped
|
||||
}
|
||||
|
||||
// NewProgressBar creates a new progress bar displaying the given progress
|
||||
// level.
|
||||
func NewProgressBar (progress float64) (element *ProgressBar) {
|
||||
element = &ProgressBar { progress: progress }
|
||||
element.theme.Case = tomo.C("tomo", "progressBar")
|
||||
element.Core, element.core = core.NewCore(element, element.draw)
|
||||
element.updateMinimumSize()
|
||||
return
|
||||
}
|
||||
|
||||
// SetProgress sets the progress level of the bar.
|
||||
func (element *ProgressBar) SetProgress (progress float64) {
|
||||
if progress == element.progress { return }
|
||||
element.progress = progress
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
// SetTheme sets the element's theme.
|
||||
func (element *ProgressBar) SetTheme (new tomo.Theme) {
|
||||
if new == element.theme.Theme { return }
|
||||
element.theme.Theme = new
|
||||
element.updateMinimumSize()
|
||||
element.redo()
|
||||
}
|
||||
|
||||
// SetConfig sets the element's configuration.
|
||||
func (element *ProgressBar) SetConfig (new tomo.Config) {
|
||||
if new == nil || new == element.config.Config { return }
|
||||
element.config.Config = new
|
||||
element.updateMinimumSize()
|
||||
element.redo()
|
||||
}
|
||||
|
||||
func (element *ProgressBar) updateMinimumSize() {
|
||||
padding := element.theme.Padding(tomo.PatternSunken)
|
||||
innerPadding := element.theme.Padding(tomo.PatternMercury)
|
||||
element.core.SetMinimumSize (
|
||||
padding.Horizontal() + innerPadding.Horizontal(),
|
||||
padding.Vertical() + innerPadding.Vertical())
|
||||
}
|
||||
|
||||
func (element *ProgressBar) redo () {
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ProgressBar) draw () {
|
||||
bounds := element.Bounds()
|
||||
|
||||
pattern := element.theme.Pattern(tomo.PatternSunken, tomo.State { })
|
||||
padding := element.theme.Padding(tomo.PatternSunken)
|
||||
pattern.Draw(element.core, bounds)
|
||||
bounds = padding.Apply(bounds)
|
||||
meterBounds := image.Rect (
|
||||
bounds.Min.X, bounds.Min.Y,
|
||||
bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
|
||||
bounds.Max.Y)
|
||||
mercury := element.theme.Pattern(tomo.PatternMercury, tomo.State { })
|
||||
mercury.Draw(element.core, meterBounds)
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
package elements
|
||||
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||
|
||||
// ScrollBar is an element similar to Slider, but it has special behavior that
|
||||
// makes it well suited for controlling the viewport position on one axis of a
|
||||
// scrollable element. Instead of having a value from zero to one, it stores
|
||||
// viewport and content boundaries. When the user drags the scroll bar handle,
|
||||
// the scroll bar calls the OnScroll callback assigned to it with the position
|
||||
// the user is trying to move the handle to. A program can check to see if this
|
||||
// value is valid, move the viewport, and give the scroll bar the new viewport
|
||||
// bounds (which will then cause it to move the handle).
|
||||
//
|
||||
// Typically, you wont't want to use a ScrollBar by itself. A ScrollContainer is
|
||||
// better for most cases.
|
||||
type ScrollBar struct {
|
||||
vertical bool
|
||||
enabled bool
|
||||
dragging bool
|
||||
dragOffset image.Point
|
||||
track image.Rectangle
|
||||
bar image.Rectangle
|
||||
|
||||
contentBounds image.Rectangle
|
||||
viewportBounds image.Rectangle
|
||||
|
||||
config config.Wrapped
|
||||
theme theme.Wrapped
|
||||
|
||||
onScroll func (viewport image.Point)
|
||||
}
|
||||
|
||||
// NewScrollBar creates a new scroll bar. If vertical is set to true, the scroll
|
||||
// bar will be vertical instead of horizontal.
|
||||
func NewScrollBar (vertical bool) (element *ScrollBar) {
|
||||
element = &ScrollBar {
|
||||
vertical: vertical,
|
||||
enabled: true,
|
||||
}
|
||||
if vertical {
|
||||
element.theme.Case = tomo.C("tomo", "scrollBarHorizontal")
|
||||
} else {
|
||||
element.theme.Case = tomo.C("tomo", "scrollBarVertical")
|
||||
}
|
||||
element.Core, element.core = core.NewCore(element, element.handleResize)
|
||||
element.updateMinimumSize()
|
||||
return
|
||||
}
|
||||
|
||||
func (element *ScrollBar) handleResize () {
|
||||
if element.core.HasImage() {
|
||||
element.recalculate()
|
||||
element.draw()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) HandleMouseDown (x, y int, button input.Button) {
|
||||
velocity := element.config.ScrollVelocity()
|
||||
point := image.Pt(x, y)
|
||||
|
||||
if point.In(element.bar) {
|
||||
// the mouse is pressed down within the bar's handle
|
||||
element.dragging = true
|
||||
element.drawAndPush()
|
||||
element.dragOffset =
|
||||
point.Sub(element.bar.Min).
|
||||
Add(element.Bounds().Min)
|
||||
element.dragTo(point)
|
||||
} else {
|
||||
// the mouse is pressed down within the bar's gutter
|
||||
switch button {
|
||||
case input.ButtonLeft:
|
||||
// start scrolling at this point, but set the offset to
|
||||
// the middle of the handle
|
||||
element.dragging = true
|
||||
element.dragOffset = element.fallbackDragOffset()
|
||||
element.dragTo(point)
|
||||
|
||||
case input.ButtonMiddle:
|
||||
// page up/down on middle click
|
||||
viewport := 0
|
||||
if element.vertical {
|
||||
viewport = element.viewportBounds.Dy()
|
||||
} else {
|
||||
viewport = element.viewportBounds.Dx()
|
||||
}
|
||||
if element.isAfterHandle(point) {
|
||||
element.scrollBy(viewport)
|
||||
} else {
|
||||
element.scrollBy(-viewport)
|
||||
}
|
||||
|
||||
case input.ButtonRight:
|
||||
// inch up/down on right click
|
||||
if element.isAfterHandle(point) {
|
||||
element.scrollBy(velocity)
|
||||
} else {
|
||||
element.scrollBy(-velocity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) HandleMouseUp (x, y int, button input.Button) {
|
||||
if element.dragging {
|
||||
element.dragging = false
|
||||
element.drawAndPush()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) HandleMotion (x, y int) {
|
||||
if element.dragging {
|
||||
element.dragTo(image.Pt(x, y))
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) HandleScroll (x, y int, deltaX, deltaY float64) {
|
||||
if element.vertical {
|
||||
element.scrollBy(int(deltaY))
|
||||
} else {
|
||||
element.scrollBy(int(deltaX))
|
||||
}
|
||||
}
|
||||
|
||||
// SetEnabled sets whether or not the scroll bar can be interacted with.
|
||||
func (element *ScrollBar) SetEnabled (enabled bool) {
|
||||
if element.enabled == enabled { return }
|
||||
element.enabled = enabled
|
||||
element.drawAndPush()
|
||||
}
|
||||
|
||||
// Enabled returns whether or not the element is enabled.
|
||||
func (element *ScrollBar) Enabled () (enabled bool) {
|
||||
return element.enabled
|
||||
}
|
||||
|
||||
// SetBounds sets the content and viewport bounds of the scroll bar.
|
||||
func (element *ScrollBar) SetBounds (content, viewport image.Rectangle) {
|
||||
element.contentBounds = content
|
||||
element.viewportBounds = viewport
|
||||
element.recalculate()
|
||||
element.drawAndPush()
|
||||
}
|
||||
|
||||
// OnScroll sets a function to be called when the user tries to move the scroll
|
||||
// bar's handle. The callback is passed a point representing the new viewport
|
||||
// position. For the scroll bar's position to visually update, the callback must
|
||||
// check if the position is valid and call ScrollBar.SetBounds with the new
|
||||
// viewport bounds.
|
||||
func (element *ScrollBar) OnScroll (callback func (viewport image.Point)) {
|
||||
element.onScroll = callback
|
||||
}
|
||||
|
||||
// SetTheme sets the element's theme.
|
||||
func (element *ScrollBar) SetTheme (new tomo.Theme) {
|
||||
if new == element.theme.Theme { return }
|
||||
element.theme.Theme = new
|
||||
element.drawAndPush()
|
||||
}
|
||||
|
||||
// SetConfig sets the element's configuration.
|
||||
func (element *ScrollBar) SetConfig (new tomo.Config) {
|
||||
if new == element.config.Config { return }
|
||||
element.config.Config = new
|
||||
element.updateMinimumSize()
|
||||
element.drawAndPush()
|
||||
}
|
||||
|
||||
func (element *ScrollBar) isAfterHandle (point image.Point) bool {
|
||||
if element.vertical {
|
||||
return point.Y > element.bar.Min.Y
|
||||
} else {
|
||||
return point.X > element.bar.Min.X
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) fallbackDragOffset () image.Point {
|
||||
if element.vertical {
|
||||
return element.Bounds().Min.
|
||||
Add(image.Pt(0, element.bar.Dy() / 2))
|
||||
} else {
|
||||
return element.Bounds().Min.
|
||||
Add(image.Pt(element.bar.Dx() / 2, 0))
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) scrollBy (delta int) {
|
||||
deltaPoint := image.Point { }
|
||||
if element.vertical {
|
||||
deltaPoint.Y = delta
|
||||
} else {
|
||||
deltaPoint.X = delta
|
||||
}
|
||||
if element.onScroll != nil {
|
||||
element.onScroll(element.viewportBounds.Min.Add(deltaPoint))
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) dragTo (point image.Point) {
|
||||
point = point.Sub(element.dragOffset)
|
||||
var scrollX, scrollY float64
|
||||
|
||||
if element.vertical {
|
||||
ratio :=
|
||||
float64(element.contentBounds.Dy()) /
|
||||
float64(element.track.Dy())
|
||||
scrollX = float64(element.viewportBounds.Min.X)
|
||||
scrollY = float64(point.Y) * ratio
|
||||
} else {
|
||||
ratio :=
|
||||
float64(element.contentBounds.Dx()) /
|
||||
float64(element.track.Dx())
|
||||
scrollX = float64(point.X) * ratio
|
||||
scrollY = float64(element.viewportBounds.Min.Y)
|
||||
}
|
||||
|
||||
if element.onScroll != nil {
|
||||
element.onScroll(image.Pt(int(scrollX), int(scrollY)))
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) recalculate () {
|
||||
if element.vertical {
|
||||
element.recalculateVertical()
|
||||
} else {
|
||||
element.recalculateHorizontal()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) recalculateVertical () {
|
||||
bounds := element.Bounds()
|
||||
padding := element.theme.Padding(tomo.PatternGutter)
|
||||
element.track = padding.Apply(bounds)
|
||||
|
||||
contentBounds := element.contentBounds
|
||||
viewportBounds := element.viewportBounds
|
||||
if element.Enabled() {
|
||||
element.bar.Min.X = element.track.Min.X
|
||||
element.bar.Max.X = element.track.Max.X
|
||||
|
||||
ratio :=
|
||||
float64(element.track.Dy()) /
|
||||
float64(contentBounds.Dy())
|
||||
element.bar.Min.Y = int(float64(viewportBounds.Min.Y) * ratio)
|
||||
element.bar.Max.Y = int(float64(viewportBounds.Max.Y) * ratio)
|
||||
|
||||
element.bar.Min.Y += element.track.Min.Y
|
||||
element.bar.Max.Y += element.track.Min.Y
|
||||
}
|
||||
|
||||
// if the handle is out of bounds, don't display it
|
||||
if element.bar.Dy() >= element.track.Dy() {
|
||||
element.bar = image.Rectangle { }
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) recalculateHorizontal () {
|
||||
bounds := element.Bounds()
|
||||
padding := element.theme.Padding(tomo.PatternGutter)
|
||||
element.track = padding.Apply(bounds)
|
||||
|
||||
contentBounds := element.contentBounds
|
||||
viewportBounds := element.viewportBounds
|
||||
if element.Enabled() {
|
||||
element.bar.Min.Y = element.track.Min.Y
|
||||
element.bar.Max.Y = element.track.Max.Y
|
||||
|
||||
ratio :=
|
||||
float64(element.track.Dx()) /
|
||||
float64(contentBounds.Dx())
|
||||
element.bar.Min.X = int(float64(viewportBounds.Min.X) * ratio)
|
||||
element.bar.Max.X = int(float64(viewportBounds.Max.X) * ratio)
|
||||
|
||||
element.bar.Min.X += element.track.Min.X
|
||||
element.bar.Max.X += element.track.Min.X
|
||||
}
|
||||
|
||||
// if the handle is out of bounds, don't display it
|
||||
if element.bar.Dx() >= element.track.Dx() {
|
||||
element.bar = image.Rectangle { }
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) updateMinimumSize () {
|
||||
gutterPadding := element.theme.Padding(tomo.PatternGutter)
|
||||
handlePadding := element.theme.Padding(tomo.PatternHandle)
|
||||
if element.vertical {
|
||||
element.core.SetMinimumSize (
|
||||
gutterPadding.Horizontal() + handlePadding.Horizontal(),
|
||||
gutterPadding.Vertical() + handlePadding.Vertical() * 2)
|
||||
} else {
|
||||
element.core.SetMinimumSize (
|
||||
gutterPadding.Horizontal() + handlePadding.Horizontal() * 2,
|
||||
gutterPadding.Vertical() + handlePadding.Vertical())
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) drawAndPush () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *ScrollBar) draw () {
|
||||
bounds := element.Bounds()
|
||||
state := tomo.State {
|
||||
Disabled: !element.Enabled(),
|
||||
Pressed: element.dragging,
|
||||
}
|
||||
element.theme.Pattern(tomo.PatternGutter, state).Draw (
|
||||
element.core,
|
||||
bounds)
|
||||
element.theme.Pattern(tomo.PatternHandle, state).Draw (
|
||||
element.core,
|
||||
element.bar)
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
package elements
|
||||
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||
|
||||
// Slider is a slider control with a floating point value between zero and one.
|
||||
type Slider struct {
|
||||
value float64
|
||||
vertical bool
|
||||
dragging bool
|
||||
dragOffset int
|
||||
track image.Rectangle
|
||||
bar image.Rectangle
|
||||
|
||||
config config.Wrapped
|
||||
theme theme.Wrapped
|
||||
|
||||
onSlide func ()
|
||||
onRelease func ()
|
||||
}
|
||||
|
||||
// NewSlider creates a new slider with the specified value. If vertical is set
|
||||
// to true,
|
||||
func NewSlider (value float64, vertical bool) (element *Slider) {
|
||||
element = &Slider {
|
||||
value: value,
|
||||
vertical: vertical,
|
||||
}
|
||||
if vertical {
|
||||
element.theme.Case = tomo.C("tomo", "sliderVertical")
|
||||
} else {
|
||||
element.theme.Case = tomo.C("tomo", "sliderHorizontal")
|
||||
}
|
||||
element.Core, element.core = core.NewCore(element, element.draw)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore(element.core, element.redo)
|
||||
element.updateMinimumSize()
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Slider) HandleMouseDown (x, y int, button input.Button) {
|
||||
if !element.Enabled() { return }
|
||||
element.Focus()
|
||||
if button == input.ButtonLeft {
|
||||
element.dragging = true
|
||||
element.value = element.valueFor(x, y)
|
||||
if element.onSlide != nil {
|
||||
element.onSlide()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Slider) HandleMouseUp (x, y int, button input.Button) {
|
||||
if button != input.ButtonLeft || !element.dragging { return }
|
||||
element.dragging = false
|
||||
if element.onRelease != nil {
|
||||
element.onRelease()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
|
||||
func (element *Slider) HandleMotion (x, y int) {
|
||||
if element.dragging {
|
||||
element.dragging = true
|
||||
element.value = element.valueFor(x, y)
|
||||
if element.onSlide != nil {
|
||||
element.onSlide()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Slider) HandleScroll (x, y int, deltaX, deltaY float64) { }
|
||||
|
||||
func (element *Slider) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
|
||||
switch key {
|
||||
case input.KeyUp:
|
||||
element.changeValue(0.1)
|
||||
case input.KeyDown:
|
||||
element.changeValue(-0.1)
|
||||
case input.KeyRight:
|
||||
if element.vertical {
|
||||
element.changeValue(-0.1)
|
||||
} else {
|
||||
element.changeValue(0.1)
|
||||
}
|
||||
case input.KeyLeft:
|
||||
if element.vertical {
|
||||
element.changeValue(0.1)
|
||||
} else {
|
||||
element.changeValue(-0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Slider) HandleKeyUp (key input.Key, modifiers input.Modifiers) { }
|
||||
|
||||
// Value returns the slider's value.
|
||||
func (element *Slider) Value () (value float64) {
|
||||
return element.value
|
||||
}
|
||||
|
||||
// SetEnabled sets whether or not the slider can be interacted with.
|
||||
func (element *Slider) SetEnabled (enabled bool) {
|
||||
element.focusableControl.SetEnabled(enabled)
|
||||
}
|
||||
|
||||
// SetValue sets the slider's value.
|
||||
func (element *Slider) SetValue (value float64) {
|
||||
if value < 0 { value = 0 }
|
||||
if value > 1 { value = 1 }
|
||||
|
||||
if element.value == value { return }
|
||||
|
||||
element.value = value
|
||||
if element.onRelease != nil {
|
||||
element.onRelease()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
|
||||
// OnSlide sets a function to be called every time the slider handle changes
|
||||
// position while being dragged.
|
||||
func (element *Slider) OnSlide (callback func ()) {
|
||||
element.onSlide = callback
|
||||
}
|
||||
|
||||
// OnRelease sets a function to be called when the handle stops being dragged.
|
||||
func (element *Slider) OnRelease (callback func ()) {
|
||||
element.onRelease = callback
|
||||
}
|
||||
|
||||
// SetTheme sets the element's theme.
|
||||
func (element *Slider) SetTheme (new tomo.Theme) {
|
||||
if new == element.theme.Theme { return }
|
||||
element.theme.Theme = new
|
||||
element.redo()
|
||||
}
|
||||
|
||||
// SetConfig sets the element's configuration.
|
||||
func (element *Slider) SetConfig (new tomo.Config) {
|
||||
if new == element.config.Config { return }
|
||||
element.config.Config = new
|
||||
element.updateMinimumSize()
|
||||
element.redo()
|
||||
}
|
||||
|
||||
func (element *Slider) changeValue (delta float64) {
|
||||
element.value += delta
|
||||
if element.value < 0 {
|
||||
element.value = 0
|
||||
}
|
||||
if element.value > 1 {
|
||||
element.value = 1
|
||||
}
|
||||
if element.onRelease != nil {
|
||||
element.onRelease()
|
||||
}
|
||||
element.redo()
|
||||
}
|
||||
|
||||
func (element *Slider) valueFor (x, y int) (value float64) {
|
||||
if element.vertical {
|
||||
value =
|
||||
float64(y - element.track.Min.Y - element.bar.Dy() / 2) /
|
||||
float64(element.track.Dy() - element.bar.Dy())
|
||||
value = 1 - value
|
||||
} else {
|
||||
value =
|
||||
float64(x - element.track.Min.X - element.bar.Dx() / 2) /
|
||||
float64(element.track.Dx() - element.bar.Dx())
|
||||
}
|
||||
|
||||
if value < 0 { value = 0 }
|
||||
if value > 1 { value = 1 }
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Slider) updateMinimumSize () {
|
||||
gutterPadding := element.theme.Padding(tomo.PatternGutter)
|
||||
handlePadding := element.theme.Padding(tomo.PatternHandle)
|
||||
if element.vertical {
|
||||
element.core.SetMinimumSize (
|
||||
gutterPadding.Horizontal() + handlePadding.Horizontal(),
|
||||
gutterPadding.Vertical() + handlePadding.Vertical() * 2)
|
||||
} else {
|
||||
element.core.SetMinimumSize (
|
||||
gutterPadding.Horizontal() + handlePadding.Horizontal() * 2,
|
||||
gutterPadding.Vertical() + handlePadding.Vertical())
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Slider) redo () {
|
||||
if element.core.HasImage () {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Slider) draw () {
|
||||
bounds := element.Bounds()
|
||||
element.track = element.theme.Padding(tomo.PatternGutter).Apply(bounds)
|
||||
if element.vertical {
|
||||
barSize := element.track.Dx()
|
||||
element.bar = image.Rect(0, 0, barSize, barSize).Add(bounds.Min)
|
||||
barOffset :=
|
||||
float64(element.track.Dy() - barSize) *
|
||||
(1 - element.value)
|
||||
element.bar = element.bar.Add(image.Pt(0, int(barOffset)))
|
||||
} else {
|
||||
barSize := element.track.Dy()
|
||||
element.bar = image.Rect(0, 0, barSize, barSize).Add(bounds.Min)
|
||||
barOffset :=
|
||||
float64(element.track.Dx() - barSize) *
|
||||
element.value
|
||||
element.bar = element.bar.Add(image.Pt(int(barOffset), 0))
|
||||
}
|
||||
|
||||
state := tomo.State {
|
||||
Focused: element.Focused(),
|
||||
Disabled: !element.Enabled(),
|
||||
Pressed: element.dragging,
|
||||
}
|
||||
element.theme.Pattern(tomo.PatternGutter, state).Draw (
|
||||
element.core,
|
||||
bounds)
|
||||
element.theme.Pattern(tomo.PatternHandle, state).Draw (
|
||||
element.core,
|
||||
element.bar)
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package elements
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/tomo"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||
|
||||
// Spacer can be used to put space between two elements..
|
||||
type Spacer struct {
|
||||
line bool
|
||||
|
||||
config config.Wrapped
|
||||
theme theme.Wrapped
|
||||
}
|
||||
|
||||
// NewSpacer creates a new spacer. If line is set to true, the spacer will be
|
||||
// filled with a line color, and if compressed to its minimum width or height,
|
||||
// will appear as a line.
|
||||
func NewSpacer (line bool) (element *Spacer) {
|
||||
element = &Spacer { line: line }
|
||||
element.theme.Case = tomo.C("tomo", "spacer")
|
||||
element.Core, element.core = core.NewCore(element, element.draw)
|
||||
element.updateMinimumSize()
|
||||
return
|
||||
}
|
||||
|
||||
/// SetLine sets whether or not the spacer will appear as a colored line.
|
||||
func (element *Spacer) SetLine (line bool) {
|
||||
if element.line == line { return }
|
||||
element.line = line
|
||||
element.updateMinimumSize()
|
||||
if element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
// SetTheme sets the element's theme.
|
||||
func (element *Spacer) SetTheme (new tomo.Theme) {
|
||||
if new == element.theme.Theme { return }
|
||||
element.theme.Theme = new
|
||||
element.redo()
|
||||
}
|
||||
|
||||
// SetConfig sets the element's configuration.
|
||||
func (element *Spacer) SetConfig (new tomo.Config) {
|
||||
if new == element.config.Config { return }
|
||||
element.config.Config = new
|
||||
element.redo()
|
||||
}
|
||||
|
||||
func (element *Spacer) updateMinimumSize () {
|
||||
if element.line {
|
||||
padding := element.theme.Padding(tomo.PatternLine)
|
||||
element.core.SetMinimumSize (
|
||||
padding.Horizontal(),
|
||||
padding.Vertical())
|
||||
} else {
|
||||
element.core.SetMinimumSize(1, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Spacer) redo () {
|
||||
if !element.core.HasImage() {
|
||||
element.draw()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Spacer) draw () {
|
||||
bounds := element.Bounds()
|
||||
|
||||
if element.line {
|
||||
pattern := element.theme.Pattern (
|
||||
tomo.PatternLine,
|
||||
tomo.State { })
|
||||
pattern.Draw(element.core, bounds)
|
||||
} else {
|
||||
pattern := element.theme.Pattern (
|
||||
tomo.PatternBackground,
|
||||
tomo.State { })
|
||||
pattern.Draw(element.core, bounds)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user