From 8346c521670fdaa04c8c5f05689fc983e678f196 Mon Sep 17 00:00:00 2001 From: "Luke I. Wilson" Date: Thu, 8 Apr 2021 12:41:24 -0500 Subject: [PATCH] Added baseComponent to ui to save boilerplate lines --- ui/button.go | 31 +++--------------- ui/component.go | 55 ++++++++++++++++++++++++++++---- ui/fileselectordialog.go | 32 ++++++------------- ui/inputfield.go | 27 ++-------------- ui/label.go | 4 +-- ui/menu.go | 68 +++++++++------------------------------- ui/messagedialog.go | 21 ++----------- ui/tabcontainer.go | 29 ++++------------- ui/textedit.go | 42 ++++--------------------- 9 files changed, 96 insertions(+), 213 deletions(-) diff --git a/ui/button.go b/ui/button.go index cf1a635..dd98660 100644 --- a/ui/button.go +++ b/ui/button.go @@ -9,19 +9,14 @@ import ( type Button struct { Text string Callback func() - - x, y int - width, height int - focused bool - - Theme *Theme + baseComponent } func NewButton(text string, theme *Theme, callback func()) *Button { return &Button{ - Text: text, - Callback: callback, - Theme: theme, + text, + callback, + baseComponent{theme: theme}, } } @@ -32,23 +27,7 @@ func (b *Button) Draw(s tcell.Screen) { } else { str = fmt.Sprintf(" %s ", b.Text) } - 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 + DrawStr(s, b.x, b.y, str, b.theme.GetOrDefault("Button")) } func (b *Button) GetMinSize() (int, int) { diff --git a/ui/component.go b/ui/component.go index d291b6f..d9af66a 100644 --- a/ui/component.go +++ b/ui/component.go @@ -23,17 +23,60 @@ type Component interface { SetTheme(*Theme) // Get position of the Component. - GetPos() (int, int) + GetPos() (x, y int) // Set position of the Component. - SetPos(int, int) + SetPos(x, y int) // Returns the smallest size the Component can be. - GetMinSize() (int, int) + GetMinSize() (w, h int) // 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 // 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 } diff --git a/ui/fileselectordialog.go b/ui/fileselectordialog.go index b071ecc..71ee90f 100644 --- a/ui/fileselectordialog.go +++ b/ui/fileselectordialog.go @@ -9,14 +9,9 @@ import ( // 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). type FileSelectorDialog struct { + Title string 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. - Theme *Theme - - title string - x, y int - width, height int - focused bool tabOrder []Component tabOrderIdx int @@ -24,14 +19,17 @@ type FileSelectorDialog struct { inputField *InputField confirmButton *Button cancelButton *Button + + baseComponent } func NewFileSelectorDialog(screen *tcell.Screen, title string, mustExist bool, theme *Theme, filesChosenCallback func([]string), cancelCallback func()) *FileSelectorDialog { dialog := &FileSelectorDialog{ + Title: title, MustExist: mustExist, FilesChosenCallback: filesChosenCallback, - Theme: theme, - title: title, + + baseComponent: baseComponent{theme: theme}, } 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 } -func (d *FileSelectorDialog) SetTitle(title string) { - d.title = title -} - 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() ) btnWidth, _ := d.confirmButton.GetSize() @@ -79,16 +73,12 @@ func (d *FileSelectorDialog) SetFocused(v bool) { } func (d *FileSelectorDialog) SetTheme(theme *Theme) { - d.Theme = theme + d.theme = theme d.inputField.SetStyle(theme.GetOrDefault("Window")) d.confirmButton.SetTheme(theme) d.cancelButton.SetTheme(theme) } -func (d *FileSelectorDialog) GetPos() (int, int) { - return d.x, d.y -} - func (d *FileSelectorDialog) SetPos(x, y int) { d.x, d.y = x, y 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) { - return Max(len(d.title), 8) + 2, 6 -} - -func (d *FileSelectorDialog) GetSize() (int, int) { - return d.width, d.height + return Max(len(d.Title), 8) + 2, 6 } func (d *FileSelectorDialog) SetSize(width, height int) { diff --git a/ui/inputfield.go b/ui/inputfield.go index c4b2210..7827c82 100644 --- a/ui/inputfield.go +++ b/ui/inputfield.go @@ -12,11 +12,10 @@ type InputField struct { cursorPos int scrollPos int - x, y int - width, height int - focused bool screen *tcell.Screen style tcell.Style + + baseComponent } 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 } -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 { switch ev := event.(type) { case *tcell.EventKey: diff --git a/ui/label.go b/ui/label.go index 49556e9..737ef7e 100644 --- a/ui/label.go +++ b/ui/label.go @@ -19,7 +19,7 @@ const ( // bounding box and allows for left-align, right-align, and justify. type Label struct { Text string - x, y int - width, height int Alignment Align + + baseComponent } diff --git a/ui/menu.go b/ui/menu.go index 5f8c1c4..2d87509 100644 --- a/ui/menu.go +++ b/ui/menu.go @@ -74,19 +74,15 @@ func (m *Menu) GetShortcut() string { // A MenuBar is a horizontal list of menus. type MenuBar struct { menus []*Menu - x, y int - width, height int - focused bool selected int // Index of selection in MenuBar menusVisible bool // Whether to draw the selected menu - - Theme *Theme + baseComponent } func NewMenuBar(theme *Theme) *MenuBar { return &MenuBar{ 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. 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 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 { sty := normalStyle 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) @@ -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) { 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 // any of them handled the event. func (b *MenuBar) HandleEvent(event tcell.Event) bool { @@ -288,12 +260,10 @@ type Menu struct { QuickChar int // Character/rune index of Name Items []Item - x, y int - width, height int // Size may not be settable selected int // Index of selected Item itemSelectedCallback func() // Used internally to hide menus on selection - Theme *Theme + baseComponent } // New creates a new Menu. `items` can be `nil`. @@ -302,7 +272,8 @@ func NewMenu(name string, quickChar int, theme *Theme) *Menu { Name: name, QuickChar: quickChar, 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. 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 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 var sty tcell.Style if m.selected == i { - sty = m.Theme.GetOrDefault("MenuSelected") + sty = m.theme.GetOrDefault("MenuSelected") } else { 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) { - 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 var widestShortcut int = 0 // Will contribute to the width for i := range m.Items { @@ -441,6 +396,11 @@ func (m *Menu) GetSize() (int, int) { 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. func (m *Menu) SetSize(width, height int) { // Cannot set the size of a Menu diff --git a/ui/messagedialog.go b/ui/messagedialog.go index 28a326f..6bbe3ea 100755 --- a/ui/messagedialog.go +++ b/ui/messagedialog.go @@ -30,13 +30,10 @@ type MessageDialog struct { message string messageWrapped string - x, y int - width, height int - focused bool - theme *Theme - buttons []*Button selectedIdx int + + baseComponent } 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, Callback: callback, - theme: theme, + baseComponent: baseComponent{theme: theme}, } 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) { lines := strings.Count(d.messageWrapped, "\n") + 1 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) { minWidth, minHeight := d.GetMinSize() d.width, d.height = Max(width, minWidth), Max(height, minHeight) diff --git a/ui/tabcontainer.go b/ui/tabcontainer.go index d0c12f9..bd74fc3 100644 --- a/ui/tabcontainer.go +++ b/ui/tabcontainer.go @@ -15,18 +15,15 @@ type Tab struct { // A TabContainer organizes children by showing only one of them at a time. type TabContainer struct { children []Tab - x, y int - width, height int - focused bool selected int - Theme *Theme + baseComponent } func NewTabContainer(theme *Theme) *TabContainer { return &TabContainer{ 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. func (c *TabContainer) Draw(s tcell.Screen) { // 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 for _, tab := range c.children { @@ -103,9 +100,9 @@ func (c *TabContainer) Draw(s tcell.Screen) { for i, tab := range c.children { var sty tcell.Style if c.selected == i { - sty = c.Theme.GetOrDefault("TabSelected") + sty = c.theme.GetOrDefault("TabSelected") } else { - sty = c.Theme.GetOrDefault("Tab") + sty = c.theme.GetOrDefault("Tab") } var dirty bool @@ -141,21 +138,12 @@ func (c *TabContainer) SetFocused(v bool) { // SetTheme sets the theme. func (c *TabContainer) SetTheme(theme *Theme) { - c.Theme = theme + c.theme = theme for _, tab := range c.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. func (c *TabContainer) SetPos(x, y int) { 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. func (c *TabContainer) SetSize(width, height int) { c.width, c.height = width, height diff --git a/ui/textedit.go b/ui/textedit.go index 830d4db..d1f6703 100644 --- a/ui/textedit.go +++ b/ui/textedit.go @@ -39,9 +39,6 @@ type TextEdit struct { 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. - x, y int - width, height int - focused bool 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 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 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, @@ -62,8 +59,9 @@ func NewTextEdit(screen *tcell.Screen, filePath string, contents []byte, theme * UseHardTabs: true, TabSize: 4, FilePath: filePath, - screen: screen, - Theme: theme, + + screen: screen, + baseComponent: baseComponent{theme: theme}, } te.SetContents(contents) return te @@ -383,8 +381,8 @@ func (t *TextEdit) Draw(s tcell.Screen) { columnWidth := t.getColumnWidth() bufferLines := t.Buffer.Lines() - selectedStyle := t.Theme.GetOrDefault("TextEditSelected") - columnStyle := t.Theme.GetOrDefault("TextEditColumn") + selectedStyle := t.theme.GetOrDefault("TextEditSelected") + columnStyle := t.theme.GetOrDefault("TextEditColumn") 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 // whether the TextEdit handled the event. func (t *TextEdit) HandleEvent(event tcell.Event) bool {