init layout, notifications, tabs & more

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2026-08-17 22:09:32 +02:00
parent f3a1877832
commit 5f99fa7521
68 changed files with 6375 additions and 330 deletions
+109
View File
@@ -0,0 +1,109 @@
// Package router owns every page's own layout.Model for the lifetime of
// the whole app, so switching pages never discards their state - only
// which one is currently active/rendered changes. It slots into the root
// tree's "router" leaf exactly like a single nested layout.Model would
// (see examples/layout/nested), because Model implements layout.Navigable
// itself on top of layout.Pane, delegating everything to whichever page is
// active: ctrl+hjkl, the help bar, and SendMsg/RequestFocusMsg addressed to
// ids inside that page all keep working transparently.
package router
import (
"charm.land/bubbles/v2/key"
tea "charm.land/bubbletea/v2"
"github.com/anotherhadi/ilovetui/examples/layout/fullapp/first"
"github.com/anotherhadi/ilovetui/examples/layout/fullapp/second"
"github.com/anotherhadi/ilovetui/examples/layout/fullapp/third"
"github.com/anotherhadi/ilovetui/layout"
)
// SelectMsg asks Model to switch its active page, by id ("first", "second"
// or "third"). Sent by the sidebar via layout.SendMsg{Target: "router"} -
// it never holds a reference to Model either.
type SelectMsg struct{ Page string }
type Model struct {
active string
pages map[string]layout.Model
}
func New() *Model {
return &Model{
active: "first",
pages: map[string]layout.Model{
"first": first.NewWorkspace(),
"second": second.NewWorkspace(),
"third": third.NewWorkspace(),
},
}
}
func (m *Model) current() layout.Model { return m.pages[m.active] }
func (m *Model) Init() tea.Cmd {
var cmds []tea.Cmd
for _, p := range m.pages {
if cmd := p.Init(); cmd != nil {
cmds = append(cmds, cmd)
}
}
return tea.Batch(cmds...)
}
func (m *Model) Update(msg tea.Msg) (layout.Pane, tea.Cmd) {
if sel, ok := msg.(SelectMsg); ok {
if _, exists := m.pages[sel.Page]; exists {
m.active = sel.Page
}
return m, nil
}
if size, ok := msg.(layout.SizeMsg); ok {
// Every page needs to stay correctly sized even while hidden - the
// outer tree only re-sends SizeMsg when the "router" leaf's own
// Rect changes, never on a plain page switch, so a page that was
// inactive during a resize must still learn about it here, or it
// renders at a stale size whenever it becomes active again.
var cmds []tea.Cmd
for id, p := range m.pages {
updated, cmd := p.Update(size)
m.pages[id] = updated.(layout.Model)
if cmd != nil {
cmds = append(cmds, cmd)
}
}
return m, tea.Batch(cmds...)
}
updated, cmd := m.current().Update(msg)
m.pages[m.active] = updated.(layout.Model)
return m, cmd
}
func (m *Model) View() string {
return m.current().View()
}
// Leaves, MoveFocus, Route, Focus and FocusedHelp implement
// layout.Navigable, delegating to whichever page is currently active.
func (m *Model) Leaves() []layout.LeafRect {
return m.current().Leaves()
}
func (m *Model) MoveFocus(dir layout.FocusDirection) bool {
return m.current().MoveFocus(dir)
}
func (m *Model) Route(target string, msg tea.Msg) (bool, tea.Cmd) {
return m.current().Route(target, msg)
}
func (m *Model) Focus(id string) (bool, tea.Cmd) {
return m.current().Focus(id)
}
func (m *Model) FocusedHelp() []key.Binding {
return m.current().FocusedHelp()
}