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

Scroll Form Inputs #95

Merged
merged 3 commits into from
Jan 8, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion field_confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Confirm struct {

// options
width int
height int
accessible bool
theme *Theme
keymap *ConfirmKeyMap
Expand Down Expand Up @@ -216,12 +217,18 @@ func (c *Confirm) WithAccessible(accessible bool) Field {
return c
}

// WithWidth sets the accessible mode of the confirm field.
// WithWidth sets the width of the confirm field.
func (c *Confirm) WithWidth(width int) Field {
c.width = width
return c
}

// WithHeight sets the height of the confirm field.
func (c *Confirm) WithHeight(height int) Field {
c.height = height
return c
}

// GetKey returns the key of the field.
func (c *Confirm) GetKey() string {
return c.key
Expand Down
7 changes: 7 additions & 0 deletions field_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Input struct {

// options
width int
height int
accessible bool
theme *Theme
keymap *InputKeyMap
Expand Down Expand Up @@ -281,6 +282,12 @@ func (i *Input) WithWidth(width int) Field {
return i
}

// WithHeight sets the height of the input field.
func (i *Input) WithHeight(height int) Field {
i.height = height
return i
}

// GetKey returns the key of the field.
func (i *Input) GetKey() string {
return i.key
Expand Down
8 changes: 5 additions & 3 deletions field_multiselect.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,6 @@ func (m *MultiSelect[T]) View() string {

var sb strings.Builder
sb.WriteString(m.titleView())
if m.err != nil {
sb.WriteString(styles.ErrorIndicator.String())
}
sb.WriteString("\n")
if m.description != "" {
sb.WriteString(m.descriptionView() + "\n")
Expand Down Expand Up @@ -490,6 +487,11 @@ func (m *MultiSelect[T]) WithWidth(width int) Field {
return m
}

// WithHeight sets the height of the multi-select field.
func (m *MultiSelect[T]) WithHeight(height int) Field {
return m.Height(height)
}

// GetKey returns the multi-select's key.
func (m *MultiSelect[T]) GetKey() string {
return m.key
Expand Down
7 changes: 7 additions & 0 deletions field_note.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Note struct {

// options
width int
height int
accessible bool
theme *Theme
keymap *NoteKeyMap
Expand Down Expand Up @@ -165,6 +166,12 @@ func (n *Note) WithWidth(width int) Field {
return n
}

// WithHeight sets the height of the note field.
func (n *Note) WithHeight(height int) Field {
n.height = height
return n
}

// GetValue satisfies the Field interface, notes do not have values.
func (n *Note) GetValue() any {
return nil
Expand Down
29 changes: 15 additions & 14 deletions field_select.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,17 +303,14 @@ func (s *Select[T]) titleView() string {
}

func (s *Select[T]) descriptionView() string {
if s.description == "" {
return ""
}
return s.activeStyles().Description.Render(s.description) + "\n"
return s.activeStyles().Description.Render(s.description)
}

func (s *Select[T]) choicesView() string {
var (
styles = s.activeStyles()
sb = strings.Builder{}
c = styles.SelectSelector.String()
sb strings.Builder
)
for i, option := range s.filteredOptions {
if s.selected == i {
Expand All @@ -335,17 +332,16 @@ func (s *Select[T]) choicesView() string {

// View renders the select field.
func (s *Select[T]) View() string {
var (
styles = s.activeStyles()
sb = strings.Builder{}
)

sb.WriteString(s.titleView() + "\n")
sb.WriteString(s.descriptionView())

styles := s.activeStyles()
s.viewport.SetContent(s.choicesView())
sb.WriteString(s.viewport.View())

var sb strings.Builder
sb.WriteString(s.titleView())
sb.WriteString("\n")
if s.description != "" {
sb.WriteString(s.descriptionView() + "\n")
}
sb.WriteString(s.viewport.View())
return styles.Base.Render(sb.String())
}

Expand Down Expand Up @@ -426,6 +422,11 @@ func (s *Select[T]) WithWidth(width int) Field {
return s
}

// WithHeight sets the height of the select field.
func (s *Select[T]) WithHeight(height int) Field {
return s.Height(height)
}

// GetKey returns the key of the field.
func (s *Select[T]) GetKey() string {
return s.key
Expand Down
13 changes: 13 additions & 0 deletions field_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,19 @@ func (t *Text) WithWidth(width int) Field {
return t
}

// WithHeight sets the height of the text field.
func (t *Text) WithHeight(height int) Field {
adjust := 0
if t.title != "" {
adjust++
}
if t.description != "" {
adjust++
}
t.textarea.SetHeight(height - t.theme.Blurred.Base.GetVerticalFrameSize() - adjust)
return t
}

// GetKey returns the key of the field.
func (t *Text) GetKey() string {
return t.key
Expand Down
26 changes: 26 additions & 0 deletions form.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Form struct {

// options
width int
height int
theme *Theme
keymap *KeyMap
}
Expand All @@ -74,6 +75,7 @@ func NewForm(groups ...*Group) *Form {
theme: ThemeCharm(),
keymap: NewDefaultKeyMap(),
width: 0,
height: 0,
results: make(map[string]any),
}

Expand All @@ -82,6 +84,7 @@ func NewForm(groups ...*Group) *Form {
f.WithTheme(f.theme)
f.WithKeyMap(f.keymap)
f.WithWidth(f.width)
f.WithHeight(f.height)

return f
}
Expand Down Expand Up @@ -123,6 +126,9 @@ type Field interface {
// WithWidth sets the width of a field.
WithWidth(int) Field

// WithHeight sets the height of a field.
WithHeight(int) Field

// GetKey returns the field's key.
GetKey() string

Expand Down Expand Up @@ -225,6 +231,18 @@ func (f *Form) WithWidth(width int) *Form {
return f
}

// WithHeight sets the height of a form.
func (f *Form) WithHeight(height int) *Form {
if height <= 0 {
return f
}
f.height = height
for _, group := range f.groups {
group.WithHeight(height)
}
return f
}

// Errors returns the current groups' errors.
func (f *Form) Errors() []error {
return f.groups[f.paginator.Page].Errors()
Expand Down Expand Up @@ -329,6 +347,14 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
for _, group := range f.groups {
group.WithWidth(msg.Width)
}
if f.height > 0 {
break
}
for _, group := range f.groups {
if group.fullHeight() > msg.Height {
group.WithHeight(msg.Height)
}
}
case tea.KeyMsg:
switch {
case key.Matches(msg, f.keymap.Quit):
Expand Down
66 changes: 56 additions & 10 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/paginator"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

// Group is a collection of fields that are displayed together with a page of
Expand All @@ -24,6 +26,7 @@

// navigation
paginator paginator.Model
viewport viewport.Model

// help
showHelp bool
Expand All @@ -34,6 +37,7 @@

// group options
width int
height int
theme *Theme
keymap *KeyMap
hide func() bool
Expand All @@ -44,13 +48,20 @@
p := paginator.New()
p.SetTotalPages(len(fields))

return &Group{
group := &Group{
fields: fields,
paginator: p,
help: help.New(),
showHelp: true,
showErrors: true,
}

height := group.fullHeight()
v := viewport.New(80, height)

Check failure on line 60 in group.go

View workflow job for this annotation

GitHub Actions / lint-soft

mnd: Magic number: 80, in <argument> detected (gomnd)

Check failure on line 60 in group.go

View workflow job for this annotation

GitHub Actions / lint-soft

mnd: Magic number: 80, in <argument> detected (gomnd)
group.viewport = v
group.height = height

return group
}

// Title sets the group's title.
Expand Down Expand Up @@ -99,12 +110,26 @@
// WithWidth sets the width on a group.
func (g *Group) WithWidth(width int) *Group {
g.width = width
g.viewport.Width = width
for _, field := range g.fields {
field.WithWidth(width)
}
return g
}

// WithHeight sets the height on a group.
func (g *Group) WithHeight(height int) *Group {
g.height = height
g.viewport.Height = height - 1
for _, field := range g.fields {
// A field height must not exceed the form height.
if height-1 <= lipgloss.Height(field.View()) {
field.WithHeight(height - 1)
}
}
return g
}

// WithHide sets whether this group should be skipped.
func (g *Group) WithHide(hide bool) *Group {
g.WithHideFunc(func() bool { return hide })
Expand Down Expand Up @@ -198,6 +223,12 @@
break
}

offset := 0
for i := 0; i <= current; i++ {
offset += lipgloss.Height(g.fields[i].View()) + 1
}
g.viewport.SetYOffset(offset)

cmds = append(cmds, cmd)

case prevFieldMsg:
Expand All @@ -209,43 +240,58 @@
break
}

offset := 0
for i := 0; i < current-1; i++ {
offset += lipgloss.Height(g.fields[i].View()) + 1
}
g.viewport.SetYOffset(offset)

cmds = append(cmds, cmd)
}

return g, tea.Batch(cmds...)
}

// height returns the full height of the group

Check failure on line 255 in group.go

View workflow job for this annotation

GitHub Actions / lint-soft

Comment should end in a period (godot)

Check failure on line 255 in group.go

View workflow job for this annotation

GitHub Actions / lint-soft

Comment should end in a period (godot)
func (g *Group) fullHeight() int {
var height int
for _, f := range g.fields {
height += lipgloss.Height(f.View()) + 1 // + gap
}
return height
}

// View renders the group.
func (g *Group) View() string {
var s strings.Builder
var fields strings.Builder
var view strings.Builder

gap := g.theme.FieldSeparator.String()
if gap == "" {
gap = "\n"
}

for i, field := range g.fields {
s.WriteString(field.View())
fields.WriteString(field.View())
if i < len(g.fields)-1 {
s.WriteString(gap)
fields.WriteString(gap)
}
}

errors := g.Errors()
s.WriteString(gap)
g.viewport.SetContent(fields.String() + "\n")

if g.showHelp && len(errors) <= 0 {
s.WriteString(g.help.ShortHelpView(g.fields[g.paginator.Page].KeyBinds()))
view.WriteString(g.help.ShortHelpView(g.fields[g.paginator.Page].KeyBinds()))
}

if !g.showErrors {
return s.String()
return g.viewport.View() + "\n" + view.String()
}

for _, err := range errors {
s.WriteString(g.theme.Focused.ErrorMessage.Render(err.Error()))
s.WriteString("\n")
view.WriteString(g.theme.Focused.ErrorMessage.Render(err.Error()))
}

return s.String()
return g.viewport.View() + "\n" + view.String()
}
Loading