refactor: create separate struct for main Flex
This commit is contained in:
parent
c9b4e114e0
commit
11201c8649
11 changed files with 419 additions and 396 deletions
53
main.go
53
main.go
|
|
@ -2,16 +2,11 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/ayntgl/discordo/config"
|
||||
"github.com/ayntgl/discordo/ui"
|
||||
"github.com/diamondburned/arikawa/v3/api"
|
||||
"github.com/diamondburned/arikawa/v3/gateway"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/zalando/go-keyring"
|
||||
)
|
||||
|
||||
|
|
@ -22,26 +17,6 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
tview.Borders.TopLeftFocus = tview.Borders.TopLeft
|
||||
tview.Borders.TopRightFocus = tview.Borders.TopRight
|
||||
tview.Borders.BottomLeftFocus = tview.Borders.BottomLeft
|
||||
tview.Borders.BottomRightFocus = tview.Borders.BottomRight
|
||||
tview.Borders.HorizontalFocus = tview.Borders.Horizontal
|
||||
tview.Borders.VerticalFocus = tview.Borders.Vertical
|
||||
tview.Borders.TopLeft = 0
|
||||
tview.Borders.TopRight = 0
|
||||
tview.Borders.BottomLeft = 0
|
||||
tview.Borders.BottomRight = 0
|
||||
tview.Borders.Horizontal = 0
|
||||
tview.Borders.Vertical = 0
|
||||
|
||||
api.UserAgent = fmt.Sprintf("%s/%s %s/%s", config.Name, "0.1", "arikawa", "v3")
|
||||
gateway.DefaultIdentity = gateway.IdentifyProperties{
|
||||
OS: runtime.GOOS,
|
||||
Browser: config.Name,
|
||||
Device: "",
|
||||
}
|
||||
|
||||
flag.StringVar(&flagToken, "token", "", "The authentication token.")
|
||||
flag.StringVar(&flagConfig, "config", config.DefaultConfigPath(), "The path to the configuration file.")
|
||||
flag.StringVar(&flagLog, "log", config.DefaultLogPath(), "The path to the log file.")
|
||||
|
|
@ -62,12 +37,15 @@ func main() {
|
|||
}
|
||||
|
||||
cfg := config.New()
|
||||
err := cfg.Load(flagConfig)
|
||||
if err != nil {
|
||||
if err := cfg.Load(flagConfig); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var token string
|
||||
var (
|
||||
token string
|
||||
err error
|
||||
)
|
||||
|
||||
if flagToken != "" {
|
||||
token = flagToken
|
||||
go keyring.Set(config.Name, "token", token)
|
||||
|
|
@ -78,21 +56,6 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
c := ui.NewCore(cfg)
|
||||
if token != "" {
|
||||
err = c.Run(token)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
c.Draw()
|
||||
} else {
|
||||
loginView := ui.NewLoginView(c)
|
||||
c.App.SetRoot(loginView, true)
|
||||
}
|
||||
|
||||
err = c.App.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
app := ui.NewApplication(cfg)
|
||||
app.Run(token)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,27 +17,27 @@ var linkRegex = regexp.MustCompile("https?://.+")
|
|||
|
||||
type ActionsView struct {
|
||||
*tview.List
|
||||
core *Core
|
||||
app *Application
|
||||
message *discord.Message
|
||||
}
|
||||
|
||||
func newActionsView(c *Core, m *discord.Message) *ActionsView {
|
||||
func newActionsView(app *Application, m *discord.Message) *ActionsView {
|
||||
v := &ActionsView{
|
||||
List: tview.NewList(),
|
||||
core: c,
|
||||
app: app,
|
||||
message: m,
|
||||
}
|
||||
|
||||
v.ShowSecondaryText(false)
|
||||
v.SetDoneFunc(func() {
|
||||
c.App.SetRoot(c.View, true)
|
||||
c.App.SetFocus(c.MessagesView)
|
||||
app.SetRoot(app.view, true)
|
||||
app.SetFocus(app.view.MessagesView)
|
||||
})
|
||||
|
||||
isDM := channelIsInDMCategory(c.ChannelsView.selectedChannel)
|
||||
isDM := channelIsInDMCategory(app.view.ChannelsView.selectedChannel)
|
||||
|
||||
// If the client user has the `SEND_MESSAGES` permission, add "Reply" and "Mention Reply" actions.
|
||||
if isDM || !isDM && hasPermission(c.State, c.ChannelsView.selectedChannel.ID, discord.PermissionSendMessages) {
|
||||
if isDM || !isDM && hasPermission(app.state, app.view.ChannelsView.selectedChannel.ID, discord.PermissionSendMessages) {
|
||||
v.AddItem("Reply", "", 'r', v.replyAction)
|
||||
v.AddItem("Mention Reply", "", 'R', v.mentionReplyAction)
|
||||
}
|
||||
|
|
@ -55,8 +55,8 @@ func newActionsView(c *Core, m *discord.Message) *ActionsView {
|
|||
go open.Run(l)
|
||||
}
|
||||
|
||||
c.App.SetRoot(c.View, true)
|
||||
c.App.SetFocus(c.MessagesView)
|
||||
app.SetRoot(app.view, true)
|
||||
app.SetFocus(app.view.MessagesView)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -66,10 +66,10 @@ func newActionsView(c *Core, m *discord.Message) *ActionsView {
|
|||
v.AddItem("Download Attachment", "", 'd', v.downloadAttachmentAction)
|
||||
}
|
||||
|
||||
me, _ := c.State.MeStore.Me()
|
||||
me, _ := app.state.MeStore.Me()
|
||||
|
||||
// If the client user has the `MANAGE_MESSAGES` permission, add a new action to delete the message.
|
||||
if (isDM && m.Author.ID == me.ID) || (!isDM && hasPermission(c.State, c.ChannelsView.selectedChannel.ID, discord.PermissionManageMessages)) {
|
||||
if (isDM && m.Author.ID == me.ID) || (!isDM && hasPermission(app.state, app.view.ChannelsView.selectedChannel.ID, discord.PermissionManageMessages)) {
|
||||
v.AddItem("Delete", "", 'd', v.deleteAction)
|
||||
}
|
||||
|
||||
|
|
@ -86,32 +86,32 @@ func newActionsView(c *Core, m *discord.Message) *ActionsView {
|
|||
}
|
||||
|
||||
func (v *ActionsView) replyAction() {
|
||||
v.core.InputView.SetTitle("Replying to " + v.message.Author.Tag())
|
||||
v.app.view.InputView.SetTitle("Replying to " + v.message.Author.Tag())
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.InputView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.InputView)
|
||||
}
|
||||
|
||||
func (v *ActionsView) mentionReplyAction() {
|
||||
v.core.InputView.SetTitle("[@] Replying to " + v.message.Author.Tag())
|
||||
v.app.view.InputView.SetTitle("[@] Replying to " + v.message.Author.Tag())
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.InputView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.InputView)
|
||||
}
|
||||
|
||||
func (v *ActionsView) selectReplyAction() {
|
||||
ms, err := v.core.State.Cabinet.Messages(v.message.ChannelID)
|
||||
ms, err := v.app.state.Cabinet.Messages(v.message.ChannelID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
v.core.MessagesView.selectedMessage, _ = findMessageByID(ms, v.message.ReferencedMessage.ID)
|
||||
v.core.MessagesView.
|
||||
v.app.view.MessagesView.selectedMessage, _ = findMessageByID(ms, v.message.ReferencedMessage.ID)
|
||||
v.app.view.MessagesView.
|
||||
Highlight(v.message.ReferencedMessage.ID.String()).
|
||||
ScrollToHighlight()
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.MessagesView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.MessagesView)
|
||||
}
|
||||
|
||||
func (v *ActionsView) openAttachmentAction() {
|
||||
|
|
@ -137,8 +137,8 @@ func (v *ActionsView) openAttachmentAction() {
|
|||
go open.Run(f.Name())
|
||||
}
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.MessagesView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.MessagesView)
|
||||
}
|
||||
|
||||
func (v *ActionsView) downloadAttachmentAction() {
|
||||
|
|
@ -168,38 +168,38 @@ func (v *ActionsView) downloadAttachmentAction() {
|
|||
f.Write(d)
|
||||
}
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.MessagesView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.MessagesView)
|
||||
}
|
||||
|
||||
func (v *ActionsView) deleteAction() {
|
||||
v.core.MessagesView.Clear()
|
||||
v.app.view.MessagesView.Clear()
|
||||
|
||||
err := v.core.State.MessageRemove(v.message.ChannelID, v.message.ID)
|
||||
err := v.app.state.MessageRemove(v.message.ChannelID, v.message.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = v.core.State.DeleteMessage(v.message.ChannelID, v.message.ID, "Unknown")
|
||||
err = v.app.state.DeleteMessage(v.message.ChannelID, v.message.ID, "Unknown")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// The returned slice will be sorted from latest to oldest.
|
||||
ms, err := v.core.State.Cabinet.Messages(v.message.ChannelID)
|
||||
ms, err := v.app.state.Cabinet.Messages(v.message.ChannelID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := len(ms) - 1; i >= 0; i-- {
|
||||
_, err = v.core.MessagesView.Write(buildMessage(v.core, ms[i]))
|
||||
_, err = v.app.view.MessagesView.Write(buildMessage(v.app, ms[i]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.MessagesView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.MessagesView)
|
||||
}
|
||||
|
||||
func (v *ActionsView) copyContentAction() {
|
||||
|
|
@ -208,8 +208,8 @@ func (v *ActionsView) copyContentAction() {
|
|||
return
|
||||
}
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.MessagesView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.MessagesView)
|
||||
}
|
||||
|
||||
func (v *ActionsView) copyIDAction() {
|
||||
|
|
@ -218,8 +218,8 @@ func (v *ActionsView) copyIDAction() {
|
|||
return
|
||||
}
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.MessagesView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.MessagesView)
|
||||
}
|
||||
|
||||
func (v *ActionsView) copyLinkAction() {
|
||||
|
|
@ -228,6 +228,6 @@ func (v *ActionsView) copyLinkAction() {
|
|||
return
|
||||
}
|
||||
|
||||
v.core.App.SetRoot(v.core.View, true)
|
||||
v.core.App.SetFocus(v.core.MessagesView)
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.MessagesView)
|
||||
}
|
||||
|
|
|
|||
202
ui/application.go
Normal file
202
ui/application.go
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ayntgl/discordo/config"
|
||||
"github.com/diamondburned/arikawa/v3/api"
|
||||
"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"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tview.Borders.TopLeftFocus = tview.Borders.TopLeft
|
||||
tview.Borders.TopRightFocus = tview.Borders.TopRight
|
||||
tview.Borders.BottomLeftFocus = tview.Borders.BottomLeft
|
||||
tview.Borders.BottomRightFocus = tview.Borders.BottomRight
|
||||
tview.Borders.HorizontalFocus = tview.Borders.Horizontal
|
||||
tview.Borders.VerticalFocus = tview.Borders.Vertical
|
||||
tview.Borders.TopLeft = 0
|
||||
tview.Borders.TopRight = 0
|
||||
tview.Borders.BottomLeft = 0
|
||||
tview.Borders.BottomRight = 0
|
||||
tview.Borders.Horizontal = 0
|
||||
tview.Borders.Vertical = 0
|
||||
|
||||
api.UserAgent = fmt.Sprintf("%s/%s %s/%s", config.Name, "0.1", "arikawa", "v3")
|
||||
gateway.DefaultIdentity = gateway.IdentifyProperties{
|
||||
OS: runtime.GOOS,
|
||||
Browser: config.Name,
|
||||
Device: "",
|
||||
}
|
||||
}
|
||||
|
||||
// Application is responsible for initialization and management of the application, widgets, configuration, and state.
|
||||
type Application struct {
|
||||
*tview.Application
|
||||
|
||||
view *View
|
||||
config *config.Config
|
||||
state *state.State
|
||||
}
|
||||
|
||||
func NewApplication(cfg *config.Config) *Application {
|
||||
app := &Application{
|
||||
Application: tview.NewApplication(),
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
app.EnableMouse(app.config.Mouse)
|
||||
app.SetBeforeDrawFunc(app.onBeforeDraw)
|
||||
|
||||
app.view = newView(app)
|
||||
|
||||
tview.Styles.PrimitiveBackgroundColor = tcell.GetColor(cfg.Theme.Background)
|
||||
tview.Styles.BorderColor = tcell.GetColor(cfg.Theme.Border)
|
||||
tview.Styles.TitleColor = tcell.GetColor(cfg.Theme.Title)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *Application) Run(token string) {
|
||||
if token != "" {
|
||||
if err := app.Connect(token); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
app.SetRoot(app.view, true)
|
||||
app.SetFocus(app.view.GuildsView)
|
||||
} else {
|
||||
loginView := newLoginView(app)
|
||||
app.SetRoot(loginView, true)
|
||||
}
|
||||
|
||||
if err := app.Application.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *Application) Connect(token string) error {
|
||||
app.state = state.New(token)
|
||||
app.state.AddHandler(app.onReady)
|
||||
app.state.AddHandler(app.onGuildCreate)
|
||||
app.state.AddHandler(app.onGuildDelete)
|
||||
app.state.AddHandler(app.onMessageCreate)
|
||||
|
||||
return app.state.Open(context.Background())
|
||||
}
|
||||
|
||||
func (app *Application) onBeforeDraw(screen tcell.Screen) bool {
|
||||
if app.config.Theme.Background == "default" {
|
||||
screen.Clear()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Application) onReady(r *gateway.ReadyEvent) {
|
||||
root := c.view.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.view.GuildsView.SetCurrentNode(root)
|
||||
c.SetFocus(c.view.GuildsView)
|
||||
}
|
||||
|
||||
func (c *Application) onGuildCreate(g *gateway.GuildCreateEvent) {
|
||||
guildNode := tview.NewTreeNode(g.Name)
|
||||
guildNode.SetReference(g.ID)
|
||||
|
||||
rootNode := c.view.GuildsView.GetRoot()
|
||||
rootNode.AddChild(guildNode)
|
||||
|
||||
c.view.GuildsView.SetCurrentNode(rootNode)
|
||||
c.SetFocus(c.view.GuildsView)
|
||||
c.Draw()
|
||||
}
|
||||
|
||||
func (c *Application) onGuildDelete(g *gateway.GuildDeleteEvent) {
|
||||
rootNode := c.view.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.Draw()
|
||||
}
|
||||
|
||||
func (c *Application) onMessageCreate(m *gateway.MessageCreateEvent) {
|
||||
if c.view.ChannelsView.selectedChannel != nil && c.view.ChannelsView.selectedChannel.ID == m.ChannelID {
|
||||
_, err := c.view.MessagesView.Write(buildMessage(c, m.Message))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(c.view.MessagesView.GetHighlights()) == 0 {
|
||||
c.view.MessagesView.ScrollToEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
)
|
||||
|
||||
func buildMessage(c *Core, m discord.Message) []byte {
|
||||
func buildMessage(c *Application, m discord.Message) []byte {
|
||||
var b strings.Builder
|
||||
|
||||
switch m.Type {
|
||||
|
|
@ -20,25 +20,25 @@ func buildMessage(c *Core, m discord.Message) []byte {
|
|||
b.WriteString(m.ID.String())
|
||||
b.WriteString("\"]")
|
||||
// Build the message associated with crosspost, channel follow add, pin, or a reply.
|
||||
buildReferencedMessage(&b, m.ReferencedMessage, c.State.Ready().User.ID)
|
||||
buildReferencedMessage(&b, m.ReferencedMessage, c.state.Ready().User.ID)
|
||||
|
||||
if c.Config.Timestamps {
|
||||
loc, err := time.LoadLocation(c.Config.Timezone)
|
||||
if c.config.Timestamps {
|
||||
loc, err := time.LoadLocation(c.config.Timezone)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.WriteString("[::d]")
|
||||
b.WriteString(m.Timestamp.Time().In(loc).Format(c.Config.TimeFormat))
|
||||
b.WriteString(m.Timestamp.Time().In(loc).Format(c.config.TimeFormat))
|
||||
b.WriteString("[::-]")
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
|
||||
// Build the author of this message.
|
||||
buildAuthor(&b, m.Author, c.State.Ready().User.ID)
|
||||
buildAuthor(&b, m.Author, c.state.Ready().User.ID)
|
||||
|
||||
// Build the contents of the message.
|
||||
buildContent(&b, m, c.State.Ready().User.ID)
|
||||
buildContent(&b, m, c.state.Ready().User.ID)
|
||||
|
||||
if m.EditedTimestamp.IsValid() {
|
||||
b.WriteString(" [::d](edited)[::-]")
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ import (
|
|||
type ChannelsView struct {
|
||||
*tview.TreeView
|
||||
selectedChannel *discord.Channel
|
||||
core *Core
|
||||
app *Application
|
||||
}
|
||||
|
||||
func newChannelsView(c *Core) *ChannelsView {
|
||||
func newChannelsView(app *Application) *ChannelsView {
|
||||
v := &ChannelsView{
|
||||
TreeView: tview.NewTreeView(),
|
||||
core: c,
|
||||
app: app,
|
||||
}
|
||||
|
||||
v.SetRoot(tview.NewTreeNode(""))
|
||||
|
|
@ -34,15 +34,15 @@ func newChannelsView(c *Core) *ChannelsView {
|
|||
|
||||
func (v *ChannelsView) onSelected(node *tview.TreeNode) {
|
||||
v.selectedChannel = nil
|
||||
v.core.MessagesView.selectedMessage = -1
|
||||
v.core.MessagesView.
|
||||
v.app.view.MessagesView.selectedMessage = -1
|
||||
v.app.view.MessagesView.
|
||||
Highlight().
|
||||
Clear().
|
||||
SetTitle("")
|
||||
v.core.InputView.SetText("")
|
||||
v.app.view.InputView.SetText("")
|
||||
|
||||
ref := node.GetReference()
|
||||
c, err := v.core.State.Cabinet.Channel(ref.(discord.ChannelID))
|
||||
c, err := v.app.state.Cabinet.Channel(ref.(discord.ChannelID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -54,31 +54,31 @@ func (v *ChannelsView) onSelected(node *tview.TreeNode) {
|
|||
}
|
||||
|
||||
v.selectedChannel = c
|
||||
v.core.App.SetFocus(v.core.InputView)
|
||||
v.app.SetFocus(v.app.view.InputView)
|
||||
|
||||
title := channelToString(*c)
|
||||
if c.Topic != "" {
|
||||
title += " - " + parseMarkdown(c.Topic)
|
||||
}
|
||||
v.core.MessagesView.SetTitle(title)
|
||||
v.app.view.MessagesView.SetTitle(title)
|
||||
|
||||
go func() {
|
||||
// The returned slice will be sorted from latest to oldest.
|
||||
ms, err := v.core.State.Messages(c.ID, v.core.Config.MessagesLimit)
|
||||
ms, err := v.app.state.Messages(c.ID, v.app.config.MessagesLimit)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := len(ms) - 1; i >= 0; i-- {
|
||||
_, err = v.core.MessagesView.Write(buildMessage(v.core, ms[i]))
|
||||
_, err = v.app.view.MessagesView.Write(buildMessage(v.app, ms[i]))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
v.core.MessagesView.ScrollToEnd()
|
||||
v.app.view.MessagesView.ScrollToEnd()
|
||||
}()
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ func (v *ChannelsView) createChannelNode(c discord.Channel) *tview.TreeNode {
|
|||
}
|
||||
|
||||
func (v *ChannelsView) createPrivateChannelNodes(root *tview.TreeNode) {
|
||||
cs, err := v.core.State.Cabinet.PrivateChannels()
|
||||
cs, err := v.app.state.Cabinet.PrivateChannels()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
|
|
@ -106,7 +106,7 @@ func (v *ChannelsView) createPrivateChannelNodes(root *tview.TreeNode) {
|
|||
}
|
||||
|
||||
func (v *ChannelsView) createGuildChannelNodes(root *tview.TreeNode, gID discord.GuildID) {
|
||||
cs, err := v.core.State.Cabinet.Channels(gID)
|
||||
cs, err := v.app.state.Cabinet.Channels(gID)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
|
|
|
|||
242
ui/core.go
242
ui/core.go
|
|
@ -1,242 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,13 +7,13 @@ import (
|
|||
|
||||
type GuildsView struct {
|
||||
*tview.TreeView
|
||||
core *Core
|
||||
app *Application
|
||||
}
|
||||
|
||||
func newGuildsView(c *Core) *GuildsView {
|
||||
func newGuildsView(app *Application) *GuildsView {
|
||||
v := &GuildsView{
|
||||
TreeView: tview.NewTreeView(),
|
||||
core: c,
|
||||
app: app,
|
||||
}
|
||||
|
||||
root := tview.NewTreeNode("")
|
||||
|
|
@ -32,15 +32,15 @@ func newGuildsView(c *Core) *GuildsView {
|
|||
}
|
||||
|
||||
func (v *GuildsView) onSelected(node *tview.TreeNode) {
|
||||
v.core.ChannelsView.selectedChannel = nil
|
||||
v.core.MessagesView.selectedMessage = -1
|
||||
rootNode := v.core.ChannelsView.GetRoot()
|
||||
v.app.view.ChannelsView.selectedChannel = nil
|
||||
v.app.view.MessagesView.selectedMessage = -1
|
||||
rootNode := v.app.view.ChannelsView.GetRoot()
|
||||
rootNode.ClearChildren()
|
||||
v.core.MessagesView.
|
||||
v.app.view.MessagesView.
|
||||
Highlight().
|
||||
Clear().
|
||||
SetTitle("")
|
||||
v.core.InputView.SetText("")
|
||||
v.app.view.InputView.SetText("")
|
||||
|
||||
// If the selected node has children (guild folder), expand the selected node if it is collapsed, otherwise collapse.
|
||||
if len(node.GetChildren()) != 0 {
|
||||
|
|
@ -51,11 +51,11 @@ func (v *GuildsView) onSelected(node *tview.TreeNode) {
|
|||
ref := node.GetReference()
|
||||
// If the reference of the selected node is nil, it must be the direct messages node.
|
||||
if ref == nil {
|
||||
v.core.ChannelsView.createPrivateChannelNodes(rootNode)
|
||||
v.app.view.ChannelsView.createPrivateChannelNodes(rootNode)
|
||||
} else { // Guild
|
||||
v.core.ChannelsView.createGuildChannelNodes(rootNode, ref.(discord.GuildID))
|
||||
v.app.view.ChannelsView.createGuildChannelNodes(rootNode, ref.(discord.GuildID))
|
||||
}
|
||||
|
||||
v.core.ChannelsView.SetCurrentNode(rootNode)
|
||||
v.core.App.SetFocus(v.core.ChannelsView)
|
||||
v.app.view.ChannelsView.SetCurrentNode(rootNode)
|
||||
v.app.SetFocus(v.app.view.ChannelsView)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ import (
|
|||
|
||||
type InputView struct {
|
||||
*tview.InputField
|
||||
core *Core
|
||||
app *Application
|
||||
}
|
||||
|
||||
func newInputView(c *Core) *InputView {
|
||||
func newInputView(app *Application) *InputView {
|
||||
v := &InputView{
|
||||
InputField: tview.NewInputField(),
|
||||
core: c,
|
||||
app: app,
|
||||
}
|
||||
|
||||
v.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
|
||||
|
|
@ -42,16 +42,16 @@ func (v *InputView) inputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
switch event.Name() {
|
||||
case "Enter":
|
||||
return v.sendMessage()
|
||||
case v.core.Config.Keys.InputView.OpenExternalEditor:
|
||||
case v.app.config.Keys.InputView.OpenExternalEditor:
|
||||
return v.openExternalEditor()
|
||||
case v.core.Config.Keys.InputView.PasteClipboard:
|
||||
case v.app.config.Keys.InputView.PasteClipboard:
|
||||
return v.pasteClipboard()
|
||||
case "Esc":
|
||||
v.
|
||||
SetText("").
|
||||
SetTitle("")
|
||||
v.core.MessagesView.selectedMessage = -1
|
||||
v.core.MessagesView.Highlight()
|
||||
v.app.view.MessagesView.selectedMessage = -1
|
||||
v.app.view.MessagesView.Highlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ func (v *InputView) inputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (v *InputView) sendMessage() *tcell.EventKey {
|
||||
if v.core.ChannelsView.selectedChannel == nil {
|
||||
if v.app.view.ChannelsView.selectedChannel == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -68,14 +68,14 @@ func (v *InputView) sendMessage() *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
ms, err := v.core.State.Messages(v.core.ChannelsView.selectedChannel.ID, v.core.Config.MessagesLimit)
|
||||
ms, err := v.app.state.Messages(v.app.view.ChannelsView.selectedChannel.ID, v.app.config.MessagesLimit)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(v.core.MessagesView.GetHighlights()) != 0 {
|
||||
mID, err := discord.ParseSnowflake(v.core.MessagesView.GetHighlights()[0])
|
||||
if len(v.app.view.MessagesView.GetHighlights()) != 0 {
|
||||
mID, err := discord.ParseSnowflake(v.app.view.MessagesView.GetHighlights()[0])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
|
|
@ -83,8 +83,8 @@ func (v *InputView) sendMessage() *tcell.EventKey {
|
|||
|
||||
_, m := findMessageByID(ms, discord.MessageID(mID))
|
||||
d := api.SendMessageData{
|
||||
Content: t,
|
||||
Reference: &discord.MessageReference{
|
||||
Content: t,
|
||||
Reference: &discord.MessageReference{
|
||||
MessageID: m.ID,
|
||||
},
|
||||
AllowedMentions: &api.AllowedMentions{RepliedUser: option.False},
|
||||
|
|
@ -95,14 +95,14 @@ func (v *InputView) sendMessage() *tcell.EventKey {
|
|||
d.AllowedMentions.RepliedUser = option.True
|
||||
}
|
||||
|
||||
go v.core.State.SendMessageComplex(m.ChannelID, d)
|
||||
go v.app.state.SendMessageComplex(m.ChannelID, d)
|
||||
|
||||
v.core.MessagesView.selectedMessage = -1
|
||||
v.core.MessagesView.Highlight()
|
||||
v.app.view.MessagesView.selectedMessage = -1
|
||||
v.app.view.MessagesView.Highlight()
|
||||
|
||||
v.SetTitle("")
|
||||
} else {
|
||||
go v.core.State.SendMessage(v.core.ChannelsView.selectedChannel.ID, t)
|
||||
go v.app.state.SendMessage(v.app.view.ChannelsView.selectedChannel.ID, t)
|
||||
}
|
||||
|
||||
v.SetText("")
|
||||
|
|
@ -139,7 +139,7 @@ func (v *InputView) openExternalEditor() *tcell.EventKey {
|
|||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
|
||||
v.core.App.Suspend(func() {
|
||||
v.app.Suspend(func() {
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ import (
|
|||
|
||||
type LoginView struct {
|
||||
*tview.Form
|
||||
core *Core
|
||||
app *Application
|
||||
}
|
||||
|
||||
func NewLoginView(c *Core) *LoginView {
|
||||
func newLoginView(app *Application) *LoginView {
|
||||
v := &LoginView{
|
||||
Form: tview.NewForm(),
|
||||
core: c,
|
||||
app: app,
|
||||
}
|
||||
|
||||
v.AddInputField("Email", "", 0, nil, nil)
|
||||
|
|
@ -63,11 +63,13 @@ func (v *LoginView) onLoginButtonSelected() {
|
|||
}
|
||||
}
|
||||
|
||||
err = v.core.Run(l.Token)
|
||||
err = v.app.Connect(l.Token)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
v.core.Draw()
|
||||
v.app.SetRoot(v.app.view, true)
|
||||
v.app.SetFocus(v.app.view.GuildsView)
|
||||
|
||||
go keyring.Set(config.Name, "token", l.Token)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ type MessagesView struct {
|
|||
*tview.TextView
|
||||
// The index of the currently selected message. A negative index indicates that there is no currently selected message.
|
||||
selectedMessage int
|
||||
core *Core
|
||||
app *Application
|
||||
}
|
||||
|
||||
func newMessagesView(c *Core) *MessagesView {
|
||||
func newMessagesView(app *Application) *MessagesView {
|
||||
v := &MessagesView{
|
||||
TextView: tview.NewTextView(),
|
||||
selectedMessage: -1,
|
||||
core: c,
|
||||
app: app,
|
||||
}
|
||||
|
||||
v.SetDynamicColors(true)
|
||||
|
|
@ -25,7 +25,7 @@ func newMessagesView(c *Core) *MessagesView {
|
|||
v.SetWordWrap(true)
|
||||
v.SetInputCapture(v.onInputCapture)
|
||||
v.SetChangedFunc(func() {
|
||||
v.core.App.Draw()
|
||||
v.app.Draw()
|
||||
})
|
||||
|
||||
v.SetTitle("Messages")
|
||||
|
|
@ -37,31 +37,31 @@ func newMessagesView(c *Core) *MessagesView {
|
|||
}
|
||||
|
||||
func (v *MessagesView) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
|
||||
if v.core.ChannelsView.selectedChannel == nil {
|
||||
if v.app.view.ChannelsView.selectedChannel == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Messages should return messages ordered from latest to earliest.
|
||||
ms, err := v.core.State.Cabinet.Messages(v.core.ChannelsView.selectedChannel.ID)
|
||||
ms, err := v.app.state.Cabinet.Messages(v.app.view.ChannelsView.selectedChannel.ID)
|
||||
if err != nil || len(ms) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch e.Name() {
|
||||
case v.core.Config.Keys.MessagesView.OpenActionsView:
|
||||
case v.app.config.Keys.MessagesView.OpenActionsView:
|
||||
return v.openActionsView(ms)
|
||||
|
||||
case v.core.Config.Keys.MessagesView.SelectPreviousMessage:
|
||||
case v.app.config.Keys.MessagesView.SelectPreviousMessage:
|
||||
return v.selectPreviousMessage(ms)
|
||||
case v.core.Config.Keys.MessagesView.SelectNextMessage:
|
||||
case v.app.config.Keys.MessagesView.SelectNextMessage:
|
||||
return v.selectNextMessage(ms)
|
||||
case v.core.Config.Keys.MessagesView.SelectFirstMessage:
|
||||
case v.app.config.Keys.MessagesView.SelectFirstMessage:
|
||||
return v.selectFirstMessage(ms)
|
||||
case v.core.Config.Keys.MessagesView.SelectLastMessage:
|
||||
case v.app.config.Keys.MessagesView.SelectLastMessage:
|
||||
return v.selectLastMessage(ms)
|
||||
case "Esc":
|
||||
v.selectedMessage = -1
|
||||
v.core.App.SetFocus(v.core.View)
|
||||
v.app.SetFocus(v.app.view)
|
||||
v.
|
||||
Clear().
|
||||
Highlight().
|
||||
|
|
@ -141,7 +141,7 @@ func (v *MessagesView) openActionsView(ms []discord.Message) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
actionsView := newActionsView(v.core, m)
|
||||
v.core.App.SetRoot(actionsView, true)
|
||||
actionsView := newActionsView(v.app, m)
|
||||
v.app.SetRoot(actionsView, true)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
98
ui/view.go
Normal file
98
ui/view.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type FocusedID int
|
||||
|
||||
const (
|
||||
guildsView FocusedID = iota
|
||||
channelsView
|
||||
messagesView
|
||||
inputView
|
||||
)
|
||||
|
||||
type View struct {
|
||||
*tview.Flex
|
||||
|
||||
GuildsView *GuildsView
|
||||
ChannelsView *ChannelsView
|
||||
MessagesView *MessagesView
|
||||
InputView *InputView
|
||||
|
||||
app *Application
|
||||
focused FocusedID
|
||||
}
|
||||
|
||||
func newView(app *Application) *View {
|
||||
v := &View{
|
||||
Flex: tview.NewFlex(),
|
||||
GuildsView: newGuildsView(app),
|
||||
ChannelsView: newChannelsView(app),
|
||||
MessagesView: newMessagesView(app),
|
||||
InputView: newInputView(app),
|
||||
|
||||
app: app,
|
||||
}
|
||||
|
||||
left := tview.NewFlex().
|
||||
SetDirection(tview.FlexRow).
|
||||
AddItem(v.GuildsView, 10, 1, false).
|
||||
AddItem(v.ChannelsView, 0, 1, false)
|
||||
right := tview.NewFlex().
|
||||
SetDirection(tview.FlexRow).
|
||||
AddItem(v.MessagesView, 0, 1, false).
|
||||
AddItem(v.InputView, 3, 1, false)
|
||||
|
||||
v.AddItem(left, 0, 1, false)
|
||||
v.AddItem(right, 0, 4, false)
|
||||
|
||||
v.SetInputCapture(v.onInputCapture)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *View) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Key() {
|
||||
case tcell.KeyEsc:
|
||||
v.focused = 0
|
||||
case tcell.KeyBacktab:
|
||||
// If the currently focused view is the guilds view (first), then focus the input view (last)
|
||||
if v.focused == 0 {
|
||||
v.focused = inputView
|
||||
} else {
|
||||
v.focused--
|
||||
}
|
||||
|
||||
v.setFocus()
|
||||
case tcell.KeyTab:
|
||||
// If the currently focused view is the input view (last), then focus the guilds view (first)
|
||||
if v.focused == inputView {
|
||||
v.focused = guildsView
|
||||
} else {
|
||||
v.focused++
|
||||
}
|
||||
|
||||
v.setFocus()
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
func (v *View) setFocus() {
|
||||
var p tview.Primitive
|
||||
switch v.focused {
|
||||
case guildsView:
|
||||
p = v.GuildsView
|
||||
case channelsView:
|
||||
p = v.ChannelsView
|
||||
case messagesView:
|
||||
p = v.MessagesView
|
||||
case inputView:
|
||||
p = v.InputView
|
||||
}
|
||||
|
||||
v.app.SetFocus(p)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue