Terminal-based user interface toolkit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

362 lines
8.7 KiB

package cview
import (
"math"
"sync"
"github.com/gdamore/tcell/v2"
)
// Slider is a progress bar which may be modified via keyboard and mouse.
type Slider struct {
*ProgressBar
// The text to be displayed before the slider.
label []byte
// The screen width of the label area. A value of 0 means use the width of
// the label text.
labelWidth int
// The label color.
labelColor tcell.Color
// The label color when focused.
labelColorFocused tcell.Color
// The background color of the input area.
fieldBackgroundColor tcell.Color
// The background color of the input area when focused.
fieldBackgroundColorFocused tcell.Color
// The text color of the input area.
fieldTextColor tcell.Color
// The text color of the input area when focused.
fieldTextColorFocused tcell.Color
// The amount to increment by when modified via keyboard.
increment int
// Set to true when mouse dragging is in progress.
dragging bool
// An optional function which is called when the user changes the value of
// this slider.
changed func(value int)
// An optional function which is called when the user indicated that they
// are done entering text. The key which was pressed is provided (tab,
// shift-tab, or escape).
done func(tcell.Key)
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
sync.RWMutex
}
// NewSlider returns a new slider.
func NewSlider() *Slider {
s := &Slider{
ProgressBar: NewProgressBar(),
increment: 10,
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.MoreContrastBackgroundColor,
fieldBackgroundColorFocused: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
labelColorFocused: ColorUnset,
fieldTextColorFocused: ColorUnset,
}
return s
}
// SetLabel sets the text to be displayed before the input area.
func (s *Slider) SetLabel(label string) {
s.Lock()
defer s.Unlock()
s.label = []byte(label)
}
// GetLabel returns the text to be displayed before the input area.
func (s *Slider) GetLabel() string {
s.RLock()
defer s.RUnlock()
return string(s.label)
}
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
// primitive to use the width of the label string.
func (s *Slider) SetLabelWidth(width int) {
s.Lock()
defer s.Unlock()
s.labelWidth = width
}
// SetLabelColor sets the color of the label.
func (s *Slider) SetLabelColor(color tcell.Color) {
s.Lock()
defer s.Unlock()
s.labelColor = color
}
// SetLabelColorFocused sets the color of the label when focused.
func (s *Slider) SetLabelColorFocused(color tcell.Color) {
s.Lock()
defer s.Unlock()
s.labelColorFocused = color
}
// SetFieldBackgroundColor sets the background color of the input area.
func (s *Slider) SetFieldBackgroundColor(color tcell.Color) {
s.Lock()
defer s.Unlock()
s.fieldBackgroundColor = color
}
// SetFieldBackgroundColorFocused sets the background color of the input area when focused.
func (s *Slider) SetFieldBackgroundColorFocused(color tcell.Color) {
s.Lock()
defer s.Unlock()
s.fieldBackgroundColorFocused = color
}
// SetFieldTextColor sets the text color of the input area.
func (s *Slider) SetFieldTextColor(color tcell.Color) {
s.Lock()
defer s.Unlock()
s.fieldTextColor = color
}
// SetFieldTextColorFocused sets the text color of the input area when focused.
func (s *Slider) SetFieldTextColorFocused(color tcell.Color) {
s.Lock()
defer s.Unlock()
s.fieldTextColorFocused = color
}
// GetFieldHeight returns the height of the field.
func (s *Slider) GetFieldHeight() int {
return 1
}
// GetFieldWidth returns this primitive's field width.
func (s *Slider) GetFieldWidth() int {
return 0
}
// SetIncrement sets the amount the slider is incremented by when modified via
// keyboard.
func (s *Slider) SetIncrement(increment int) {
s.Lock()
defer s.Unlock()
s.increment = increment
}
// SetChangedFunc sets a handler which is called when the value of this slider
// was changed by the user. The handler function receives the new value.
func (s *Slider) SetChangedFunc(handler func(value int)) {
s.Lock()
defer s.Unlock()
s.changed = handler
}
// SetDoneFunc sets a handler which is called when the user is done using the
// slider. The callback function is provided with the key that was pressed,
// which is one of the following:
//
// - KeyEscape: Abort text input.
// - KeyTab: Move to the next field.
// - KeyBacktab: Move to the previous field.
func (s *Slider) SetDoneFunc(handler func(key tcell.Key)) {
s.Lock()
defer s.Unlock()
s.done = handler
}
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
func (s *Slider) SetFinishedFunc(handler func(key tcell.Key)) {
s.Lock()
defer s.Unlock()
s.finished = handler
}
// Draw draws this primitive onto the screen.
func (s *Slider) Draw(screen tcell.Screen) {
if !s.GetVisible() {
return
}
s.Box.Draw(screen)
hasFocus := s.GetFocusable().HasFocus()
s.Lock()
// Select colors
labelColor := s.labelColor
fieldBackgroundColor := s.fieldBackgroundColor
fieldTextColor := s.fieldTextColor
if hasFocus {
if s.labelColorFocused != ColorUnset {
labelColor = s.labelColorFocused
}
if s.fieldBackgroundColorFocused != ColorUnset {
fieldBackgroundColor = s.fieldBackgroundColorFocused
}
if s.fieldTextColorFocused != ColorUnset {
fieldTextColor = s.fieldTextColorFocused
}
}
// Prepare.
x, y, width, height := s.GetInnerRect()
rightLimit := x + width
if height < 1 || rightLimit <= x {
s.Unlock()
return
}
// Draw label.
if len(s.label) > 0 {
if s.vertical {
height--
// TODO draw label on bottom
} else {
if s.labelWidth > 0 {
labelWidth := s.labelWidth
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
Print(screen, []byte(s.label), x, y, labelWidth, AlignLeft, labelColor)
x += labelWidth + 1
width -= labelWidth + 1
} else {
_, drawnWidth := Print(screen, []byte(s.label), x, y, rightLimit-x, AlignLeft, labelColor)
x += drawnWidth + 1
width -= drawnWidth + 1
}
}
}
// Draw slider.
s.Unlock()
s.ProgressBar.SetRect(x, y, width, height)
s.ProgressBar.SetEmptyColor(fieldBackgroundColor)
s.ProgressBar.SetFilledColor(fieldTextColor)
s.ProgressBar.Draw(screen)
}
// InputHandler returns the handler for this primitive.
func (s *Slider) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
return s.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
if HitShortcut(event, Keys.Cancel, Keys.MovePreviousField, Keys.MoveNextField) {
if s.done != nil {
s.done(event.Key())
}
if s.finished != nil {
s.finished(event.Key())
}
return
}
previous := s.progress
if HitShortcut(event, Keys.MoveFirst, Keys.MoveFirst2) {
s.SetProgress(0)
} else if HitShortcut(event, Keys.MoveLast, Keys.MoveLast2) {
s.SetProgress(s.max)
} else if HitShortcut(event, Keys.MoveUp, Keys.MoveUp2, Keys.MoveRight, Keys.MoveRight2, Keys.MovePreviousField) {
s.AddProgress(s.increment)
} else if HitShortcut(event, Keys.MoveDown, Keys.MoveDown2, Keys.MoveLeft, Keys.MoveLeft2, Keys.MoveNextField) {
s.AddProgress(s.increment * -1)
}
if s.progress != previous && s.changed != nil {
s.changed(s.progress)
}
})
}
// MouseHandler returns the mouse handler for this primitive.
func (s *Slider) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return s.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
x, y := event.Position()
if !s.InRect(x, y) {
s.dragging = false
return false, nil
}
// Process mouse event.
if action == MouseLeftClick {
setFocus(s)
consumed = true
}
handleMouse := func() {
if !s.ProgressBar.InRect(x, y) {
s.dragging = false
return
}
bx, by, bw, bh := s.GetInnerRect()
var clickPos, clickRange int
if s.ProgressBar.vertical {
clickPos = (bh - 1) - (y - by)
clickRange = bh - 1
} else {
clickPos = x - bx
clickRange = bw - 1
}
setValue := int(math.Floor(float64(s.max) * (float64(clickPos) / float64(clickRange))))
if setValue != s.progress {
s.SetProgress(setValue)
if s.changed != nil {
s.changed(s.progress)
}
}
}
// Handle dragging. Clicks are implicitly handled by this logic.
switch action {
case MouseLeftDown:
setFocus(s)
consumed = true
capture = s
s.dragging = true
handleMouse()
case MouseMove:
if s.dragging {
consumed = true
capture = s
handleMouse()
}
case MouseLeftUp:
if s.dragging {
consumed = true
s.dragging = false
handleMouse()
}
}
return
})
}