initial commit
This commit is contained in:
300
internal/ui/dialogs/command.go
Normal file
300
internal/ui/dialogs/command.go
Normal file
@@ -0,0 +1,300 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
203
internal/ui/dialogs/confirm.go
Normal file
203
internal/ui/dialogs/confirm.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package dialogs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type ConfirmDialog struct {
|
||||
*tview.Box
|
||||
logger *zap.Logger
|
||||
layout *tview.Flex
|
||||
textview *tview.TextView
|
||||
form *tview.Form
|
||||
x int
|
||||
y int
|
||||
width int
|
||||
height int
|
||||
message string
|
||||
display bool
|
||||
cancelHandler func()
|
||||
selectHandler func()
|
||||
}
|
||||
|
||||
func NewConfirmDialog(logger *zap.Logger) *ConfirmDialog {
|
||||
dialog := &ConfirmDialog{
|
||||
Box: tview.NewBox(),
|
||||
logger: logger,
|
||||
display: false,
|
||||
}
|
||||
|
||||
dialog.textview = tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetWrap(true).
|
||||
SetTextAlign(tview.AlignLeft)
|
||||
|
||||
dialog.textview.SetBackgroundColor(style.DialogBgColor)
|
||||
dialog.textview.SetTextColor(style.DialogFgColor)
|
||||
|
||||
dialog.form = tview.NewForm().
|
||||
AddButton("Cancel", nil).
|
||||
AddButton(" OK ", nil).
|
||||
SetButtonsAlign(tview.AlignRight)
|
||||
dialog.form.SetBackgroundColor(style.DialogBgColor)
|
||||
dialog.form.SetButtonBackgroundColor(style.ButtonBgColor)
|
||||
dialog.form.SetButtonTextColor(style.ButtonFgColor)
|
||||
|
||||
activatedStyle := tcell.StyleDefault.
|
||||
Background(style.ButtonSelectedBgColor).
|
||||
Foreground(style.ButtonSelectedFgColor)
|
||||
|
||||
dialog.form.SetButtonActivatedStyle(activatedStyle)
|
||||
|
||||
dialog.layout = tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
dialog.layout.SetBorder(true)
|
||||
dialog.layout.SetBorderColor(style.DialogBorderColor)
|
||||
dialog.layout.SetBackgroundColor(style.DialogBgColor)
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) Display() {
|
||||
d.display = true
|
||||
|
||||
d.form.SetFocus(1)
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) IsDisplay() bool {
|
||||
return d.display
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) Hide() {
|
||||
d.textview.SetText("")
|
||||
d.message = ""
|
||||
d.display = false
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) SetTitle(title string) {
|
||||
d.layout.SetTitle(strings.ToUpper(title))
|
||||
d.layout.SetTitleColor(style.DialogFgColor)
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) SetText(message string) {
|
||||
d.message = message
|
||||
d.textview.Clear()
|
||||
|
||||
msg := "\n" + message
|
||||
|
||||
d.textview.SetText(msg)
|
||||
d.textview.ScrollToBeginning()
|
||||
d.setRect()
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) Focus(delegate func(tview.Primitive)) {
|
||||
delegate(d.form)
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) HasFocus() bool {
|
||||
return d.form.HasFocus()
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) SetRect(x, y, width, height int) {
|
||||
d.x = x + DialogPadding
|
||||
d.y = y + DialogPadding
|
||||
d.width = width - (2 * DialogPadding) //nolint:gomnd
|
||||
d.height = height - (2 * DialogPadding) //nolint:gomnd
|
||||
d.setRect()
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) setRect() {
|
||||
maxHeight := d.height
|
||||
maxWidth := d.width
|
||||
messageHeight := len(strings.Split(d.message, "\n"))
|
||||
messageWidth := getMessageWidth(d.message)
|
||||
|
||||
layoutHeight := messageHeight + 2 //nolint:gomnd
|
||||
|
||||
if maxHeight > layoutHeight+DialogFormHeight {
|
||||
d.height = layoutHeight + DialogFormHeight + 2 //nolint:gomnd
|
||||
} else {
|
||||
d.height = maxHeight
|
||||
layoutHeight = d.height - DialogFormHeight - 2 //nolint:gomnd
|
||||
}
|
||||
|
||||
if maxHeight > d.height {
|
||||
emptyHeight := (maxHeight - d.height) / 2 //nolint:gomnd
|
||||
d.y += emptyHeight
|
||||
}
|
||||
|
||||
if d.width > DialogMinWidth {
|
||||
if messageWidth < DialogMinWidth {
|
||||
d.width = DialogMinWidth + 2 //nolint:gomnd
|
||||
} else if messageWidth < d.width {
|
||||
d.width = messageWidth + 2 //nolint:gomnd
|
||||
}
|
||||
}
|
||||
|
||||
if maxWidth > d.width {
|
||||
emptyWidth := (maxWidth - d.width) / 2 //nolint:gomnd
|
||||
d.x += emptyWidth
|
||||
}
|
||||
|
||||
msgLayout := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
msgLayout.AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false)
|
||||
msgLayout.AddItem(d.textview, 0, 1, true)
|
||||
msgLayout.AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false)
|
||||
|
||||
d.layout.Clear()
|
||||
d.layout.AddItem(msgLayout, layoutHeight, 0, true)
|
||||
d.layout.AddItem(d.form, DialogFormHeight, 0, true)
|
||||
|
||||
d.Box.SetRect(d.x, d.y, d.width, d.height)
|
||||
}
|
||||
|
||||
// Draw draws this primitive onto the screen.
|
||||
func (d *ConfirmDialog) Draw(screen tcell.Screen) {
|
||||
if !d.display {
|
||||
return
|
||||
}
|
||||
|
||||
d.Box.DrawForSubclass(screen, d)
|
||||
|
||||
x, y, width, height := d.Box.GetInnerRect()
|
||||
d.layout.SetRect(x, y, width, height)
|
||||
d.layout.Draw(screen)
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) {
|
||||
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(tview.Primitive)) {
|
||||
d.logger.Sugar().Debugf("confirm dialog event %v received", event)
|
||||
if event.Key() == utils.CloseDialogKey.EventKey() {
|
||||
d.cancelHandler()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if formHandler := d.form.InputHandler(); formHandler != nil {
|
||||
formHandler(event, setFocus)
|
||||
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) SetCancelFunc(handler func()) *ConfirmDialog {
|
||||
d.cancelHandler = handler
|
||||
cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2)
|
||||
cancelButton.SetSelectedFunc(handler)
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *ConfirmDialog) SetSelectedFunc(handler func()) *ConfirmDialog {
|
||||
d.selectHandler = handler
|
||||
enterButton := d.form.GetButton(d.form.GetButtonCount() - 1)
|
||||
enterButton.SetSelectedFunc(handler)
|
||||
|
||||
return d
|
||||
}
|
||||
106
internal/ui/dialogs/error.go
Normal file
106
internal/ui/dialogs/error.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package dialogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.brettb.xyz/goinv/client/internal/ui/style"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ErrorDialog struct {
|
||||
*tview.Box
|
||||
logger *zap.Logger
|
||||
modal *tview.Modal
|
||||
title string
|
||||
message string
|
||||
display bool
|
||||
}
|
||||
|
||||
func NewErrorDialog(logger *zap.Logger) *ErrorDialog {
|
||||
bgColor := style.ErrorDialogBgColor
|
||||
dialog := ErrorDialog{
|
||||
Box: tview.NewBox(),
|
||||
logger: logger,
|
||||
modal: tview.NewModal().SetBackgroundColor(bgColor).AddButtons([]string{"OK"}),
|
||||
display: false,
|
||||
}
|
||||
|
||||
dialog.modal.SetButtonBackgroundColor(style.ErrorDialogButtonBgColor)
|
||||
|
||||
dialog.modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
dialog.Hide()
|
||||
})
|
||||
|
||||
return &dialog
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) Display() {
|
||||
e.display = true
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) Hide() {
|
||||
e.SetText("")
|
||||
e.title = ""
|
||||
e.message = ""
|
||||
e.display = false
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) IsDisplay() bool {
|
||||
return e.display
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) SetText(message string) {
|
||||
e.message = message
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) SetTitle(title string) {
|
||||
e.title = title
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) HasFocus() bool {
|
||||
return e.modal.HasFocus()
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) Focus(delegate func(tview.Primitive)) {
|
||||
delegate(e.modal)
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) {
|
||||
return e.WrapInputHandler(func(event *tcell.EventKey, setFocus func(tview.Primitive)) {
|
||||
e.logger.Sugar().Debugf("error dialog event %v received", event)
|
||||
if modalHandler := e.modal.InputHandler(); modalHandler != nil {
|
||||
modalHandler(event, setFocus)
|
||||
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) SetRect(x, y, width, height int) {
|
||||
e.Box.SetRect(x, y, width, height)
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) Draw(screen tcell.Screen) {
|
||||
hFgColor := style.FgColor
|
||||
headerColor := style.GetColorHex(hFgColor)
|
||||
|
||||
var errorMessage string
|
||||
|
||||
if e.title != "" {
|
||||
errorMessage = fmt.Sprintf("[%s::b]%s[-::-]\n", headerColor, e.title)
|
||||
}
|
||||
|
||||
errorMessage += e.message
|
||||
e.modal.SetText(errorMessage)
|
||||
e.modal.Draw(screen)
|
||||
}
|
||||
|
||||
func (e *ErrorDialog) SetDoneFunc(handler func()) *ErrorDialog {
|
||||
e.modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
handler()
|
||||
})
|
||||
|
||||
return e
|
||||
}
|
||||
238
internal/ui/dialogs/message.go
Normal file
238
internal/ui/dialogs/message.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package dialogs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type MessageDialog struct {
|
||||
*tview.Box
|
||||
logger *zap.Logger
|
||||
layout *tview.Flex
|
||||
infoType *tview.InputField
|
||||
textView *tview.TextView
|
||||
form *tview.Form
|
||||
display bool
|
||||
message string
|
||||
cancelHandler func()
|
||||
}
|
||||
|
||||
type messageInfo int
|
||||
|
||||
const (
|
||||
MessageGeneric messageInfo = iota
|
||||
)
|
||||
|
||||
func NewMessageDialog(logger *zap.Logger, text string) *MessageDialog {
|
||||
dialog := MessageDialog{
|
||||
Box: tview.NewBox(),
|
||||
logger: logger,
|
||||
infoType: tview.NewInputField(),
|
||||
display: false,
|
||||
message: text,
|
||||
}
|
||||
|
||||
dialog.infoType.SetBackgroundColor(style.ButtonBgColor)
|
||||
dialog.infoType.SetFieldStyle(tcell.StyleDefault.
|
||||
Background(style.ButtonBgColor).
|
||||
Foreground(style.ButtonFgColor))
|
||||
dialog.infoType.SetLabelStyle(tcell.StyleDefault.
|
||||
Background(style.ButtonBgColor).
|
||||
Foreground(style.ButtonFgColor))
|
||||
|
||||
dialog.textView = tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetWrap(true).
|
||||
SetTextAlign(tview.AlignLeft)
|
||||
|
||||
dialog.textView.SetBackgroundColor(style.DialogSubBoxBgColor)
|
||||
dialog.textView.SetBorderColor(style.DialogSubBoxBorderColor)
|
||||
dialog.textView.SetBorder(true)
|
||||
dialog.textView.SetTextStyle(tcell.StyleDefault.
|
||||
Background(style.DialogSubBoxBgColor).
|
||||
Foreground(style.DialogSubBoxFgColor))
|
||||
|
||||
tlayout := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
tlayout.AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false)
|
||||
tlayout.AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
|
||||
AddItem(dialog.infoType, 1, 0, false).
|
||||
AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false).
|
||||
AddItem(dialog.textView, 0, 1, true),
|
||||
0, 1, true)
|
||||
tlayout.AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false)
|
||||
|
||||
dialog.form = tview.NewForm().
|
||||
AddButton("Cancel", nil).
|
||||
SetButtonsAlign(tview.AlignRight)
|
||||
|
||||
dialog.form.SetFocus(0)
|
||||
|
||||
dialog.form.SetBackgroundColor(style.DialogBgColor)
|
||||
dialog.form.SetButtonBackgroundColor(style.ButtonBgColor)
|
||||
dialog.form.SetButtonTextColor(style.ButtonFgColor)
|
||||
dialog.form.SetButtonActivatedStyle(tcell.StyleDefault.
|
||||
Background(style.ButtonSelectedBgColor).
|
||||
Foreground(style.ButtonSelectedFgColor))
|
||||
|
||||
dialog.layout = tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
dialog.layout.AddItem(utils.EmptyBoxSpace(style.DialogBgColor), 1, 0, false)
|
||||
dialog.layout.AddItem(tlayout, 0, 1, true)
|
||||
dialog.layout.AddItem(dialog.form, DialogFormHeight, 0, true)
|
||||
dialog.layout.SetBorder(true)
|
||||
dialog.layout.SetBorderColor(style.DialogBorderColor)
|
||||
dialog.layout.SetBackgroundColor(style.DialogBgColor)
|
||||
dialog.layout.SetTitleColor(style.DialogFgColor)
|
||||
|
||||
return &dialog
|
||||
}
|
||||
|
||||
func (d *MessageDialog) Display() {
|
||||
d.display = true
|
||||
}
|
||||
|
||||
func (d *MessageDialog) IsDisplay() bool {
|
||||
return d.display
|
||||
}
|
||||
|
||||
func (d *MessageDialog) Hide() {
|
||||
d.message = ""
|
||||
d.textView.SetText("")
|
||||
d.display = false
|
||||
}
|
||||
|
||||
func (d *MessageDialog) SetTitle(title string) {
|
||||
d.layout.SetTitle(strings.ToUpper(title))
|
||||
}
|
||||
|
||||
func (d *MessageDialog) SetText(headerType messageInfo, headerMessage string, message string) {
|
||||
msgTypeLabel := ""
|
||||
|
||||
switch headerType {
|
||||
case MessageGeneric:
|
||||
msgTypeLabel = "SYSTEM:"
|
||||
}
|
||||
|
||||
if msgTypeLabel != "" {
|
||||
d.infoType.SetLabel("[::b]" + msgTypeLabel)
|
||||
d.infoType.SetLabelWidth(len(msgTypeLabel) + 1)
|
||||
d.infoType.SetText(headerMessage)
|
||||
}
|
||||
|
||||
d.message = message
|
||||
d.textView.Clear()
|
||||
|
||||
if d.message == "" {
|
||||
d.textView.SetBorder(false)
|
||||
d.textView.SetText("")
|
||||
} else {
|
||||
//d.textView.SetTextColor(style.DialogFgColor)
|
||||
//d.textView.SetBackgroundColor(style.DialogSubBoxBorderColor)
|
||||
//d.textView.SetBorder(true)
|
||||
//d.textView.SetBorderColor(style.DialogBorderColor)
|
||||
//d.textView.SetTextStyle(tcell.StyleDefault.
|
||||
// Background(style.DialogSubBoxBorderColor).
|
||||
// Foreground(style.ButtonFgColor))
|
||||
d.textView.SetBorder(true)
|
||||
d.textView.SetText(message)
|
||||
}
|
||||
|
||||
d.textView.ScrollToBeginning()
|
||||
}
|
||||
|
||||
func (d *MessageDialog) TextScrollToEnd() {
|
||||
d.textView.ScrollToEnd()
|
||||
}
|
||||
|
||||
func (d *MessageDialog) Focus(delegate func(tview.Primitive)) {
|
||||
delegate(d.form)
|
||||
}
|
||||
|
||||
func (d *MessageDialog) HasFocus() bool {
|
||||
return utils.CheckFocus(d.form, d.textView, d.Box)
|
||||
}
|
||||
|
||||
func (d *MessageDialog) SetRect(x, y, width, height int) {
|
||||
messageHeight := 0
|
||||
if d.message != "" {
|
||||
messageHeight = len(strings.Split(d.message, "\n"))
|
||||
}
|
||||
|
||||
messageWidth := getMessageWidth(d.message)
|
||||
|
||||
headerWidth := len(d.infoType.GetText()) + len(d.infoType.GetLabel()) + 4
|
||||
if messageWidth < headerWidth {
|
||||
messageWidth = headerWidth
|
||||
}
|
||||
|
||||
dWidth := width - (2 * DialogPadding)
|
||||
if messageWidth+4 < dWidth {
|
||||
dWidth = messageWidth + 4
|
||||
}
|
||||
|
||||
if DialogMinWidth < width && dWidth < DialogMinWidth {
|
||||
dWidth = DialogMinWidth
|
||||
}
|
||||
|
||||
emptySpace := (width - dWidth) / 2
|
||||
dX := x + emptySpace
|
||||
|
||||
dHeight := messageHeight + DialogFormHeight + DialogPadding + 4
|
||||
if dHeight > height {
|
||||
dHeight = height - DialogPadding - 1
|
||||
}
|
||||
|
||||
textviewHeight := dHeight - DialogFormHeight - 2
|
||||
hs := (height - dHeight) / 2
|
||||
dY := y + hs
|
||||
|
||||
d.Box.SetRect(dX, dY, dWidth, dHeight)
|
||||
|
||||
d.layout.ResizeItem(d.textView, textviewHeight, 0)
|
||||
}
|
||||
|
||||
func (d *MessageDialog) Draw(screen tcell.Screen) {
|
||||
if !d.display {
|
||||
return
|
||||
}
|
||||
|
||||
d.Box.DrawForSubclass(screen, d)
|
||||
x, y, width, height := d.Box.GetInnerRect()
|
||||
d.layout.SetRect(x, y, width, height)
|
||||
d.layout.Draw(screen)
|
||||
}
|
||||
|
||||
func (d *MessageDialog) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) {
|
||||
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(tview.Primitive)) {
|
||||
d.logger.Sugar().Debugf("message dialog event %v received", event)
|
||||
if event.Key() == utils.CloseDialogKey.EventKey() || event.Key() == tcell.KeyEnter {
|
||||
d.cancelHandler()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if event.Key() == utils.SwitchFocusKey.EventKey() {
|
||||
if formHandler := d.form.InputHandler(); formHandler != nil {
|
||||
formHandler(event, setFocus)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if textHandler := d.textView.InputHandler(); textHandler != nil {
|
||||
textHandler(event, setFocus)
|
||||
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (d *MessageDialog) SetCancelFunc(handler func()) *MessageDialog {
|
||||
d.cancelHandler = handler
|
||||
|
||||
return d
|
||||
}
|
||||
128
internal/ui/dialogs/progress.go
Normal file
128
internal/ui/dialogs/progress.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package dialogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.brettb.xyz/goinv/client/internal/ui/style"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
prgCell = "▉"
|
||||
prgMinWidth = 40
|
||||
)
|
||||
|
||||
type ProgressDialog struct {
|
||||
*tview.Box
|
||||
logger *zap.Logger
|
||||
x int
|
||||
y int
|
||||
width int
|
||||
height int
|
||||
counterValue int
|
||||
display bool
|
||||
}
|
||||
|
||||
func NewProgressDialog(logger *zap.Logger) *ProgressDialog {
|
||||
return &ProgressDialog{
|
||||
logger: logger,
|
||||
Box: tview.NewBox().
|
||||
SetBorder(true).
|
||||
SetBorderColor(style.DialogBorderColor),
|
||||
display: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) SetTitle(title string) {
|
||||
d.Box.SetTitle(title)
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) Draw(screen tcell.Screen) {
|
||||
if !d.display || d.height < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
d.Box.DrawForSubclass(screen, d)
|
||||
x, y, width, _ := d.Box.GetInnerRect()
|
||||
tickStr := d.tickStr(width)
|
||||
tview.Print(screen, tickStr, x, y, width, tview.AlignLeft, style.PrgBarBgColor)
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) SetRect(x, y, width, height int) {
|
||||
d.x = x
|
||||
d.y = y
|
||||
d.width = width
|
||||
|
||||
if d.width > prgMinWidth {
|
||||
d.width = prgMinWidth
|
||||
spaceWidth := (width - d.width) / 2
|
||||
d.x = x + spaceWidth
|
||||
}
|
||||
|
||||
if height > 3 {
|
||||
d.height = 3
|
||||
spaceHeight := (height - d.height) / 2
|
||||
d.y = y + spaceHeight
|
||||
}
|
||||
|
||||
d.Box.SetRect(d.x, d.y, d.width, d.height)
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) Hide() {
|
||||
d.display = false
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) Display() {
|
||||
d.counterValue = 0
|
||||
d.display = true
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) IsDisplay() bool {
|
||||
return d.display
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) Focus(delegate func(tview.Primitive)) {}
|
||||
|
||||
func (d *ProgressDialog) HasFocus() bool {
|
||||
return d.Box.HasFocus()
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) {
|
||||
return d.WrapInputHandler(func(e *tcell.EventKey, setFocus func(tview.Primitive)) {
|
||||
d.logger.Sugar().Debugf("progress dialog event %v received", e)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *ProgressDialog) tickStr(max int) string {
|
||||
barColor := style.GetColorHex(style.PrgBarColor)
|
||||
counter := d.counterValue
|
||||
|
||||
if counter < max-4 {
|
||||
d.counterValue++
|
||||
} else {
|
||||
d.counterValue = 0
|
||||
}
|
||||
|
||||
prgHeadStr := ""
|
||||
hWidth := 0
|
||||
prgEndStr := ""
|
||||
prgStr := ""
|
||||
|
||||
for i := 0; i < d.counterValue; i++ {
|
||||
prgHeadStr += fmt.Sprintf("[black::]%s", prgCell)
|
||||
hWidth++
|
||||
}
|
||||
|
||||
prgStr = strings.Repeat(prgCell, 4)
|
||||
|
||||
for i := 0; i < max+hWidth; i++ {
|
||||
prgEndStr += fmt.Sprintf("[black::]%s", prgCell)
|
||||
}
|
||||
|
||||
progress := fmt.Sprintf("%s[%s::]%s%s", prgHeadStr, barColor, prgStr, prgEndStr)
|
||||
|
||||
return progress
|
||||
}
|
||||
42
internal/ui/dialogs/utils.go
Normal file
42
internal/ui/dialogs/utils.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package dialogs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const (
|
||||
DialogFormHeight = 3
|
||||
DialogMinWidth = 40
|
||||
DialogPadding = 3
|
||||
TableHeightOffset = 3
|
||||
)
|
||||
|
||||
type Dialog interface {
|
||||
tview.Primitive
|
||||
Display()
|
||||
Hide()
|
||||
IsDisplay() bool
|
||||
}
|
||||
|
||||
func getMessageWidth(message string) int {
|
||||
var messageWidth int
|
||||
|
||||
for _, msg := range strings.Split(message, "\n") {
|
||||
if len(msg) > messageWidth {
|
||||
messageWidth = len(msg)
|
||||
}
|
||||
}
|
||||
|
||||
return messageWidth
|
||||
}
|
||||
|
||||
func CheckDialogFocus(dialogs ...Dialog) bool {
|
||||
for _, dia := range dialogs {
|
||||
if dia.HasFocus() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user