diff --git a/config/config.go b/config/config.go index 2af5601..49b919d 100644 --- a/config/config.go +++ b/config/config.go @@ -86,6 +86,23 @@ type Config struct { } } + PopupList struct { + List struct { + Focused ThemeItemConfig + Blurred ThemeItemConfig + } + Item struct { + Focused ThemeItemConfig + Blurred ThemeItemConfig + Selected ThemeItemConfig + } + ItemDetail struct { + Focused ThemeItemConfig + Blurred ThemeItemConfig + Selected ThemeItemConfig + } + } + Post struct { Author ThemeItemConfig Subject ThemeItemConfig @@ -165,6 +182,7 @@ func SetDefaults(cacheDir string) { viper.SetDefault("Debug", "true") viper.SetDefault("Log", path.Join(cacheDir, "gobbs.log")) + // --- DialogBox --- // DialogBox Window:Focused viper.SetDefault("Theme.DialogBox.Window.Focused.Margin", []int{0, 0, 0, 0}) @@ -219,6 +237,7 @@ func SetDefaults(cacheDir string) { viper.SetDefault("Theme.DialogBox.Bottombar.Foreground", lipgloss.AdaptiveColor{Light: "#aaaaaa", Dark: "#999999"}) + // --- ErrorDialogBox --- // ErrorDialogBox Window:Focused viper.SetDefault("Theme.ErrorDialogBox.Window.Focused.Margin", []int{0, 0, 0, 0}) @@ -273,6 +292,7 @@ func SetDefaults(cacheDir string) { viper.SetDefault("Theme.ErrorDialogBox.Bottombar.Foreground", lipgloss.AdaptiveColor{Light: "#aaaaaa", Dark: "#999999"}) + // --- PostsList --- // PostsList List:Focused viper.SetDefault("Theme.PostsList.List.Focused.Margin", []int{0, 0, 0, 0}) @@ -349,6 +369,84 @@ func SetDefaults(cacheDir string) { viper.SetDefault("Theme.PostsList.ItemDetail.Selected.Foreground", lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}) + // --- PopupList --- + // PopupList List:Focused + viper.SetDefault("Theme.PopupList.List.Focused.Margin", + []int{0, 0, 0, 0}) + viper.SetDefault("Theme.PopupList.List.Focused.Padding", + []int{1, 1, 1, 1}) + viper.SetDefault("Theme.PopupList.List.Focused.Border.Border", + lipgloss.HiddenBorder()) + viper.SetDefault("Theme.PopupList.List.Focused.Border.Sides", + []bool{true, true, true, true}, + ) + viper.SetDefault("Theme.PopupList.List.Focused.Border.Foreground", + lipgloss.AdaptiveColor{Light: "#00ffff", Dark: "#00ffff"}) + + // PopupList List:Blurred + viper.SetDefault("Theme.PopupList.List.Blurred.Margin", + []int{0, 0, 0, 0}) + viper.SetDefault("Theme.PopupList.List.Blurred.Padding", + []int{1, 1, 1, 1}) + viper.SetDefault("Theme.PopupList.List.Blurred.Border.Border", + lipgloss.HiddenBorder()) + viper.SetDefault("Theme.PopupList.List.Blurred.Border.Sides", + []bool{true, true, true, true}, + ) + viper.SetDefault("Theme.PopupList.List.Blurred.Border.Foreground", + lipgloss.AdaptiveColor{Light: "#cccccc", Dark: "#333333"}) + + // PopupList Item:Focused + viper.SetDefault("Theme.PopupList.Item.Focused.Padding", + []int{0, 0, 0, 2}) + viper.SetDefault("Theme.PopupList.Item.Focused.Foreground", + lipgloss.AdaptiveColor{Light: "#333333", Dark: "#cccccc"}) + + // PopupList Item:Blurred + viper.SetDefault("Theme.PopupList.Item.Blurred.Padding", + []int{0, 0, 0, 2}) + viper.SetDefault("Theme.PopupList.Item.Blurred.Foreground", + lipgloss.AdaptiveColor{Light: "#cccccc", Dark: "#333333"}) + + // PopupList Item:Selected + viper.SetDefault("Theme.PopupList.Item.Selected.Padding", + []int{0, 0, 0, 1}) + viper.SetDefault("Theme.PopupList.Item.Selected.Border.Border", + lipgloss.NormalBorder()) + viper.SetDefault("Theme.PopupList.Item.Selected.Border.Sides", + []bool{false, false, false, true}, + ) + viper.SetDefault("Theme.PopupList.Item.Selected.Border.Foreground", + lipgloss.AdaptiveColor{Light: "#ffd500", Dark: "#ffd500"}) + viper.SetDefault("Theme.PopupList.Item.Selected.Foreground", + lipgloss.AdaptiveColor{Light: "#F25D94", Dark: "#F25D94"}) + + // PopupList ItemDetail:Focused + viper.SetDefault("Theme.PopupList.ItemDetail.Focused.Padding", + []int{0, 0, 0, 2}) + viper.SetDefault("Theme.PopupList.ItemDetail.Focused.Foreground", + lipgloss.AdaptiveColor{Light: "#666666", Dark: "#4d4d4d"}) + + // PopupList ItemDetail:Blurred + viper.SetDefault("Theme.PopupList.ItemDetail.Blurred.Padding", + []int{0, 0, 0, 2}) + viper.SetDefault("Theme.PopupList.ItemDetail.Blurred.Foreground", + lipgloss.AdaptiveColor{Light: "#666666", Dark: "#4d4d4d"}) + + // PopupList ItemDetail:Selected + viper.SetDefault("Theme.PopupList.ItemDetail.Selected.Padding", + []int{0, 0, 0, 1}) + viper.SetDefault("Theme.PopupList.ItemDetail.Selected.Border.Border", + lipgloss.NormalBorder()) + viper.SetDefault("Theme.PopupList.ItemDetail.Selected.Border.Sides", + []bool{false, false, false, true}, + ) + viper.SetDefault("Theme.PopupList.ItemDetail.Selected.Border.Foreground", + lipgloss.AdaptiveColor{Light: "#ffd500", Dark: "#ffd500"}) + viper.SetDefault("Theme.PopupList.ItemDetail.Selected.Foreground", + lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}) + + // --- Post --- // Post Author viper.SetDefault("Theme.Post.Author.Padding", []int{0, 1, 0, 1}) diff --git a/ui/theme/theme.go b/ui/theme/theme.go index 4bc0203..ffa5b64 100644 --- a/ui/theme/theme.go +++ b/ui/theme/theme.go @@ -30,6 +30,23 @@ type Theme struct { Bottombar lipgloss.Style } + PopupList struct { + List struct { + Focused lipgloss.Style + Blurred lipgloss.Style + } + Item struct { + Focused lipgloss.Style + Blurred lipgloss.Style + Selected lipgloss.Style + } + ItemDetail struct { + Focused lipgloss.Style + Blurred lipgloss.Style + Selected lipgloss.Style + } + } + PostsList struct { List struct { Focused lipgloss.Style @@ -99,6 +116,23 @@ func New(cfg *config.Config) *Theme { t.PostsList.ItemDetail.Selected = t.fromConfig(&cfg.Theme.PostsList.ItemDetail.Selected) + t.PopupList.List.Focused = + t.fromConfig(&cfg.Theme.PopupList.List.Focused) + t.PopupList.List.Blurred = + t.fromConfig(&cfg.Theme.PopupList.List.Blurred) + t.PopupList.Item.Focused = + t.fromConfig(&cfg.Theme.PopupList.Item.Focused) + t.PopupList.Item.Blurred = + t.fromConfig(&cfg.Theme.PopupList.Item.Blurred) + t.PopupList.Item.Selected = + t.fromConfig(&cfg.Theme.PopupList.Item.Selected) + t.PopupList.ItemDetail.Focused = + t.fromConfig(&cfg.Theme.PopupList.ItemDetail.Focused) + t.PopupList.ItemDetail.Blurred = + t.fromConfig(&cfg.Theme.PopupList.ItemDetail.Blurred) + t.PopupList.ItemDetail.Selected = + t.fromConfig(&cfg.Theme.PopupList.ItemDetail.Selected) + t.Post.Author = t.fromConfig(&cfg.Theme.Post.Author) t.Post.Subject = diff --git a/ui/windows/popuplist/handlers.go b/ui/windows/popuplist/handlers.go new file mode 100644 index 0000000..27da531 --- /dev/null +++ b/ui/windows/popuplist/handlers.go @@ -0,0 +1,55 @@ +package popuplist + +import ( + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/mrusme/gobbs/ui/cmd" +) + +func handleSelect(mi interface{}) (bool, []tea.Cmd) { + var m *Model = mi.(*Model) + var cmds []tea.Cmd + + cmds = append(cmds, cmd.New( + cmd.WMCloseWin, + WIN_ID, + cmd.Arg{Name: "selectionID", Value: m.selectionID}, + cmd.Arg{Name: "selected", Value: m.list.SelectedItem()}, + ).Tea()) + return true, cmds +} + +func handleViewResize(mi interface{}) (bool, []tea.Cmd) { + var m *Model = mi.(*Model) + var cmds []tea.Cmd + + m.ctx.Logger.Debugf("received WindowSizeMsg: %vx%v\n", m.tk.ViewWidth(), m.tk.ViewHeight()) + listWidth := m.tk.ViewWidth() - 2 + listHeight := m.tk.ViewHeight() - 1 + + m.ctx.Theme.PopupList.List.Focused.Width(listWidth) + m.ctx.Theme.PopupList.List.Blurred.Width(listWidth) + m.ctx.Theme.PopupList.List.Focused.Height(listHeight) + m.ctx.Theme.PopupList.List.Blurred.Height(listHeight) + m.list.SetSize( + listWidth-2, + listHeight-2, + ) + + return false, cmds +} + +func handleWinOpenCmd(mi interface{}, c cmd.Command) (bool, []tea.Cmd) { + var m *Model = mi.(*Model) + var cmds []tea.Cmd + + if c.Target == WIN_ID { + m.ctx.Logger.Debug("got own WinOpen command") + m.selectionID = c.GetArg("selectionID").(string) + m.items = c.GetArg("items").([]list.Item) + m.list.SetItems(m.items) + return true, cmds + } + + return false, cmds +} diff --git a/ui/windows/popuplist/popuplist.go b/ui/windows/popuplist/popuplist.go new file mode 100644 index 0000000..1bdf9ac --- /dev/null +++ b/ui/windows/popuplist/popuplist.go @@ -0,0 +1,84 @@ +package popuplist + +import ( + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/mrusme/gobbs/aggregator" + "github.com/mrusme/gobbs/ui/ctx" + "github.com/mrusme/gobbs/ui/toolkit" +) + +var ( + WIN_ID = "popuplist" +) + +type Model struct { + ctx *ctx.Ctx + tk *toolkit.ToolKit + + selectionID string + list list.Model + items []list.Item + + a *aggregator.Aggregator +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func NewModel(c *ctx.Ctx) Model { + m := Model{ + ctx: c, + tk: toolkit.New( + WIN_ID, + c.Theme, + c.Logger, + ), + } + + listDelegate := list.NewDefaultDelegate() + listDelegate.Styles.NormalTitle = m.ctx.Theme.PopupList.Item.Focused + listDelegate.Styles.DimmedTitle = m.ctx.Theme.PopupList.Item.Blurred + listDelegate.Styles.SelectedTitle = m.ctx.Theme.PopupList.Item.Selected + listDelegate.Styles.NormalDesc = m.ctx.Theme.PopupList.ItemDetail.Focused + listDelegate.Styles.DimmedDesc = m.ctx.Theme.PopupList.ItemDetail.Blurred + listDelegate.Styles.SelectedDesc = m.ctx.Theme.PopupList.ItemDetail.Selected + + m.list = list.New(m.items, listDelegate, 0, 0) + m.list.SetShowTitle(false) + m.list.SetShowStatusBar(false) + + m.tk.KeymapAdd("enter", "choose selection", "enter") + + m.a, _ = aggregator.New(m.ctx) + + m.tk.SetViewFunc(buildView) + m.tk.SetMsgHandling(toolkit.MsgHandling{ + OnKeymapKey: []toolkit.MsgHandlingKeymapKey{ + { + ID: "enter", + Handler: handleSelect, + }, + }, + OnViewResize: handleViewResize, + OnWinOpenCmd: handleWinOpenCmd, + }) + + return m +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + + ret, cmds := m.tk.HandleMsg(&m, msg) + if ret { + return m, tea.Batch(cmds...) + } + + var cmd tea.Cmd + m.list, cmd = m.list.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) +} diff --git a/ui/windows/popuplist/view.go b/ui/windows/popuplist/view.go new file mode 100644 index 0000000..fbae7c6 --- /dev/null +++ b/ui/windows/popuplist/view.go @@ -0,0 +1,32 @@ +package popuplist + +import "github.com/charmbracelet/lipgloss" + +func (m Model) View() string { + return m.tk.View(&m, true) +} + +func buildView(mi interface{}, cached bool) string { + var m *Model = mi.(*Model) + + if vcache := m.tk.DefaultCaching(cached); vcache != "" { + m.ctx.Logger.Debugln("Cached View()") + return vcache + } + m.ctx.Logger.Debugln("View()") + m.ctx.Logger.Debugf("IsFocused: %v\n", m.tk.IsFocused()) + + var style lipgloss.Style + if m.tk.IsFocused() { + style = m.ctx.Theme.PopupList.List.Focused + } else { + style = m.ctx.Theme.PopupList.List.Blurred + } + l := style.Render(m.list.View()) + + return m.tk.Dialog( + "Select", + l, + false, + ) +}