diff --git a/ui/drawfunctions.go b/ui/drawfunctions.go index eab6c1c..77f2729 100644 --- a/ui/drawfunctions.go +++ b/ui/drawfunctions.go @@ -74,5 +74,15 @@ func DrawRectOutlineDefault(s tcell.Screen, x, y, width, height int, style tcell DrawRectOutline(s, x, y, width, height, '┌', '┐', '└', '┘', '─', '│', style) } +// DrawWindow draws a window-like object at x and y as the top-left corner. This window +// has an optional title. The Theme values "WindowHeader" and "Window" are used. +func DrawWindow(s tcell.Screen, x, y, width, height int, title string, theme *Theme) { + headerStyle := theme.GetOrDefault("WindowHeader") + + DrawRect(s, x, y, width, 1, ' ', headerStyle) // Draw header background + DrawStr(s, x + width/2 - len(title)/2, y, title, headerStyle) // Draw header title + + DrawRect(s, x, y+1, width, height-1, ' ', theme.GetOrDefault("Window")) // Draw body +} + // TODO: add DrawShadow(x, y, width, height int) -// TODO: add DrawWindow(x, y, width, height int, style tcell.Style) diff --git a/ui/fileselectordialog.go b/ui/fileselectordialog.go index bc85a66..c2d770b 100644 --- a/ui/fileselectordialog.go +++ b/ui/fileselectordialog.go @@ -11,9 +11,9 @@ import ( type FileSelectorDialog struct { MustExist bool // Whether the dialog should have a user select an existing file. FilesChosenCallback func([]string) // Returns slice of filenames selected. nil if user canceled. - CancelCallback func() // Called when the dialog has been canceled by the user + Theme *Theme - container *WindowContainer + title string x, y int width, height int focused bool @@ -24,16 +24,14 @@ type FileSelectorDialog struct { inputField *InputField confirmButton *Button cancelButton *Button - - Theme *Theme } func NewFileSelectorDialog(screen *tcell.Screen, title string, mustExist bool, theme *Theme, filesChosenCallback func([]string), cancelCallback func()) *FileSelectorDialog { dialog := &FileSelectorDialog{ MustExist: mustExist, FilesChosenCallback: filesChosenCallback, - container: NewWindowContainer(title, nil, theme), Theme: theme, + title: title, } dialog.inputField = NewInputField(screen, "", theme) @@ -55,12 +53,16 @@ func (d *FileSelectorDialog) onConfirm() { } } +func (d *FileSelectorDialog) SetCancelCallback(callback func()) { + d.cancelButton.Callback = callback +} + func (d *FileSelectorDialog) SetTitle(title string) { - d.container.Title = title + d.title = title } func (d *FileSelectorDialog) Draw(s tcell.Screen) { - d.container.Draw(s) + DrawWindow(s, d.x, d.y, d.width, d.height, d.title, d.Theme) // Update positions of child components (dependent on size information that may not be available at SetPos() ) btnWidth, _ := d.confirmButton.GetSize() @@ -78,6 +80,9 @@ func (d *FileSelectorDialog) SetFocused(v bool) { func (d *FileSelectorDialog) SetTheme(theme *Theme) { d.Theme = theme + d.inputField.SetTheme(theme) + d.confirmButton.SetTheme(theme) + d.cancelButton.SetTheme(theme) } func (d *FileSelectorDialog) GetPos() (int, int) { @@ -86,13 +91,12 @@ func (d *FileSelectorDialog) GetPos() (int, int) { func (d *FileSelectorDialog) SetPos(x, y int) { d.x, d.y = x, y - d.container.SetPos(x, y) d.inputField.SetPos(d.x+1, d.y+2) // Center input field d.cancelButton.SetPos(d.x+1, d.y+4) // Place "Cancel" button on left, bottom } func (d *FileSelectorDialog) GetMinSize() (int, int) { - return len(d.container.Title) + 2, 6 + return Max(len(d.title), 8) + 2, 6 } func (d *FileSelectorDialog) GetSize() (int, int) { @@ -102,7 +106,6 @@ func (d *FileSelectorDialog) GetSize() (int, int) { func (d *FileSelectorDialog) SetSize(width, height int) { minX, minY := d.GetMinSize() d.width, d.height = Max(width, minX), Max(height, minY) - d.container.SetSize(d.width, d.height) d.inputField.SetSize(d.width-2, 1) d.cancelButton.SetSize(d.cancelButton.GetMinSize()) @@ -112,7 +115,8 @@ func (d *FileSelectorDialog) SetSize(width, height int) { func (d *FileSelectorDialog) HandleEvent(event tcell.Event) bool { switch ev := event.(type) { case *tcell.EventKey: - if ev.Key() == tcell.KeyTab { + switch ev.Key() { + case tcell.KeyTab: d.tabOrder[d.tabOrderIdx].SetFocused(false) d.tabOrderIdx++ @@ -123,6 +127,16 @@ func (d *FileSelectorDialog) HandleEvent(event tcell.Event) bool { d.tabOrder[d.tabOrderIdx].SetFocused(true) return true + case tcell.KeyEsc: + if d.cancelButton.Callback != nil { + d.cancelButton.Callback() + } + return true + case tcell.KeyEnter: + if d.tabOrder[d.tabOrderIdx] == d.inputField { + d.onConfirm() + return true + } } } return d.tabOrder[d.tabOrderIdx].HandleEvent(event) diff --git a/ui/container.go b/ui/tabcontainer.go similarity index 51% rename from ui/container.go rename to ui/tabcontainer.go index 9438849..d0c12f9 100644 --- a/ui/container.go +++ b/ui/tabcontainer.go @@ -6,99 +6,6 @@ import ( "github.com/gdamore/tcell/v2" ) -// A Container has zero or more Components. Containers decide how Components are -// laid out in view, and may draw decorations like bounding boxes. -type Container interface { - Component -} - -// A BoxContainer draws an outline using the `Character` with `Style` attributes -// around the `Child` Component. -type BoxContainer struct { - Child Component - - x, y int - width, height int - ULRune rune // Rune for upper-left - URRune rune // Rune for upper-right - BLRune rune // Rune for bottom-left - BRRune rune // Rune for bottom-right - HorRune rune // Rune for horizontals - VertRune rune // Rune for verticals - - Style tcell.Style -} - -// New constructs a default BoxContainer using the terminal default style. -func NewBoxContainer(child Component, style tcell.Style) *BoxContainer { - return &BoxContainer{ - Child: child, - ULRune: '╭', - URRune: '╮', - BLRune: '╰', - BRRune: '╯', - HorRune: '—', - VertRune: '│', - Style: style, - } -} - -// Draw will draws the border of the BoxContainer, then it draws its child component. -func (c *BoxContainer) Draw(s tcell.Screen) { - DrawRectOutline(s, c.x, c.y, c.width, c.height, c.ULRune, c.URRune, c.BLRune, c.BRRune, c.HorRune, c.VertRune, c.Style) - - if c.Child != nil { - c.Child.Draw(s) - } -} - -// SetFocused calls SetFocused on the child Component. -func (c *BoxContainer) SetFocused(v bool) { - if c.Child != nil { - c.Child.SetFocused(v) - } -} - -func (c *BoxContainer) SetTheme(theme *Theme) {} - -// GetPos returns the position of the container. -func (c *BoxContainer) GetPos() (int, int) { - return c.x, c.y -} - -// SetPos sets the position of the container and updates the child Component. -func (c *BoxContainer) SetPos(x, y int) { - c.x, c.y = x, y - if c.Child != nil { - c.Child.SetPos(x+1, y+1) - } -} - -func (c *BoxContainer) GetMinSize() (int, int) { - return 0, 0 -} - -// GetSize gets the size of the container. -func (c *BoxContainer) GetSize() (int, int) { - return c.width, c.height -} - -// SetSize sets the size of the container and updates the size of the child Component. -func (c *BoxContainer) SetSize(width, height int) { - c.width, c.height = width, height - if c.Child != nil { - c.Child.SetSize(width-2, height-2) - } -} - -// HandleEvent forwards the event to the child Component and returns whether it was handled. -func (c *BoxContainer) HandleEvent(event tcell.Event) bool { - if c.Child != nil { - return c.Child.HandleEvent(event) - } - return false -} - // A Tab is a child of a TabContainer; has a name and child Component. type Tab struct { Name string @@ -297,84 +204,3 @@ func (c *TabContainer) HandleEvent(event tcell.Event) bool { return false } - -// TODO: replace window container with draw function -// A WindowContainer has a border, a title, and a button to close the window. -type WindowContainer struct { - Title string - Child Component - - x, y int - width, height int - focused bool - - Theme *Theme -} - -// New constructs a default WindowContainer using the terminal default style. -func NewWindowContainer(title string, child Component, theme *Theme) *WindowContainer { - return &WindowContainer{ - Title: title, - Child: child, - Theme: theme, - } -} - -// Draw will draws the border of the WindowContainer, then it draws its child component. -func (w *WindowContainer) Draw(s tcell.Screen) { - headerStyle := w.Theme.GetOrDefault("WindowHeader") - - DrawRect(s, w.x, w.y, w.width, 1, ' ', headerStyle) // Draw header - DrawStr(s, w.x+w.width/2-len(w.Title)/2, w.y, w.Title, headerStyle) // Draw title - DrawRect(s, w.x, w.y+1, w.width, w.height-1, ' ', w.Theme.GetOrDefault("Window")) // Draw body background - - if w.Child != nil { - w.Child.Draw(s) - } -} - -// SetFocused calls SetFocused on the child Component. -func (w *WindowContainer) SetFocused(v bool) { - w.focused = v - if w.Child != nil { - w.Child.SetFocused(v) - } -} - -// GetPos returns the position of the container. -func (w *WindowContainer) GetPos() (int, int) { - return w.x, w.y -} - -// SetPos sets the position of the container and updates the child Component. -func (w *WindowContainer) SetPos(x, y int) { - w.x, w.y = x, y - if w.Child != nil { - w.Child.SetPos(x, y+1) - } -} - -func (w *WindowContainer) GetMinSize() (int, int) { - return 0, 0 -} - -// GetSize gets the size of the container. -func (w *WindowContainer) GetSize() (int, int) { - return w.width, w.height -} - -// SetSize sets the size of the container and updates the size of the child Component. -func (w *WindowContainer) SetSize(width, height int) { - w.width, w.height = width, height - if w.Child != nil { - w.Child.SetSize(width, height-2) - } -} - -// HandleEvent forwards the event to the child Component and returns whether it was handled. -func (w *WindowContainer) HandleEvent(event tcell.Event) bool { - if w.Child != nil { - return w.Child.HandleEvent(event) - } - return false -}