refactor: merge Application with Layout

This commit is contained in:
ayn2op 2024-10-12 19:28:35 -04:00
parent 1769fa97e1
commit 9d05f2999c
8 changed files with 153 additions and 166 deletions

View file

@ -1,68 +0,0 @@
package cmd
import (
"log/slog"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
type Application struct {
*tview.Application
}
func newApplication() *Application {
app := &Application{
Application: tview.NewApplication(),
}
app.EnableMouse(cfg.Mouse)
app.SetInputCapture(app.onInputCapture)
return app
}
func (app *Application) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Name() {
case cfg.Keys.Quit:
app.Stop()
case "Ctrl+C":
// https://github.com/rivo/tview/blob/a64fc48d7654432f71922c8b908280cdb525805c/application.go#L153
return tcell.NewEventKey(tcell.KeyCtrlC, 0, tcell.ModNone)
}
return event
}
func (app *Application) show(token string) error {
if token == "" {
loginForm := newLoginForm(func(token string, err error) {
if err != nil {
slog.Error("failed to login", "err", err)
return
}
if err := app.show(token); err != nil {
slog.Error("failed to show app", "err", err)
}
})
app.SetRoot(loginForm, true)
} else {
// mainFlex must be initialized before opening a new state.
mainFlex = newMainFlex()
if err := openState(token); err != nil {
return err
}
app.SetRoot(mainFlex, true)
}
return nil
}
func (app *Application) Run(token string) error {
if err := app.show(token); err != nil {
return err
}
return app.Application.Run()
}

View file

@ -14,13 +14,14 @@ import (
type GuildsTree struct {
*tview.TreeView
app *tview.Application
selectedChannelID discord.ChannelID
}
func newGuildsTree() *GuildsTree {
func newGuildsTree(app *tview.Application) *GuildsTree {
gt := &GuildsTree{
TreeView: tview.NewTreeView(),
app: app,
}
root := tview.NewTreeNode("")
@ -209,7 +210,7 @@ func (gt *GuildsTree) onSelected(n *tview.TreeNode) {
mainFlex.messagesText.SetTitle(gt.channelToString(*c))
gt.selectedChannelID = ref
app.SetFocus(mainFlex.messageInput)
gt.app.SetFocus(mainFlex.messageInput)
case nil: // Direct messages
cs, err := discordState.PrivateChannels()
if err != nil {

134
cmd/layout.go Normal file
View file

@ -0,0 +1,134 @@
package cmd
import (
"log/slog"
"github.com/ayn2op/discordo/internal/constants"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/zalando/go-keyring"
)
type Layout struct {
app *tview.Application
flex *tview.Flex
guildsTree *GuildsTree
messagesText *MessagesText
messageInput *MessageInput
}
func newLayout() *Layout {
app := tview.NewApplication()
l := &Layout{
app: app,
flex: tview.NewFlex(),
guildsTree: newGuildsTree(app),
messagesText: newMessagesText(app),
messageInput: newMessageInput(app),
}
l.init()
l.app.EnableMouse(cfg.Mouse)
l.app.SetInputCapture(l.onAppInputCapture)
l.flex.SetInputCapture(l.onFlexInputCapture)
return l
}
func (l *Layout) show(token string) error {
if token == "" {
loginForm := newLoginForm(func(token string, err error) {
if err != nil {
slog.Error("failed to login", "err", err)
return
}
if err := l.show(token); err != nil {
slog.Error("failed to show app", "err", err)
}
})
l.app.SetRoot(loginForm, true)
} else {
if err := openState(token, l.app); err != nil {
return err
}
l.app.SetRoot(l.flex, true)
}
return nil
}
func (l *Layout) run(token string) error {
if err := l.show(token); err != nil {
return err
}
return l.app.Run()
}
func (l *Layout) init() {
l.flex.Clear()
right := tview.NewFlex()
right.SetDirection(tview.FlexRow)
right.AddItem(l.messagesText, 0, 1, false)
right.AddItem(l.messageInput, 3, 1, false)
// The guilds tree is always focused first at start-up.
l.flex.AddItem(l.guildsTree, 0, 1, true)
l.flex.AddItem(right, 0, 4, false)
}
func (l *Layout) onAppInputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Name() {
case cfg.Keys.Quit:
l.app.Stop()
case "Ctrl+C":
// https://github.com/rivo/tview/blob/a64fc48d7654432f71922c8b908280cdb525805c/application.go#L153
return tcell.NewEventKey(tcell.KeyCtrlC, 0, tcell.ModNone)
}
return event
}
func (l *Layout) onFlexInputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Name() {
case cfg.Keys.FocusGuildsTree:
l.app.SetFocus(l.guildsTree)
return nil
case cfg.Keys.FocusMessagesText:
l.app.SetFocus(l.messagesText)
return nil
case cfg.Keys.FocusMessageInput:
l.app.SetFocus(l.messageInput)
return nil
case cfg.Keys.Logout:
l.app.Stop()
if err := keyring.Delete(constants.Name, "token"); err != nil {
slog.Error("failed to delete token from keyring", "err", err)
return nil
}
return nil
case cfg.Keys.ToggleGuildsTree:
// The guilds tree is visible if the numbers of items is two.
if l.flex.GetItemCount() == 2 {
l.flex.RemoveItem(l.guildsTree)
if l.guildsTree.HasFocus() {
l.app.SetFocus(l.flex)
}
} else {
l.init()
l.app.SetFocus(l.guildsTree)
}
return nil
}
return event
}

View file

@ -1,83 +0,0 @@
package cmd
import (
"log/slog"
"github.com/ayn2op/discordo/internal/constants"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/zalando/go-keyring"
)
type MainFlex struct {
*tview.Flex
guildsTree *GuildsTree
messagesText *MessagesText
messageInput *MessageInput
}
func newMainFlex() *MainFlex {
mf := &MainFlex{
Flex: tview.NewFlex(),
guildsTree: newGuildsTree(),
messagesText: newMessagesText(),
messageInput: newMessageInput(),
}
mf.init()
mf.SetInputCapture(mf.onInputCapture)
return mf
}
func (mf *MainFlex) init() {
mf.Clear()
right := tview.NewFlex()
right.SetDirection(tview.FlexRow)
right.AddItem(mf.messagesText, 0, 1, false)
right.AddItem(mf.messageInput, 3, 1, false)
// The guilds tree is always focused first at start-up.
mf.AddItem(mf.guildsTree, 0, 1, true)
mf.AddItem(right, 0, 4, false)
}
func (mf *MainFlex) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Name() {
case cfg.Keys.FocusGuildsTree:
app.SetFocus(mf.guildsTree)
return nil
case cfg.Keys.FocusMessagesText:
app.SetFocus(mf.messagesText)
return nil
case cfg.Keys.FocusMessageInput:
app.SetFocus(mf.messageInput)
return nil
case cfg.Keys.Logout:
app.Stop()
if err := keyring.Delete(constants.Name, "token"); err != nil {
slog.Error("failed to delete token from keyring", "err", err)
return nil
}
return nil
case cfg.Keys.ToggleGuildsTree:
// The guilds tree is visible if the numbers of items is two.
if mf.GetItemCount() == 2 {
mf.RemoveItem(mf.guildsTree)
if mf.guildsTree.HasFocus() {
app.SetFocus(mf)
}
} else {
mf.init()
app.SetFocus(mf.guildsTree)
}
return nil
}
return event
}

View file

@ -17,12 +17,14 @@ import (
type MessageInput struct {
*tview.TextArea
app *tview.Application
replyMessageID discord.MessageID
}
func newMessageInput() *MessageInput {
func newMessageInput(app *tview.Application) *MessageInput {
mi := &MessageInput{
TextArea: tview.NewTextArea(),
app: app,
}
mi.SetTextStyle(tcell.StyleDefault.Background(tcell.GetColor(cfg.Theme.BackgroundColor)))
@ -131,7 +133,7 @@ func (mi *MessageInput) editor() {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
app.Suspend(func() {
mi.app.Suspend(func() {
err := cmd.Run()
if err != nil {
slog.Error("failed to run command", "err", err, "command", cmd)

View file

@ -21,13 +21,14 @@ import (
type MessagesText struct {
*tview.TextView
app *tview.Application
selectedMessageID discord.MessageID
}
func newMessagesText() *MessagesText {
func newMessagesText(app *tview.Application) *MessagesText {
mt := &MessagesText{
TextView: tview.NewTextView(),
app: app,
}
mt.SetDynamicColors(true)
@ -345,7 +346,7 @@ func (mt *MessagesText) reply(mention bool) {
title += msg.Author.Tag()
mainFlex.messageInput.SetTitle(title)
mainFlex.messageInput.replyMessageID = mt.selectedMessageID
app.SetFocus(mainFlex.messageInput)
mt.app.SetFocus(mainFlex.messageInput)
}
func (mt *MessagesText) delete() {

View file

@ -9,8 +9,7 @@ var (
discordState *State
cfg *config.Config
app *Application
mainFlex *MainFlex
mainFlex *Layout
)
func Run(token string) error {
@ -24,7 +23,6 @@ func Run(token string) error {
return err
}
// app must be initialized after configuration is loaded
app = newApplication()
return app.Run(token)
mainFlex = newLayout()
return mainFlex.run(token)
}

View file

@ -27,11 +27,13 @@ func init() {
type State struct {
*ningen.State
app *tview.Application
}
func openState(token string) error {
func openState(token string, app *tview.Application) error {
discordState = &State{
State: ningen.New(token),
app: app,
}
// Handlers
@ -78,7 +80,7 @@ func (s *State) onReady(r *gateway.ReadyEvent) {
}
mainFlex.guildsTree.SetCurrentNode(root)
app.SetFocus(mainFlex.guildsTree)
s.app.SetFocus(mainFlex.guildsTree)
}
func (s *State) onMessageCreate(m *gateway.MessageCreateEvent) {