301 lines
8.8 KiB
Go
301 lines
8.8 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/ayn2op/arikawa/v3/discord"
|
|
"github.com/ayn2op/tview"
|
|
"github.com/gdamore/tcell/v3"
|
|
"github.com/gdamore/tcell/v3/color"
|
|
)
|
|
|
|
var errInvalidType = errors.New("invalid type")
|
|
|
|
var (
|
|
attributes = enum[tcell.AttrMask]{
|
|
{"bold", tcell.AttrBold},
|
|
{"dim", tcell.AttrDim},
|
|
{"italic", tcell.AttrItalic},
|
|
{"blink", tcell.AttrBlink},
|
|
{"reverse", tcell.AttrReverse},
|
|
{"strikethrough", tcell.AttrStrikeThrough},
|
|
}
|
|
underlines = enum[tcell.UnderlineStyle]{
|
|
{"", tcell.UnderlineStyleNone},
|
|
{"solid", tcell.UnderlineStyleSolid},
|
|
{"double", tcell.UnderlineStyleDouble},
|
|
{"curly", tcell.UnderlineStyleCurly},
|
|
{"dotted", tcell.UnderlineStyleDotted},
|
|
{"dashed", tcell.UnderlineStyleDashed},
|
|
}
|
|
)
|
|
|
|
type StyleWrapper struct{ tview.Style }
|
|
|
|
func (sw *StyleWrapper) UnmarshalTOML(v any) error {
|
|
m, ok := v.(map[string]any)
|
|
if !ok {
|
|
return errInvalidType
|
|
}
|
|
|
|
// Reset on new styles
|
|
sw.Style = tcell.StyleDefault
|
|
|
|
for key, val := range m {
|
|
switch key {
|
|
case "foreground":
|
|
if s, ok := val.(string); ok {
|
|
sw.Style = sw.Foreground(tcell.GetColor(s))
|
|
}
|
|
case "background":
|
|
if s, ok := val.(string); ok {
|
|
sw.Style = sw.Background(tcell.GetColor(s))
|
|
}
|
|
case "attributes":
|
|
switch val := val.(type) {
|
|
case string:
|
|
sw.parseAttr(val)
|
|
case []any:
|
|
for _, attr := range val {
|
|
if s, ok := attr.(string); ok {
|
|
sw.parseAttr(s)
|
|
}
|
|
}
|
|
}
|
|
case "underline":
|
|
if s, ok := val.(string); ok {
|
|
underline, err := underlines.parse(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sw.Style = sw.Underline(underline)
|
|
}
|
|
case "underline_color":
|
|
if s, ok := val.(string); ok {
|
|
sw.Style = sw.Underline(tcell.GetColor(s))
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sw *StyleWrapper) parseAttr(s string) {
|
|
if s == "underline" {
|
|
sw.Style = sw.Underline(true)
|
|
return
|
|
}
|
|
// An unknown attribute parses to no attribute.
|
|
attr, _ := attributes.parse(s)
|
|
sw.Style = sw.Attributes(sw.GetAttributes() | attr) //nolint:staticcheck
|
|
}
|
|
|
|
// MarshalTOML writes the style as the inline table UnmarshalTOML reads.
|
|
func (sw StyleWrapper) MarshalTOML() ([]byte, error) {
|
|
var fields []string
|
|
add := func(key string, value any) {
|
|
b, _ := json.Marshal(value)
|
|
fields = append(fields, key+" = "+string(b))
|
|
}
|
|
if c := sw.GetForeground(); c != tcell.ColorDefault {
|
|
add("foreground", colorName(c))
|
|
}
|
|
if c := sw.GetBackground(); c != tcell.ColorDefault {
|
|
add("background", colorName(c))
|
|
}
|
|
var attrs []string
|
|
for _, a := range attributes {
|
|
if sw.GetAttributes()&a.value != 0 {
|
|
attrs = append(attrs, a.name)
|
|
}
|
|
}
|
|
switch len(attrs) {
|
|
case 0:
|
|
case 1:
|
|
add("attributes", attrs[0])
|
|
default:
|
|
add("attributes", attrs)
|
|
}
|
|
if u := sw.GetUnderlineStyle(); u != tcell.UnderlineStyleNone {
|
|
name, _ := underlines.text(u)
|
|
add("underline", string(name))
|
|
}
|
|
if c := sw.GetUnderlineColor(); c != tcell.ColorDefault {
|
|
add("underline_color", colorName(c))
|
|
}
|
|
if fields == nil {
|
|
return []byte("{}"), nil
|
|
}
|
|
return []byte("{ " + strings.Join(fields, ", ") + " }"), nil
|
|
}
|
|
|
|
// colorName returns the first of the names of c, or its hex code if it has none.
|
|
func colorName(c tcell.Color) string {
|
|
var names []string
|
|
for name, color := range tcell.ColorNames {
|
|
if color == c {
|
|
names = append(names, name)
|
|
}
|
|
}
|
|
if names == nil {
|
|
return c.CSS()
|
|
}
|
|
return slices.Min(names)
|
|
}
|
|
|
|
type (
|
|
HelpThemeConfig struct {
|
|
ShortKeyStyle StyleWrapper `toml:"short_key_style"`
|
|
ShortDescStyle StyleWrapper `toml:"short_desc_style"`
|
|
FullKeyStyle StyleWrapper `toml:"full_key_style"`
|
|
FullDescStyle StyleWrapper `toml:"full_desc_style"`
|
|
}
|
|
|
|
StyleThemeConfig struct {
|
|
NormalStyle StyleWrapper `toml:"normal_style"`
|
|
// The style used while the widget is focused.
|
|
ActiveStyle StyleWrapper `toml:"active_style"`
|
|
}
|
|
|
|
MessagesListThemeConfig struct {
|
|
AuthorStyle StyleWrapper `toml:"author_style"`
|
|
MentionStyle StyleWrapper `toml:"mention_style"`
|
|
EmojiStyle StyleWrapper `toml:"emoji_style"`
|
|
URLStyle StyleWrapper `toml:"url_style"`
|
|
AttachmentStyle StyleWrapper `toml:"attachment_style"`
|
|
ReactionStyle StyleWrapper `toml:"reaction_style"`
|
|
OwnReactionStyle StyleWrapper `toml:"own_reaction_style"`
|
|
|
|
MessageStyle StyleWrapper `toml:"message_style"`
|
|
SelectedMessageStyle StyleWrapper `toml:"selected_message_style"`
|
|
|
|
Embeds MessagesListEmbedsThemeConfig `toml:"embeds"`
|
|
}
|
|
|
|
MessagesListEmbedsThemeConfig struct {
|
|
ProviderStyle StyleWrapper `toml:"provider_style"`
|
|
AuthorStyle StyleWrapper `toml:"author_style"`
|
|
TitleStyle StyleWrapper `toml:"title_style"`
|
|
DescriptionStyle StyleWrapper `toml:"description_style"`
|
|
FieldNameStyle StyleWrapper `toml:"field_name_style"`
|
|
FieldValueStyle StyleWrapper `toml:"field_value_style"`
|
|
FooterStyle StyleWrapper `toml:"footer_style"`
|
|
URLStyle StyleWrapper `toml:"url_style"`
|
|
}
|
|
|
|
MentionsListThemeConfig struct {
|
|
SelectedStyle StyleWrapper `toml:"selected_style"`
|
|
}
|
|
|
|
DialogThemeConfig struct {
|
|
Style StyleWrapper `toml:"style"`
|
|
// The style of everything behind the dialog.
|
|
BackgroundStyle StyleWrapper `toml:"background_style"`
|
|
}
|
|
|
|
ScrollBarThemeConfig struct {
|
|
TrackStyle StyleWrapper `toml:"track_style"`
|
|
ThumbStyle StyleWrapper `toml:"thumb_style"`
|
|
}
|
|
|
|
ThemeConfig struct {
|
|
Title StyleThemeConfig `toml:"title"`
|
|
Footer StyleThemeConfig `toml:"footer"`
|
|
Border StyleThemeConfig `toml:"border"`
|
|
// The status styles are those of direct-message recipients.
|
|
GuildsTree GuildsTreeThemeConfig `toml:"guilds_tree"`
|
|
// The status styles are those of the dot before each member.
|
|
MembersTree TreeThemeConfig `toml:"members_tree"`
|
|
ScrollBar ScrollBarThemeConfig `toml:"scroll_bar"`
|
|
MessagesList MessagesListThemeConfig `toml:"messages_list"`
|
|
MentionsList MentionsListThemeConfig `toml:"mentions_list"`
|
|
Dialog DialogThemeConfig `toml:"dialog"`
|
|
Help HelpThemeConfig `toml:"help"`
|
|
}
|
|
)
|
|
|
|
type GuildsTreeThemeConfig struct {
|
|
TreeThemeConfig
|
|
// The style of whoever is speaking in the voice channel joined.
|
|
SpeakingStyle StyleWrapper `toml:"speaking_style"`
|
|
// The style of how full a voice channel that has a user limit is.
|
|
VoiceLimitStyle StyleWrapper `toml:"voice_limit_style"`
|
|
}
|
|
|
|
type TreeThemeConfig struct {
|
|
// The color of the lines that give the tree its shape.
|
|
GraphicsColor string `toml:"graphics_color"`
|
|
|
|
OnlineStyle StyleWrapper `toml:"online_style"`
|
|
IdleStyle StyleWrapper `toml:"idle_style"`
|
|
DNDStyle StyleWrapper `toml:"dnd_style"`
|
|
OfflineStyle StyleWrapper `toml:"offline_style"`
|
|
}
|
|
|
|
// StatusStyle returns the style for a user with status.
|
|
func (ctt TreeThemeConfig) StatusStyle(status discord.Status) tview.Style {
|
|
switch status {
|
|
case discord.OnlineStatus:
|
|
return ctt.OnlineStyle.Style
|
|
case discord.IdleStatus:
|
|
return ctt.IdleStyle.Style
|
|
case discord.DoNotDisturbStatus:
|
|
return ctt.DNDStyle.Style
|
|
default:
|
|
return ctt.OfflineStyle.Style
|
|
}
|
|
}
|
|
|
|
func defaultTheme() ThemeConfig {
|
|
var (
|
|
plain = tcell.StyleDefault
|
|
dim = StyleWrapper{plain.Dim(true)}
|
|
bold = plain.Bold(true)
|
|
italic = plain.Italic(true)
|
|
blue = plain.Foreground(color.Blue)
|
|
focus = StyleThemeConfig{dim, StyleWrapper{bold.Foreground(color.Green)}}
|
|
tree = TreeThemeConfig{
|
|
GraphicsColor: "default",
|
|
OnlineStyle: StyleWrapper{plain.Foreground(color.Green)},
|
|
IdleStyle: StyleWrapper{plain.Foreground(color.Yellow)},
|
|
DNDStyle: StyleWrapper{plain.Foreground(color.Red)},
|
|
OfflineStyle: StyleWrapper{plain.Foreground(color.Gray)},
|
|
}
|
|
)
|
|
return ThemeConfig{
|
|
Title: focus,
|
|
Footer: focus,
|
|
Border: focus,
|
|
GuildsTree: GuildsTreeThemeConfig{
|
|
TreeThemeConfig: tree,
|
|
SpeakingStyle: StyleWrapper{plain.Foreground(color.Green)},
|
|
VoiceLimitStyle: StyleWrapper{plain.Foreground(color.DodgerBlue)},
|
|
},
|
|
MembersTree: tree,
|
|
ScrollBar: ScrollBarThemeConfig{TrackStyle: dim},
|
|
MessagesList: MessagesListThemeConfig{
|
|
MentionStyle: StyleWrapper{blue.Bold(true)},
|
|
EmojiStyle: StyleWrapper{plain.Foreground(color.Green)},
|
|
URLStyle: StyleWrapper{blue},
|
|
AttachmentStyle: StyleWrapper{plain.Foreground(color.Yellow)},
|
|
ReactionStyle: dim,
|
|
OwnReactionStyle: StyleWrapper{bold},
|
|
SelectedMessageStyle: StyleWrapper{plain.Reverse(true)},
|
|
Embeds: MessagesListEmbedsThemeConfig{
|
|
ProviderStyle: StyleWrapper{italic.Dim(true)},
|
|
AuthorStyle: StyleWrapper{italic},
|
|
TitleStyle: StyleWrapper{blue.Bold(true)},
|
|
DescriptionStyle: dim,
|
|
FieldNameStyle: StyleWrapper{bold.Underline(true)},
|
|
FooterStyle: StyleWrapper{italic.Dim(true)},
|
|
URLStyle: StyleWrapper{blue.Underline(true)},
|
|
},
|
|
},
|
|
MentionsList: MentionsListThemeConfig{SelectedStyle: StyleWrapper{plain.Reverse(true)}},
|
|
Dialog: DialogThemeConfig{BackgroundStyle: dim},
|
|
Help: HelpThemeConfig{ShortKeyStyle: dim, FullKeyStyle: dim},
|
|
}
|
|
}
|