Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Viewport quality-of-life improvements #92

Merged
merged 3 commits into from Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 42 additions & 0 deletions viewport/keymap.go
@@ -0,0 +1,42 @@
package viewport

import "github.com/charmbracelet/bubbles/key"

const spacebar = " "

// KeyMap defines the keybindings for the viewport. Note that you don't
// necessary need to use keybindings at all; the viewport can be controlled
// programmatically with methods like Model.LineDown(1). See the GoDocs for
// details.
type KeyMap struct {
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
Down key.Binding
Up key.Binding
}

// DefaultKeyMap returns a set of pager-like default keybindings.
func DefaultKeyMap() KeyMap {
return KeyMap{
PageDown: key.NewBinding(
key.WithKeys("pgdown", spacebar, "f"),
),
PageUp: key.NewBinding(
key.WithKeys("pgup", "b"),
),
HalfPageUp: key.NewBinding(
key.WithKeys("u", "ctrl+u"),
),
HalfPageDown: key.NewBinding(
key.WithKeys("d", "ctrl+d"),
),
Up: key.NewBinding(
key.WithKeys("up", "k"),
),
Down: key.NewBinding(
key.WithKeys("down", "j"),
),
}
}
90 changes: 62 additions & 28 deletions viewport/viewport.go
Expand Up @@ -4,18 +4,32 @@ import (
"math"
"strings"

"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

const (
spacebar = " "
mouseWheelDelta = 3
)
// New returns a new model with the given width and height as well as default
// keymappings.
func New(width, height int) (m Model) {
m.Width = width
m.Height = height
m.setInitialValues()
return m
}

// Model is the Bubble Tea model for this viewport element.
type Model struct {
Width int
Height int
KeyMap KeyMap

// Whether or not to respond to the mouse. The mouse must be enabled in
// Bubble Tea for this to work. For details, see the Bubble Tea docs.
MouseWheelEnabled bool

// The number of lines the mouse wheel will scroll. By default, this is 3.
MouseWheelDelta int

// YOffset is the vertical scroll position.
YOffset int
Expand All @@ -24,6 +38,10 @@ type Model struct {
// window. It's used in high performance rendering only.
YPosition int

// Style applies a lipgloss style to the viewport. Realistically, it's most
// useful for setting borders, margins and padding.
Style lipgloss.Style

// HighPerformanceRendering bypasses the normal Bubble Tea renderer to
// provide higher performance rendering. Most of the time the normal Bubble
// Tea rendering methods will suffice, but if you're passing content with
Expand All @@ -34,7 +52,20 @@ type Model struct {
// which is usually via the alternate screen buffer.
HighPerformanceRendering bool

lines []string
initialized bool
lines []string
}

func (m *Model) setInitialValues() {
m.KeyMap = DefaultKeyMap()
m.MouseWheelEnabled = true
m.MouseWheelDelta = 3
m.initialized = true
}

// Init exists to satisfy the tea.Model interface for composability purposes.
func (m Model) Init() tea.Cmd {
return nil
}

// AtTop returns whether or not the viewport is in the very top position.
Expand Down Expand Up @@ -227,70 +258,75 @@ func ViewUp(m Model, lines []string) tea.Cmd {
return tea.ScrollUp(lines, top, bottom)
}

// UPDATE

// Update runs the update loop with default keybindings similar to popular
// pagers. To define your own keybindings use the methods on Model (i.e.
// Model.LineDown()) and define your own update function.
// Update handles standard message-based viewport updates.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
var cmd tea.Cmd
m, cmd = m.updateAsModel(msg)
return m, cmd
}

// Author's note: this method has been broken out to make it easier to
// potentially transition Update to satisfy tea.Model.
func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) {
if !m.initialized {
m.setInitialValues()
}

var cmd tea.Cmd

switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
// Down one page
case "pgdown", spacebar, "f":
switch {
case key.Matches(msg, m.KeyMap.PageDown):
lines := m.ViewDown()
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}

// Up one page
case "pgup", "b":
case key.Matches(msg, m.KeyMap.PageUp):
lines := m.ViewUp()
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}

// Down half page
case "d", "ctrl+d":
case key.Matches(msg, m.KeyMap.HalfPageDown):
lines := m.HalfViewDown()
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}

// Up half page
case "u", "ctrl+u":
case key.Matches(msg, m.KeyMap.HalfPageUp):
lines := m.HalfViewUp()
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}

// Down one line
case "down", "j":
case key.Matches(msg, m.KeyMap.Down):
lines := m.LineDown(1)
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}

// Up one line
case "up", "k":
case key.Matches(msg, m.KeyMap.Up):
lines := m.LineUp(1)
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
}

case tea.MouseMsg:
if !m.MouseWheelEnabled {
break
}
switch msg.Type {
case tea.MouseWheelUp:
lines := m.LineUp(mouseWheelDelta)
lines := m.LineUp(m.MouseWheelDelta)
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}

case tea.MouseWheelDown:
lines := m.LineDown(mouseWheelDelta)
lines := m.LineDown(m.MouseWheelDelta)
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
Expand All @@ -300,8 +336,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
return m, cmd
}

// VIEW

// View renders the viewport into a string.
func (m Model) View() string {
if m.HighPerformanceRendering {
Expand All @@ -320,7 +354,7 @@ func (m Model) View() string {
extraLines = strings.Repeat("\n", max(0, m.Height-len(lines)))
}

return strings.Join(lines, "\n") + extraLines
return m.Style.Render(strings.Join(lines, "\n") + extraLines)
}

// ETC
Expand Down