feat: wip tui updates

This commit is contained in:
cato 2025-12-07 11:42:57 +01:00
parent 5d7e9d79c4
commit dde2d98a84
5 changed files with 158 additions and 24 deletions

View file

@ -1,6 +1,7 @@
package tui package tui
import ( import (
"fmt"
"strings" "strings"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@ -11,13 +12,19 @@ type (
Size Size Size Size
Header HeaderModel Header HeaderModel
Screen tea.Model Screen tea.Model
Screens []tea.Model
} }
) )
func NewApp() AppModel { func NewApp() AppModel {
screens := []tea.Model{
NewHomeScreen(),
NewNotificationScreen(),
}
return AppModel{ return AppModel{
Header: NewHeader(), 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) { switch msg := msg.(type) {
case tea.WindowSizeMsg: 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) 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) 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 { func (m AppModel) View() string {
return strings.Join([]string{ return strings.Join([]string{
fmt.Sprintf("%d x %d", m.Size.Width, m.Size.Height),
m.Header.View(), m.Header.View(),
m.Screen.View(), m.Screen.View(),
}, "\n") }, "\n")

View file

@ -9,13 +9,15 @@ import (
type ( type (
HeaderModel struct{ HeaderModel struct{
Size Size
Tabs []HeaderTabModel Tabs []HeaderTabModel
Width int
} }
HeaderTabModel struct{ HeaderTabModel struct{
Current bool Size Size
Active bool
Name string Name string
Key rune
Unread int Unread int
} }
) )
@ -23,16 +25,16 @@ type (
func NewHeader() HeaderModel { func NewHeader() HeaderModel {
return HeaderModel{ return HeaderModel{
Tabs: []HeaderTabModel { Tabs: []HeaderTabModel {
NewHeaderTab("Home", 10, true), NewHeaderTab("Home", 'H', 10, true),
NewHeaderTab("Notifications", 3, false), NewHeaderTab("Notifications", 'N', 3, false),
NewHeaderTab("Lists", 5, false), NewHeaderTab("Lists", 'L', 5, false),
NewHeaderTab("Private", 0, false), NewHeaderTab("Private", 'P', 0, false),
}, },
} }
} }
func NewHeaderTab(name string, unread int, current bool) HeaderTabModel { func NewHeaderTab(name string, key rune, unread int, current bool) HeaderTabModel {
return HeaderTabModel{ Name: name, Unread: unread, Current: current } return HeaderTabModel{ Name: name, Key: key, Unread: unread, Active: current }
} }
var ( var (
@ -59,6 +61,7 @@ var (
} }
headerTabStyle = lipgloss.NewStyle(). headerTabStyle = lipgloss.NewStyle().
Padding(0, 1).
Border(tabBorder, true) Border(tabBorder, true)
activeHeaderTabStyle = headerTabStyle. activeHeaderTabStyle = headerTabStyle.
@ -72,43 +75,80 @@ func (m HeaderModel) Init() tea.Cmd {
func (m HeaderModel) Update(msg tea.Msg) (HeaderModel, tea.Cmd) { func (m HeaderModel) Update(msg tea.Msg) (HeaderModel, tea.Cmd) {
var cmd tea.Cmd var cmd tea.Cmd
switch msg := msg.(type) { switch msg.(type) {
case Size: case SwitchHomeScreenMsg:
m.Width = msg.Width m.SetActive(0)
case SwitchNotificationScreenMsg:
m.SetActive(1)
} }
return m, cmd 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 { func (m HeaderModel) View() string {
header := lipgloss.NewStyle(). header := lipgloss.NewStyle().
Padding(1, 0). Padding(1, 0).
Align(lipgloss.Center). Align(lipgloss.Center).
Width(m.Width) Width(m.Size.Width)
renderedTabs := make([]string, len(m.Tabs)) renderedTabs := make([]string, len(m.Tabs))
for i, tab := range m.Tabs { for i, tab := range m.Tabs {
renderedTabs[i] = tab.View() renderedTabs[i] = tab.View()
} }
joinedTabs := lipgloss.JoinHorizontal(lipgloss.Bottom, renderedTabs...)
return header.Render(renderedTabs...) return header.Render(joinedTabs)
} }
func (m HeaderTabModel) Init() tea.Cmd { func (m HeaderTabModel) Init() tea.Cmd {
return nil 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 var cmd tea.Cmd
return m, cmd return m, cmd
} }
func (m *HeaderTabModel) SetSize(width int) {
m.Size = Size{
Width: width,
Height: 1,
}
}
func (m HeaderTabModel) View() string { func (m HeaderTabModel) View() string {
content := lipgloss.JoinHorizontal(lipgloss.Center, m.Name, fmt.Sprintf("(%d)", m.Unread)) 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.Current { if m.Active {
return lipgloss.NewStyle().Underline(true).Render(content) return activeHeaderTabStyle.Render(content)
} }
return content return headerTabStyle.Render(content)
} }

View file

@ -11,8 +11,14 @@ type (
HomeScreenModel struct{ HomeScreenModel struct{
Size Size Size Size
} }
SwitchHomeScreenMsg struct{}
) )
func SwitchHomeScreen() tea.Msg {
return SwitchHomeScreenMsg{}
}
func NewHomeScreen() HomeScreenModel { func NewHomeScreen() HomeScreenModel {
return HomeScreenModel{} return HomeScreenModel{}
} }
@ -29,6 +35,8 @@ func (m HomeScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.String() { switch msg.String() {
case "q", "ctrl-c": case "q", "ctrl-c":
cmd = tea.Quit cmd = tea.Quit
case "N":
cmd = SwitchNotificationScreen
} }
case Size: case Size:
m.Size = msg m.Size = msg
@ -37,6 +45,11 @@ func (m HomeScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd return m, cmd
} }
func (m HomeScreenModel) SetSize(width, height int) HomeScreenModel {
m.Size = NewSize(width, height)
return m
}
func (m HomeScreenModel) View() string { func (m HomeScreenModel) View() string {
header := lipgloss.NewStyle(). header := lipgloss.NewStyle().
Align(lipgloss.Center). Align(lipgloss.Center).

43
tui/notifications.go Normal file
View file

@ -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"
}

View file

@ -2,10 +2,22 @@ package tui
import tea "github.com/charmbracelet/bubbletea" import tea "github.com/charmbracelet/bubbletea"
type Size struct{ type (
Size struct{
Width, Height int 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 { func NewSize(width, height int) Size {
return Size { Width: width, Height: height } return Size { Width: width, Height: height }
} }