Add Slider

This commit is contained in:
Trevor Slocum 2020-10-11 13:39:04 -07:00
parent b47c35329c
commit 9c3564dc42
10 changed files with 437 additions and 9 deletions

View File

@ -1,5 +1,5 @@
v1.5.1 (WIP)
- Store TextView buffer as [][]byte instead of []string
- Add Slider
- Add TextView.SetBytes and TextView.GetBytes
- Add TableCell.SetBytes, TableCell.GetBytes and TableCell.GetText
- Fix List.Transform not calling handler set via SetChangedFunc

View File

@ -40,5 +40,5 @@ func Form(nextSlide func()) (title string, content cview.Primitive) {
f.AddButton("Cancel", nextSlide)
f.SetBorder(true)
f.SetTitle("Employee Information")
return "Forms", Code(f, 36, 15, form)
return "Form", Code(f, 36, 15, form)
}

View File

@ -10,7 +10,7 @@ const inputField = `[green]package[white] main
[green]import[white] (
[red]"strconv"[white]
[red]"github.com/gdamore/tcell"[white]
[red]"github.com/gdamore/tcell/v2"[white]
[red]"gitlab.com/tslocum/cview"[white]
)
@ -37,5 +37,5 @@ func InputField(nextSlide func()) (title string, content cview.Primitive) {
input.SetDoneFunc(func(key tcell.Key) {
nextSlide()
})
return "Input", Code(input, 30, 1, inputField)
return "InputField", Code(input, 30, 1, inputField)
}

View File

@ -54,6 +54,7 @@ func main() {
TextView1,
TextView2,
InputField,
Slider,
Form,
Table,
TreeView,

View File

@ -0,0 +1,45 @@
package main
import (
"fmt"
"github.com/gdamore/tcell/v2"
"gitlab.com/tslocum/cview"
)
const sliderCode = `[green]package[white] main
[green]import[white] (
[red]"fmt"[white]
[red]"github.com/gdamore/tcell/v2"[white]
[red]"gitlab.com/tslocum/cview"[white]
)
[green]func[white] [yellow]main[white]() {
slider := cview.[yellow]NewSlider[white]()
slider.[yellow]SetLabel[white]([red]"Volume: 0%"[white])
slider.[yellow][yellow]SetChangedFunc[white]([yellow]func[white](key tcell.Key) {
label := fmt.[yellow]Sprintf[white]("Volume: %3d%%", value)
slider.[yellow]SetLabel[white](label)
})
slider.[yellow][yellow]SetDoneFunc[white]([yellow]func[white](key tcell.Key) {
[yellow]nextSlide[white]()
})
app := cview.[yellow]NewApplication[white]()
app.[yellow]SetRoot[white](slider, true)
app.[yellow]Run[white]()
}`
// Slider demonstrates the Slider.
func Slider(nextSlide func()) (title string, content cview.Primitive) {
slider := cview.NewSlider()
slider.SetLabel("Volume: 0%")
slider.SetChangedFunc(func(value int) {
slider.SetLabel(fmt.Sprintf("Volume: %3d%%", value))
})
slider.SetDoneFunc(func(key tcell.Key) {
nextSlide()
})
return "Slider", Code(slider, 30, 1, sliderCode)
}

View File

@ -57,7 +57,7 @@ func TextView1(nextSlide func()) (title string, content cview.Primitive) {
}()
textView.SetBorder(true)
textView.SetTitle("TextView implements io.Writer")
return "Text 1", Code(textView, 36, 13, textView1)
return "TextView 1", Code(textView, 36, 13, textView1)
}
const textView2 = `[green]package[white] main
@ -65,7 +65,7 @@ const textView2 = `[green]package[white] main
[green]import[white] (
[red]"strconv"[white]
[red]"github.com/gdamore/tcell"[white]
[red]"github.com/gdamore/tcell/v2"[white]
[red]"gitlab.com/tslocum/cview"[white]
)
@ -158,5 +158,5 @@ func TextView2(nextSlide func()) (title string, content cview.Primitive) {
flex.AddItem(textView, 0, 1, true)
flex.AddItem(codeView, 0, 1, false)
return "Text 2", flex
return "TextView 2", flex
}

18
form.go
View File

@ -383,6 +383,24 @@ func (f *Form) AddCheckBox(label string, message string, checked bool, changed f
f.items = append(f.items, c)
}
// AddSlider adds a slider to the form. It has a label, an initial value, a
// maximum value, an amount to increment by when modified via keyboard, and an
// (optional) callback function which is invoked when the state of the slider
// was changed by the user.
func (f *Form) AddSlider(label string, current, max, increment int, changed func(value int)) {
f.Lock()
defer f.Unlock()
s := NewSlider()
s.SetLabel(label)
s.SetMax(max)
s.SetProgress(current)
s.SetIncrement(increment)
s.SetChangedFunc(changed)
f.items = append(f.items, s)
}
// AddButton adds a new button to the form. The "selected" function is called
// when the user selects this button. It may be nil.
func (f *Form) AddButton(label string, selected func()) {

View File

@ -40,7 +40,7 @@ type ProgressBar struct {
func NewProgressBar() *ProgressBar {
p := &ProgressBar{
Box: NewBox(),
emptyRune: ' ',
emptyRune: tcell.RuneBlock,
emptyColor: Styles.PrimitiveBackgroundColor,
filledRune: tcell.RuneBlock,
filledColor: Styles.PrimaryTextColor,
@ -112,6 +112,11 @@ func (p *ProgressBar) AddProgress(progress int) {
defer p.Unlock()
p.progress += progress
if p.progress < 0 {
p.progress = 0
} else if p.progress > p.max {
p.progress = p.max
}
}
// SetProgress sets the current progress.
@ -120,6 +125,11 @@ func (p *ProgressBar) SetProgress(progress int) {
defer p.Unlock()
p.progress = progress
if p.progress < 0 {
p.progress = 0
} else if p.progress > p.max {
p.progress = p.max
}
}
// GetProgress gets the current progress.

354
slider.go Normal file
View File

@ -0,0 +1,354 @@
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.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
labelColorFocused: ColorUnset,
fieldBackgroundColorFocused: 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 {
s.RLock()
defer s.RUnlock()
return 7
}
// 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) {
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.MoveDown, Keys.MoveDown2, Keys.MoveLeft, Keys.MoveLeft2) {
s.AddProgress(s.increment * -1)
if s.changed != nil {
s.changed(s.progress)
}
} else if HitShortcut(event, Keys.MoveUp, Keys.MoveUp2, Keys.MoveRight, Keys.MoveRight2) {
s.AddProgress(s.increment)
if s.changed != nil {
s.changed(s.progress)
}
} else 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())
}
}
})
}
// 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
})
}

View File

@ -819,7 +819,7 @@ func (t *TextView) reindexBuffer(width int) {
// Split the line if required.
var splitLines []string
str := string(strippedStr) // TODO
str := string(strippedStr)
if t.wrap && len(str) > 0 {
for len(str) > 0 {
extract := runewidth.Truncate(str, width, "")