UI: removed WindowContainer, and replaced it with DrawWindow function

This commit is contained in:
Luke I. Wilson 2021-04-01 20:34:18 -05:00
parent a4d5f3ccf4
commit 0dcdd62773
3 changed files with 36 additions and 186 deletions

View File

@ -74,5 +74,15 @@ func DrawRectOutlineDefault(s tcell.Screen, x, y, width, height int, style tcell
DrawRectOutline(s, x, y, width, height, '┌', '┐', '└', '┘', '─', '│', style) 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 DrawShadow(x, y, width, height int)
// TODO: add DrawWindow(x, y, width, height int, style tcell.Style)

View File

@ -11,9 +11,9 @@ import (
type FileSelectorDialog struct { type FileSelectorDialog struct {
MustExist bool // Whether the dialog should have a user select an existing file. 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. 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 x, y int
width, height int width, height int
focused bool focused bool
@ -24,16 +24,14 @@ type FileSelectorDialog struct {
inputField *InputField inputField *InputField
confirmButton *Button confirmButton *Button
cancelButton *Button cancelButton *Button
Theme *Theme
} }
func NewFileSelectorDialog(screen *tcell.Screen, title string, mustExist bool, theme *Theme, filesChosenCallback func([]string), cancelCallback func()) *FileSelectorDialog { func NewFileSelectorDialog(screen *tcell.Screen, title string, mustExist bool, theme *Theme, filesChosenCallback func([]string), cancelCallback func()) *FileSelectorDialog {
dialog := &FileSelectorDialog{ dialog := &FileSelectorDialog{
MustExist: mustExist, MustExist: mustExist,
FilesChosenCallback: filesChosenCallback, FilesChosenCallback: filesChosenCallback,
container: NewWindowContainer(title, nil, theme),
Theme: theme, Theme: theme,
title: title,
} }
dialog.inputField = NewInputField(screen, "", theme) 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) { func (d *FileSelectorDialog) SetTitle(title string) {
d.container.Title = title d.title = title
} }
func (d *FileSelectorDialog) Draw(s tcell.Screen) { 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() ) // Update positions of child components (dependent on size information that may not be available at SetPos() )
btnWidth, _ := d.confirmButton.GetSize() btnWidth, _ := d.confirmButton.GetSize()
@ -78,6 +80,9 @@ func (d *FileSelectorDialog) SetFocused(v bool) {
func (d *FileSelectorDialog) SetTheme(theme *Theme) { func (d *FileSelectorDialog) SetTheme(theme *Theme) {
d.Theme = theme d.Theme = theme
d.inputField.SetTheme(theme)
d.confirmButton.SetTheme(theme)
d.cancelButton.SetTheme(theme)
} }
func (d *FileSelectorDialog) GetPos() (int, int) { func (d *FileSelectorDialog) GetPos() (int, int) {
@ -86,13 +91,12 @@ func (d *FileSelectorDialog) GetPos() (int, int) {
func (d *FileSelectorDialog) SetPos(x, y int) { func (d *FileSelectorDialog) SetPos(x, y int) {
d.x, d.y = x, y d.x, d.y = x, y
d.container.SetPos(x, y)
d.inputField.SetPos(d.x+1, d.y+2) // Center input field 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 d.cancelButton.SetPos(d.x+1, d.y+4) // Place "Cancel" button on left, bottom
} }
func (d *FileSelectorDialog) GetMinSize() (int, int) { 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) { func (d *FileSelectorDialog) GetSize() (int, int) {
@ -102,7 +106,6 @@ func (d *FileSelectorDialog) GetSize() (int, int) {
func (d *FileSelectorDialog) SetSize(width, height int) { func (d *FileSelectorDialog) SetSize(width, height int) {
minX, minY := d.GetMinSize() minX, minY := d.GetMinSize()
d.width, d.height = Max(width, minX), Max(height, minY) 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.inputField.SetSize(d.width-2, 1)
d.cancelButton.SetSize(d.cancelButton.GetMinSize()) 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 { func (d *FileSelectorDialog) HandleEvent(event tcell.Event) bool {
switch ev := event.(type) { switch ev := event.(type) {
case *tcell.EventKey: case *tcell.EventKey:
if ev.Key() == tcell.KeyTab { switch ev.Key() {
case tcell.KeyTab:
d.tabOrder[d.tabOrderIdx].SetFocused(false) d.tabOrder[d.tabOrderIdx].SetFocused(false)
d.tabOrderIdx++ d.tabOrderIdx++
@ -123,6 +127,16 @@ func (d *FileSelectorDialog) HandleEvent(event tcell.Event) bool {
d.tabOrder[d.tabOrderIdx].SetFocused(true) d.tabOrder[d.tabOrderIdx].SetFocused(true)
return 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) return d.tabOrder[d.tabOrderIdx].HandleEvent(event)

View File

@ -6,99 +6,6 @@ import (
"github.com/gdamore/tcell/v2" "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. // A Tab is a child of a TabContainer; has a name and child Component.
type Tab struct { type Tab struct {
Name string Name string
@ -297,84 +204,3 @@ func (c *TabContainer) HandleEvent(event tcell.Event) bool {
return false 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
}