10 Commits

Author SHA1 Message Date
Christian Rocha
5357dd61bd Fix bug where scroll wheel could create duplicate lines on top and bottom 2020-07-20 17:46:15 -04:00
Christian Rocha
5a26cb0d8e Use pgup/pgdown as default paginator keystrokes 2020-07-20 12:13:53 -04:00
Christian Rocha
88469a499e Tidy up the key case switch in viewport 2020-07-20 11:56:35 -04:00
Christian Rocha
b130d96434 Add methods for jumping to the tom and bottom of the viewport 2020-07-17 19:22:08 -04:00
Christian Rocha
185a19e56f Fix cases where pager would add blank lines to bottom of document 2020-07-17 18:38:38 -04:00
Christian Rocha
dbb1d93970 Remove redundant viewport commands 2020-07-17 18:22:51 -04:00
Christian Rocha
2f909886c1 Be more consistent with max() argument order 2020-07-17 18:13:48 -04:00
Christian Rocha
5720cfb35a Correct scroll percentage reporting 2020-07-17 18:13:05 -04:00
Christian Rocha
3c07b97d37 Correct typo, add potential TODO 2020-07-17 17:02:37 -04:00
Christian Rocha
9b3e5476c3 Update README a little. 2020-07-17 14:03:02 -04:00
3 changed files with 141 additions and 103 deletions

View File

@@ -1,8 +1,33 @@
# Tea Party
# Bubbles
Components for [Tea](https://github.com/charmbraclet/tea)
Some components for [Bubble Tea](https://github.com/charmbraclet/bubbletea):
⚠️ This project is a pre-release! Check back later.
* Spinner
* Text Input
* Paginator
* Viewport
## Spinner
A spinner, useful for indicating that some kind of invisible operation is
happening. There are a couple default ones, but you can also pass your own
”frames.”
## Text Input
A text input field, akin to an `<input type="text">` in HTML.
## Paginator
A component for handling pagination logic and optionally drawing pagination UI.
## Viewport
A viewport for vertically scrolling content which optionally includes standard
pager keybindings and mouse wheel support. A high performance mode is available
for applications which make use of the alterate screen buffer. This is
generally only necessary when dealing with content with a very large amount of
ANSI escape sequences.
## License

View File

@@ -28,6 +28,7 @@ type Model struct {
ActiveDot string
InactiveDot string
ArabicFormat string
UsePgUpPgDownKeys bool
UseLeftRightKeys bool
UseUpDownKeys bool
UseHLKeys bool
@@ -105,6 +106,7 @@ func NewModel() Model {
ActiveDot: "•",
InactiveDot: "○",
ArabicFormat: "%d/%d",
UsePgUpPgDownKeys: true,
UseLeftRightKeys: true,
UseUpDownKeys: false,
UseHLKeys: true,
@@ -116,6 +118,14 @@ func NewModel() Model {
func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if m.UsePgUpPgDownKeys {
switch msg.String() {
case "pgup":
m.PrevPage()
case "pgdown":
m.NextPage()
}
}
if m.UseLeftRightKeys {
switch msg.String() {
case "left":

View File

@@ -7,6 +7,10 @@ import (
tea "github.com/charmbracelet/bubbletea"
)
const (
spacebar = " "
)
// MODEL
type Model struct {
@@ -53,9 +57,16 @@ func (m Model) AtTop() bool {
return m.YOffset <= 0
}
// AtBottom returns whether or not the viewport is at the very botom position.
// AtBottom returns whether or not the viewport is at or past the very bottom
// position.
func (m Model) AtBottom() bool {
return m.YOffset >= len(m.lines)-m.Height-1
return m.YOffset >= len(m.lines)-1-m.Height
}
// PastBottom returns whether or not the viewport is scrolled beyond the last
// line. This can happen when adjusting the viewport height.
func (m Model) PastBottom() bool {
return m.YOffset > len(m.lines)-1-m.Height
}
// Scrollpercent returns the amount scrolled as a float between 0 and 1.
@@ -65,7 +76,7 @@ func (m Model) ScrollPercent() float64 {
}
y := float64(m.YOffset)
h := float64(m.Height)
t := float64(len(m.lines))
t := float64(len(m.lines) - 1)
v := y / (t - h)
return math.Max(0.0, math.Min(1.0, v))
}
@@ -96,7 +107,7 @@ func (m *Model) ViewDown() []string {
m.YOffset = min(
m.YOffset+m.Height, // target
len(m.lines)-m.Height, // fallback
len(m.lines)-1-m.Height, // fallback
)
return m.visibleLines()
@@ -124,7 +135,7 @@ func (m *Model) HalfViewDown() (lines []string) {
m.YOffset = min(
m.YOffset+m.Height/2, // target
len(m.lines)-m.Height, // fallback
len(m.lines)-1-m.Height, // fallback
)
if len(m.lines) > 0 {
@@ -156,20 +167,26 @@ func (m *Model) HalfViewUp() (lines []string) {
return lines
}
// LineDown moves the view up by the given number of lines.
// LineDown moves the view down by the given number of lines.
func (m *Model) LineDown(n int) (lines []string) {
if m.AtBottom() || n == 0 {
return nil
}
// Make sure the number of lines by which we're going to scroll isn't
// greater than the number of lines we actually have left before we reach
// the bottom.
maxDelta := (len(m.lines) - 1) - (m.YOffset + m.Height) // number of lines - viewport bottom edge
n = min(n, maxDelta)
m.YOffset = min(
m.YOffset+n, // target
len(m.lines)-m.Height, // fallback
len(m.lines)-1-m.Height, // fallback
)
if len(m.lines) > 0 {
top := max(0, m.YOffset+m.Height-n)
bottom := min(len(m.lines)-1, m.YOffset+m.Height)
top := max(m.YOffset+m.Height-n, 0)
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
lines = m.lines[top:bottom]
}
@@ -183,11 +200,45 @@ func (m *Model) LineUp(n int) (lines []string) {
return nil
}
// Make sure the number of lines by which we're going to scroll isn't
// greater than the number of lines we are from the top.
n = min(n, m.YOffset)
m.YOffset = max(m.YOffset-n, 0)
if len(m.lines) > 0 {
top := max(0, m.YOffset)
bottom := min(len(m.lines)-1, m.YOffset+n)
bottom := min(m.YOffset+n, len(m.lines)-1)
lines = m.lines[top:bottom]
}
return lines
}
// GotoTop sets the viewport to the top position.
func (m *Model) GotoTop() (lines []string) {
if m.AtTop() {
return nil
}
m.YOffset = 0
if len(m.lines) > 0 {
top := m.YOffset
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
lines = m.lines[top:bottom]
}
return lines
}
// GotoTop sets the viewport to the bottom position.
func (m *Model) GotoBottom() (lines []string) {
m.YOffset = max(len(m.lines)-1-m.Height, 0)
if len(m.lines) > 0 {
top := m.YOffset
bottom := max(len(m.lines)-1, 0)
lines = m.lines[top:bottom]
}
@@ -206,6 +257,8 @@ func Sync(m Model) tea.Cmd {
return nil
}
// TODO: we should probably use m.visibleLines() rather than these two
// expressions.
top := max(m.YOffset, 0)
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
@@ -216,9 +269,9 @@ func Sync(m Model) tea.Cmd {
)
}
// ViewDown is a high performance command that moves the viewport up by one
// viewport height. Use Model.ViewDown to get the lines that should be
// rendered. For example:
// ViewDown is a high performance command that moves the viewport up by a given
// numer of lines. Use Model.ViewDown to get the lines that should be rendered.
// For example:
//
// lines := model.ViewDown(1)
// cmd := ViewDown(m, lines)
@@ -230,8 +283,8 @@ func ViewDown(m Model, lines []string) tea.Cmd {
return tea.ScrollDown(lines, m.YPosition, m.YPosition+m.Height)
}
// ViewUp is a high performance command the moves the viewport down by one
// viewport height. Use Model.ViewDown to get the lines that should be
// ViewUp is a high performance command the moves the viewport down by a given
// number of lines height. Use Model.ViewDown to get the lines that should be
// rendered.
func ViewUp(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
@@ -240,46 +293,6 @@ func ViewUp(m Model, lines []string) tea.Cmd {
return tea.ScrollUp(lines, m.YPosition, m.YPosition+m.Height)
}
// HalfViewDown is a high performance command the moves the viewport down by
// half of the height of the viewport. Use Model.HalfViewDown to get the lines
// that should be rendered.
func HalfViewDown(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
return nil
}
return tea.ScrollDown(lines, m.YPosition, m.YPosition+m.Height)
}
// HalfViewUp is a high performance command the moves the viewport up by
// half of the height of the viewport. Use Model.HalfViewUp to get the lines
// that should be rendered.
func HalfViewUp(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
return nil
}
return tea.ScrollUp(lines, m.YPosition, m.YPosition+m.Height)
}
// LineDown is a high performance command the moves the viewport down by
// a given number of lines. Use Model.LineDown to get the lines that should be
// rendered.
func LineDown(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
return nil
}
return tea.ScrollDown(lines, m.YPosition, m.YPosition+m.Height)
}
// LineDown is a high performance command the moves the viewport up by a given
// number of lines. Use Model.LineDown to get the lines that should be
// rendered.
func LineUp(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
return nil
}
return tea.ScrollUp(lines, m.YPosition, m.YPosition+m.Height)
}
// UPDATE
// Update runs the update loop with default keybindings similar to popular
@@ -293,20 +306,14 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
case tea.KeyMsg:
switch msg.String() {
// Down one page
case "pgdown":
fallthrough
case " ": // spacebar
fallthrough
case "f":
case "pgdown", spacebar, "f":
lines := m.ViewDown()
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
// Up one page
case "pgup":
fallthrough
case "b":
case "pgup", "b":
lines := m.ViewUp()
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
@@ -316,32 +323,28 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
case "d":
lines := m.HalfViewDown()
if m.HighPerformanceRendering {
cmd = HalfViewDown(m, lines)
cmd = ViewDown(m, lines)
}
// Up half page
case "u":
lines := m.HalfViewUp()
if m.HighPerformanceRendering {
cmd = HalfViewUp(m, lines)
cmd = ViewUp(m, lines)
}
// Down one line
case "down":
fallthrough
case "j":
case "down", "j":
lines := m.LineDown(1)
if m.HighPerformanceRendering {
cmd = LineDown(m, lines)
cmd = ViewDown(m, lines)
}
// Up one line
case "up":
fallthrough
case "k":
case "up", "k":
lines := m.LineUp(1)
if m.HighPerformanceRendering {
cmd = LineUp(m, lines)
cmd = ViewUp(m, lines)
}
}
@@ -351,13 +354,13 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
case tea.MouseWheelUp:
lines := m.LineUp(3)
if m.HighPerformanceRendering {
cmd = LineUp(m, lines)
cmd = ViewUp(m, lines)
}
case tea.MouseWheelDown:
lines := m.LineDown(3)
if m.HighPerformanceRendering {
cmd = LineDown(m, lines)
cmd = ViewDown(m, lines)
}
}
}