mirror of
				https://github.com/Maks1mS/bubbles.git
				synced 2025-10-30 21:37:13 +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