From 4f621b02802df51171dbf60b661e851e04efb548 Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Sun, 23 Apr 2023 00:17:34 +0100 Subject: [PATCH] custom modalinput from witchard/toukan --- internal/ui/column.go | 41 +++++++++++--- internal/ui/init.go | 35 ------------ internal/ui/modalinput.go | 86 ++++++++++++++++++++++++++++ internal/ui/ui.go | 114 +++++++++++++++++--------------------- 4 files changed, 170 insertions(+), 106 deletions(-) delete mode 100644 internal/ui/init.go create mode 100644 internal/ui/modalinput.go diff --git a/internal/ui/column.go b/internal/ui/column.go index 969770d..fd0c523 100644 --- a/internal/ui/column.go +++ b/internal/ui/column.go @@ -12,16 +12,16 @@ type column struct { } func (u *UI) newColumn(statusID int, statusName string) column { - l := tview.NewList() + cards := tview.NewList() - l.SetBorder(true) - l.ShowSecondaryText(false) - l.SetTitle(" " + statusName + " ") - l.SetHighlightFullLine(true) - l.SetSelectedFocusOnly(true) - l.SetWrapAround(false) + cards.SetBorder(true) + cards.ShowSecondaryText(false) + cards.SetTitle(" " + statusName + " ") + cards.SetHighlightFullLine(true) + cards.SetSelectedFocusOnly(true) + cards.SetWrapAround(false) - l.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + cards.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Rune() == 'h' || event.Key() == tcell.KeyLeft { u.shiftColumnFocus(shiftLeft) } else if event.Rune() == 'l' || event.Key() == tcell.KeyRight { @@ -34,8 +34,31 @@ func (u *UI) newColumn(statusID int, statusName string) column { c := column{ statusID: statusID, statusName: statusName, - cards: l, + cards: cards, } return c } + +func (u *UI) setColumnFocus() { + u.SetFocus(u.columns[u.focusedColumn].cards) +} + +func (u *UI) shiftColumnFocus(s int) { + switch s { + case shiftRight: + if u.focusedColumn == len(u.columns)-1 { + u.focusedColumn = 0 + } else { + u.focusedColumn++ + } + case shiftLeft: + if u.focusedColumn == 0 { + u.focusedColumn = len(u.columns) - 1 + } else { + u.focusedColumn-- + } + } + + u.setColumnFocus() +} diff --git a/internal/ui/init.go b/internal/ui/init.go deleted file mode 100644 index c3a1759..0000000 --- a/internal/ui/init.go +++ /dev/null @@ -1,35 +0,0 @@ -package ui - -import ( - "github.com/rivo/tview" -) - -// newAddForm creates a new Form primitive for creating a new card. -func newAddForm(u *UI) *tview.Form { - add := tview.NewForm() - - titleField := "Title" - - add.AddInputField(titleField, "", 0, nil, nil) - - add.AddButton("Save", func() { - title := add.GetFormItemByLabel(titleField).(*tview.InputField).GetText() - // TODO: error value needs handling - _ = u.newCard(title, "") - add.GetFormItemByLabel(titleField).(*tview.InputField).SetText("") - u.pages.SwitchToPage(mainPageName) - u.setColumnFocus() - }) - - add.AddButton("Cancel", func() { - u.pages.SwitchToPage(mainPageName) - add.GetFormItemByLabel(titleField).(*tview.InputField).SetText("") - u.setColumnFocus() - }) - - add.SetBorder(true) - - add.SetTitle(" New Card ") - - return add -} diff --git a/internal/ui/modalinput.go b/internal/ui/modalinput.go new file mode 100644 index 0000000..890d63e --- /dev/null +++ b/internal/ui/modalinput.go @@ -0,0 +1,86 @@ +package ui + +import ( + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +type modalInput struct { + *tview.Form + frame *tview.Frame + text string + done func(string, bool) +} + +func NewModalInput() *modalInput { + form := tview.NewForm() + m := modalInput{ + Form: form, + frame: tview.NewFrame(form), + text: "", + done: nil, + } + + m.SetButtonsAlign(tview.AlignCenter). + SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor). + SetButtonTextColor(tview.Styles.PrimaryTextColor). + SetBackgroundColor(tview.Styles.ContrastBackgroundColor). + SetBorderPadding(0, 0, 0, 0) + + m.AddInputField("", "", 50, nil, func(text string) { + m.text = text + }) + + m.AddButton("OK", func() { + if m.done != nil { + m.done(m.text, true) + } + }) + + m.AddButton("Cancel", func() { + if m.done != nil { + m.done(m.text, false) + } + }) + + m.frame.SetBorders(0, 0, 1, 0, 0, 0). + SetBorder(true). + SetBackgroundColor(tview.Styles.ContrastBackgroundColor). + SetBorderPadding(1, 1, 1, 1) + + return &m +} + +func (m *modalInput) SetValue(text string) { + m.Clear(false) + m.AddInputField("", text, 50, nil, func(text string) { + m.text = text + }) +} + +func (m *modalInput) SetDoneFunc(handler func(string, bool)) *modalInput { + m.done = handler + return m +} + +func (m *modalInput) Draw(screen tcell.Screen) { + buttonsWidth := 50 + screenWidth, screenHeight := screen.Size() + width := screenWidth / 3 + + if width < buttonsWidth { + width = buttonsWidth + } + + height := 7 + width += 4 + + // Set the modal's position and size. + x := (screenWidth - width) / 2 + y := (screenHeight - height) / 2 + m.SetRect(x, y, width, height) + + // Draw the frame. + m.frame.SetRect(x, y, width, height) + m.frame.Draw(screen) +} diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 735d2da..7264bb7 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -1,11 +1,13 @@ package ui +// TODO: Update card status (card ID, oldStatus, newStatus) + import ( "fmt" "codeflow.dananglin.me.uk/apollo/canal/internal/board" - "github.com/rivo/tview" "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" ) const ( @@ -29,6 +31,7 @@ type UI struct { focusedColumn int board board.Board quit *tview.Modal + add *modalInput } // NewUI returns a new UI value. @@ -38,27 +41,39 @@ func NewUI() UI { pages: tview.NewPages(), flex: tview.NewFlex(), quit: tview.NewModal(), + add: NewModalInput(), focusedColumn: 0, } - u.initialise() + u.init() return u } -// initialise the UI. -func (u *UI) initialise() { +// closeBoard closes the BoltDB database. +func (u *UI) closeBoard() { + _ = u.board.Close() +} + +// init the UI. +func (u *UI) init() { u.pages.AddPage(mainPageName, u.flex, true, true) u.initQuitModal() u.pages.AddPage(quitPageName, u.quit, false, false) + u.initAddInputModal() + u.pages.AddPage(addPageName, u.add, false, false) + u.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Rune() == 'q' { u.pages.ShowPage(quitPageName) u.SetFocus(u.quit) } else if event.Rune() == 'o' { u.openBoard("") + } else if event.Rune() == 'a' { + u.pages.ShowPage(addPageName) + u.SetFocus(u.add) } return event @@ -67,6 +82,18 @@ func (u *UI) initialise() { u.SetRoot(u.pages, true) } +func (u *UI) initAddInputModal() { + doneFunc := func(text string, success bool) { + if success { + _ = u.newCard(text, "") + } + u.pages.HidePage(addPageName) + u.setColumnFocus() + } + + u.add.SetDoneFunc(doneFunc) +} + // initQuitModal initialises the quit modal. func (u *UI) initQuitModal() { quitDoneFunc := func(_ int, buttonLabel string) { @@ -74,7 +101,7 @@ func (u *UI) initQuitModal() { case "Quit": u.shutdown() default: - u.pages.SwitchToPage("main") + u.pages.SwitchToPage(mainPageName) u.setColumnFocus() } } @@ -84,15 +111,20 @@ func (u *UI) initQuitModal() { SetDoneFunc(quitDoneFunc) } -// shutdown shuts down the application. -func (u *UI) shutdown() { - u.closeBoard() - u.Stop() -} +// newCard creates a new card and saves it to the database. +func (u *UI) newCard(title, content string) error { + args := board.CardArgs{ + NewTitle: title, + NewContent: content, + } -// closeBoard closes the BoltDB database. -func (u *UI) closeBoard() { - _ = u.board.Close() + if err := u.board.CreateCard(args); err != nil { + return fmt.Errorf("unable to create card, %w", err) + } + + u.refresh() + + return nil } // openBoard opens the kanban project. @@ -111,7 +143,7 @@ func (u *UI) openBoard(path string) error { return nil } -// refresh refreshes the UI. +// refresh the UI. func (u *UI) refresh() error { statusList, err := u.board.StatusList() if err != nil { @@ -125,6 +157,12 @@ func (u *UI) refresh() error { return nil } +// shutdown shuts down the application. +func (u *UI) shutdown() { + u.closeBoard() + u.Stop() +} + func (u *UI) updateBoard(statusList []board.Status) error { u.flex.Clear() @@ -151,51 +189,3 @@ func (u *UI) updateBoard(statusList []board.Status) error { return nil } - -func (u *UI) shiftColumnFocus(s int) { - switch s { - case shiftRight: - if u.focusedColumn == len(u.columns)-1 { - u.focusedColumn = 0 - } else { - u.focusedColumn++ - } - case shiftLeft: - if u.focusedColumn == 0 { - u.focusedColumn = len(u.columns) - 1 - } else { - u.focusedColumn-- - } - } - - u.setColumnFocus() -} - -func (u *UI) setColumnFocus() { - u.SetFocus(u.columns[u.focusedColumn].cards) -} - -// newCard creates a new card and saves it to the database. -func (u *UI) newCard(title, content string) error { - args := board.CardArgs{ - NewTitle: title, - NewContent: content, - } - - if err := u.board.CreateCard(args); err != nil { - return fmt.Errorf("unable to create card, %w", err) - } - - u.refresh() - - return nil -} - -// TODO: Move 'Add' to the centre of the app -// TODO: Customize list primitive or create a new one -// TODO: If customizing existing list primitive, wrap list around a column type. Add statusID to it. -// TODO: Update card status (card ID, oldStatus, newStatus) - -//func viewCard() error { -//return nil -//}