forked from tslocum/cview
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
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 |
|
}) |
|
}
|
|
|