Add Slider
This commit is contained in:
parent
b47c35329c
commit
9c3564dc42
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ func main() {
|
|||
TextView1,
|
||||
TextView2,
|
||||
InputField,
|
||||
Slider,
|
||||
Form,
|
||||
Table,
|
||||
TreeView,
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
18
form.go
|
@ -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()) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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, "")
|
||||
|
|
Loading…
Reference in New Issue