core.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package ui
  2. import (
  3. "context"
  4. "strings"
  5. "github.com/ayntgl/discordo/config"
  6. "github.com/diamondburned/arikawa/v3/discord"
  7. "github.com/diamondburned/arikawa/v3/gateway"
  8. "github.com/diamondburned/arikawa/v3/state"
  9. "github.com/gdamore/tcell/v2"
  10. "github.com/rivo/tview"
  11. )
  12. type FocusedID int
  13. const (
  14. guildsView FocusedID = iota
  15. channelsView
  16. messagesView
  17. inputView
  18. )
  19. // Core is responsible for the following:
  20. // - Initialization of the application, UI elements, configuration, and state.
  21. // - Configuration of the application and state when Run is called.
  22. // - Management of the application and state.
  23. type Core struct {
  24. App *tview.Application
  25. View *tview.Flex
  26. GuildsView *GuildsView
  27. ChannelsView *ChannelsView
  28. MessagesView *MessagesView
  29. InputView *InputView
  30. Config *config.Config
  31. State *state.State
  32. focusedID FocusedID
  33. }
  34. func NewCore(cfg *config.Config) *Core {
  35. c := &Core{
  36. Config: cfg,
  37. }
  38. tview.Styles.PrimitiveBackgroundColor = tcell.GetColor(cfg.Theme.Background)
  39. tview.Styles.BorderColor = tcell.GetColor(cfg.Theme.Border)
  40. tview.Styles.TitleColor = tcell.GetColor(cfg.Theme.Title)
  41. c.App = tview.NewApplication()
  42. c.App.EnableMouse(c.Config.Mouse)
  43. c.App.SetBeforeDrawFunc(c.onAppBeforeDraw)
  44. c.View = tview.NewFlex()
  45. c.View.SetInputCapture(c.onViewInputCapture)
  46. c.GuildsView = newGuildsView(c)
  47. c.ChannelsView = newChannelsView(c)
  48. c.MessagesView = newMessagesView(c)
  49. c.InputView = newInputView(c)
  50. return c
  51. }
  52. func (c *Core) Run(token string) error {
  53. c.State = state.New(token)
  54. c.State.AddHandler(c.onReady)
  55. c.State.AddHandler(c.onGuildCreate)
  56. c.State.AddHandler(c.onGuildDelete)
  57. c.State.AddHandler(c.onMessageCreate)
  58. return c.State.Open(context.Background())
  59. }
  60. func (c *Core) Draw() {
  61. left := tview.NewFlex().
  62. SetDirection(tview.FlexRow).
  63. AddItem(c.GuildsView, 10, 1, false).
  64. AddItem(c.ChannelsView, 0, 1, false)
  65. right := tview.NewFlex().
  66. SetDirection(tview.FlexRow).
  67. AddItem(c.MessagesView, 0, 1, false).
  68. AddItem(c.InputView, 3, 1, false)
  69. c.View.AddItem(left, 0, 1, false)
  70. c.View.AddItem(right, 0, 4, false)
  71. c.App.SetRoot(c.View, true)
  72. c.App.SetFocus(c.GuildsView)
  73. }
  74. func (c *Core) onAppBeforeDraw(screen tcell.Screen) bool {
  75. if c.Config.Theme.Background == "default" {
  76. screen.Clear()
  77. }
  78. return false
  79. }
  80. func (c *Core) onViewInputCapture(event *tcell.EventKey) *tcell.EventKey {
  81. switch event.Key() {
  82. case tcell.KeyEsc:
  83. c.focusedID = 0
  84. case tcell.KeyBacktab:
  85. // If the currently focused view is the guilds view (first), then focus the input view (last)
  86. if c.focusedID == 0 {
  87. c.focusedID = inputView
  88. } else {
  89. c.focusedID--
  90. }
  91. c.setFocus()
  92. case tcell.KeyTab:
  93. // If the currently focused view is the input view (last), then focus the guilds view (first)
  94. if c.focusedID == inputView {
  95. c.focusedID = guildsView
  96. } else {
  97. c.focusedID++
  98. }
  99. c.setFocus()
  100. }
  101. return event
  102. }
  103. func (c *Core) setFocus() {
  104. var p tview.Primitive
  105. switch c.focusedID {
  106. case guildsView:
  107. p = c.GuildsView
  108. case channelsView:
  109. p = c.ChannelsView
  110. case messagesView:
  111. p = c.MessagesView
  112. case inputView:
  113. p = c.InputView
  114. }
  115. c.App.SetFocus(p)
  116. }
  117. func (c *Core) onReady(r *gateway.ReadyEvent) {
  118. root := c.GuildsView.GetRoot()
  119. for _, gf := range r.UserSettings.GuildFolders {
  120. if gf.ID == 0 {
  121. for _, gID := range gf.GuildIDs {
  122. g, err := c.State.Cabinet.Guild(gID)
  123. if err != nil {
  124. return
  125. }
  126. guildNode := tview.NewTreeNode(g.Name)
  127. guildNode.SetReference(g.ID)
  128. root.AddChild(guildNode)
  129. }
  130. } else {
  131. var b strings.Builder
  132. if gf.Color != discord.NullColor {
  133. b.WriteByte('[')
  134. b.WriteString(gf.Color.String())
  135. b.WriteByte(']')
  136. } else {
  137. b.WriteString("[#ED4245]")
  138. }
  139. if gf.Name != "" {
  140. b.WriteString(gf.Name)
  141. } else {
  142. b.WriteString("Folder")
  143. }
  144. b.WriteString("[-]")
  145. folderNode := tview.NewTreeNode(b.String())
  146. root.AddChild(folderNode)
  147. for _, gID := range gf.GuildIDs {
  148. g, err := c.State.Cabinet.Guild(gID)
  149. if err != nil {
  150. return
  151. }
  152. guildNode := tview.NewTreeNode(g.Name)
  153. guildNode.SetReference(g.ID)
  154. folderNode.AddChild(guildNode)
  155. }
  156. }
  157. }
  158. c.GuildsView.SetCurrentNode(root)
  159. c.App.SetFocus(c.GuildsView)
  160. }
  161. func (c *Core) onGuildCreate(g *gateway.GuildCreateEvent) {
  162. guildNode := tview.NewTreeNode(g.Name)
  163. guildNode.SetReference(g.ID)
  164. rootNode := c.GuildsView.GetRoot()
  165. rootNode.AddChild(guildNode)
  166. c.GuildsView.SetCurrentNode(rootNode)
  167. c.App.SetFocus(c.GuildsView)
  168. c.App.Draw()
  169. }
  170. func (c *Core) onGuildDelete(g *gateway.GuildDeleteEvent) {
  171. rootNode := c.GuildsView.GetRoot()
  172. var parentNode *tview.TreeNode
  173. rootNode.Walk(func(node, _ *tview.TreeNode) bool {
  174. if node.GetReference() == g.ID {
  175. parentNode = node
  176. return false
  177. }
  178. return true
  179. })
  180. if parentNode != nil {
  181. rootNode.RemoveChild(parentNode)
  182. }
  183. c.App.Draw()
  184. }
  185. func (c *Core) onMessageCreate(m *gateway.MessageCreateEvent) {
  186. if c.ChannelsView.selectedChannel != nil && c.ChannelsView.selectedChannel.ID == m.ChannelID {
  187. _, err := c.MessagesView.Write(buildMessage(c, m.Message))
  188. if err != nil {
  189. return
  190. }
  191. if len(c.MessagesView.GetHighlights()) == 0 {
  192. c.MessagesView.ScrollToEnd()
  193. }
  194. }
  195. }