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