diff --git a/README.asciidoc b/README.asciidoc index 7618f94..97a1d6e 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -81,10 +81,13 @@ and initialises the database with an empty project. |Create a new card |CTRL + d -|Delete card +|Delete a card |m -|Move card between statuses +|Move a card from one column to another (press `Enter` to confirm your selection). + +|Enter +|View the details of a card (press `ESC` to go back to the main board). |=== == Inspiration diff --git a/internal/ui/cardview.go b/internal/ui/cardview.go new file mode 100644 index 0000000..8b7ead7 --- /dev/null +++ b/internal/ui/cardview.go @@ -0,0 +1,69 @@ +package ui + +import ( + "fmt" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +type cardView struct { + *tview.TextView + frame *tview.Frame + contentFormat string +} + +func newCardView() *cardView { + border := tcell.ColorOrangeRed.TrueColor() + background := tcell.ColorBlack.TrueColor() + + content := tview.NewTextView() + + // Stylise the TextView + content.SetDynamicColors(true). + SetBorder(true). + SetBorderPadding(0, 0, 1, 1). + SetBorderColor(border). + SetBackgroundColor(background) + + cardContentFormat := `[green::b][#%d[] %s[-:-:-:-] + +[green::b]Status:[white::-] %s [green::b]Created:[white::-] %s + +[green::b]Description:[white::-] + +%s +` + + view := cardView{ + TextView: content, + frame: tview.NewFrame(content), + contentFormat: cardContentFormat, + } + + return &view +} + +func (c *cardView) setDoneFunc(handler func(key tcell.Key)) { + c.SetDoneFunc(handler) +} + +func (c *cardView) print(id int, title, status, created, description string) { + fmt.Fprintf(c, c.contentFormat, id, title, status, created, description) +} + +func (c *cardView) Draw(screen tcell.Screen) { + height := 25 + width := 50 + + screenWidth, screenHeight := screen.Size() + + // Set the form's position and size. + x := (screenWidth - width) / 2 + y := (screenHeight - height) / 2 + c.SetRect(x, y, width, height) + + // Draw the frame. + c.frame.SetRect(x, y, width, height) + c.frame.Draw(screen) +} diff --git a/internal/ui/column.go b/internal/ui/column.go index 72b8af8..e1c9454 100644 --- a/internal/ui/column.go +++ b/internal/ui/column.go @@ -19,6 +19,7 @@ type column struct { *tview.Box statusID int + statusName string cards []card focusedCard int getBoardModeFunc func() boardMode @@ -28,6 +29,7 @@ func newColumn(statusID int, statusName string, getBoardModeFunc func() boardMod column := column{ Box: tview.NewBox(), statusID: statusID, + statusName: statusName, cards: nil, focusedCard: 0, getBoardModeFunc: getBoardModeFunc, diff --git a/internal/ui/ui.go b/internal/ui/ui.go index e6a32df..a116f3c 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -28,6 +28,7 @@ const ( quitPage string = "quit" cardFormPage string = "card form" deleteCardModalPage string = "delete card modal" + viewPage string = "view" ) type UI struct { @@ -43,6 +44,7 @@ type UI struct { cardForm *cardForm deleteCardModal *tview.Modal statusSelection statusSelection + view *cardView } // NewUI returns a new UI value. @@ -64,6 +66,7 @@ func NewUI(path string) (UI, error) { deleteCardModal: tview.NewModal(), mode: normal, statusSelection: statusSelection{0, 0, 0}, + view: newCardView(), } if err := userInterface.init(); err != nil { @@ -85,7 +88,33 @@ func (u *UI) init() error { return fmt.Errorf("error initialising the status columns; %w", err) } - u.flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + u.flex.SetInputCapture(u.inputCapture()) + + u.pages.AddPage(mainPage, u.flex, true, true) + + u.initQuitModal() + u.pages.AddPage(quitPage, u.quitModal, false, false) + + u.initCardForm() + u.pages.AddPage(cardFormPage, u.cardForm, false, false) + + u.initDeleteCardModal() + u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false) + + u.initView() + u.pages.AddPage(viewPage, u.view, false, false) + + u.SetRoot(u.pages, true) + + if err := u.refresh(false); err != nil { + return fmt.Errorf("error refreshing the board; %w", err) + } + + return nil +} + +func (u *UI) inputCapture() func(event *tcell.EventKey) *tcell.EventKey { + return func(event *tcell.EventKey) *tcell.EventKey { key, letter := event.Key(), event.Rune() switch { @@ -131,11 +160,19 @@ func (u *UI) init() error { u.mode = normal } case key == tcell.KeyEnter: - if u.mode == selection { + switch u.mode { + case normal: + card, _ := u.getFocusedCard() + status := u.focusedStatusName() + u.view.print(card.ID, card.Title, status, card.Created, card.Description) + u.pages.ShowPage(viewPage) + u.SetFocus(u.view) + case selection: u.statusSelection.nextStatusID = u.focusedStatusID() if u.statusSelection.currentStatusID != u.statusSelection.nextStatusID { u.statusSelection.moveCard(u.board) } + u.statusSelection = statusSelection{0, 0, 0} u.mode = normal _ = u.refresh(false) @@ -143,26 +180,7 @@ func (u *UI) init() error { } return event - }) - - u.pages.AddPage(mainPage, u.flex, true, true) - - u.initQuitModal() - u.pages.AddPage(quitPage, u.quitModal, false, false) - - u.initCardForm() - u.pages.AddPage(cardFormPage, u.cardForm, false, false) - - u.initDeleteCardModal() - u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false) - - u.SetRoot(u.pages, true) - - if err := u.refresh(false); err != nil { - return fmt.Errorf("error refreshing the board; %w", err) } - - return nil } // initCardForm initialises the card form. @@ -218,6 +236,19 @@ func (u *UI) initQuitModal() { SetDoneFunc(doneFunc) } +// initView initialises the view window for displaying the card. +func (u *UI) initView() { + doneFunc := func(key tcell.Key) { + if key == tcell.KeyEsc { + u.pages.HidePage(viewPage) + u.view.Clear() + u.setColumnFocus() + } + } + + u.view.setDoneFunc(doneFunc) +} + // newCard creates and saves a new card to the database. func (u *UI) newCard(title, description string) error { args := board.CardArgs{ @@ -376,7 +407,12 @@ func (u *UI) focusedCardID() int { return id } -// focusedStatusID returns the ID of thestatus (column) in focus. +// focusedStatusID returns the ID of the status (column) in focus. func (u *UI) focusedStatusID() int { return u.columns[u.focusedColumn].statusID } + +// focusedStatusName returns the name of the status (column) in focus. +func (u *UI) focusedStatusName() string { + return u.columns[u.focusedColumn].statusName +}