feat(config): granular control over theme config (#564)

This commit is contained in:
Ayyan 2025-06-19 22:27:21 -04:00 • committed by GitHub
parent e97e697a03
commit 7eff390646
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 94 additions and 66 deletions

View file

@ -28,7 +28,7 @@ func newGuildsTree(cfg *config.Config) *guildsTree {
cfg: cfg,
}
gt.Box = ui.NewConfiguredBox(gt.Box, &cfg.Theme)
gt.Box = ui.ConfigureBox(gt.Box, &cfg.Theme)
gt.
SetRoot(tview.NewTreeNode("")).
@ -63,9 +63,11 @@ func (gt *guildsTree) createFolderNode(folder gateway.GuildFolder) {
}
func (gt *guildsTree) createGuildNode(n *tview.TreeNode, g discord.Guild) {
guildNode := tview.NewTreeNode(g.Name)
guildNode.SetReference(g.ID)
guildNode.SetColor(tcell.GetColor(gt.cfg.Theme.GuildsTree.GuildColor))
style := gt.cfg.Theme.GuildsTree.GuildStyle.Style
guildNode := tview.NewTreeNode(g.Name).
SetReference(g.ID).
SetTextStyle(style).
SetSelectedTextStyle(style.Reverse(true))
n.AddChild(guildNode)
}
@ -110,9 +112,11 @@ func (gt *guildsTree) createChannelNode(node *tview.TreeNode, channel discord.Ch
}
}
style := gt.cfg.Theme.GuildsTree.ChannelStyle.Style
channelNode := tview.NewTreeNode(gt.channelToString(channel)).
SetReference(channel.ID).
SetColor(tcell.GetColor(gt.cfg.Theme.GuildsTree.ChannelColor))
SetTextStyle(style).
SetSelectedTextStyle(style.Reverse(true))
node.AddChild(channelNode)
}

View file

@ -48,7 +48,7 @@ func newMessageInput(cfg *config.Config) *messageInput {
autocomplete: tview.NewList(),
}
mi.Box = ui.NewConfiguredBox(mi.Box, &cfg.Theme)
mi.Box = ui.ConfigureBox(mi.Box, &cfg.Theme)
mi.
SetTextStyle(tcell.StyleDefault.Background(tcell.GetColor(cfg.Theme.BackgroundColor))).
SetClipboard(func(s string) {
@ -59,7 +59,7 @@ func newMessageInput(cfg *config.Config) *messageInput {
}).
SetInputCapture(mi.onInputCapture)
mi.autocomplete.Box = ui.NewConfiguredBox(mi.autocomplete.Box, &mi.cfg.Theme)
mi.autocomplete.Box = ui.ConfigureBox(mi.autocomplete.Box, &mi.cfg.Theme)
mi.autocomplete.
ShowSecondaryText(false).
SetSelectedStyle(tcell.StyleDefault.Background(tcell.ColorWhite).Foreground(tcell.ColorBlack)).

View file

@ -46,7 +46,7 @@ func newMessagesText(cfg *config.Config) *messagesText {
cfg: cfg,
}
mt.Box = ui.NewConfiguredBox(mt.Box, &cfg.Theme)
mt.Box = ui.ConfigureBox(mt.Box, &cfg.Theme)
mt.
SetDynamicColors(true).
SetRegions(true).
@ -455,7 +455,7 @@ func (mt *messagesText) showUrlSelector(urls []string, attachments []discord.Att
SetHighlightFullLine(true).
ShowSecondaryText(false).
SetDoneFunc(done)
list.Box = ui.NewConfiguredBox(list.Box, &mt.cfg.Theme)
list.Box = ui.ConfigureBox(list.Box, &mt.cfg.Theme)
list.
SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {

View file

@ -12,7 +12,6 @@ import (
"github.com/diamondburned/arikawa/v3/utils/httputil/httpdriver"
"github.com/diamondburned/arikawa/v3/utils/ws"
"github.com/diamondburned/ningen/v3"
"github.com/gdamore/tcell/v2"
)
func openState(token string) error {
@ -76,7 +75,10 @@ func onReady(r *gateway.ReadyEvent) {
root := app.guildsTree.GetRoot()
root.ClearChildren()
dmNode := tview.NewTreeNode("Direct Messages").SetColor(tcell.GetColor(app.cfg.Theme.GuildsTree.PrivateChannelColor))
style := app.cfg.Theme.GuildsTree.PrivateChannelStyle.Style
dmNode := tview.NewTreeNode("Direct Messages").
SetTextStyle(style).
SetSelectedTextStyle(style.Reverse(true))
root.AddChild(dmNode)
for _, folder := range r.UserSettings.GuildFolders {

View file

@ -106,29 +106,28 @@ down = "Down"
background_color = "default"
[theme.title]
# The values can be one of the following: `"left"`, `"center"`, and `"right"`.
# `"left"`, `"center"`, or `"right"`.
alignment = "left"
# style supports foreground and background.
style = { foreground = "default" }
active_style = { foreground = "green" }
normal_style = { foreground = "default", background = "default" }
active_style = { foreground = "green", background = "default" }
[theme.border]
enabled = true
# [top, bottom, left, right]
padding = [0, 0, 1, 1]
# The values can be one of the following: `"plain"`, `"round"`, `"thick"`, and `"double"`.
# `"plain"`, `"round"`, `"thick"`, or `"double"`.
set = "plain"
style = { foreground = "default" }
active_style = { foreground = "green" }
normal_style = { foreground = "default", background = "default" }
active_style = { foreground = "green", background = "default" }
[theme.guilds_tree]
auto_expand_folders = true
# Give tree-like shape
graphics = true
graphics_color = "default"
private_channel_color = "white"
guild_color = "white"
channel_color = "white"
private_channel_style = { foreground = "white", background = "default" }
guild_style = { foreground = "white", background = "default" }
channel_style = { foreground = "white", background = "default" }
[theme.messages_text]
# Set to false to show messages with usernames instead of nicknames
@ -138,11 +137,11 @@ show_user_colors = true
reply_indicator = ">"
forwarded_indicator = "<"
author_style = { foreground = "aqua" }
mention_style = { foreground = "blue" }
emoji_style = { foreground = "green" }
url_style = { foreground = "blue" }
attachment_style = { foreground = "yellow" }
author_style = { foreground = "aqua", background = "default" }
mention_style = { foreground = "blue", background = "default" }
emoji_style = { foreground = "green", background = "default" }
url_style = { foreground = "blue", background = "default" }
attachment_style = { foreground = "yellow", background = "default" }
[theme.autocomplete]
show_user_nicks = true

View file

@ -1,14 +1,25 @@
package config
import (
"errors"
"github.com/ayn2op/tview"
"github.com/gdamore/tcell/v2"
)
var (
errInvalidType = errors.New("invalid type")
)
type BorderSetWrapper struct{ tview.BorderSet }
func (bw *BorderSetWrapper) UnmarshalTOML(v any) error {
switch v.(string) {
func (bw *BorderSetWrapper) UnmarshalTOML(val any) error {
s, ok := val.(string)
if !ok {
return errInvalidType
}
switch s {
case "plain":
bw.BorderSet = tview.BorderSetPlain()
case "round":
@ -25,7 +36,12 @@ func (bw *BorderSetWrapper) UnmarshalTOML(v any) error {
type AlignmentWrapper struct{ tview.Alignment }
func (aw *AlignmentWrapper) UnmarshalTOML(v any) error {
switch v.(string) {
s, ok := v.(string)
if !ok {
return errInvalidType
}
switch s {
case "left":
aw.Alignment = tview.AlignmentLeft
case "center":
@ -46,16 +62,16 @@ func NewStyleWrapper(style tcell.Style) StyleWrapper {
func (sw *StyleWrapper) UnmarshalTOML(v any) error {
m, ok := v.(map[string]any)
if !ok {
return nil
return errInvalidType
}
for k, v := range m {
s, ok := v.(string)
for key, val := range m {
s, ok := val.(string)
if !ok {
continue
}
switch k {
switch key {
case "foreground":
color := tcell.GetColor(s)
sw.Style = sw.Foreground(color)
@ -69,29 +85,21 @@ func (sw *StyleWrapper) UnmarshalTOML(v any) error {
}
type (
BorderTheme struct {
Enabled bool `toml:"enabled"`
Padding [4]int `toml:"padding"`
Set BorderSetWrapper `toml:"set"`
Style StyleWrapper `toml:"style"`
ThemeStyle struct {
NormalStyle StyleWrapper `toml:"normal_style"`
ActiveStyle StyleWrapper `toml:"active_style"`
}
TitleTheme struct {
Style StyleWrapper `toml:"style"`
ActiveStyle StyleWrapper `toml:"active_style"`
Alignment AlignmentWrapper `toml:"alignment"`
ThemeStyle
Alignment AlignmentWrapper `toml:"alignment"`
}
Theme struct {
BackgroundColor string `toml:"background_color"`
Title TitleTheme `toml:"title"`
Border BorderTheme `toml:"border"`
GuildsTree GuildsTreeTheme `toml:"guilds_tree"`
MessagesText MessagesTextTheme `toml:"messages_text"`
Autocomplete AutocompleteTheme `toml:"autocomplete"`
BorderTheme struct {
ThemeStyle
Enabled bool `toml:"enabled"`
Padding [4]int `toml:"padding"`
Set BorderSetWrapper `toml:"set"`
}
GuildsTreeTheme struct {
@ -100,9 +108,9 @@ type (
Graphics bool `toml:"graphics"`
GraphicsColor string `toml:"graphics_color"`
PrivateChannelColor string `toml:"private_channel_color"`
GuildColor string `toml:"guild_color"`
ChannelColor string `toml:"channel_color"`
PrivateChannelStyle StyleWrapper `toml:"private_channel_style"`
GuildStyle StyleWrapper `toml:"guild_style"`
ChannelStyle StyleWrapper `toml:"channel_style"`
}
MessagesTextTheme struct {
@ -126,4 +134,15 @@ type (
MinWidth uint `toml:"min_width"`
MaxHeight uint `toml:"max_height"`
}
Theme struct {
BackgroundColor string `toml:"background_color"`
Title TitleTheme `toml:"title"`
Border BorderTheme `toml:"border"`
GuildsTree GuildsTreeTheme `toml:"guilds_tree"`
MessagesText MessagesTextTheme `toml:"messages_text"`
Autocomplete AutocompleteTheme `toml:"autocomplete"`
}
)

View file

@ -5,32 +5,36 @@ import (
"github.com/ayn2op/tview"
)
func NewConfiguredBox(box *tview.Box, cfg *config.Theme) *tview.Box {
b := cfg.Border
t := cfg.Title
p := b.Padding
// ConfigureBox configures the provided box according to the provided theme.
func ConfigureBox(box *tview.Box, cfg *config.Theme) *tview.Box {
border := cfg.Border
title := cfg.Title
normalBorderStyle, activeBorderStyle := border.NormalStyle.Style, border.ActiveStyle.Style
normalTitleStyle, activeTitleStyle := title.NormalStyle.Style, title.ActiveStyle.Style
p := border.Padding
box.
SetBorderStyle(b.Style.Style).
SetBorderSet(b.Set.BorderSet).
SetBorderStyle(normalBorderStyle).
SetBorderSet(border.Set.BorderSet).
SetBorderPadding(p[0], p[1], p[2], p[3]).
SetTitleStyle(t.Style.Style).
SetTitleAlignment(t.Alignment.Alignment).
SetTitleStyle(normalTitleStyle).
SetTitleAlignment(title.Alignment.Alignment).
SetFocusFunc(func() {
box.SetBorderStyle(b.ActiveStyle.Style)
box.SetTitleStyle(t.ActiveStyle.Style)
box.SetBorderStyle(activeBorderStyle)
box.SetTitleStyle(activeTitleStyle)
}).
SetBlurFunc(func() {
box.SetBorderStyle(b.Style.Style)
box.SetTitleStyle(t.Style.Style)
box.SetBorderStyle(normalBorderStyle)
box.SetTitleStyle(normalTitleStyle)
})
if b.Enabled {
if border.Enabled {
box.SetBorders(tview.BordersAll)
}
return box
}
// Centered creates a new grid with provided primitive aligned in the center.
func Centered(p tview.Primitive, width, height int) tview.Primitive {
return tview.NewGrid().
SetColumns(0, width, 0).