mirror of
https://github.com/Maks1mS/bubbles.git
synced 2025-10-19 00:49:54 +03:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5357dd61bd | ||
|
5a26cb0d8e | ||
|
88469a499e | ||
|
b130d96434 | ||
|
185a19e56f | ||
|
dbb1d93970 | ||
|
2f909886c1 | ||
|
5720cfb35a | ||
|
3c07b97d37 | ||
|
9b3e5476c3 |
31
README.md
31
README.md
@@ -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
|
## License
|
||||||
|
|
||||||
|
@@ -21,17 +21,18 @@ const (
|
|||||||
|
|
||||||
// Model is the Tea model for this user interface.
|
// Model is the Tea model for this user interface.
|
||||||
type Model struct {
|
type Model struct {
|
||||||
Type Type
|
Type Type
|
||||||
Page int
|
Page int
|
||||||
PerPage int
|
PerPage int
|
||||||
TotalPages int
|
TotalPages int
|
||||||
ActiveDot string
|
ActiveDot string
|
||||||
InactiveDot string
|
InactiveDot string
|
||||||
ArabicFormat string
|
ArabicFormat string
|
||||||
UseLeftRightKeys bool
|
UsePgUpPgDownKeys bool
|
||||||
UseUpDownKeys bool
|
UseLeftRightKeys bool
|
||||||
UseHLKeys bool
|
UseUpDownKeys bool
|
||||||
UseJKKeys bool
|
UseHLKeys bool
|
||||||
|
UseJKKeys bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTotalPages is a helper function for calculatng the total number of pages
|
// SetTotalPages is a helper function for calculatng the total number of pages
|
||||||
@@ -98,17 +99,18 @@ func (m Model) OnLastPage() bool {
|
|||||||
// NewModel creates a new model with defaults.
|
// NewModel creates a new model with defaults.
|
||||||
func NewModel() Model {
|
func NewModel() Model {
|
||||||
return Model{
|
return Model{
|
||||||
Type: Arabic,
|
Type: Arabic,
|
||||||
Page: 0,
|
Page: 0,
|
||||||
PerPage: 1,
|
PerPage: 1,
|
||||||
TotalPages: 1,
|
TotalPages: 1,
|
||||||
ActiveDot: "•",
|
ActiveDot: "•",
|
||||||
InactiveDot: "○",
|
InactiveDot: "○",
|
||||||
ArabicFormat: "%d/%d",
|
ArabicFormat: "%d/%d",
|
||||||
UseLeftRightKeys: true,
|
UsePgUpPgDownKeys: true,
|
||||||
UseUpDownKeys: false,
|
UseLeftRightKeys: true,
|
||||||
UseHLKeys: true,
|
UseUpDownKeys: false,
|
||||||
UseJKKeys: false,
|
UseHLKeys: true,
|
||||||
|
UseJKKeys: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +118,14 @@ func NewModel() Model {
|
|||||||
func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
|
if m.UsePgUpPgDownKeys {
|
||||||
|
switch msg.String() {
|
||||||
|
case "pgup":
|
||||||
|
m.PrevPage()
|
||||||
|
case "pgdown":
|
||||||
|
m.NextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
if m.UseLeftRightKeys {
|
if m.UseLeftRightKeys {
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "left":
|
case "left":
|
||||||
|
@@ -7,6 +7,10 @@ import (
|
|||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
spacebar = " "
|
||||||
|
)
|
||||||
|
|
||||||
// MODEL
|
// MODEL
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
@@ -53,9 +57,16 @@ func (m Model) AtTop() bool {
|
|||||||
return m.YOffset <= 0
|
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 {
|
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.
|
// 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)
|
y := float64(m.YOffset)
|
||||||
h := float64(m.Height)
|
h := float64(m.Height)
|
||||||
t := float64(len(m.lines))
|
t := float64(len(m.lines) - 1)
|
||||||
v := y / (t - h)
|
v := y / (t - h)
|
||||||
return math.Max(0.0, math.Min(1.0, v))
|
return math.Max(0.0, math.Min(1.0, v))
|
||||||
}
|
}
|
||||||
@@ -95,8 +106,8 @@ func (m *Model) ViewDown() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.YOffset = min(
|
m.YOffset = min(
|
||||||
m.YOffset+m.Height, // target
|
m.YOffset+m.Height, // target
|
||||||
len(m.lines)-m.Height, // fallback
|
len(m.lines)-1-m.Height, // fallback
|
||||||
)
|
)
|
||||||
|
|
||||||
return m.visibleLines()
|
return m.visibleLines()
|
||||||
@@ -123,8 +134,8 @@ func (m *Model) HalfViewDown() (lines []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.YOffset = min(
|
m.YOffset = min(
|
||||||
m.YOffset+m.Height/2, // target
|
m.YOffset+m.Height/2, // target
|
||||||
len(m.lines)-m.Height, // fallback
|
len(m.lines)-1-m.Height, // fallback
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
@@ -156,20 +167,26 @@ func (m *Model) HalfViewUp() (lines []string) {
|
|||||||
return lines
|
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) {
|
func (m *Model) LineDown(n int) (lines []string) {
|
||||||
if m.AtBottom() || n == 0 {
|
if m.AtBottom() || n == 0 {
|
||||||
return nil
|
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 = min(
|
||||||
m.YOffset+n, // target
|
m.YOffset+n, // target
|
||||||
len(m.lines)-m.Height, // fallback
|
len(m.lines)-1-m.Height, // fallback
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
top := max(0, m.YOffset+m.Height-n)
|
top := max(m.YOffset+m.Height-n, 0)
|
||||||
bottom := min(len(m.lines)-1, m.YOffset+m.Height)
|
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
|
||||||
lines = m.lines[top:bottom]
|
lines = m.lines[top:bottom]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,11 +200,45 @@ func (m *Model) LineUp(n int) (lines []string) {
|
|||||||
return nil
|
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)
|
m.YOffset = max(m.YOffset-n, 0)
|
||||||
|
|
||||||
if len(m.lines) > 0 {
|
if len(m.lines) > 0 {
|
||||||
top := max(0, m.YOffset)
|
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]
|
lines = m.lines[top:bottom]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +257,8 @@ func Sync(m Model) tea.Cmd {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: we should probably use m.visibleLines() rather than these two
|
||||||
|
// expressions.
|
||||||
top := max(m.YOffset, 0)
|
top := max(m.YOffset, 0)
|
||||||
bottom := min(m.YOffset+m.Height, len(m.lines)-1)
|
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
|
// ViewDown is a high performance command that moves the viewport up by a given
|
||||||
// viewport height. Use Model.ViewDown to get the lines that should be
|
// numer of lines. Use Model.ViewDown to get the lines that should be rendered.
|
||||||
// rendered. For example:
|
// For example:
|
||||||
//
|
//
|
||||||
// lines := model.ViewDown(1)
|
// lines := model.ViewDown(1)
|
||||||
// cmd := ViewDown(m, lines)
|
// 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)
|
return tea.ScrollDown(lines, m.YPosition, m.YPosition+m.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewUp is a high performance command the moves the viewport down by one
|
// ViewUp is a high performance command the moves the viewport down by a given
|
||||||
// viewport height. Use Model.ViewDown to get the lines that should be
|
// number of lines height. Use Model.ViewDown to get the lines that should be
|
||||||
// rendered.
|
// rendered.
|
||||||
func ViewUp(m Model, lines []string) tea.Cmd {
|
func ViewUp(m Model, lines []string) tea.Cmd {
|
||||||
if len(lines) == 0 {
|
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)
|
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
|
||||||
|
|
||||||
// Update runs the update loop with default keybindings similar to popular
|
// 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:
|
case tea.KeyMsg:
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
// Down one page
|
// Down one page
|
||||||
case "pgdown":
|
case "pgdown", spacebar, "f":
|
||||||
fallthrough
|
|
||||||
case " ": // spacebar
|
|
||||||
fallthrough
|
|
||||||
case "f":
|
|
||||||
lines := m.ViewDown()
|
lines := m.ViewDown()
|
||||||
if m.HighPerformanceRendering {
|
if m.HighPerformanceRendering {
|
||||||
cmd = ViewDown(m, lines)
|
cmd = ViewDown(m, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Up one page
|
// Up one page
|
||||||
case "pgup":
|
case "pgup", "b":
|
||||||
fallthrough
|
|
||||||
case "b":
|
|
||||||
lines := m.ViewUp()
|
lines := m.ViewUp()
|
||||||
if m.HighPerformanceRendering {
|
if m.HighPerformanceRendering {
|
||||||
cmd = ViewUp(m, lines)
|
cmd = ViewUp(m, lines)
|
||||||
@@ -316,32 +323,28 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
|||||||
case "d":
|
case "d":
|
||||||
lines := m.HalfViewDown()
|
lines := m.HalfViewDown()
|
||||||
if m.HighPerformanceRendering {
|
if m.HighPerformanceRendering {
|
||||||
cmd = HalfViewDown(m, lines)
|
cmd = ViewDown(m, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Up half page
|
// Up half page
|
||||||
case "u":
|
case "u":
|
||||||
lines := m.HalfViewUp()
|
lines := m.HalfViewUp()
|
||||||
if m.HighPerformanceRendering {
|
if m.HighPerformanceRendering {
|
||||||
cmd = HalfViewUp(m, lines)
|
cmd = ViewUp(m, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Down one line
|
// Down one line
|
||||||
case "down":
|
case "down", "j":
|
||||||
fallthrough
|
|
||||||
case "j":
|
|
||||||
lines := m.LineDown(1)
|
lines := m.LineDown(1)
|
||||||
if m.HighPerformanceRendering {
|
if m.HighPerformanceRendering {
|
||||||
cmd = LineDown(m, lines)
|
cmd = ViewDown(m, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Up one line
|
// Up one line
|
||||||
case "up":
|
case "up", "k":
|
||||||
fallthrough
|
|
||||||
case "k":
|
|
||||||
lines := m.LineUp(1)
|
lines := m.LineUp(1)
|
||||||
if m.HighPerformanceRendering {
|
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:
|
case tea.MouseWheelUp:
|
||||||
lines := m.LineUp(3)
|
lines := m.LineUp(3)
|
||||||
if m.HighPerformanceRendering {
|
if m.HighPerformanceRendering {
|
||||||
cmd = LineUp(m, lines)
|
cmd = ViewUp(m, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
case tea.MouseWheelDown:
|
case tea.MouseWheelDown:
|
||||||
lines := m.LineDown(3)
|
lines := m.LineDown(3)
|
||||||
if m.HighPerformanceRendering {
|
if m.HighPerformanceRendering {
|
||||||
cmd = LineDown(m, lines)
|
cmd = ViewDown(m, lines)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user