165 lines
5.1 KiB
Go
165 lines
5.1 KiB
Go
package ui
|
|
|
|
import (
|
|
"cmp"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/ayn2op/arikawa/v3/discord"
|
|
"github.com/ayn2op/discordo/internal/config"
|
|
"github.com/ayn2op/ningen/v3"
|
|
"github.com/ayn2op/tview"
|
|
"github.com/ayn2op/tview/grid"
|
|
"github.com/ayn2op/tview/list"
|
|
"github.com/ayn2op/tview/picker"
|
|
)
|
|
|
|
// ConfigureBox configures the provided box according to the provided theme.
|
|
func ConfigureBox(box *tview.Box, cfg *config.Theme) {
|
|
border := cfg.Border
|
|
normalBorderStyle, activeBorderStyle := border.NormalStyle.Style, border.ActiveStyle.Style
|
|
normalBorderSet, activeBorderSet := border.NormalSet.BorderSet, border.ActiveSet.BorderSet
|
|
|
|
title := cfg.Title
|
|
normalTitleStyle, activeTitleStyle := title.NormalStyle.Style, title.ActiveStyle.Style
|
|
|
|
footer := cfg.Footer
|
|
normalFooterStyle, activeFooterStyle := footer.NormalStyle.Style, footer.ActiveStyle.Style
|
|
|
|
padding := border.Padding
|
|
|
|
box.
|
|
SetBorderStyle(normalBorderStyle).
|
|
SetBorderSet(normalBorderSet).
|
|
SetBorderPadding(padding[0], padding[1], padding[2], padding[3]).
|
|
SetTitleStyle(normalTitleStyle).
|
|
SetTitleAlignment(title.Alignment.Alignment).
|
|
SetFooterStyle(normalFooterStyle).
|
|
SetFooterAlignment(footer.Alignment.Alignment).
|
|
SetBlurFunc(func() {
|
|
box.
|
|
SetBorderStyle(normalBorderStyle).
|
|
SetBorderSet(normalBorderSet)
|
|
box.SetTitleStyle(normalTitleStyle).SetFooterStyle(normalFooterStyle)
|
|
}).
|
|
SetFocusFunc(func() {
|
|
box.
|
|
SetBorderStyle(activeBorderStyle).
|
|
SetBorderSet(activeBorderSet)
|
|
box.SetTitleStyle(activeTitleStyle).SetFooterStyle(activeFooterStyle)
|
|
})
|
|
|
|
if border.Enabled {
|
|
box.SetBorders(tview.BordersAll)
|
|
}
|
|
}
|
|
|
|
func ConfigurePicker(model *picker.Model, cfg *config.Config, title string) {
|
|
model.Box = tview.NewBox()
|
|
ConfigureBox(model.Box, &cfg.Theme)
|
|
// When a child of the parent flex is focused, the parent layout itself is not reported as focused.
|
|
// Instead, the focused child (picker) is considered focused.
|
|
// Therefore, we manually set the active border style on the picker to ensure it displays the correct focused appearance.
|
|
model.
|
|
SetBlurFunc(nil).
|
|
SetFocusFunc(nil).
|
|
SetBorderSet(cfg.Theme.Border.ActiveSet.BorderSet).
|
|
SetBorderStyle(cfg.Theme.Border.ActiveStyle.Style).
|
|
SetTitleStyle(cfg.Theme.Title.ActiveStyle.Style).
|
|
SetFooterStyle(cfg.Theme.Footer.ActiveStyle.Style)
|
|
|
|
model.SetTitle(title)
|
|
model.SetScrollBarVisibility(cfg.Theme.ScrollBar.Visibility.ScrollBarVisibility)
|
|
model.SetScrollBar(tview.NewScrollBar().
|
|
SetTrackStyle(cfg.Theme.ScrollBar.TrackStyle.Style).
|
|
SetThumbStyle(cfg.Theme.ScrollBar.ThumbStyle.Style).
|
|
SetGlyphSet(cfg.Theme.ScrollBar.GlyphSet.GlyphSet))
|
|
model.SetKeybinds(picker.Keybinds{
|
|
Cancel: cfg.Keybinds.Picker.Cancel.Keybind,
|
|
Keybinds: list.Keybinds{
|
|
SelectUp: cfg.Keybinds.Picker.SelectUp.Keybind,
|
|
SelectDown: cfg.Keybinds.Picker.SelectDown.Keybind,
|
|
SelectTop: cfg.Keybinds.Picker.SelectTop.Keybind,
|
|
SelectBottom: cfg.Keybinds.Picker.SelectBottom.Keybind,
|
|
},
|
|
Select: cfg.Keybinds.Picker.Select.Keybind,
|
|
})
|
|
}
|
|
|
|
// Centered creates a new grid with provided primitive aligned in the center.
|
|
func Centered(m tview.Model, width, height int) tview.Model {
|
|
return grid.NewModel().
|
|
SetColumns(0, width, 0).
|
|
SetRows(0, height, 0).
|
|
AddItem(m, 1, 1, 1, 1, 0, 0, true)
|
|
}
|
|
|
|
func ChannelToString(channel discord.Channel, icons config.Icons, state *ningen.State) string {
|
|
var icon string
|
|
switch channel.Type {
|
|
case discord.DirectMessage, discord.GroupDM:
|
|
if channel.Name != "" {
|
|
return channel.Name
|
|
}
|
|
|
|
recipients := make([]string, len(channel.DMRecipients))
|
|
for i, r := range channel.DMRecipients {
|
|
if state != nil && channel.Type == discord.DirectMessage {
|
|
if rel, ok := state.RelationshipState.FullRelationship(r.ID); ok && rel.Type == discord.FriendRelationship {
|
|
if rel.Nickname != nil && *rel.Nickname != "" {
|
|
recipients[i] = *rel.Nickname
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
recipients[i] = r.DisplayOrUsername()
|
|
}
|
|
|
|
return strings.Join(recipients, ", ")
|
|
|
|
case discord.GuildCategory:
|
|
icon = icons.GuildCategory
|
|
case discord.GuildText:
|
|
icon = icons.GuildText
|
|
case discord.GuildVoice:
|
|
icon = icons.GuildVoice
|
|
case discord.GuildStageVoice:
|
|
icon = icons.GuildStageVoice
|
|
|
|
case discord.GuildAnnouncementThread:
|
|
icon = icons.GuildAnnouncementThread
|
|
case discord.GuildPublicThread:
|
|
icon = icons.GuildPublicThread
|
|
case discord.GuildPrivateThread:
|
|
icon = icons.GuildPrivateThread
|
|
|
|
case discord.GuildAnnouncement:
|
|
icon = icons.GuildAnnouncement
|
|
case discord.GuildForum:
|
|
icon = icons.GuildForum
|
|
case discord.GuildStore:
|
|
icon = icons.GuildStore
|
|
}
|
|
|
|
return icon + channel.Name
|
|
}
|
|
|
|
func SortGuildChannels(channels []discord.Channel) {
|
|
slices.SortFunc(channels, func(a, b discord.Channel) int {
|
|
return cmp.Compare(a.Position, b.Position)
|
|
})
|
|
}
|
|
|
|
func SortPrivateChannels(channels []discord.Channel) {
|
|
slices.SortFunc(channels, func(a, b discord.Channel) int {
|
|
// Descending order
|
|
return cmp.Compare(getMessageIDFromChannel(b), getMessageIDFromChannel(a))
|
|
})
|
|
}
|
|
|
|
func getMessageIDFromChannel(channel discord.Channel) discord.MessageID {
|
|
if channel.LastMessageID.IsValid() {
|
|
return channel.LastMessageID
|
|
}
|
|
return discord.MessageID(channel.ID)
|
|
}
|