core.go 5.2 KB

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