mirror of
				https://github.com/Maks1mS/bubbles.git
				synced 2025-11-03 23:21:22 +03:00 
			
		
		
		
	Add vertical layout
This commit is contained in:
		
							
								
								
									
										118
									
								
								layouts/vertical.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								layouts/vertical.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
package layouts
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	tea "github.com/charmbracelet/bubbletea"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Model is the Bubble Tea model for a vertical layout element.
 | 
			
		||||
type Model struct {
 | 
			
		||||
	Index int
 | 
			
		||||
	Items []tea.Model
 | 
			
		||||
 | 
			
		||||
	// Focus indicates whether user focus should be on this component
 | 
			
		||||
	focus bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FocusItem interface {
 | 
			
		||||
	Focus() tea.Model
 | 
			
		||||
	Blur() tea.Model
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewModel creates a new model with default settings.
 | 
			
		||||
func NewModel() Model {
 | 
			
		||||
	return Model{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update is the Tea update loop.
 | 
			
		||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
 | 
			
		||||
	if !m.focus {
 | 
			
		||||
		return m, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch msg := msg.(type) {
 | 
			
		||||
	case tea.KeyMsg:
 | 
			
		||||
		switch msg.String() {
 | 
			
		||||
		case "shift+tab", "up":
 | 
			
		||||
			m.Index--
 | 
			
		||||
			if m.Index < 0 {
 | 
			
		||||
				m.Index = len(m.Items) - 1
 | 
			
		||||
			}
 | 
			
		||||
			m.updateFocus()
 | 
			
		||||
 | 
			
		||||
		case "tab", "down":
 | 
			
		||||
			m.Index++
 | 
			
		||||
			if m.Index >= len(m.Items) {
 | 
			
		||||
				m.Index = 0
 | 
			
		||||
			}
 | 
			
		||||
			m.updateFocus()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := m.updateItems(msg)
 | 
			
		||||
	return m, cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// View renders the layout in its current state.
 | 
			
		||||
func (m Model) View() string {
 | 
			
		||||
	var view string
 | 
			
		||||
 | 
			
		||||
	for _, v := range m.Items {
 | 
			
		||||
		if mi, ok := v.(tea.Model); ok {
 | 
			
		||||
			view += mi.View() + "\n"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return view
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Model) updateFocus() {
 | 
			
		||||
	for i, v := range m.Items {
 | 
			
		||||
		if m.Index == i {
 | 
			
		||||
			if fi, ok := v.(FocusItem); ok {
 | 
			
		||||
				// new focused item
 | 
			
		||||
				m.Items[i] = fi.Focus()
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if fi, ok := v.(FocusItem); ok {
 | 
			
		||||
				m.Items[i] = fi.Blur()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Pass messages and models through to text input components. Only text inputs
 | 
			
		||||
// with Focus() set will respond, so it's safe to simply update all of them
 | 
			
		||||
// here without any further logic.
 | 
			
		||||
func (m *Model) updateItems(msg tea.Msg) tea.Cmd {
 | 
			
		||||
	var (
 | 
			
		||||
		cmd  tea.Cmd
 | 
			
		||||
		cmds []tea.Cmd
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	for i, v := range m.Items {
 | 
			
		||||
		if mi, ok := v.(tea.Model); ok {
 | 
			
		||||
			m.Items[i], cmd = mi.Update(msg)
 | 
			
		||||
			if cmd != nil {
 | 
			
		||||
				cmds = append(cmds, cmd)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tea.Batch(cmds...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Focused returns the focus state on the model.
 | 
			
		||||
func (m Model) Focused() bool {
 | 
			
		||||
	return m.focus
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Focus sets the focus state on the model.
 | 
			
		||||
func (m *Model) Focus() {
 | 
			
		||||
	m.focus = true
 | 
			
		||||
	m.updateFocus()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Blur removes the focus state on the model.
 | 
			
		||||
func (m *Model) Blur() {
 | 
			
		||||
	m.focus = false
 | 
			
		||||
}
 | 
			
		||||
@@ -67,6 +67,7 @@ type Model struct {
 | 
			
		||||
	Cursor           string
 | 
			
		||||
	BlinkSpeed       time.Duration
 | 
			
		||||
	TextColor        string
 | 
			
		||||
	FocusedTextColor string
 | 
			
		||||
	BackgroundColor  string
 | 
			
		||||
	PlaceholderColor string
 | 
			
		||||
	CursorColor      string
 | 
			
		||||
@@ -114,6 +115,7 @@ func NewModel() Model {
 | 
			
		||||
		Placeholder:      "",
 | 
			
		||||
		BlinkSpeed:       defaultBlinkSpeed,
 | 
			
		||||
		TextColor:        "",
 | 
			
		||||
		FocusedTextColor: "205",
 | 
			
		||||
		PlaceholderColor: "240",
 | 
			
		||||
		CursorColor:      "",
 | 
			
		||||
		EchoCharacter:    '*',
 | 
			
		||||
@@ -182,15 +184,19 @@ func (m Model) Focused() bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Focus sets the focus state on the model.
 | 
			
		||||
func (m *Model) Focus() {
 | 
			
		||||
func (m Model) Focus() tea.Model {
 | 
			
		||||
	m.focus = true
 | 
			
		||||
	m.blink = m.cursorMode == cursorHide // show the cursor unless we've explicitly hidden it
 | 
			
		||||
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Blur removes the focus state on the model.
 | 
			
		||||
func (m *Model) Blur() {
 | 
			
		||||
func (m Model) Blur() tea.Model {
 | 
			
		||||
	m.focus = false
 | 
			
		||||
	m.blink = true
 | 
			
		||||
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reset sets the input to its default state with no input. Returns whether
 | 
			
		||||
@@ -450,8 +456,12 @@ func (m Model) echoTransform(v string) string {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m Model) Init() tea.Cmd {
 | 
			
		||||
	return Blink
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update is the Bubble Tea update loop.
 | 
			
		||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
 | 
			
		||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 | 
			
		||||
	if !m.focus {
 | 
			
		||||
		m.blink = true
 | 
			
		||||
		return m, nil
 | 
			
		||||
@@ -560,9 +570,14 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
 | 
			
		||||
 | 
			
		||||
// View renders the textinput in its current state.
 | 
			
		||||
func (m Model) View() string {
 | 
			
		||||
	prompt := termenv.String(m.Prompt)
 | 
			
		||||
	if m.focus {
 | 
			
		||||
		prompt = prompt.Foreground(color(m.FocusedTextColor))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Placeholder text
 | 
			
		||||
	if len(m.value) == 0 && m.Placeholder != "" {
 | 
			
		||||
		return m.placeholderView()
 | 
			
		||||
		return prompt.String() + m.placeholderView()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	value := m.value[m.offset:m.offsetRight]
 | 
			
		||||
@@ -590,7 +605,7 @@ func (m Model) View() string {
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m.Prompt + v
 | 
			
		||||
	return prompt.String() + v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// placeholderView returns the prompt and placeholder view, if any.
 | 
			
		||||
@@ -610,7 +625,7 @@ func (m Model) placeholderView() string {
 | 
			
		||||
	// The rest of the placeholder text
 | 
			
		||||
	v += m.colorPlaceholder(p[1:])
 | 
			
		||||
 | 
			
		||||
	return m.Prompt + v
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cursorView styles the cursor.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user