First Commit
This commit is contained in:
129
absolute_layout.go
Normal file
129
absolute_layout.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type AbsoluteLayout struct {
|
||||
id string
|
||||
style tcell.Style
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
widgets []Widget
|
||||
wCoords map[Widget]Coord
|
||||
|
||||
active bool
|
||||
visible bool
|
||||
|
||||
logger func(string)
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) SetLogger(l func(string)) { w.logger = l }
|
||||
func (w *AbsoluteLayout) Log(txt string) {
|
||||
if w.logger != nil {
|
||||
w.logger(txt)
|
||||
}
|
||||
}
|
||||
|
||||
func NewAbsoluteLayout(id string, s tcell.Style) *AbsoluteLayout {
|
||||
ret := &AbsoluteLayout{style: s}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
w.wCoords = make(map[Widget]Coord)
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) Id() string { return w.id }
|
||||
func (w *AbsoluteLayout) HandleResize(ev *tcell.EventResize) {
|
||||
for _, wi := range w.widgets {
|
||||
wi.HandleResize(ev)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) HandleKey(ev *tcell.EventKey) bool {
|
||||
for _, wi := range w.widgets {
|
||||
w.Log(fmt.Sprintf("Passing key (%s) to %s", ev.Name(), wi.Id()))
|
||||
if wi.HandleKey(ev) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) HandleTime(ev *tcell.EventTime) {
|
||||
for _, wi := range w.widgets {
|
||||
wi.HandleTime(ev)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
for i := len(w.widgets) - 1; i >= 0; i-- {
|
||||
var p Coord
|
||||
var ok bool
|
||||
if p, ok = w.wCoords[w.widgets[i]]; !ok {
|
||||
// Don't know where to put this widget
|
||||
continue
|
||||
}
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: w.x, Y: w.y}))
|
||||
w.widgets[i].Draw(screen)
|
||||
}
|
||||
}
|
||||
func (w *AbsoluteLayout) Active() bool { return w.active }
|
||||
func (w *AbsoluteLayout) SetActive(a bool) { w.active = a }
|
||||
func (w *AbsoluteLayout) Visible() bool { return w.visible }
|
||||
func (w *AbsoluteLayout) SetVisible(a bool) { w.visible = a }
|
||||
func (w *AbsoluteLayout) Focusable() bool { return true }
|
||||
func (w *AbsoluteLayout) SetX(x int) { w.x = x }
|
||||
func (w *AbsoluteLayout) SetY(y int) { w.y = y }
|
||||
func (w *AbsoluteLayout) GetX() int { return w.x }
|
||||
func (w *AbsoluteLayout) GetY() int { return w.y }
|
||||
func (w *AbsoluteLayout) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *AbsoluteLayout) GetW() int { return w.w }
|
||||
func (w *AbsoluteLayout) GetH() int { return w.h }
|
||||
func (w *AbsoluteLayout) SetW(wd int) { w.w = wd }
|
||||
func (w *AbsoluteLayout) SetH(h int) { w.h = h }
|
||||
func (w *AbsoluteLayout) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *AbsoluteLayout) WantW() int { return w.w }
|
||||
func (w *AbsoluteLayout) WantH() int { return w.h }
|
||||
|
||||
// Add a widget at x/y
|
||||
func (w *AbsoluteLayout) Add(n Widget, pos Coord) {
|
||||
w.widgets = append(w.widgets, n)
|
||||
w.wCoords[n] = pos
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) Clear() {
|
||||
w.widgets = []Widget{}
|
||||
w.wCoords = make(map[Widget]Coord)
|
||||
}
|
||||
110
bordered_widget.go
Normal file
110
bordered_widget.go
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type BorderedWidget struct {
|
||||
id string
|
||||
style tcell.Style
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
widget Widget
|
||||
border []rune
|
||||
|
||||
title string
|
||||
active bool
|
||||
visible bool
|
||||
|
||||
logger func(string)
|
||||
}
|
||||
|
||||
func (w *BorderedWidget) SetLogger(l func(string)) { w.logger = l }
|
||||
func (w *BorderedWidget) Log(txt string) {
|
||||
if w.logger != nil {
|
||||
w.logger(txt)
|
||||
}
|
||||
}
|
||||
|
||||
func NewBorderedWidget(id string, wd Widget, s tcell.Style) *BorderedWidget {
|
||||
ret := &BorderedWidget{
|
||||
style: s,
|
||||
widget: wd,
|
||||
}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *BorderedWidget) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
w.border = h.BRD_CSIMPLE
|
||||
}
|
||||
|
||||
func (w *BorderedWidget) Id() string { return w.id }
|
||||
func (w *BorderedWidget) HandleResize(ev *tcell.EventResize) {
|
||||
w.widget.HandleResize(ev)
|
||||
}
|
||||
|
||||
func (w *BorderedWidget) HandleKey(ev *tcell.EventKey) bool {
|
||||
return w.HandleKey(ev)
|
||||
}
|
||||
|
||||
func (w *BorderedWidget) HandleTime(ev *tcell.EventTime) {
|
||||
w.HandleTime(ev)
|
||||
}
|
||||
|
||||
func (w *BorderedWidget) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
if len(w.title) > 0 {
|
||||
h.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, w.border, w.style, screen)
|
||||
} else {
|
||||
h.BorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.border, w.style, screen)
|
||||
}
|
||||
w.widget.SetPos(Coord{X: w.x + 1, Y: w.y + 1})
|
||||
w.widget.Draw(screen)
|
||||
}
|
||||
func (w *BorderedWidget) Active() bool { return w.active }
|
||||
func (w *BorderedWidget) SetActive(a bool) { w.active = a }
|
||||
func (w *BorderedWidget) Visible() bool { return w.visible }
|
||||
func (w *BorderedWidget) SetVisible(a bool) { w.visible = a }
|
||||
func (w *BorderedWidget) Focusable() bool { return true }
|
||||
func (w *BorderedWidget) SetX(x int) { w.x = x }
|
||||
func (w *BorderedWidget) SetY(y int) { w.y = y }
|
||||
func (w *BorderedWidget) GetX() int { return w.x }
|
||||
func (w *BorderedWidget) GetY() int { return w.y }
|
||||
func (w *BorderedWidget) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *BorderedWidget) GetW() int { return w.w }
|
||||
func (w *BorderedWidget) GetH() int { return w.h }
|
||||
func (w *BorderedWidget) SetW(wd int) { w.w = wd }
|
||||
func (w *BorderedWidget) SetH(h int) { w.h = h }
|
||||
func (w *BorderedWidget) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *BorderedWidget) WantW() int { return w.w }
|
||||
func (w *BorderedWidget) WantH() int { return w.h }
|
||||
|
||||
func (w *BorderedWidget) SetBorder(r []rune) { w.border = r }
|
||||
130
button.go
Normal file
130
button.go
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type Button struct {
|
||||
id string
|
||||
label string
|
||||
style tcell.Style
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
active bool
|
||||
visible bool
|
||||
|
||||
onPressed func() bool
|
||||
}
|
||||
|
||||
var _ Widget = (*Button)(nil)
|
||||
|
||||
func NewButton(id string, style tcell.Style) *Button {
|
||||
b := &Button{style: style}
|
||||
b.Init(id)
|
||||
return b
|
||||
}
|
||||
|
||||
func (w *Button) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
w.onPressed = func() bool { return false }
|
||||
}
|
||||
func (w *Button) Id() string { return w.id }
|
||||
func (w *Button) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Button) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
if ev.Key() == tcell.KeyEnter {
|
||||
return w.onPressed()
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (w *Button) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *Button) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
dStyle := w.style
|
||||
if w.active {
|
||||
dStyle = w.style.Bold(true)
|
||||
}
|
||||
if w.h == 1 {
|
||||
lbl := w.label
|
||||
if w.w < len(lbl) {
|
||||
lbl = lbl[:w.w]
|
||||
} else if w.w == len(w.label)+2 {
|
||||
lbl = fmt.Sprintf("[%s]", lbl)
|
||||
} else if w.w > len(w.label)+2 {
|
||||
lbl = fmt.Sprintf("[%s]", h.Center(lbl, w.w-2))
|
||||
}
|
||||
h.DrawText(w.x, w.y, lbl, dStyle, screen)
|
||||
} else if w.h == 2 {
|
||||
lbl := w.label
|
||||
if w.w < len(lbl) {
|
||||
lbl = lbl[:w.w]
|
||||
} else if w.w == len(lbl)+2 {
|
||||
lbl = fmt.Sprintf("╭%s╮", lbl)
|
||||
}
|
||||
h.DrawText(w.x, w.y, lbl, dStyle, screen)
|
||||
h.DrawText(w.x, w.y+1, fmt.Sprintf("╰%s╯", strings.Repeat("─", len(lbl)-2)), dStyle, screen)
|
||||
return
|
||||
}
|
||||
if w.w < 2 {
|
||||
h.DrawText(w.x, w.y, "╬", dStyle, screen)
|
||||
return
|
||||
} else if w.w == 2 {
|
||||
h.DrawText(w.x, w.y, "[]", dStyle, screen)
|
||||
return
|
||||
}
|
||||
lbl := h.Center(w.label, w.w-2)
|
||||
h.DrawText(w.x, w.y, fmt.Sprintf("╭%s╮", strings.Repeat("─", w.w-2)), dStyle, screen)
|
||||
h.DrawText(w.x, w.y, fmt.Sprintf("│%s│", h.Center(lbl, w.w-2)), dStyle, screen)
|
||||
h.DrawText(w.x, w.y, fmt.Sprintf("╰%s╯", strings.Repeat("─", w.w-2)), dStyle, screen)
|
||||
}
|
||||
func (w *Button) Active() bool { return w.active }
|
||||
func (w *Button) SetActive(a bool) { w.active = a }
|
||||
func (w *Button) Visible() bool { return w.visible }
|
||||
func (w *Button) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Button) SetX(x int) { w.x = x }
|
||||
func (w *Button) SetY(y int) { w.y = y }
|
||||
func (w *Button) GetX() int { return w.x }
|
||||
func (w *Button) GetY() int { return w.y }
|
||||
func (w *Button) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Button) SetW(x int) { w.w = x }
|
||||
func (w *Button) SetH(y int) { w.h = y }
|
||||
func (w *Button) GetW() int { return w.w }
|
||||
func (w *Button) GetH() int { return w.y }
|
||||
func (w *Button) WantW() int { return 2 + len(w.label) }
|
||||
func (w *Button) WantH() int { return 3 }
|
||||
func (w *Button) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Button) Focusable() bool { return true }
|
||||
|
||||
func (w *Button) SetLabel(l string) { w.label = l }
|
||||
func (w *Button) SetOnPressed(p func() bool) { w.onPressed = p }
|
||||
128
checkbox.go
Normal file
128
checkbox.go
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
const (
|
||||
CHECKBOX_ON = iota
|
||||
CHECKBOX_OFF
|
||||
CHECKBOX_MAYBE
|
||||
)
|
||||
|
||||
type Checkbox struct {
|
||||
id string
|
||||
label string
|
||||
style tcell.Style
|
||||
active bool
|
||||
visible bool
|
||||
state int
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
stateRunes []rune
|
||||
}
|
||||
|
||||
var _ Widget = (*Checkbox)(nil)
|
||||
|
||||
func NewCheckbox(id string, style tcell.Style) *Checkbox {
|
||||
ret := &Checkbox{style: style}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *Checkbox) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
w.stateRunes = []rune{'X', ' ', '-'}
|
||||
}
|
||||
func (w *Checkbox) Id() string { return w.id }
|
||||
func (w *Checkbox) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Checkbox) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
if ev.Key() == tcell.KeyEnter {
|
||||
if w.state == CHECKBOX_ON {
|
||||
w.state = CHECKBOX_OFF
|
||||
} else {
|
||||
w.state = CHECKBOX_ON
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (w *Checkbox) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *Checkbox) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
dStyle := w.style
|
||||
if w.active {
|
||||
dStyle = w.style.Bold(true)
|
||||
}
|
||||
h.DrawText(w.x, w.y, fmt.Sprintf("[%s] %s", string(w.state), w.label), dStyle, screen)
|
||||
}
|
||||
func (w *Checkbox) Active() bool { return w.active }
|
||||
func (w *Checkbox) SetActive(a bool) { w.active = a }
|
||||
func (w *Checkbox) Visible() bool { return w.visible }
|
||||
func (w *Checkbox) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Checkbox) SetX(x int) { w.x = x }
|
||||
func (w *Checkbox) SetY(y int) { w.y = y }
|
||||
func (w *Checkbox) GetX() int { return w.x }
|
||||
func (w *Checkbox) GetY() int { return w.y }
|
||||
func (w *Checkbox) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Checkbox) SetW(x int) { w.w = x }
|
||||
func (w *Checkbox) SetH(y int) { w.h = y }
|
||||
func (w *Checkbox) GetW() int { return w.w }
|
||||
func (w *Checkbox) GetH() int { return w.y }
|
||||
func (w *Checkbox) WantW() int {
|
||||
return len(fmt.Sprintf("[%s] %s", string(w.state), w.label))
|
||||
}
|
||||
|
||||
func (w *Checkbox) WantH() int { return 1 }
|
||||
func (w *Checkbox) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Checkbox) Focusable() bool { return true }
|
||||
|
||||
func (w *Checkbox) SetLabel(l string) { w.label = l }
|
||||
func (w *Checkbox) SetChecked(v bool) {
|
||||
if v {
|
||||
w.state = CHECKBOX_ON
|
||||
} else {
|
||||
w.state = CHECKBOX_OFF
|
||||
}
|
||||
}
|
||||
func (w *Checkbox) IsChecked() bool { return w.state == CHECKBOX_ON }
|
||||
func (w *Checkbox) SetToMaybe() { w.state = CHECKBOX_MAYBE }
|
||||
func (w *Checkbox) SetStateRunes(runes []rune) {
|
||||
for i := range runes {
|
||||
if i > 2 {
|
||||
return
|
||||
}
|
||||
w.stateRunes[i] = runes[i]
|
||||
}
|
||||
}
|
||||
214
field.go
Normal file
214
field.go
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
id string
|
||||
style tcell.Style
|
||||
|
||||
label string
|
||||
value string
|
||||
|
||||
cursor int
|
||||
visible bool
|
||||
active bool
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
filter func(*tcell.EventKey) bool
|
||||
|
||||
onChange func(prev, curr string)
|
||||
}
|
||||
|
||||
var _ Widget = (*Field)(nil)
|
||||
|
||||
func NewField(id string, style tcell.Style) *Field {
|
||||
f := &Field{style: style}
|
||||
f.Init(id)
|
||||
return f
|
||||
}
|
||||
|
||||
func (w *Field) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
w.filter = func(ev *tcell.EventKey) bool {
|
||||
return h.IsBS(*ev) ||
|
||||
h.KeyIsDisplayable(*ev)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Field) Id() string { return w.id }
|
||||
func (w *Field) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Field) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.IsBS(*ev) {
|
||||
return w.handleBackspace()
|
||||
}
|
||||
if h.HandleKeys(*ev, map[tcell.Key]func() bool{
|
||||
tcell.KeyLeft: w.handleLeft,
|
||||
tcell.KeyRight: w.handleRight,
|
||||
tcell.KeyHome: w.handleHome,
|
||||
tcell.KeyEnd: w.handleEnd,
|
||||
tcell.KeyCtrlU: w.clearValueBeforeCursor,
|
||||
}) {
|
||||
return true
|
||||
}
|
||||
if w.filter != nil && !w.filter(ev) {
|
||||
return false
|
||||
}
|
||||
if ev.Key() == tcell.KeyRune {
|
||||
w.SetValue(fmt.Sprintf("%s%s%s", w.value[:w.cursor], string(ev.Rune()), w.value[w.cursor:]))
|
||||
w.cursor++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (w *Field) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *Field) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
useStyle := w.style
|
||||
if w.active {
|
||||
useStyle = w.style.Bold(true)
|
||||
}
|
||||
x := w.x
|
||||
labelW := len(w.label)
|
||||
if labelW > 0 {
|
||||
h.DrawText(w.x, w.y, w.label+": ", useStyle, screen)
|
||||
x = x + labelW + 2
|
||||
}
|
||||
cursor := " "
|
||||
var pre, post string
|
||||
if len(w.value) > 0 {
|
||||
pre = w.value[:w.cursor]
|
||||
if w.cursor < len(w.value) {
|
||||
cursor = string(w.value[w.cursor])
|
||||
post = w.value[w.cursor+1:]
|
||||
}
|
||||
}
|
||||
|
||||
h.DrawText(x, w.y, pre, useStyle, screen)
|
||||
x += len(pre)
|
||||
if w.active {
|
||||
h.DrawText(x, w.y, cursor, useStyle.Reverse(true).Blink(true), screen)
|
||||
} else {
|
||||
h.DrawText(x, w.y, cursor, useStyle, screen)
|
||||
}
|
||||
x += 1
|
||||
h.DrawText(x, w.y, post, useStyle, screen)
|
||||
}
|
||||
func (w *Field) Active() bool { return w.active }
|
||||
func (w *Field) SetActive(a bool) { w.active = a }
|
||||
func (w *Field) Visible() bool { return w.visible }
|
||||
func (w *Field) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Field) SetX(x int) { w.x = x }
|
||||
func (w *Field) SetY(y int) { w.y = y }
|
||||
func (w *Field) GetX() int { return w.x }
|
||||
func (w *Field) GetY() int { return w.y }
|
||||
func (w *Field) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Field) SetW(wd int) { w.w = wd }
|
||||
func (w *Field) SetH(h int) { w.h = h }
|
||||
func (w *Field) GetW() int { return w.w }
|
||||
func (w *Field) GetH() int { return w.h }
|
||||
func (w *Field) WantW() int {
|
||||
return len(w.label) + 2 + len(w.value) + 1
|
||||
}
|
||||
|
||||
func (w *Field) WantH() int {
|
||||
return 1
|
||||
}
|
||||
func (w *Field) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Field) Focusable() bool { return true }
|
||||
|
||||
/* Non-Widget-Interface Functions */
|
||||
func (w *Field) handleBackspace() bool {
|
||||
st := w.cursor
|
||||
if w.cursor > 0 {
|
||||
w.cursor--
|
||||
}
|
||||
if w.cursor < len(w.value) {
|
||||
w.SetValue(w.value[:w.cursor] + w.value[w.cursor+1:])
|
||||
}
|
||||
if st != w.cursor {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Field) handleLeft() bool {
|
||||
if w.cursor > 0 {
|
||||
w.cursor--
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Field) handleRight() bool {
|
||||
if w.cursor < len(w.value) {
|
||||
w.cursor++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Field) clearValueBeforeCursor() bool {
|
||||
w.SetValue(w.value[w.cursor:])
|
||||
w.cursor = 0
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Field) handleHome() bool {
|
||||
w.cursor = 0
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Field) handleEnd() bool {
|
||||
w.cursor = len(w.value)
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Field) SetLabel(l string) { w.label = l }
|
||||
func (w *Field) Label() string { return w.label }
|
||||
func (w *Field) SetValue(v string) {
|
||||
prev := w.value
|
||||
w.value = v
|
||||
w.doOnChange(prev, v)
|
||||
}
|
||||
func (w *Field) Value() string { return w.value }
|
||||
|
||||
func (w *Field) SetOnChange(v func(prev, curr string)) { w.onChange = v }
|
||||
func (w *Field) doOnChange(prev, curr string) {
|
||||
if w.onChange != nil {
|
||||
w.onChange(prev, curr)
|
||||
}
|
||||
}
|
||||
227
menu.go
Normal file
227
menu.go
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type Menu struct {
|
||||
id string
|
||||
label string
|
||||
style tcell.Style
|
||||
active bool
|
||||
visible bool
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
menuType MenuType
|
||||
cursor int
|
||||
items []Widget
|
||||
onPressed func() bool
|
||||
manualExpand bool
|
||||
expanded bool
|
||||
}
|
||||
|
||||
type MenuType int
|
||||
|
||||
const (
|
||||
MenuTypeH = MenuType(iota)
|
||||
MenuTypeV
|
||||
)
|
||||
|
||||
func NewMenu(id string, style tcell.Style) *Menu {
|
||||
ret := &Menu{style: style}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
func (w *Menu) Id() string { return w.id }
|
||||
func (w *Menu) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
}
|
||||
func (w *Menu) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Menu) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
// See if the active menuitem consumes this event
|
||||
if w.items[w.cursor].HandleKey(ev) {
|
||||
return true
|
||||
}
|
||||
// Otherwise see if we handle it
|
||||
if h.HandleKeys(*ev, map[tcell.Key]func() bool{
|
||||
tcell.KeyRight: w.MoveRight,
|
||||
tcell.KeyLeft: w.MoveLeft,
|
||||
tcell.KeyUp: w.MoveUp,
|
||||
tcell.KeyDown: w.MoveDown,
|
||||
tcell.KeyEnter: func() bool {
|
||||
if w.onPressed != nil {
|
||||
return w.onPressed()
|
||||
}
|
||||
return false
|
||||
},
|
||||
}) {
|
||||
return true
|
||||
}
|
||||
// See if we can find an item that matches the key pressed
|
||||
return false
|
||||
}
|
||||
func (w *Menu) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *Menu) Draw(screen tcell.Screen) {
|
||||
switch w.menuType {
|
||||
case MenuTypeH:
|
||||
w.drawHMenu(screen)
|
||||
case MenuTypeV:
|
||||
w.drawVMenu(screen)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Menu) drawHMenu(screen tcell.Screen) {
|
||||
st := w.style
|
||||
if w.active {
|
||||
st = w.style.Reverse(true)
|
||||
}
|
||||
x, y := w.x, w.y
|
||||
if len(w.label) > 0 {
|
||||
h.DrawText(x, y, w.label, st, screen)
|
||||
x = x + len(w.label) + 1
|
||||
}
|
||||
h.DrawText(x, y, "-", w.style, screen)
|
||||
x += 2
|
||||
for i := range w.items {
|
||||
w.items[i].SetActive(w.active && w.cursor == i)
|
||||
w.items[i].SetPos(Coord{X: x, Y: y})
|
||||
w.items[i].Draw(screen)
|
||||
x += w.items[i].WantW() + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Menu) drawVMenu(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
x, y := w.x, w.y
|
||||
wW, wH := w.WantW(), w.WantH()
|
||||
st := w.style
|
||||
if w.active {
|
||||
st = w.style.Reverse(true)
|
||||
h.TitledBorderFilled(x-1, y, x+w.WantW(), y+w.WantH(), w.label, h.BRD_CSIMPLE, w.style, screen)
|
||||
}
|
||||
h.DrawText(w.x, w.y, w.label, st, screen)
|
||||
y++
|
||||
if w.expanded || (w.active && !w.manualExpand) {
|
||||
for i := range w.items {
|
||||
w.items[i].SetActive(w.active && w.cursor == i)
|
||||
w.items[i].SetSize(Coord{X: wW, Y: wH})
|
||||
w.items[i].SetPos(Coord{X: x, Y: y})
|
||||
w.items[i].Draw(screen)
|
||||
y++
|
||||
}
|
||||
y++
|
||||
}
|
||||
}
|
||||
func (w *Menu) Active() bool { return w.active }
|
||||
func (w *Menu) SetActive(a bool) { w.active = a }
|
||||
func (w *Menu) Visible() bool { return w.visible }
|
||||
func (w *Menu) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Menu) SetX(x int) { w.x = x }
|
||||
func (w *Menu) SetY(y int) { w.y = y }
|
||||
func (w *Menu) GetX() int { return w.x }
|
||||
func (w *Menu) GetY() int { return w.y }
|
||||
func (w *Menu) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Menu) SetW(x int) { w.w = x }
|
||||
func (w *Menu) SetH(y int) { w.h = y }
|
||||
func (w *Menu) GetW() int { return w.w }
|
||||
func (w *Menu) GetH() int { return w.y }
|
||||
func (w *Menu) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Menu) Focusable() bool { return true }
|
||||
|
||||
func (w *Menu) WantW() int {
|
||||
var maxW int
|
||||
for i := range w.items {
|
||||
if w.items[i].WantW() > maxW {
|
||||
maxW = w.items[i].WantW()
|
||||
}
|
||||
}
|
||||
return maxW
|
||||
}
|
||||
|
||||
func (w *Menu) WantH() int {
|
||||
if w.menuType == MenuTypeH {
|
||||
return 1
|
||||
} else {
|
||||
var ret int
|
||||
if len(w.label) > 0 {
|
||||
ret = 1
|
||||
}
|
||||
return ret + len(w.items)
|
||||
}
|
||||
}
|
||||
func (w *Menu) SetType(tp MenuType) { w.menuType = tp }
|
||||
func (w *Menu) SetLabel(lbl string) { w.label = lbl }
|
||||
func (w *Menu) SetFocusable(f bool) {}
|
||||
|
||||
func (w *Menu) SetOnPressed(p func() bool) { w.onPressed = p }
|
||||
func (w *Menu) AddItems(iL ...Widget) {
|
||||
var maxW int
|
||||
for i := range iL {
|
||||
if iL[i].WantW() > maxW {
|
||||
maxW = iL[i].WantW()
|
||||
}
|
||||
w.items = append(w.items, iL[i])
|
||||
}
|
||||
w.SetW(maxW)
|
||||
}
|
||||
|
||||
func (w *Menu) MoveRight() bool {
|
||||
if w.menuType != MenuTypeH {
|
||||
return false
|
||||
}
|
||||
w.cursor = (w.cursor + 1) % len(w.items)
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Menu) MoveLeft() bool {
|
||||
if w.menuType != MenuTypeH {
|
||||
return false
|
||||
}
|
||||
w.cursor = (w.cursor - 1 + len(w.items)) % len(w.items)
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Menu) MoveUp() bool {
|
||||
if w.menuType != MenuTypeV {
|
||||
return false
|
||||
}
|
||||
w.cursor = (w.cursor - 1 + len(w.items)) % len(w.items)
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Menu) MoveDown() bool {
|
||||
if w.menuType != MenuTypeV {
|
||||
return false
|
||||
}
|
||||
w.cursor = (w.cursor + 1) % len(w.items)
|
||||
return true
|
||||
}
|
||||
148
menu_item.go
Normal file
148
menu_item.go
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type MenuItem struct {
|
||||
id string
|
||||
label string
|
||||
style tcell.Style
|
||||
active bool
|
||||
visible bool
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
menuType MenuType
|
||||
items []*MenuItem
|
||||
onPressed func() bool
|
||||
|
||||
manualExpand bool
|
||||
expanded bool
|
||||
disabled bool
|
||||
}
|
||||
|
||||
func NewMenuItem(id string, style tcell.Style) *MenuItem {
|
||||
ret := &MenuItem{style: style}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *MenuItem) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
}
|
||||
func (w *MenuItem) Id() string { return w.id }
|
||||
func (w *MenuItem) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *MenuItem) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
if ev.Key() == tcell.KeyEnter {
|
||||
if w.onPressed != nil {
|
||||
return w.onPressed()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (w *MenuItem) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *MenuItem) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
st := w.style
|
||||
if w.active {
|
||||
st = w.style.Reverse(true)
|
||||
}
|
||||
|
||||
x, y := w.x, w.y
|
||||
wd := w.w
|
||||
h.DrawText(x, y, h.PadR(w.label, wd), st, screen)
|
||||
if w.expanded {
|
||||
x += 2
|
||||
for i := range w.items {
|
||||
w.items[i].SetPos(Coord{X: x, Y: y})
|
||||
w.items[i].Draw(screen)
|
||||
y++
|
||||
}
|
||||
}
|
||||
}
|
||||
func (w *MenuItem) Active() bool { return w.active }
|
||||
func (w *MenuItem) SetActive(a bool) {
|
||||
w.active = a
|
||||
if !w.manualExpand {
|
||||
w.expanded = a
|
||||
}
|
||||
}
|
||||
func (w *MenuItem) Visible() bool { return w.visible }
|
||||
func (w *MenuItem) SetVisible(a bool) { w.visible = a }
|
||||
func (w *MenuItem) SetX(x int) { w.x = x }
|
||||
func (w *MenuItem) SetY(y int) { w.y = y }
|
||||
func (w *MenuItem) GetX() int { return w.x }
|
||||
func (w *MenuItem) GetY() int { return w.y }
|
||||
func (w *MenuItem) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *MenuItem) SetW(x int) { w.w = x }
|
||||
func (w *MenuItem) SetH(y int) { w.h = y }
|
||||
func (w *MenuItem) GetW() int { return w.w }
|
||||
func (w *MenuItem) GetH() int { return w.y }
|
||||
func (w *MenuItem) WantW() int {
|
||||
ret := len(w.label) + 2
|
||||
if len(w.items) > 0 {
|
||||
for i := range w.items {
|
||||
if w.items[i].WantW() > ret {
|
||||
ret = w.items[i].WantW()
|
||||
// TODO: Figure offset of subitems
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *MenuItem) WantH() int {
|
||||
ret := 1
|
||||
if len(w.items) > 0 {
|
||||
for i := range len(w.items) {
|
||||
ret += w.items[i].WantH()
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
func (w *MenuItem) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *MenuItem) Focusable() bool { return !w.disabled }
|
||||
|
||||
// How much width this item wants
|
||||
func (w *MenuItem) Expand(e bool) {
|
||||
if w.manualExpand {
|
||||
w.expanded = e
|
||||
}
|
||||
}
|
||||
|
||||
func (w *MenuItem) SetLabel(lbl string) { w.label = lbl }
|
||||
func (w *MenuItem) SetDisabled(d bool) { w.disabled = d }
|
||||
|
||||
func (w *MenuItem) AddItems(iL ...*MenuItem) {
|
||||
w.items = append(w.items, iL...)
|
||||
}
|
||||
func (w *MenuItem) SetOnPressed(p func() bool) { w.onPressed = p }
|
||||
108
relative_layout.go
Normal file
108
relative_layout.go
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
|
||||
type RelativeLayout struct {
|
||||
id string
|
||||
style tcell.Style
|
||||
widgetRelations map[Widget]widgetRelation
|
||||
|
||||
active bool
|
||||
visible bool
|
||||
x, y int
|
||||
w, h int
|
||||
}
|
||||
|
||||
var _ Widget = (*RelativeLayout)(nil)
|
||||
|
||||
type widgetRelation struct {
|
||||
relation RelativeRelation
|
||||
relTo Widget
|
||||
}
|
||||
|
||||
type RelativeRelation int
|
||||
|
||||
const (
|
||||
RRAbove = iota // Above Widget
|
||||
RRToRightOf // To Right of Widget
|
||||
RRBelow // Below Widget
|
||||
RRToLeftOf // To Left of Widget
|
||||
RATop // Anchored to parent Top
|
||||
RARight // Anchored to parent Right
|
||||
RABottom // Anchored to parent Bottom
|
||||
RALeft // Anchored to parent Left
|
||||
)
|
||||
|
||||
func NewRelativeLayout(id string, s tcell.Style) *RelativeLayout {
|
||||
ret := &RelativeLayout{style: s}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *RelativeLayout) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
}
|
||||
func (w *RelativeLayout) Id() string { return w.id }
|
||||
func (w *RelativeLayout) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *RelativeLayout) HandleKey(ev *tcell.EventKey) bool { return false }
|
||||
func (w *RelativeLayout) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *RelativeLayout) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
done := make(map[Widget]widgetRelation)
|
||||
rem := make(map[Widget]widgetRelation)
|
||||
for k, v := range w.widgetRelations {
|
||||
if v.relTo == w {
|
||||
done[k] = v
|
||||
} else {
|
||||
rem[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
func (w *RelativeLayout) Active() bool { return w.active }
|
||||
func (w *RelativeLayout) SetActive(a bool) { w.active = a }
|
||||
func (w *RelativeLayout) Visible() bool { return w.visible }
|
||||
func (w *RelativeLayout) SetVisible(a bool) { w.visible = a }
|
||||
func (w *RelativeLayout) Focusable() bool { return true }
|
||||
func (w *RelativeLayout) SetX(x int) { w.x = x }
|
||||
func (w *RelativeLayout) SetY(y int) { w.y = y }
|
||||
func (w *RelativeLayout) GetX() int { return w.x }
|
||||
func (w *RelativeLayout) GetY() int { return w.y }
|
||||
func (w *RelativeLayout) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *RelativeLayout) SetW(wd int) { w.w = wd }
|
||||
func (w *RelativeLayout) SetH(h int) { w.h = h }
|
||||
func (w *RelativeLayout) GetW() int { return w.w }
|
||||
func (w *RelativeLayout) GetH() int { return w.h }
|
||||
func (w *RelativeLayout) WantW() int { return 1 }
|
||||
func (w *RelativeLayout) WantH() int { return 1 }
|
||||
func (w *RelativeLayout) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
|
||||
func (w *RelativeLayout) Add(n Widget, relTo Widget, relation RelativeRelation) {
|
||||
w.widgetRelations[n] = widgetRelation{
|
||||
relation: relation,
|
||||
relTo: relTo,
|
||||
}
|
||||
}
|
||||
297
searcher.go
Normal file
297
searcher.go
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type Searcher struct {
|
||||
id string
|
||||
style tcell.Style
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
active bool
|
||||
visible bool
|
||||
|
||||
title string
|
||||
search *Field
|
||||
data []string
|
||||
filteredData []string
|
||||
cursor int
|
||||
|
||||
selectFunc func(idx int, s string) bool
|
||||
hideOnSelect bool
|
||||
logger func(string)
|
||||
}
|
||||
|
||||
var _ Widget = (*Searcher)(nil)
|
||||
|
||||
func NewSearcher(id string, style tcell.Style) *Searcher {
|
||||
ret := &Searcher{
|
||||
style: style,
|
||||
search: NewField(fmt.Sprintf("%s-searcher-field", id), style),
|
||||
}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *Searcher) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
w.search.SetOnChange(func(prev, curr string) {
|
||||
w.updateFilter()
|
||||
w.Log("Updated Search:" + curr)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Searcher) Id() string { return w.id }
|
||||
func (w *Searcher) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Searcher) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.HandleKeys(*ev, map[tcell.Key]func() bool{
|
||||
tcell.KeyUp: w.handleKeyUp,
|
||||
tcell.KeyDown: w.handleKeyDown,
|
||||
tcell.KeyHome: w.handleKeyHome,
|
||||
tcell.KeyEnd: w.handleKeyEnd,
|
||||
tcell.KeyPgUp: w.handleKeyPgUp,
|
||||
tcell.KeyPgDn: w.handleKeyPgDn,
|
||||
tcell.KeyEnter: w.handleKeyEnter,
|
||||
}) {
|
||||
return true
|
||||
}
|
||||
return w.search.HandleKey(ev)
|
||||
}
|
||||
|
||||
func (w *Searcher) handleKeyUp() bool {
|
||||
w.cursor = ((w.cursor - 1) + len(w.filteredData)) % len(w.filteredData)
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Searcher) handleKeyDown() bool {
|
||||
w.cursor = ((w.cursor + 1) + len(w.filteredData)) % len(w.filteredData)
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Searcher) handleKeyHome() bool {
|
||||
if w.cursor == 0 {
|
||||
return false
|
||||
}
|
||||
w.cursor = 0
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Searcher) handleKeyEnd() bool {
|
||||
if w.cursor == len(w.filteredData)-1 {
|
||||
return false
|
||||
}
|
||||
w.cursor = len(w.filteredData) - 1
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Searcher) handleKeyPgUp() bool {
|
||||
if w.cursor == 0 {
|
||||
return false
|
||||
}
|
||||
w.cursor -= w.h
|
||||
if w.cursor < 0 {
|
||||
w.cursor = 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Searcher) handleKeyPgDn() bool {
|
||||
mx := len(w.filteredData) - 1
|
||||
if w.cursor == mx {
|
||||
return false
|
||||
}
|
||||
w.cursor += w.h
|
||||
if w.cursor > mx {
|
||||
w.cursor = mx
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Searcher) handleKeyEnter() bool {
|
||||
if w.hideOnSelect {
|
||||
w.visible = false
|
||||
}
|
||||
if w.selectFunc != nil {
|
||||
// Figure out our true index
|
||||
var idx int
|
||||
selV := w.filteredData[w.cursor]
|
||||
for i := range w.data {
|
||||
if w.data[i] == selV {
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
return w.selectFunc(idx, selV)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Searcher) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *Searcher) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
w.search.SetPos(Coord{X: w.x + 1, Y: w.y + 1})
|
||||
w.search.SetSize(Coord{X: w.w - 2, Y: 1})
|
||||
dStyle := w.style
|
||||
if !w.active {
|
||||
dStyle = dStyle.Dim(true)
|
||||
}
|
||||
if len(w.title) > 0 {
|
||||
h.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, h.BRD_CSIMPLE, dStyle, screen)
|
||||
} else {
|
||||
h.BorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, h.BRD_CSIMPLE, dStyle, screen)
|
||||
}
|
||||
w.search.Draw(screen)
|
||||
x, y := w.x+1, w.y+2
|
||||
var stIdx int
|
||||
if w.cursor > w.h/2 {
|
||||
stIdx = w.cursor - (w.h / 2)
|
||||
}
|
||||
fD := len(w.filteredData)
|
||||
if w.cursor+w.h/2 > fD {
|
||||
stIdx = fD - w.h/2
|
||||
}
|
||||
stIdx = h.Max(stIdx, 0)
|
||||
for i := stIdx; i < fD; i++ {
|
||||
st := dStyle
|
||||
if i == w.cursor {
|
||||
st = st.Reverse(true)
|
||||
}
|
||||
h.DrawText(x, y, w.filteredData[i], st, screen)
|
||||
y++
|
||||
if y >= w.y+w.h {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
func (w *Searcher) Active() bool { return w.active }
|
||||
func (w *Searcher) SetActive(a bool) {
|
||||
w.active = a
|
||||
w.search.SetActive(a)
|
||||
}
|
||||
func (w *Searcher) Visible() bool { return w.visible }
|
||||
func (w *Searcher) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Searcher) SetX(x int) { w.x = x }
|
||||
func (w *Searcher) SetY(y int) { w.y = y }
|
||||
func (w *Searcher) GetX() int { return w.x }
|
||||
func (w *Searcher) GetY() int { return w.y }
|
||||
|
||||
func (w *Searcher) WantW() int {
|
||||
ret := 2 + w.search.WantW()
|
||||
var maxData int
|
||||
for i := range w.filteredData {
|
||||
maxData = h.Max(maxData, len(w.filteredData[i]))
|
||||
}
|
||||
return ret + maxData
|
||||
}
|
||||
|
||||
func (w *Searcher) WantH() int {
|
||||
return 2 + w.search.WantH() + len(w.filteredData) // Border + Field + Data
|
||||
}
|
||||
|
||||
func (w *Searcher) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Searcher) SetW(x int) { w.w = x }
|
||||
func (w *Searcher) SetH(y int) { w.h = y }
|
||||
func (w *Searcher) GetW() int { return w.w }
|
||||
func (w *Searcher) GetH() int { return w.y }
|
||||
func (w *Searcher) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Searcher) Focusable() bool { return true }
|
||||
|
||||
func (w *Searcher) SetHideOnSelect(t bool) { w.hideOnSelect = t }
|
||||
func (w *Searcher) SetTitle(ttl string) { w.title = ttl }
|
||||
func (w *Searcher) SetData(data []string) {
|
||||
w.data = data
|
||||
w.updateFilter()
|
||||
}
|
||||
|
||||
func (w *Searcher) updateFilter() {
|
||||
var selVal string
|
||||
var data []string
|
||||
copy(data, w.filteredData)
|
||||
if len(data) > 0 && len(data) > w.cursor {
|
||||
selVal = data[w.cursor]
|
||||
}
|
||||
filter := w.search.Value()
|
||||
cS := filter != strings.ToLower(filter)
|
||||
w.filteredData = []string{}
|
||||
for i := range w.data {
|
||||
if cS {
|
||||
if strings.Contains(w.data[i], filter) {
|
||||
w.filteredData = append(w.filteredData, w.data[i])
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(strings.ToLower(w.data[i]), filter) {
|
||||
w.filteredData = append(w.filteredData, w.data[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range w.filteredData {
|
||||
if w.filteredData[i] == selVal {
|
||||
w.cursor = i
|
||||
return
|
||||
}
|
||||
}
|
||||
if w.cursor > len(w.filteredData) {
|
||||
w.cursor = len(w.filteredData) - 1
|
||||
return
|
||||
}
|
||||
w.cursor = 0
|
||||
}
|
||||
|
||||
func (w *Searcher) SelectedValue() string {
|
||||
return w.filteredData[w.cursor]
|
||||
}
|
||||
|
||||
func (w *Searcher) SetSearchValue(val string) {
|
||||
w.search.SetValue(val)
|
||||
}
|
||||
|
||||
func (w *Searcher) SetSelectFunc(f func(idx int, s string) bool) {
|
||||
w.selectFunc = f
|
||||
}
|
||||
|
||||
func (w *Searcher) ClearSearch() {
|
||||
w.cursor = 0
|
||||
w.filteredData = w.data
|
||||
w.search.SetValue("")
|
||||
}
|
||||
|
||||
func (w *Searcher) SetLogger(l func(string)) { w.logger = l }
|
||||
func (w *Searcher) Log(txt string) {
|
||||
if w.logger != nil {
|
||||
w.logger(txt)
|
||||
}
|
||||
}
|
||||
201
table.go
Normal file
201
table.go
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
id string
|
||||
title string
|
||||
style tcell.Style
|
||||
active bool
|
||||
visible bool
|
||||
focusable bool
|
||||
|
||||
header []string
|
||||
footer []string
|
||||
data [][]string
|
||||
|
||||
minimized bool
|
||||
cursorX, cursorY int
|
||||
wrapColumns bool
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
wantW, wantH int
|
||||
|
||||
columnWidths []int
|
||||
}
|
||||
|
||||
var _ Widget = (*Table)(nil)
|
||||
|
||||
type TableSelectMode int
|
||||
|
||||
const (
|
||||
TableSelectCell = iota
|
||||
TableSelectRow
|
||||
TableSelectColumn
|
||||
)
|
||||
|
||||
type TableSortDirection int
|
||||
|
||||
const (
|
||||
TableSortAsc = iota
|
||||
TableSortDesc
|
||||
)
|
||||
|
||||
func NewTable(id string, style tcell.Style) *Table {
|
||||
ret := &Table{style: style}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *Table) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
}
|
||||
func (w *Table) Id() string { return w.id }
|
||||
func (w *Table) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Table) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (w *Table) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *Table) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
dStyle := w.style
|
||||
if w.active {
|
||||
dStyle = w.style.Bold(true)
|
||||
}
|
||||
var dat [][]string
|
||||
for i := range w.data {
|
||||
dat = append(dat, w.data[i])
|
||||
}
|
||||
x, y := w.x, w.y
|
||||
width, height := w.w, w.h
|
||||
if width <= 0 || height <= 0 {
|
||||
return
|
||||
}
|
||||
if !w.active {
|
||||
dStyle = dStyle.Dim(true)
|
||||
}
|
||||
if w.minimized {
|
||||
h.DrawText(x, y, fmt.Sprintf("├%s (%d rows)┤", w.title, len(dat)), dStyle, screen)
|
||||
return
|
||||
}
|
||||
if w.title != "" {
|
||||
h.TitledBorder(x, y, w.x+width, w.y+height, w.title, h.BRD_CSIMPLE, dStyle, screen)
|
||||
} else {
|
||||
h.Border(x, y, w.x+width, w.y+height, h.BRD_CSIMPLE, dStyle, screen)
|
||||
}
|
||||
if len(w.header) > 0 {
|
||||
h.DrawText(x, y, fmt.Sprintf("│%s│", strings.Join(w.header, "│")), dStyle, screen)
|
||||
y++
|
||||
}
|
||||
if len(dat) == 0 {
|
||||
h.DrawText(x, y, "No data in table", dStyle, screen)
|
||||
y++
|
||||
} else {
|
||||
for i := range dat {
|
||||
h.DrawText(x, y, fmt.Sprintf("│%s│", strings.Join(dat[i], "│")), dStyle, screen)
|
||||
y++
|
||||
}
|
||||
}
|
||||
if len(w.footer) > 0 {
|
||||
h.DrawText(x, y, fmt.Sprintf("│%s│", strings.Join(w.header, "│")), dStyle, screen)
|
||||
y++
|
||||
}
|
||||
}
|
||||
func (w *Table) Active() bool { return w.active }
|
||||
func (w *Table) SetActive(a bool) { w.active = a }
|
||||
func (w *Table) Visible() bool { return w.visible }
|
||||
func (w *Table) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Table) SetX(x int) { w.x = x }
|
||||
func (w *Table) SetY(y int) { w.y = y }
|
||||
func (w *Table) GetX() int { return w.x }
|
||||
func (w *Table) GetY() int { return w.y }
|
||||
func (w *Table) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Table) SetW(x int) { w.w = x }
|
||||
func (w *Table) SetH(y int) { w.h = y }
|
||||
func (w *Table) GetW() int { return w.w }
|
||||
func (w *Table) GetH() int { return w.y }
|
||||
|
||||
func (w *Table) WantW() int {
|
||||
if w.minimized {
|
||||
return len(fmt.Sprintf("├%s (%d rows)┤", w.title, len(w.data)))
|
||||
}
|
||||
// For each column, find the longest (in header, data, and footer)
|
||||
var totalW int
|
||||
colCnt := h.Max(len(w.header), len(w.footer))
|
||||
for i := range w.data {
|
||||
colCnt = h.Max(colCnt, len(w.data[i]))
|
||||
}
|
||||
for i := 0; i < colCnt; i++ {
|
||||
var cols []int
|
||||
if len(w.header) > i {
|
||||
cols = append(cols, len(w.header[i]))
|
||||
}
|
||||
for j := range w.data {
|
||||
if len(w.data[j]) > i {
|
||||
cols = append(cols, len(w.data[j][i]))
|
||||
}
|
||||
}
|
||||
if len(w.footer) > i {
|
||||
cols = append(cols, len(w.footer[i]))
|
||||
}
|
||||
totalW += h.Max(cols...)
|
||||
}
|
||||
return totalW
|
||||
}
|
||||
|
||||
func (w *Table) WantH() int {
|
||||
if w.minimized {
|
||||
return 1
|
||||
}
|
||||
datLen := len(w.data) + 2 // Data length + Border
|
||||
if len(w.header) > 0 {
|
||||
datLen += len(w.header) + 1 // Header length + separator
|
||||
}
|
||||
if datLen == 0 {
|
||||
datLen = 1
|
||||
}
|
||||
if len(w.footer) > 0 {
|
||||
datLen += len(w.footer) + 1 // Footer length + separator
|
||||
}
|
||||
return datLen
|
||||
}
|
||||
|
||||
func (w *Table) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Table) Focusable() bool { return w.focusable }
|
||||
|
||||
func (w *Table) SetTitle(ttl string) { w.title = ttl }
|
||||
func (w *Table) SetFocusable(f bool) { w.focusable = f }
|
||||
78
text.go
Normal file
78
text.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import (
|
||||
h "git.bullercodeworks.com/brian/dhcli/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type Text struct {
|
||||
id string
|
||||
text string
|
||||
style tcell.Style
|
||||
x, y int
|
||||
w, h int
|
||||
visible bool
|
||||
}
|
||||
|
||||
var _ Widget = (*Text)(nil)
|
||||
|
||||
func NewText(id string, style tcell.Style) *Text {
|
||||
ret := &Text{style: style}
|
||||
ret.Init(id)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *Text) Init(id string) {
|
||||
w.id = id
|
||||
w.visible = true
|
||||
}
|
||||
func (w *Text) Id() string { return w.id }
|
||||
func (w *Text) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Text) HandleKey(ev *tcell.EventKey) bool { return false }
|
||||
func (w *Text) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *Text) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
h.DrawText(w.x, w.y, w.text, w.style, screen)
|
||||
}
|
||||
func (w *Text) Active() bool { return false }
|
||||
func (w *Text) SetActive(a bool) {}
|
||||
func (w *Text) Visible() bool { return w.visible }
|
||||
func (w *Text) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Text) SetX(x int) { w.x = x }
|
||||
func (w *Text) SetY(y int) { w.y = y }
|
||||
func (w *Text) GetX() int { return w.x }
|
||||
func (w *Text) GetY() int { return w.y }
|
||||
func (w *Text) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Text) SetW(x int) { w.w = x }
|
||||
func (w *Text) SetH(y int) { w.h = y }
|
||||
func (w *Text) GetW() int { return w.w }
|
||||
func (w *Text) GetH() int { return w.y }
|
||||
func (w *Text) WantW() int { return h.Max(w.w, len(w.text)) }
|
||||
func (w *Text) WantH() int { return h.Max(w.h, 1) }
|
||||
func (w *Text) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Text) Focusable() bool { return false }
|
||||
|
||||
func (w *Text) SetText(txt string) { w.text = txt }
|
||||
80
widget.go
Normal file
80
widget.go
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright © Brian Buller <brian@bullercodeworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package widgets
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
|
||||
type Widget interface {
|
||||
Init(id string)
|
||||
Id() string
|
||||
HandleResize(*tcell.EventResize)
|
||||
HandleKey(*tcell.EventKey) bool
|
||||
HandleTime(*tcell.EventTime)
|
||||
Draw(tcell.Screen)
|
||||
Active() bool
|
||||
SetActive(bool)
|
||||
Visible() bool
|
||||
SetVisible(bool)
|
||||
Focusable() bool
|
||||
SetX(int)
|
||||
SetY(int)
|
||||
GetX() int
|
||||
GetY() int
|
||||
SetPos(Coord)
|
||||
SetW(int)
|
||||
SetH(int)
|
||||
GetW() int
|
||||
GetH() int
|
||||
WantW() int
|
||||
WantH() int
|
||||
SetSize(Coord)
|
||||
}
|
||||
|
||||
type Coord struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
func (p *Coord) Add(o Coord) Coord {
|
||||
return Coord{
|
||||
X: p.X + o.X,
|
||||
Y: p.Y + o.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// To validate that a struct satisfies this interface, you can do:
|
||||
// var _ Widget - (*<struct>)(nil)
|
||||
// where <struct> is the actual struct that you're validating.
|
||||
|
||||
type WidgetList []Widget
|
||||
|
||||
func (w *WidgetList) Contains(id string) bool {
|
||||
return w.Find(id) != nil
|
||||
}
|
||||
|
||||
func (w *WidgetList) Find(id string) Widget {
|
||||
for _, wi := range *w {
|
||||
if wi.Id() == id {
|
||||
return wi
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user