diff --git a/tui/app.go b/tui/app.go index 74f7f2b..37656ba 100644 --- a/tui/app.go +++ b/tui/app.go @@ -1,6 +1,7 @@ package tui import ( + "fmt" "strings" tea "github.com/charmbracelet/bubbletea" @@ -11,13 +12,19 @@ type ( Size Size Header HeaderModel Screen tea.Model + Screens []tea.Model } ) func NewApp() AppModel { + screens := []tea.Model{ + NewHomeScreen(), + NewNotificationScreen(), + } return AppModel{ Header: NewHeader(), - Screen: NewHomeScreen(), + Screen: screens[0], + Screens: screens, } } @@ -30,7 +37,11 @@ func (m AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: - m.Size = NewSizeFromWindow(msg) + m.SetSize(msg.Width, msg.Height) + case SwitchHomeScreenMsg: + m.Screen = m.Screens[0] + case SwitchNotificationScreenMsg: + m.Screen = m.Screens[1] } m.Header, headerCmd = m.Header.Update(msg) @@ -39,8 +50,23 @@ func (m AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmd, headerCmd, screenCmd) } +func (m *AppModel) SetSize(width, height int) { + m.Size = NewSize(width, height) + m.Header.SetWidth(width) + screenHeight := height - m.Header.Size.Height + switch screen := m.Screen.(type) { + case Sized: + m.Screen = screen.SetSize(width, screenHeight) + case SizedWidth: + m.Screen = screen.SetWidth(width) + case SizedHeight: + m.Screen = screen.SetHeight(screenHeight) + } +} + func (m AppModel) View() string { return strings.Join([]string{ + fmt.Sprintf("%d x %d", m.Size.Width, m.Size.Height), m.Header.View(), m.Screen.View(), }, "\n") diff --git a/tui/header.go b/tui/header.go index e9a4973..dcb7279 100644 --- a/tui/header.go +++ b/tui/header.go @@ -9,13 +9,15 @@ import ( type ( HeaderModel struct{ + Size Size Tabs []HeaderTabModel - Width int } HeaderTabModel struct{ - Current bool + Size Size + Active bool Name string + Key rune Unread int } ) @@ -23,16 +25,16 @@ type ( func NewHeader() HeaderModel { return HeaderModel{ Tabs: []HeaderTabModel { - NewHeaderTab("Home", 10, true), - NewHeaderTab("Notifications", 3, false), - NewHeaderTab("Lists", 5, false), - NewHeaderTab("Private", 0, false), + NewHeaderTab("Home", 'H', 10, true), + NewHeaderTab("Notifications", 'N', 3, false), + NewHeaderTab("Lists", 'L', 5, false), + NewHeaderTab("Private", 'P', 0, false), }, } } -func NewHeaderTab(name string, unread int, current bool) HeaderTabModel { - return HeaderTabModel{ Name: name, Unread: unread, Current: current } +func NewHeaderTab(name string, key rune, unread int, current bool) HeaderTabModel { + return HeaderTabModel{ Name: name, Key: key, Unread: unread, Active: current } } var ( @@ -59,6 +61,7 @@ var ( } headerTabStyle = lipgloss.NewStyle(). + Padding(0, 1). Border(tabBorder, true) activeHeaderTabStyle = headerTabStyle. @@ -72,43 +75,80 @@ func (m HeaderModel) Init() tea.Cmd { func (m HeaderModel) Update(msg tea.Msg) (HeaderModel, tea.Cmd) { var cmd tea.Cmd - switch msg := msg.(type) { - case Size: - m.Width = msg.Width + switch msg.(type) { + case SwitchHomeScreenMsg: + m.SetActive(0) + case SwitchNotificationScreenMsg: + m.SetActive(1) } return m, cmd } +func (m *HeaderModel) SetActive(tabIndex int) { + for i := range m.Tabs { + m.Tabs[i].Active = i == tabIndex + } +} + +func (m *HeaderModel) SetWidth(width int) { + tabWidth := width / len(m.Tabs) + var maxTabHeight int + for i := range m.Tabs { + m.Tabs[i].SetSize(tabWidth) + maxTabHeight = max(maxTabHeight, m.Tabs[i].Size.Height) + } + m.Size = Size{ + Width: width, + Height: 3+maxTabHeight, + } +} + func (m HeaderModel) View() string { header := lipgloss.NewStyle(). Padding(1, 0). Align(lipgloss.Center). - Width(m.Width) + Width(m.Size.Width) renderedTabs := make([]string, len(m.Tabs)) for i, tab := range m.Tabs { renderedTabs[i] = tab.View() } + joinedTabs := lipgloss.JoinHorizontal(lipgloss.Bottom, renderedTabs...) - return header.Render(renderedTabs...) + return header.Render(joinedTabs) } func (m HeaderTabModel) Init() tea.Cmd { return nil } -func (m HeaderTabModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m HeaderTabModel) Update(msg tea.Msg) (HeaderTabModel, tea.Cmd) { var cmd tea.Cmd return m, cmd } -func (m HeaderTabModel) View() string { - content := lipgloss.JoinHorizontal(lipgloss.Center, m.Name, fmt.Sprintf("(%d)", m.Unread)) +func (m *HeaderTabModel) SetSize(width int) { + m.Size = Size{ + Width: width, + Height: 1, + } +} - if m.Current { - return lipgloss.NewStyle().Underline(true).Render(content) +func (m HeaderTabModel) View() string { + contentWidth := m.Size.Width - 4 + content := fmt.Sprintf("[%c] %s - %d", m.Key, m.Name, m.Unread) + if len(content) > contentWidth { + content = fmt.Sprintf("[%c] %d", m.Key, m.Unread) + if len(content) > contentWidth { + content = fmt.Sprintf("[%c]", m.Key) + } + } + content = lipgloss.PlaceHorizontal(contentWidth, lipgloss.Center, content) + + if m.Active { + return activeHeaderTabStyle.Render(content) } - return content + return headerTabStyle.Render(content) } diff --git a/tui/home.go b/tui/home.go index 41a8e78..7fbee99 100644 --- a/tui/home.go +++ b/tui/home.go @@ -11,8 +11,14 @@ type ( HomeScreenModel struct{ Size Size } + + SwitchHomeScreenMsg struct{} ) +func SwitchHomeScreen() tea.Msg { + return SwitchHomeScreenMsg{} +} + func NewHomeScreen() HomeScreenModel { return HomeScreenModel{} } @@ -29,6 +35,8 @@ func (m HomeScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.String() { case "q", "ctrl-c": cmd = tea.Quit + case "N": + cmd = SwitchNotificationScreen } case Size: m.Size = msg @@ -37,6 +45,11 @@ func (m HomeScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } +func (m HomeScreenModel) SetSize(width, height int) HomeScreenModel { + m.Size = NewSize(width, height) + return m +} + func (m HomeScreenModel) View() string { header := lipgloss.NewStyle(). Align(lipgloss.Center). diff --git a/tui/notifications.go b/tui/notifications.go new file mode 100644 index 0000000..a266250 --- /dev/null +++ b/tui/notifications.go @@ -0,0 +1,43 @@ +package tui + +import tea "github.com/charmbracelet/bubbletea" + +type ( + NotificationScreenModel struct{ + Size Size + } + + SwitchNotificationScreenMsg struct{} +) + +func SwitchNotificationScreen() tea.Msg { + return SwitchNotificationScreenMsg{} +} + +func NewNotificationScreen() NotificationScreenModel { + return NotificationScreenModel{} +} + +func (m NotificationScreenModel) Init() tea.Cmd { + return nil +} + +func (m NotificationScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "q", "ctrl-c": + cmd = tea.Quit + case "H": + cmd = SwitchHomeScreen + } + } + + return m, cmd +} + +func (m NotificationScreenModel) View() string { + return "notification screen" +} diff --git a/tui/size.go b/tui/size.go index f66bdc9..831dd29 100644 --- a/tui/size.go +++ b/tui/size.go @@ -2,9 +2,21 @@ package tui import tea "github.com/charmbracelet/bubbletea" -type Size struct{ - Width, Height int -} +type ( + Size struct{ + Width, Height int + } + + Sized interface{ + SetSize(width, height int) tea.Model + } + SizedWidth interface{ + SetWidth(width int) tea.Model + } + SizedHeight interface{ + SetHeight(height int) tea.Model + } +) func NewSize(width, height int) Size { return Size { Width: width, Height: height }