| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- package ui
- import (
- "context"
- "log"
- "strings"
- "github.com/ayntgl/discordo/config"
- "github.com/diamondburned/arikawa/v3/discord"
- "github.com/diamondburned/arikawa/v3/gateway"
- "github.com/diamondburned/arikawa/v3/state"
- "github.com/gdamore/tcell/v2"
- "github.com/rivo/tview"
- )
- type FocusedID int
- const (
- guildsView FocusedID = iota
- channelsView
- messagesView
- inputView
- )
- // Core is responsible for the following:
- // - Initialization of the application, UI elements, configuration, and state.
- // - Configuration of the application and state when Run is called.
- // - Management of the application and state.
- type Core struct {
- App *tview.Application
- View *tview.Flex
- GuildsView *GuildsView
- ChannelsView *ChannelsView
- MessagesView *MessagesView
- InputView *InputView
- Config *config.Config
- State *state.State
- focusedID FocusedID
- }
- func NewCore(cfg *config.Config) *Core {
- c := &Core{
- Config: cfg,
- }
- tview.Styles.PrimitiveBackgroundColor = tcell.GetColor(cfg.Theme.Background)
- tview.Styles.BorderColor = tcell.GetColor(cfg.Theme.Border)
- tview.Styles.TitleColor = tcell.GetColor(cfg.Theme.Title)
- c.App = tview.NewApplication()
- c.App.EnableMouse(c.Config.Mouse)
- c.App.SetBeforeDrawFunc(c.onAppBeforeDraw)
- c.View = tview.NewFlex()
- c.View.SetInputCapture(c.onViewInputCapture)
- c.GuildsView = newGuildsView(c)
- c.ChannelsView = newChannelsView(c)
- c.MessagesView = newMessagesView(c)
- c.InputView = newInputView(c)
- return c
- }
- func (c *Core) Run(token string) error {
- c.State = state.New(token)
- c.State.AddHandler(c.onReady)
- c.State.AddHandler(c.onGuildCreate)
- c.State.AddHandler(c.onGuildDelete)
- c.State.AddHandler(c.onMessageCreate)
- return c.State.Open(context.Background())
- }
- func (c *Core) Draw() {
- left := tview.NewFlex().
- SetDirection(tview.FlexRow).
- AddItem(c.GuildsView, 10, 1, false).
- AddItem(c.ChannelsView, 0, 1, false)
- right := tview.NewFlex().
- SetDirection(tview.FlexRow).
- AddItem(c.MessagesView, 0, 1, false).
- AddItem(c.InputView, 3, 1, false)
- c.View.AddItem(left, 0, 1, false)
- c.View.AddItem(right, 0, 4, false)
- c.App.SetRoot(c.View, true)
- c.App.SetFocus(c.GuildsView)
- }
- func (c *Core) onAppBeforeDraw(screen tcell.Screen) bool {
- if c.Config.Theme.Background == "default" {
- screen.Clear()
- }
- return false
- }
- func (c *Core) onViewInputCapture(event *tcell.EventKey) *tcell.EventKey {
- switch event.Key() {
- case tcell.KeyEsc:
- c.focusedID = 0
- case tcell.KeyBacktab:
- // If the currently focused view is the guilds view (first), then focus the input view (last)
- if c.focusedID == 0 {
- c.focusedID = inputView
- } else {
- c.focusedID--
- }
- c.setFocus()
- case tcell.KeyTab:
- // If the currently focused view is the input view (last), then focus the guilds view (first)
- if c.focusedID == inputView {
- c.focusedID = guildsView
- } else {
- c.focusedID++
- }
- c.setFocus()
- }
- return event
- }
- func (c *Core) setFocus() {
- var p tview.Primitive
- switch c.focusedID {
- case guildsView:
- p = c.GuildsView
- case channelsView:
- p = c.ChannelsView
- case messagesView:
- p = c.MessagesView
- case inputView:
- p = c.InputView
- }
- c.App.SetFocus(p)
- }
- func (c *Core) onReady(r *gateway.ReadyEvent) {
- root := c.GuildsView.GetRoot()
- for _, gf := range r.UserSettings.GuildFolders {
- if gf.ID == 0 {
- for _, gID := range gf.GuildIDs {
- g, err := c.State.Cabinet.Guild(gID)
- if err != nil {
- log.Println(err)
- continue
- }
- guildNode := tview.NewTreeNode(g.Name)
- guildNode.SetReference(g.ID)
- root.AddChild(guildNode)
- }
- } else {
- var b strings.Builder
- if gf.Color != discord.NullColor {
- b.WriteByte('[')
- b.WriteString(gf.Color.String())
- b.WriteByte(']')
- } else {
- b.WriteString("[#ED4245]")
- }
- if gf.Name != "" {
- b.WriteString(gf.Name)
- } else {
- b.WriteString("Folder")
- }
- b.WriteString("[-]")
- folderNode := tview.NewTreeNode(b.String())
- root.AddChild(folderNode)
- for _, gID := range gf.GuildIDs {
- g, err := c.State.Cabinet.Guild(gID)
- if err != nil {
- log.Println(err)
- continue
- }
- guildNode := tview.NewTreeNode(g.Name)
- guildNode.SetReference(g.ID)
- folderNode.AddChild(guildNode)
- }
- }
- }
- c.GuildsView.SetCurrentNode(root)
- c.App.SetFocus(c.GuildsView)
- }
- func (c *Core) onGuildCreate(g *gateway.GuildCreateEvent) {
- guildNode := tview.NewTreeNode(g.Name)
- guildNode.SetReference(g.ID)
- rootNode := c.GuildsView.GetRoot()
- rootNode.AddChild(guildNode)
- c.GuildsView.SetCurrentNode(rootNode)
- c.App.SetFocus(c.GuildsView)
- c.App.Draw()
- }
- func (c *Core) onGuildDelete(g *gateway.GuildDeleteEvent) {
- rootNode := c.GuildsView.GetRoot()
- var parentNode *tview.TreeNode
- rootNode.Walk(func(node, _ *tview.TreeNode) bool {
- if node.GetReference() == g.ID {
- parentNode = node
- return false
- }
- return true
- })
- if parentNode != nil {
- rootNode.RemoveChild(parentNode)
- }
- c.App.Draw()
- }
- func (c *Core) onMessageCreate(m *gateway.MessageCreateEvent) {
- if c.ChannelsView.selectedChannel != nil && c.ChannelsView.selectedChannel.ID == m.ChannelID {
- _, err := c.MessagesView.Write(buildMessage(c, m.Message))
- if err != nil {
- return
- }
- if len(c.MessagesView.GetHighlights()) == 0 {
- c.MessagesView.ScrollToEnd()
- }
- }
- }
|