...

Source file src/code.rocketnine.space/tslocum/cview/slider.go

Documentation: code.rocketnine.space/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"math"
     5  	"sync"
     6  
     7  	"github.com/gdamore/tcell/v2"
     8  )
     9  
    10  // Slider is a progress bar which may be modified via keyboard and mouse.
    11  type Slider struct {
    12  	*ProgressBar
    13  
    14  	// The text to be displayed before the slider.
    15  	label []byte
    16  
    17  	// The screen width of the label area. A value of 0 means use the width of
    18  	// the label text.
    19  	labelWidth int
    20  
    21  	// The label color.
    22  	labelColor tcell.Color
    23  
    24  	// The label color when focused.
    25  	labelColorFocused tcell.Color
    26  
    27  	// The background color of the input area.
    28  	fieldBackgroundColor tcell.Color
    29  
    30  	// The background color of the input area when focused.
    31  	fieldBackgroundColorFocused tcell.Color
    32  
    33  	// The text color of the input area.
    34  	fieldTextColor tcell.Color
    35  
    36  	// The text color of the input area when focused.
    37  	fieldTextColorFocused tcell.Color
    38  
    39  	// The amount to increment by when modified via keyboard.
    40  	increment int
    41  
    42  	// Set to true when mouse dragging is in progress.
    43  	dragging bool
    44  
    45  	// An optional function which is called when the user changes the value of
    46  	// this slider.
    47  	changed func(value int)
    48  
    49  	// An optional function which is called when the user indicated that they
    50  	// are done entering text. The key which was pressed is provided (tab,
    51  	// shift-tab, or escape).
    52  	done func(tcell.Key)
    53  
    54  	// A callback function set by the Form class and called when the user leaves
    55  	// this form item.
    56  	finished func(tcell.Key)
    57  
    58  	sync.RWMutex
    59  }
    60  
    61  // NewSlider returns a new slider.
    62  func NewSlider() *Slider {
    63  	s := &Slider{
    64  		ProgressBar:                 NewProgressBar(),
    65  		increment:                   10,
    66  		labelColor:                  Styles.SecondaryTextColor,
    67  		fieldBackgroundColor:        Styles.MoreContrastBackgroundColor,
    68  		fieldBackgroundColorFocused: Styles.ContrastBackgroundColor,
    69  		fieldTextColor:              Styles.PrimaryTextColor,
    70  		labelColorFocused:           ColorUnset,
    71  		fieldTextColorFocused:       ColorUnset,
    72  	}
    73  	return s
    74  }
    75  
    76  // SetLabel sets the text to be displayed before the input area.
    77  func (s *Slider) SetLabel(label string) {
    78  	s.Lock()
    79  	defer s.Unlock()
    80  
    81  	s.label = []byte(label)
    82  }
    83  
    84  // GetLabel returns the text to be displayed before the input area.
    85  func (s *Slider) GetLabel() string {
    86  	s.RLock()
    87  	defer s.RUnlock()
    88  
    89  	return string(s.label)
    90  }
    91  
    92  // SetLabelWidth sets the screen width of the label. A value of 0 will cause the
    93  // primitive to use the width of the label string.
    94  func (s *Slider) SetLabelWidth(width int) {
    95  	s.Lock()
    96  	defer s.Unlock()
    97  
    98  	s.labelWidth = width
    99  }
   100  
   101  // SetLabelColor sets the color of the label.
   102  func (s *Slider) SetLabelColor(color tcell.Color) {
   103  	s.Lock()
   104  	defer s.Unlock()
   105  
   106  	s.labelColor = color
   107  }
   108  
   109  // SetLabelColorFocused sets the color of the label when focused.
   110  func (s *Slider) SetLabelColorFocused(color tcell.Color) {
   111  	s.Lock()
   112  	defer s.Unlock()
   113  
   114  	s.labelColorFocused = color
   115  }
   116  
   117  // SetFieldBackgroundColor sets the background color of the input area.
   118  func (s *Slider) SetFieldBackgroundColor(color tcell.Color) {
   119  	s.Lock()
   120  	defer s.Unlock()
   121  
   122  	s.fieldBackgroundColor = color
   123  }
   124  
   125  // SetFieldBackgroundColorFocused sets the background color of the input area when focused.
   126  func (s *Slider) SetFieldBackgroundColorFocused(color tcell.Color) {
   127  	s.Lock()
   128  	defer s.Unlock()
   129  
   130  	s.fieldBackgroundColorFocused = color
   131  }
   132  
   133  // SetFieldTextColor sets the text color of the input area.
   134  func (s *Slider) SetFieldTextColor(color tcell.Color) {
   135  	s.Lock()
   136  	defer s.Unlock()
   137  
   138  	s.fieldTextColor = color
   139  }
   140  
   141  // SetFieldTextColorFocused sets the text color of the input area when focused.
   142  func (s *Slider) SetFieldTextColorFocused(color tcell.Color) {
   143  	s.Lock()
   144  	defer s.Unlock()
   145  
   146  	s.fieldTextColorFocused = color
   147  }
   148  
   149  // GetFieldHeight returns the height of the field.
   150  func (s *Slider) GetFieldHeight() int {
   151  	return 1
   152  }
   153  
   154  // GetFieldWidth returns this primitive's field width.
   155  func (s *Slider) GetFieldWidth() int {
   156  	return 0
   157  }
   158  
   159  // SetIncrement sets the amount the slider is incremented by when modified via
   160  // keyboard.
   161  func (s *Slider) SetIncrement(increment int) {
   162  	s.Lock()
   163  	defer s.Unlock()
   164  
   165  	s.increment = increment
   166  }
   167  
   168  // SetChangedFunc sets a handler which is called when the value of this slider
   169  // was changed by the user. The handler function receives the new value.
   170  func (s *Slider) SetChangedFunc(handler func(value int)) {
   171  	s.Lock()
   172  	defer s.Unlock()
   173  
   174  	s.changed = handler
   175  }
   176  
   177  // SetDoneFunc sets a handler which is called when the user is done using the
   178  // slider. The callback function is provided with the key that was pressed,
   179  // which is one of the following:
   180  //
   181  //   - KeyEscape: Abort text input.
   182  //   - KeyTab: Move to the next field.
   183  //   - KeyBacktab: Move to the previous field.
   184  func (s *Slider) SetDoneFunc(handler func(key tcell.Key)) {
   185  	s.Lock()
   186  	defer s.Unlock()
   187  
   188  	s.done = handler
   189  }
   190  
   191  // SetFinishedFunc sets a callback invoked when the user leaves this form item.
   192  func (s *Slider) SetFinishedFunc(handler func(key tcell.Key)) {
   193  	s.Lock()
   194  	defer s.Unlock()
   195  
   196  	s.finished = handler
   197  }
   198  
   199  // Draw draws this primitive onto the screen.
   200  func (s *Slider) Draw(screen tcell.Screen) {
   201  	if !s.GetVisible() {
   202  		return
   203  	}
   204  
   205  	s.Box.Draw(screen)
   206  	hasFocus := s.GetFocusable().HasFocus()
   207  
   208  	s.Lock()
   209  
   210  	// Select colors
   211  	labelColor := s.labelColor
   212  	fieldBackgroundColor := s.fieldBackgroundColor
   213  	fieldTextColor := s.fieldTextColor
   214  	if hasFocus {
   215  		if s.labelColorFocused != ColorUnset {
   216  			labelColor = s.labelColorFocused
   217  		}
   218  		if s.fieldBackgroundColorFocused != ColorUnset {
   219  			fieldBackgroundColor = s.fieldBackgroundColorFocused
   220  		}
   221  		if s.fieldTextColorFocused != ColorUnset {
   222  			fieldTextColor = s.fieldTextColorFocused
   223  		}
   224  	}
   225  
   226  	// Prepare.
   227  	x, y, width, height := s.GetInnerRect()
   228  	rightLimit := x + width
   229  	if height < 1 || rightLimit <= x {
   230  		s.Unlock()
   231  		return
   232  	}
   233  
   234  	// Draw label.
   235  	if len(s.label) > 0 {
   236  		if s.vertical {
   237  			height--
   238  
   239  			// TODO draw label on bottom
   240  		} else {
   241  			if s.labelWidth > 0 {
   242  				labelWidth := s.labelWidth
   243  				if labelWidth > rightLimit-x {
   244  					labelWidth = rightLimit - x
   245  				}
   246  				Print(screen, []byte(s.label), x, y, labelWidth, AlignLeft, labelColor)
   247  				x += labelWidth + 1
   248  				width -= labelWidth + 1
   249  			} else {
   250  				_, drawnWidth := Print(screen, []byte(s.label), x, y, rightLimit-x, AlignLeft, labelColor)
   251  				x += drawnWidth + 1
   252  				width -= drawnWidth + 1
   253  			}
   254  		}
   255  	}
   256  
   257  	// Draw slider.
   258  	s.Unlock()
   259  	s.ProgressBar.SetRect(x, y, width, height)
   260  	s.ProgressBar.SetEmptyColor(fieldBackgroundColor)
   261  	s.ProgressBar.SetFilledColor(fieldTextColor)
   262  	s.ProgressBar.Draw(screen)
   263  }
   264  
   265  // InputHandler returns the handler for this primitive.
   266  func (s *Slider) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
   267  	return s.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
   268  		if HitShortcut(event, Keys.Cancel, Keys.MovePreviousField, Keys.MoveNextField) {
   269  			if s.done != nil {
   270  				s.done(event.Key())
   271  			}
   272  			if s.finished != nil {
   273  				s.finished(event.Key())
   274  			}
   275  			return
   276  		}
   277  
   278  		previous := s.progress
   279  
   280  		if HitShortcut(event, Keys.MoveFirst, Keys.MoveFirst2) {
   281  			s.SetProgress(0)
   282  		} else if HitShortcut(event, Keys.MoveLast, Keys.MoveLast2) {
   283  			s.SetProgress(s.max)
   284  		} else if HitShortcut(event, Keys.MoveUp, Keys.MoveUp2, Keys.MoveRight, Keys.MoveRight2, Keys.MovePreviousField) {
   285  			s.AddProgress(s.increment)
   286  		} else if HitShortcut(event, Keys.MoveDown, Keys.MoveDown2, Keys.MoveLeft, Keys.MoveLeft2, Keys.MoveNextField) {
   287  			s.AddProgress(s.increment * -1)
   288  		}
   289  
   290  		if s.progress != previous && s.changed != nil {
   291  			s.changed(s.progress)
   292  		}
   293  	})
   294  }
   295  
   296  // MouseHandler returns the mouse handler for this primitive.
   297  func (s *Slider) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   298  	return s.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   299  		x, y := event.Position()
   300  		if !s.InRect(x, y) {
   301  			s.dragging = false
   302  			return false, nil
   303  		}
   304  
   305  		// Process mouse event.
   306  		if action == MouseLeftClick {
   307  			setFocus(s)
   308  			consumed = true
   309  		}
   310  
   311  		handleMouse := func() {
   312  			if !s.ProgressBar.InRect(x, y) {
   313  				s.dragging = false
   314  				return
   315  			}
   316  
   317  			bx, by, bw, bh := s.GetInnerRect()
   318  			var clickPos, clickRange int
   319  			if s.ProgressBar.vertical {
   320  				clickPos = (bh - 1) - (y - by)
   321  				clickRange = bh - 1
   322  			} else {
   323  				clickPos = x - bx
   324  				clickRange = bw - 1
   325  			}
   326  			setValue := int(math.Floor(float64(s.max) * (float64(clickPos) / float64(clickRange))))
   327  			if setValue != s.progress {
   328  				s.SetProgress(setValue)
   329  				if s.changed != nil {
   330  					s.changed(s.progress)
   331  				}
   332  			}
   333  		}
   334  
   335  		// Handle dragging. Clicks are implicitly handled by this logic.
   336  		switch action {
   337  		case MouseLeftDown:
   338  			setFocus(s)
   339  			consumed = true
   340  			capture = s
   341  			s.dragging = true
   342  
   343  			handleMouse()
   344  		case MouseMove:
   345  			if s.dragging {
   346  				consumed = true
   347  				capture = s
   348  
   349  				handleMouse()
   350  			}
   351  		case MouseLeftUp:
   352  			if s.dragging {
   353  				consumed = true
   354  				s.dragging = false
   355  
   356  				handleMouse()
   357  			}
   358  		}
   359  
   360  		return
   361  	})
   362  }
   363  

View as plain text