Added baseComponent to ui to save boilerplate lines

This commit is contained in:
Luke I. Wilson 2021-04-08 12:41:24 -05:00
parent 0918afc483
commit 8346c52167
9 changed files with 96 additions and 213 deletions

View File

@ -9,19 +9,14 @@ import (
type Button struct { type Button struct {
Text string Text string
Callback func() Callback func()
baseComponent
x, y int
width, height int
focused bool
Theme *Theme
} }
func NewButton(text string, theme *Theme, callback func()) *Button { func NewButton(text string, theme *Theme, callback func()) *Button {
return &Button{ return &Button{
Text: text, text,
Callback: callback, callback,
Theme: theme, baseComponent{theme: theme},
} }
} }
@ -32,23 +27,7 @@ func (b *Button) Draw(s tcell.Screen) {
} else { } else {
str = fmt.Sprintf(" %s ", b.Text) str = fmt.Sprintf(" %s ", b.Text)
} }
DrawStr(s, b.x, b.y, str, b.Theme.GetOrDefault("Button")) DrawStr(s, b.x, b.y, str, b.theme.GetOrDefault("Button"))
}
func (b *Button) SetFocused(v bool) {
b.focused = v
}
func (b *Button) SetTheme(theme *Theme) {
b.Theme = theme
}
func (b *Button) GetPos() (int, int) {
return b.x, b.y
}
func (b *Button) SetPos(x, y int) {
b.x, b.y = x, y
} }
func (b *Button) GetMinSize() (int, int) { func (b *Button) GetMinSize() (int, int) {

View File

@ -23,17 +23,60 @@ type Component interface {
SetTheme(*Theme) SetTheme(*Theme)
// Get position of the Component. // Get position of the Component.
GetPos() (int, int) GetPos() (x, y int)
// Set position of the Component. // Set position of the Component.
SetPos(int, int) SetPos(x, y int)
// Returns the smallest size the Component can be. // Returns the smallest size the Component can be.
GetMinSize() (int, int) GetMinSize() (w, h int)
// Get size of the Component. // Get size of the Component.
GetSize() (int, int) GetSize() (w, h int)
// Set size of the component. If size is smaller than minimum, minimum is // Set size of the component. If size is smaller than minimum, minimum is
// used, instead. // used, instead.
SetSize(int, int) SetSize(w, h int)
tcell.EventHandler // A Component can handle events // HandleEvent tells the Component to handle the provided event. The Component
// should only handle events if it is focused. An event can optionally be
// handled. If an event is handled, the function should return true. If the
// event went unhandled, the function should return false.
HandleEvent(tcell.Event) bool
}
// baseComponent can be embedded in a Component's struct to hide a few of the
// boilerplate fields and functions. The baseComponent defines defaults for
// ...Pos(), ...Size(), SetFocused(), and SetTheme() functions that can be
// overriden.
type baseComponent struct {
focused bool
x, y int
width, height int
theme *Theme
}
func (c *baseComponent) SetFocused(v bool) {
c.focused = v
}
func (c *baseComponent) SetTheme(theme *Theme) {
c.theme = theme
}
func (c *baseComponent) GetPos() (int, int) {
return c.x, c.y
}
func (c *baseComponent) SetPos(x, y int) {
c.x, c.y = x, y
}
func (c *baseComponent) GetMinSize() (int, int) {
return 0, 0
}
func (c *baseComponent) GetSize() (int, int) {
return c.width, c.height
}
func (c *baseComponent) SetSize(width, height int) {
c.width, c.height = width, height
} }

View File

@ -9,14 +9,9 @@ import (
// A FileSelectorDialog is a WindowContainer with an input and buttons for selecting files. // A FileSelectorDialog is a WindowContainer with an input and buttons for selecting files.
// It can be used to open zero or more existing files, or select one non-existant file (for saving). // It can be used to open zero or more existing files, or select one non-existant file (for saving).
type FileSelectorDialog struct { type FileSelectorDialog struct {
Title string
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.
Theme *Theme
title string
x, y int
width, height int
focused bool
tabOrder []Component tabOrder []Component
tabOrderIdx int tabOrderIdx int
@ -24,14 +19,17 @@ type FileSelectorDialog struct {
inputField *InputField inputField *InputField
confirmButton *Button confirmButton *Button
cancelButton *Button cancelButton *Button
baseComponent
} }
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{
Title: title,
MustExist: mustExist, MustExist: mustExist,
FilesChosenCallback: filesChosenCallback, FilesChosenCallback: filesChosenCallback,
Theme: theme,
title: title, baseComponent: baseComponent{theme: theme},
} }
dialog.inputField = NewInputField(screen, []byte{}, theme.GetOrDefault("Window")) // Use window's theme for InputField dialog.inputField = NewInputField(screen, []byte{}, theme.GetOrDefault("Window")) // Use window's theme for InputField
@ -57,12 +55,8 @@ func (d *FileSelectorDialog) SetCancelCallback(callback func()) {
d.cancelButton.Callback = callback d.cancelButton.Callback = callback
} }
func (d *FileSelectorDialog) SetTitle(title string) {
d.title = title
}
func (d *FileSelectorDialog) Draw(s tcell.Screen) { func (d *FileSelectorDialog) Draw(s tcell.Screen) {
DrawWindow(s, d.x, d.y, d.width, d.height, d.title, d.Theme) 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()
@ -79,16 +73,12 @@ 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.SetStyle(theme.GetOrDefault("Window")) d.inputField.SetStyle(theme.GetOrDefault("Window"))
d.confirmButton.SetTheme(theme) d.confirmButton.SetTheme(theme)
d.cancelButton.SetTheme(theme) d.cancelButton.SetTheme(theme)
} }
func (d *FileSelectorDialog) GetPos() (int, int) {
return d.x, d.y
}
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.inputField.SetPos(d.x+1, d.y+2) // Center input field d.inputField.SetPos(d.x+1, d.y+2) // Center input field
@ -96,11 +86,7 @@ func (d *FileSelectorDialog) SetPos(x, y int) {
} }
func (d *FileSelectorDialog) GetMinSize() (int, int) { func (d *FileSelectorDialog) GetMinSize() (int, int) {
return Max(len(d.title), 8) + 2, 6 return Max(len(d.Title), 8) + 2, 6
}
func (d *FileSelectorDialog) GetSize() (int, int) {
return d.width, d.height
} }
func (d *FileSelectorDialog) SetSize(width, height int) { func (d *FileSelectorDialog) SetSize(width, height int) {

View File

@ -12,11 +12,10 @@ type InputField struct {
cursorPos int cursorPos int
scrollPos int scrollPos int
x, y int
width, height int
focused bool
screen *tcell.Screen screen *tcell.Screen
style tcell.Style style tcell.Style
baseComponent
} }
func NewInputField(screen *tcell.Screen, placeholder []byte, style tcell.Style) *InputField { func NewInputField(screen *tcell.Screen, placeholder []byte, style tcell.Style) *InputField {
@ -157,28 +156,6 @@ func (f *InputField) SetStyle(style tcell.Style) {
f.style = style f.style = style
} }
func (f *InputField) SetTheme(theme *Theme) {}
func (f *InputField) GetPos() (int, int) {
return f.x, f.y
}
func (f *InputField) SetPos(x, y int) {
f.x, f.y = x, y
}
func (f *InputField) GetMinSize() (int, int) {
return 0, 0
}
func (f *InputField) GetSize() (int, int) {
return f.width, f.height
}
func (f *InputField) SetSize(width, height int) {
f.width, f.height = width, height
}
func (f *InputField) HandleEvent(event tcell.Event) bool { func (f *InputField) HandleEvent(event tcell.Event) bool {
switch ev := event.(type) { switch ev := event.(type) {
case *tcell.EventKey: case *tcell.EventKey:

View File

@ -19,7 +19,7 @@ const (
// bounding box and allows for left-align, right-align, and justify. // bounding box and allows for left-align, right-align, and justify.
type Label struct { type Label struct {
Text string Text string
x, y int
width, height int
Alignment Align Alignment Align
baseComponent
} }

View File

@ -74,19 +74,15 @@ func (m *Menu) GetShortcut() string {
// A MenuBar is a horizontal list of menus. // A MenuBar is a horizontal list of menus.
type MenuBar struct { type MenuBar struct {
menus []*Menu menus []*Menu
x, y int
width, height int
focused bool
selected int // Index of selection in MenuBar selected int // Index of selection in MenuBar
menusVisible bool // Whether to draw the selected menu menusVisible bool // Whether to draw the selected menu
baseComponent
Theme *Theme
} }
func NewMenuBar(theme *Theme) *MenuBar { func NewMenuBar(theme *Theme) *MenuBar {
return &MenuBar{ return &MenuBar{
menus: make([]*Menu, 0, 6), menus: make([]*Menu, 0, 6),
Theme: theme, baseComponent: baseComponent{theme: theme},
} }
} }
@ -152,7 +148,7 @@ func (b *MenuBar) CursorRight() {
// Draw renders the MenuBar and its sub-menus. // Draw renders the MenuBar and its sub-menus.
func (b *MenuBar) Draw(s tcell.Screen) { func (b *MenuBar) Draw(s tcell.Screen) {
normalStyle := b.Theme.GetOrDefault("MenuBar") normalStyle := b.theme.GetOrDefault("MenuBar")
// Draw menus based on whether b.focused and which is selected // Draw menus based on whether b.focused and which is selected
DrawRect(s, b.x, b.y, b.width, 1, ' ', normalStyle) DrawRect(s, b.x, b.y, b.width, 1, ' ', normalStyle)
@ -160,7 +156,7 @@ func (b *MenuBar) Draw(s tcell.Screen) {
for i, item := range b.menus { for i, item := range b.menus {
sty := normalStyle sty := normalStyle
if b.focused && b.selected == i { if b.focused && b.selected == i {
sty = b.Theme.GetOrDefault("MenuBarSelected") // Use special style for selected item sty = b.theme.GetOrDefault("MenuBarSelected") // Use special style for selected item
} }
str := fmt.Sprintf(" %s ", item.Name) str := fmt.Sprintf(" %s ", item.Name)
@ -187,34 +183,10 @@ func (b *MenuBar) SetFocused(v bool) {
} }
} }
func (b *MenuBar) SetTheme(theme *Theme) {
b.Theme = theme
}
// GetPos returns the position of the MenuBar.
func (b *MenuBar) GetPos() (int, int) {
return b.x, b.y
}
// SetPos sets the position of the MenuBar.
func (b *MenuBar) SetPos(x, y int) {
b.x, b.y = x, y
}
func (b *MenuBar) GetMinSize() (int, int) { func (b *MenuBar) GetMinSize() (int, int) {
return 0, 1 return 0, 1
} }
// GetSize returns the size of the MenuBar.
func (b *MenuBar) GetSize() (int, int) {
return b.width, b.height
}
// SetSize sets the size of the MenuBar.
func (b *MenuBar) SetSize(width, height int) {
b.width, b.height = width, height
}
// HandleEvent will propogate events to sub-menus and returns true if // HandleEvent will propogate events to sub-menus and returns true if
// any of them handled the event. // any of them handled the event.
func (b *MenuBar) HandleEvent(event tcell.Event) bool { func (b *MenuBar) HandleEvent(event tcell.Event) bool {
@ -288,12 +260,10 @@ type Menu struct {
QuickChar int // Character/rune index of Name QuickChar int // Character/rune index of Name
Items []Item Items []Item
x, y int
width, height int // Size may not be settable
selected int // Index of selected Item selected int // Index of selected Item
itemSelectedCallback func() // Used internally to hide menus on selection itemSelectedCallback func() // Used internally to hide menus on selection
Theme *Theme baseComponent
} }
// New creates a new Menu. `items` can be `nil`. // New creates a new Menu. `items` can be `nil`.
@ -302,7 +272,8 @@ func NewMenu(name string, quickChar int, theme *Theme) *Menu {
Name: name, Name: name,
QuickChar: quickChar, QuickChar: quickChar,
Items: make([]Item, 0, 6), Items: make([]Item, 0, 6),
Theme: theme,
baseComponent: baseComponent{theme: theme},
} }
} }
@ -360,7 +331,7 @@ func (m *Menu) CursorDown() {
// Draw renders the Menu at its position. // Draw renders the Menu at its position.
func (m *Menu) Draw(s tcell.Screen) { func (m *Menu) Draw(s tcell.Screen) {
defaultStyle := m.Theme.GetOrDefault("Menu") defaultStyle := m.theme.GetOrDefault("Menu")
m.GetSize() // Call this to update internal width and height m.GetSize() // Call this to update internal width and height
DrawRect(s, m.x, m.y, m.width, m.height, ' ', defaultStyle) // Fill background DrawRect(s, m.x, m.y, m.width, m.height, ' ', defaultStyle) // Fill background
@ -375,7 +346,7 @@ func (m *Menu) Draw(s tcell.Screen) {
default: // Handle sub-menus and item entries the same default: // Handle sub-menus and item entries the same
var sty tcell.Style var sty tcell.Style
if m.selected == i { if m.selected == i {
sty = m.Theme.GetOrDefault("MenuSelected") sty = m.theme.GetOrDefault("MenuSelected")
} else { } else {
sty = defaultStyle sty = defaultStyle
} }
@ -401,23 +372,7 @@ func (m *Menu) SetFocused(v bool) {
} }
} }
// GetPos returns the position of the Menu.
func (m *Menu) GetPos() (int, int) {
return m.x, m.y
}
// SetPos sets the position of the Menu.
func (m *Menu) SetPos(x, y int) {
m.x, m.y = x, y
}
func (m *Menu) GetMinSize() (int, int) { func (m *Menu) GetMinSize() (int, int) {
return m.GetSize()
}
// GetSize returns the size of the Menu.
func (m *Menu) GetSize() (int, int) {
// TODO: no, pls don't do this
maxNameLen := 0 maxNameLen := 0
var widestShortcut int = 0 // Will contribute to the width var widestShortcut int = 0 // Will contribute to the width
for i := range m.Items { for i := range m.Items {
@ -441,6 +396,11 @@ func (m *Menu) GetSize() (int, int) {
return m.width, m.height return m.width, m.height
} }
// GetSize returns the size of the Menu.
func (m *Menu) GetSize() (int, int) {
return m.GetMinSize()
}
// SetSize sets the size of the Menu. // SetSize sets the size of the Menu.
func (m *Menu) SetSize(width, height int) { func (m *Menu) SetSize(width, height int) {
// Cannot set the size of a Menu // Cannot set the size of a Menu

View File

@ -30,13 +30,10 @@ type MessageDialog struct {
message string message string
messageWrapped string messageWrapped string
x, y int
width, height int
focused bool
theme *Theme
buttons []*Button buttons []*Button
selectedIdx int selectedIdx int
baseComponent
} }
func NewMessageDialog(title string, message string, kind MessageDialogKind, options []string, theme *Theme, callback func(string)) *MessageDialog { func NewMessageDialog(title string, message string, kind MessageDialogKind, options []string, theme *Theme, callback func(string)) *MessageDialog {
@ -53,7 +50,7 @@ func NewMessageDialog(title string, message string, kind MessageDialogKind, opti
Kind: kind, Kind: kind,
Callback: callback, Callback: callback,
theme: theme, baseComponent: baseComponent{theme: theme},
} }
dialog.buttons = make([]*Button, len(options)) dialog.buttons = make([]*Button, len(options))
@ -107,24 +104,12 @@ func (d *MessageDialog) SetTheme(theme *Theme) {
} }
} }
func (d *MessageDialog) GetPos() (int, int) {
return d.x, d.y
}
func (d *MessageDialog) SetPos(x, y int) {
d.x, d.y = x, y
}
func (d *MessageDialog) GetMinSize() (int, int) { func (d *MessageDialog) GetMinSize() (int, int) {
lines := strings.Count(d.messageWrapped, "\n") + 1 lines := strings.Count(d.messageWrapped, "\n") + 1
return Max(len(d.Title)+2, 30), 2 + lines + 2 return Max(len(d.Title)+2, 30), 2 + lines + 2
} }
func (d *MessageDialog) GetSize() (int, int) {
return d.width, d.height
}
func (d *MessageDialog) SetSize(width, height int) { func (d *MessageDialog) SetSize(width, height int) {
minWidth, minHeight := d.GetMinSize() minWidth, minHeight := d.GetMinSize()
d.width, d.height = Max(width, minWidth), Max(height, minHeight) d.width, d.height = Max(width, minWidth), Max(height, minHeight)

View File

@ -15,18 +15,15 @@ type Tab struct {
// A TabContainer organizes children by showing only one of them at a time. // A TabContainer organizes children by showing only one of them at a time.
type TabContainer struct { type TabContainer struct {
children []Tab children []Tab
x, y int
width, height int
focused bool
selected int selected int
Theme *Theme baseComponent
} }
func NewTabContainer(theme *Theme) *TabContainer { func NewTabContainer(theme *Theme) *TabContainer {
return &TabContainer{ return &TabContainer{
children: make([]Tab, 0, 4), children: make([]Tab, 0, 4),
Theme: theme, baseComponent: baseComponent{theme: theme},
} }
} }
@ -90,7 +87,7 @@ func (c *TabContainer) GetTab(idx int) *Tab {
// Draw will draws the border of the BoxContainer, then it draws its child component. // Draw will draws the border of the BoxContainer, then it draws its child component.
func (c *TabContainer) Draw(s tcell.Screen) { func (c *TabContainer) Draw(s tcell.Screen) {
// Draw outline // Draw outline
DrawRectOutlineDefault(s, c.x, c.y, c.width, c.height, c.Theme.GetOrDefault("TabContainer")) DrawRectOutlineDefault(s, c.x, c.y, c.width, c.height, c.theme.GetOrDefault("TabContainer"))
combinedTabLength := 0 combinedTabLength := 0
for _, tab := range c.children { for _, tab := range c.children {
@ -103,9 +100,9 @@ func (c *TabContainer) Draw(s tcell.Screen) {
for i, tab := range c.children { for i, tab := range c.children {
var sty tcell.Style var sty tcell.Style
if c.selected == i { if c.selected == i {
sty = c.Theme.GetOrDefault("TabSelected") sty = c.theme.GetOrDefault("TabSelected")
} else { } else {
sty = c.Theme.GetOrDefault("Tab") sty = c.theme.GetOrDefault("Tab")
} }
var dirty bool var dirty bool
@ -141,21 +138,12 @@ func (c *TabContainer) SetFocused(v bool) {
// SetTheme sets the theme. // SetTheme sets the theme.
func (c *TabContainer) SetTheme(theme *Theme) { func (c *TabContainer) SetTheme(theme *Theme) {
c.Theme = theme c.theme = theme
for _, tab := range c.children { for _, tab := range c.children {
tab.Child.SetTheme(theme) // Update the theme for all children tab.Child.SetTheme(theme) // Update the theme for all children
} }
} }
func (c *TabContainer) GetMinSize() (int, int) {
return 0, 0
}
// GetPos returns the position of the container.
func (c *TabContainer) GetPos() (int, int) {
return c.x, c.y
}
// SetPos sets the position of the container and updates the child Component. // SetPos sets the position of the container and updates the child Component.
func (c *TabContainer) SetPos(x, y int) { func (c *TabContainer) SetPos(x, y int) {
c.x, c.y = x, y c.x, c.y = x, y
@ -164,11 +152,6 @@ func (c *TabContainer) SetPos(x, y int) {
} }
} }
// GetSize gets the size of the container.
func (c *TabContainer) GetSize() (int, int) {
return c.width, c.height
}
// SetSize sets the size of the container and updates the size of the child Component. // SetSize sets the size of the container and updates the size of the child Component.
func (c *TabContainer) SetSize(width, height int) { func (c *TabContainer) SetSize(width, height int) {
c.width, c.height = width, height c.width, c.height = width, height

View File

@ -39,9 +39,6 @@ type TextEdit struct {
FilePath string // Will be empty if the file has not been saved yet FilePath string // Will be empty if the file has not been saved yet
screen *tcell.Screen // We keep our own reference to the screen for cursor purposes. screen *tcell.Screen // We keep our own reference to the screen for cursor purposes.
x, y int
width, height int
focused bool
curx, cury int // Zero-based: cursor points before the character at that position. curx, cury int // Zero-based: cursor points before the character at that position.
prevCurCol int // Previous maximum column the cursor was at, when the user pressed left or right prevCurCol int // Previous maximum column the cursor was at, when the user pressed left or right
scrollx, scrolly int // X and Y offset of view, known as scroll scrollx, scrolly int // X and Y offset of view, known as scroll
@ -49,7 +46,7 @@ type TextEdit struct {
selection Region // Selection: selectMode determines if it should be used selection Region // Selection: selectMode determines if it should be used
selectMode bool // Whether the user is actively selecting text selectMode bool // Whether the user is actively selecting text
Theme *Theme baseComponent
} }
// New will initialize the buffer using the given 'contents'. If the 'filePath' or 'FilePath' is empty, // New will initialize the buffer using the given 'contents'. If the 'filePath' or 'FilePath' is empty,
@ -62,8 +59,9 @@ func NewTextEdit(screen *tcell.Screen, filePath string, contents []byte, theme *
UseHardTabs: true, UseHardTabs: true,
TabSize: 4, TabSize: 4,
FilePath: filePath, FilePath: filePath,
screen: screen,
Theme: theme, screen: screen,
baseComponent: baseComponent{theme: theme},
} }
te.SetContents(contents) te.SetContents(contents)
return te return te
@ -383,8 +381,8 @@ func (t *TextEdit) Draw(s tcell.Screen) {
columnWidth := t.getColumnWidth() columnWidth := t.getColumnWidth()
bufferLines := t.Buffer.Lines() bufferLines := t.Buffer.Lines()
selectedStyle := t.Theme.GetOrDefault("TextEditSelected") selectedStyle := t.theme.GetOrDefault("TextEditSelected")
columnStyle := t.Theme.GetOrDefault("TextEditColumn") columnStyle := t.theme.GetOrDefault("TextEditColumn")
t.Highlighter.UpdateInvalidatedLines(t.scrolly, t.scrolly+(t.height-1)) t.Highlighter.UpdateInvalidatedLines(t.scrolly, t.scrolly+(t.height-1))
@ -565,34 +563,6 @@ func (t *TextEdit) SetFocused(v bool) {
} }
} }
func (t *TextEdit) SetTheme(theme *Theme) {
t.Theme = theme
}
// GetPos gets the position of the TextEdit.
func (t *TextEdit) GetPos() (int, int) {
return t.x, t.y
}
// SetPos sets the position of the TextEdit.
func (t *TextEdit) SetPos(x, y int) {
t.x, t.y = x, y
}
func (t *TextEdit) GetMinSize() (int, int) {
return 0, 0
}
// GetSize gets the size of the TextEdit.
func (t *TextEdit) GetSize() (int, int) {
return t.width, t.height
}
// SetSize sets the size of the TextEdit.
func (t *TextEdit) SetSize(width, height int) {
t.width, t.height = width, height
}
// HandleEvent allows the TextEdit to handle `event` if it chooses, returns // HandleEvent allows the TextEdit to handle `event` if it chooses, returns
// whether the TextEdit handled the event. // whether the TextEdit handled the event.
func (t *TextEdit) HandleEvent(event tcell.Event) bool { func (t *TextEdit) HandleEvent(event tcell.Event) bool {