Did a lot of work off-repo, basically init
This commit is contained in:
336
ui/container.go
Normal file
336
ui/container.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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
|
||||
Child Component
|
||||
}
|
||||
|
||||
// A TabContainer organizes children by showing only one of them at a time.
|
||||
type TabContainer struct {
|
||||
Selected int
|
||||
|
||||
children []Tab
|
||||
x, y int
|
||||
width, height int
|
||||
focused bool
|
||||
|
||||
Theme *Theme
|
||||
}
|
||||
|
||||
func NewTabContainer(theme *Theme) *TabContainer {
|
||||
return &TabContainer{
|
||||
children: make([]Tab, 0, 4),
|
||||
Theme: theme,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TabContainer) AddTab(name string, child Component) {
|
||||
c.children = append(c.children, Tab{Name: name, Child: child})
|
||||
// Update new child's size and position
|
||||
child.SetPos(c.x+1, c.y+1)
|
||||
child.SetSize(c.width-2, c.height-2)
|
||||
}
|
||||
|
||||
// RemoveTab deletes the tab at `idx`. Returns true if the tab was found,
|
||||
// false otherwise.
|
||||
func (c *TabContainer) RemoveTab(idx int) bool {
|
||||
if idx >= 0 && idx < len(c.children) {
|
||||
copy(c.children[idx:], c.children[idx+1:]) // Shift all items after idx to the left
|
||||
c.children = c.children[:len(c.children)-1] // Shrink slice by one
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *TabContainer) GetTabCount() int {
|
||||
return len(c.children)
|
||||
}
|
||||
|
||||
func (c *TabContainer) GetTab(idx int) *Tab {
|
||||
return &c.children[idx]
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
||||
combinedTabLength := 0
|
||||
for _, tab := range c.children {
|
||||
combinedTabLength += len(tab.Name) + 2 // 2 for padding
|
||||
}
|
||||
combinedTabLength += len(c.children)-1 // add for spacing between tabs
|
||||
|
||||
// Draw tabs
|
||||
col := c.x + c.width/2 - combinedTabLength/2 // Starting column
|
||||
for i, tab := range c.children {
|
||||
var sty tcell.Style
|
||||
if c.Selected == i {
|
||||
sty = c.Theme.GetOrDefault("TabSelected")
|
||||
} else {
|
||||
sty = c.Theme.GetOrDefault("Tab")
|
||||
}
|
||||
str := fmt.Sprintf(" %s ", tab.Name)
|
||||
//DrawStr(s, c.x+c.width/2-len(str)/2, c.y, str, sty)
|
||||
DrawStr(s, c.x+col, c.y, str, sty)
|
||||
col += len(str) + 1 // Add one for spacing between tabs
|
||||
}
|
||||
|
||||
// Draw selected child in center
|
||||
if c.Selected < len(c.children) {
|
||||
c.children[c.Selected].Child.Draw(s)
|
||||
}
|
||||
}
|
||||
|
||||
// SetFocused calls SetFocused on the visible child Component.
|
||||
func (c *TabContainer) SetFocused(v bool) {
|
||||
c.focused = v
|
||||
if c.Selected < len(c.children) {
|
||||
c.children[c.Selected].Child.SetFocused(v)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTheme sets the theme.
|
||||
func (c *TabContainer) SetTheme(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
|
||||
if c.Selected < len(c.children) {
|
||||
c.children[c.Selected].Child.SetPos(x+1, y+1)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if c.Selected < len(c.children) {
|
||||
c.children[c.Selected].Child.SetSize(width-2, height-2)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleEvent forwards the event to the child Component and returns whether it was handled.
|
||||
func (c *TabContainer) HandleEvent(event tcell.Event) bool {
|
||||
switch ev := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if ev.Key() == tcell.KeyTab { // Ctrl + Tab was pressed
|
||||
if ev.Modifiers() == tcell.ModCtrl {
|
||||
c.Selected++
|
||||
if c.Selected >= len(c.children) {
|
||||
c.Selected = 0
|
||||
}
|
||||
} else if ev.Modifiers() == tcell.ModCtrl & tcell.ModShift { // Ctrl + Shift + Tab was pressed
|
||||
c.Selected--
|
||||
if c.Selected < 0 {
|
||||
c.Selected = len(c.children)-1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Selected < len(c.children) {
|
||||
return c.children[c.Selected].Child.HandleEvent(event)
|
||||
}
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user