Files
ilovetui/examples/layout/messaging/main.go
T

133 lines
3.4 KiB
Go

// Command messaging demonstrates the two ways panes talk without holding a
// reference to each other: "control" sends arbitrary commands to "editor"
// by id via SendMsg (1/2/3 keys), and asks layout to move focus there via
// RequestFocusMsg (enter key) - the same pattern a real sidebar would use
// to both drive and jump to a content pane it selected.
package main
import (
"fmt"
"os"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/layout"
)
// commandMsg is what "control" sends to "editor" - an app-defined message,
// entirely opaque to layout itself (see SendMsg).
type commandMsg struct{ text string }
type controlPane struct {
id string // learned from SizeMsg.ID, needed as RequestFocusMsg.Source
w, h int
focused bool
}
func (p *controlPane) Init() tea.Cmd { return nil }
func (p *controlPane) Update(msg tea.Msg) (layout.Pane, tea.Cmd) {
switch msg := msg.(type) {
case layout.SizeMsg:
p.id, p.w, p.h = msg.ID, msg.Width, msg.Height
case layout.FocusMsg:
p.focused = true
case layout.BlurMsg:
p.focused = false
case tea.KeyPressMsg:
switch msg.String() {
case "1", "2", "3":
text := "command " + msg.String()
return p, func() tea.Msg {
return layout.SendMsg{Target: "editor", Msg: commandMsg{text: text}}
}
case "enter":
return p, func() tea.Msg {
return layout.RequestFocusMsg{Source: p.id, Target: "editor"}
}
}
}
return p, nil
}
func (p *controlPane) View() string {
content := "control\n\n1/2/3: send a command\nenter: focus editor"
inner := lipgloss.NewStyle().Width(p.w - 2).Height(p.h - 2).Render(content)
return layout.Bordered(p.focused, p.w, p.h, inner)
}
type editorPane struct {
w, h int
focused bool
last string
}
func (p *editorPane) Init() tea.Cmd { return nil }
func (p *editorPane) Update(msg tea.Msg) (layout.Pane, tea.Cmd) {
switch msg := msg.(type) {
case layout.SizeMsg:
p.w, p.h = msg.Width, msg.Height
case layout.FocusMsg:
p.focused = true
case layout.BlurMsg:
p.focused = false
case commandMsg:
p.last = msg.text
}
return p, nil
}
func (p *editorPane) View() string {
last := p.last
if last == "" {
last = "(nothing yet)"
}
content := fmt.Sprintf("editor\n\nlast command received:\n%s", last)
inner := lipgloss.NewStyle().Width(p.w - 2).Height(p.h - 2).Render(content)
return layout.Bordered(p.focused, p.w, p.h, inner)
}
// model is the actual top-level tea.Model: layout itself reserves no quit
// key (that's an app policy, not layout's to make), so the host wraps it
// and handles ctrl+c/q itself, same as any other custom component in this
// repo (see examples/tabs).
type model struct {
layout layout.Model
}
func (m model) Init() tea.Cmd { return m.layout.Init() }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if key, ok := msg.(tea.KeyPressMsg); ok {
switch key.String() {
case "ctrl+c", "q":
return m, tea.Quit
}
}
updated, cmd := m.layout.Update(msg)
m.layout = updated.(layout.Model)
return m, cmd
}
func (m model) View() tea.View {
view := tea.NewView(m.layout.View())
view.AltScreen = true
return view
}
func main() {
root := layout.HSplit(0.35,
layout.Leaf("control", &controlPane{}),
layout.Leaf("editor", &editorPane{}),
)
m := model{layout: layout.New(root, layout.AsRoot())}
if _, err := tea.NewProgram(m).Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}