package dialogs import ( "fmt" "git.brettb.xyz/goinv/client/internal/ui/style" "git.brettb.xyz/goinv/client/internal/ui/utils" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "go.uber.org/zap" ) const ( cmdWidthOffset = 6 ) const ( cmdTableFocus = 0 + iota cmdFormFocus ) type CommandDialog struct { *tview.Box layout *tview.Flex table *tview.Table form *tview.Form logger *zap.Logger display bool options [][]string width int height int focusElement int selectedStyle tcell.Style cancelHandler func() selectHandler func() } func NewCommandDialog(logger *zap.Logger, options [][]string) *CommandDialog { form := tview.NewForm(). AddButton("Cancel", nil). SetButtonsAlign(tview.AlignRight) form.SetBackgroundColor(style.DialogBgColor) form.SetButtonBackgroundColor(style.ButtonBgColor) form.SetButtonTextColor(style.ButtonFgColor) activatedStyle := tcell.StyleDefault. Background(style.ButtonSelectedBgColor). Foreground(style.ButtonSelectedFgColor) form.SetButtonActivatedStyle(activatedStyle) cmdsTable := tview.NewTable() cmdsTable.SetBackgroundColor(style.DialogBgColor) cmdWidth := 0 cmdsTable.SetCell(0, 0, tview.NewTableCell(fmt.Sprintf("[%s::b]COMMAND", style.GetColorHex(style.TableHeaderFgColor))). SetExpansion(1). SetBackgroundColor(style.TableHeaderBgColor). SetTextColor(style.TableHeaderFgColor). SetAlign(tview.AlignLeft). SetSelectable(false)) cmdsTable.SetCell(0, 1, tview.NewTableCell(fmt.Sprintf("[%s::b]DESCRIPTION", style.GetColorHex(style.TableHeaderFgColor))). SetExpansion(1). SetBackgroundColor(style.TableHeaderBgColor). SetTextColor(style.TableHeaderFgColor). SetAlign(tview.AlignCenter). SetSelectable(false)) col1Width := 0 col2Width := 0 for i, option := range options { cmdsTable.SetCell(i+1, 0, tview.NewTableCell(option[0]). SetAlign(tview.AlignLeft). SetSelectable(true).SetTextColor(style.DialogFgColor)) cmdsTable.SetCell(i+1, 1, tview.NewTableCell(option[1]). SetAlign(tview.AlignLeft). SetSelectable(true).SetTextColor(style.DialogFgColor)) if len(option[0]) > col1Width { col1Width = len(option[0]) } if len(option[1]) > col2Width { col2Width = len(option[1]) } } cmdWidth = col1Width + col2Width + 2 cmdsTable.SetFixed(1, 1) cmdsTable.SetSelectable(true, false) cmdsTable.SetBackgroundColor(style.DialogBgColor) cmdLayout := tview.NewFlex().SetDirection(tview.FlexColumn) cmdLayout.AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false) cmdLayout.AddItem(cmdsTable, 0, 1, true) cmdLayout.AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false) layout := tview.NewFlex().SetDirection(tview.FlexRow) layout.AddItem(cmdLayout, 0, 1, true) layout.AddItem(form, DialogFormHeight, 0, true) layout.SetBorder(true) layout.SetBorderColor(style.DialogBorderColor) layout.SetBackgroundColor(style.DialogBgColor) selectedStyle := tcell.StyleDefault. Background(style.TableSelectedBgColor). Foreground(style.TableSelectedFgColor) cmdsTable.SetSelectedStyle(selectedStyle) return &CommandDialog{ Box: tview.NewBox().SetBorder(false), layout: layout, table: cmdsTable, form: form, display: false, options: options, width: cmdWidth + cmdWidthOffset, height: len(options) + TableHeightOffset + DialogFormHeight, focusElement: cmdTableFocus, selectedStyle: selectedStyle, logger: logger, } } // GetSelectedItem returns selected row item. func (cmd *CommandDialog) GetSelectedItem() string { row, _ := cmd.table.GetSelection() if row >= 0 { return cmd.options[row-1][0] } return "" } // GetCommandCount returns number of commands func (cmd *CommandDialog) GetCommandCount() int { return cmd.table.GetRowCount() } // Display this primitive. func (cmd *CommandDialog) Display() { cmd.table.Select(1, 0) cmd.form.SetFocus(1) cmd.display = true } // Hide this primitive func (cmd *CommandDialog) Hide() { cmd.display = false cmd.focusElement = cmdTableFocus cmd.table.SetSelectedStyle(cmd.selectedStyle) } // HasFocus returns whether this primitive has focus func (cmd *CommandDialog) HasFocus() bool { return utils.CheckFocus(cmd.table, cmd.form) } func (cmd *CommandDialog) IsDisplay() bool { return cmd.display } func (cmd *CommandDialog) Focus(delegate func(tview.Primitive)) { if cmd.focusElement == cmdTableFocus { delegate(cmd.table) return } button := cmd.form.GetButton(cmd.form.GetButtonCount() - 1) button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == utils.SwitchFocusKey.Key { cmd.focusElement = cmdTableFocus cmd.Focus(delegate) cmd.form.SetFocus(0) return nil } return event }) delegate(cmd.form) } func (cmd *CommandDialog) InputHandler() func(event *tcell.EventKey, setFocus func(primitive tview.Primitive)) { return cmd.WrapInputHandler(func(event *tcell.EventKey, setFocus func(primitive tview.Primitive)) { cmd.logger.Sugar().Debugf("command dialog event %v received", event) if event.Key() == utils.CloseDialogKey.Key { cmd.cancelHandler() return } if event.Key() == utils.SwitchFocusKey.Key { cmd.setFocusElement() } if cmd.form.HasFocus() { if formHandler := cmd.form.InputHandler(); formHandler != nil { formHandler(event, setFocus) return } } if cmd.table.HasFocus() { if event.Key() == tcell.KeyEnter { cmd.selectHandler() return } if tableHandler := cmd.table.InputHandler(); tableHandler != nil { tableHandler(event, setFocus) return } } }) } // SetSelectedFunc sets the form enter button selected function func (cmd *CommandDialog) SetSelectedFunc(handler func()) *CommandDialog { cmd.selectHandler = handler return cmd } // SetCancelFunc sets form cancel button selected function. func (cmd *CommandDialog) SetCancelFunc(handler func()) *CommandDialog { cmd.cancelHandler = handler cancelButton := cmd.form.GetButton(cmd.form.GetButtonCount() - 1) cancelButton.SetSelectedFunc(handler) return cmd } // SetRect set rects for this primitive func (cmd *CommandDialog) SetRect(x, y, width, height int) { ws := (width - cmd.width) / 2 hs := (height - cmd.height) / 2 dy := y + hs bWidth := cmd.width if cmd.width > width { ws = 0 bWidth = width - 1 } bHeight := cmd.height if cmd.height > height { dy = y + 1 bHeight = height - 1 } cmd.Box.SetRect(x+ws, dy, bWidth, bHeight) x, y, width, height = cmd.Box.GetInnerRect() cmd.layout.SetRect(x, y, width, height) } func (cmd *CommandDialog) Draw(screen tcell.Screen) { if !cmd.display { return } cmd.Box.DrawForSubclass(screen, cmd) cmd.layout.Draw(screen) } func (cmd *CommandDialog) setFocusElement() { if cmd.focusElement == cmdTableFocus { cmd.focusElement = cmdFormFocus cmd.table.SetSelectedStyle(tcell.StyleDefault. Background(style.DialogBgColor). Foreground(style.DialogFgColor)) } else { cmd.focusElement = cmdTableFocus cmd.table.SetSelectedStyle(cmd.selectedStyle) } }