So much work
This commit is contained in:
@@ -22,8 +22,6 @@ THE SOFTWARE.
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
@@ -36,11 +34,16 @@ type AbsoluteLayout struct {
|
||||
widgets []Widget
|
||||
wCoords map[Widget]Coord
|
||||
wAnchor map[Widget]AbsoluteAnchor
|
||||
wManualSizes map[Widget]Coord
|
||||
|
||||
defAnchor AbsoluteAnchor
|
||||
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
cursor int
|
||||
disableTab bool
|
||||
|
||||
logger func(string)
|
||||
}
|
||||
@@ -48,14 +51,15 @@ type AbsoluteLayout struct {
|
||||
type AbsoluteAnchor int
|
||||
|
||||
const (
|
||||
AnchorTL = AbsoluteAnchor(iota) // x,y starts at <startX> <startY>
|
||||
AnchorT // x,y starts at <middleX>, <startY>
|
||||
AnchorTR // x,y starts at <endX>, <startY>
|
||||
AnchorR // x,y starts at <endX>, <middleY>
|
||||
AnchorBR // x,y starts at <endX>, <endY>
|
||||
AnchorB // x,y starts at <middleX>, <endY>
|
||||
AnchorBL // x,y starts at <endX>, <startY>
|
||||
AnchorL // x,y starts at <startX>, <middleY>
|
||||
AnchorTL = AbsoluteAnchor(iota) // widget starts at <startX>, <startY>
|
||||
AnchorT // widget starts at <middleX>-<widget width/2>, <startY>
|
||||
AnchorTR // widget starts at <endX>, <startY>
|
||||
AnchorL // widget starts at <startX>, <middleY>
|
||||
AnchorC // widget starts at <middleX>-<widget width/2>, <middleY>-<widget height/2>
|
||||
AnchorR // widget starts at <endX>-<widget width>, <middleY>-<widget height/2>
|
||||
AnchorBL // widget starts at <startX>, <endY>-<widget height>
|
||||
AnchorB // widget starts at <middleX>-<widget width/2>, <endY>-<widget height>
|
||||
AnchorBR // widget starts at <endX>-<widget width>, <endY>-<widget height>
|
||||
AnchorErr
|
||||
)
|
||||
|
||||
@@ -72,18 +76,46 @@ func (w *AbsoluteLayout) Init(id string, s tcell.Style) {
|
||||
w.defAnchor = AnchorTL
|
||||
w.wCoords = make(map[Widget]Coord)
|
||||
w.wAnchor = make(map[Widget]AbsoluteAnchor)
|
||||
w.wManualSizes = make(map[Widget]Coord)
|
||||
w.tabbable = true
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) Id() string { return w.id }
|
||||
func (w *AbsoluteLayout) HandleResize(ev *tcell.EventResize) {
|
||||
for _, wi := range w.widgets {
|
||||
wi.HandleResize(ev)
|
||||
}
|
||||
w.w, w.h = ev.Size()
|
||||
w.updateWidgetLayouts()
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.disableTab && ev.Key() == tcell.KeyTab {
|
||||
fndP := -1
|
||||
for i := w.cursor; i < len(w.widgets); i++ {
|
||||
if fndP == -1 {
|
||||
if w.widgets[i].Active() {
|
||||
fndP = i
|
||||
w.widgets[i].SetActive(false)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if w.widgets[i].Focusable() && w.widgets[i].Tabbable() {
|
||||
w.widgets[i].SetActive(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we're here, we hit the end.
|
||||
if fndP == -1 { // But didn't even find the start
|
||||
return false
|
||||
}
|
||||
for i := 0; i < fndP; i++ {
|
||||
if w.widgets[i].Focusable() && w.widgets[i].Tabbable() {
|
||||
w.widgets[i].SetActive(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
for _, wi := range w.widgets {
|
||||
w.Log(fmt.Sprintf("Passing key (%s) to %s", ev.Name(), wi.Id()))
|
||||
if wi.HandleKey(ev) {
|
||||
return true
|
||||
}
|
||||
@@ -101,46 +133,12 @@ func (w *AbsoluteLayout) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
for i := len(w.widgets) - 1; i >= 0; i-- {
|
||||
var p Coord
|
||||
var a AbsoluteAnchor
|
||||
var ok bool
|
||||
if p, ok = w.wCoords[w.widgets[i]]; !ok {
|
||||
// Don't know where to put this widget
|
||||
continue
|
||||
}
|
||||
if a, ok = w.wAnchor[w.widgets[i]]; !ok {
|
||||
a = w.defAnchor
|
||||
}
|
||||
midX := (w.x + (w.x + w.w)) / 2
|
||||
// midY := (w.y + (w.y + w.h)) / 2
|
||||
switch a {
|
||||
case AnchorTL:
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: w.x, Y: w.y}))
|
||||
case AnchorT:
|
||||
wrk := w.widgets[i].GetW() / 2
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: (midX - wrk), Y: w.y}))
|
||||
case AnchorTR:
|
||||
wrk := w.widgets[i].GetW()
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: w.x + w.w - wrk, Y: w.y}))
|
||||
case AnchorR:
|
||||
wrkYmid := w.widgets[i].GetH() / 2
|
||||
wrkX := w.widgets[i].GetW()
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: w.x + w.w - wrkX, Y: w.y - (w.h / 2) - wrkYmid}))
|
||||
case AnchorBR:
|
||||
// TODO
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: w.x, Y: w.y}))
|
||||
case AnchorB:
|
||||
// TODO
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: w.x, Y: w.y}))
|
||||
case AnchorBL:
|
||||
wrkX, wrkY := (w.x+w.h)-w.widgets[i].GetH(), 0
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: wrkX, Y: wrkY}))
|
||||
case AnchorL:
|
||||
// TODO
|
||||
w.widgets[i].SetPos(p.Add(Coord{X: w.x, Y: w.y}))
|
||||
}
|
||||
w.widgets[i].Draw(screen)
|
||||
p := w.GetPos()
|
||||
for _, wd := range w.widgets {
|
||||
o := wd.GetPos()
|
||||
wd.SetPos(p.Add(o))
|
||||
wd.Draw(screen)
|
||||
wd.SetPos(o)
|
||||
}
|
||||
}
|
||||
func (w *AbsoluteLayout) Active() bool { return w.active }
|
||||
@@ -148,10 +146,13 @@ 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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *AbsoluteLayout) Tabbable() bool { return w.tabbable }
|
||||
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) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -160,6 +161,29 @@ 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 }
|
||||
func (w *AbsoluteLayout) MinW() int {
|
||||
// Find the highest value for x in all widgets GetX() + MinW()
|
||||
var minW int
|
||||
for _, wd := range w.widgets {
|
||||
wrk := wd.GetX() + wd.MinW()
|
||||
if wrk > minW {
|
||||
minW = wrk
|
||||
}
|
||||
}
|
||||
return minW
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) MinH() int {
|
||||
// Find the highest value for y in all widgets GetY() + MinH()
|
||||
var minH int
|
||||
for _, wd := range w.widgets {
|
||||
wrk := wd.GetY() + wd.MinH()
|
||||
if wrk > minH {
|
||||
minH = wrk
|
||||
}
|
||||
}
|
||||
return minH
|
||||
}
|
||||
|
||||
// Add a widget at x/y
|
||||
func (w *AbsoluteLayout) Add(n Widget, pos Coord) { w.AddAnchored(n, pos, w.defAnchor) }
|
||||
@@ -168,6 +192,8 @@ func (w *AbsoluteLayout) AddAnchored(n Widget, pos Coord, anchor AbsoluteAnchor)
|
||||
w.widgets = append(w.widgets, n)
|
||||
w.wCoords[n] = pos
|
||||
w.wAnchor[n] = anchor
|
||||
|
||||
w.updateWidgetLayouts()
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) Clear() {
|
||||
@@ -188,3 +214,100 @@ func (w *AbsoluteLayout) Log(txt string) {
|
||||
w.logger(txt)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) updateWidgetLayouts() {
|
||||
// In an Absolute Layout, widgets are given a definite position and anchor.
|
||||
// The anchor is a side of the layout (see AbsoluteAnchor type)
|
||||
for _, wd := range w.widgets {
|
||||
w.updateWidgetPos(wd)
|
||||
w.updateWidgetSize(wd)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) updateWidgetSize(wd Widget) {
|
||||
if sz, ok := w.wManualSizes[wd]; ok {
|
||||
wd.SetW(sz.X)
|
||||
wd.SetH(sz.Y)
|
||||
return
|
||||
}
|
||||
// The available space is:
|
||||
// X: Layout Width - Widget X
|
||||
// Y: Layout Height - Widget Y
|
||||
available := Coord{X: w.GetW() - wd.GetX(), Y: w.GetH() - wd.GetY()}
|
||||
ww := wd.WantW()
|
||||
if ww < available.X {
|
||||
wd.SetW(ww)
|
||||
} else if wd.MinW() < available.X {
|
||||
wd.SetW(available.X)
|
||||
} else {
|
||||
wd.SetW(wd.MinW())
|
||||
}
|
||||
|
||||
wh := wd.WantH()
|
||||
if wh < available.Y {
|
||||
wd.SetH(wh)
|
||||
} else if wd.MinH() < available.Y {
|
||||
wd.SetH(available.Y)
|
||||
} else {
|
||||
wd.SetH(wd.MinH())
|
||||
}
|
||||
}
|
||||
|
||||
// Set a widgets position relative to the layout
|
||||
func (w *AbsoluteLayout) updateWidgetPos(wd Widget) { wd.SetPos(w.getRelPos(wd)) }
|
||||
|
||||
// Manually set the size of a widget, the Layout won't override it
|
||||
func (w *AbsoluteLayout) SetWidgetSize(wd Widget, sz Coord) { w.wManualSizes[wd] = sz }
|
||||
|
||||
func (w *AbsoluteLayout) ShrinkWrap(wd Widget) {
|
||||
w.SetWidgetSize(wd, Coord{X: wd.MinW(), Y: wd.MinH()})
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) getRelPos(wd Widget) Coord {
|
||||
var p Coord
|
||||
var a AbsoluteAnchor
|
||||
var ok bool
|
||||
if p, ok = w.wCoords[wd]; !ok {
|
||||
// Default to top-left corner
|
||||
p = Coord{X: 0, Y: 0}
|
||||
}
|
||||
if a, ok = w.wAnchor[wd]; !ok {
|
||||
a = w.defAnchor
|
||||
}
|
||||
midX, midY := (w.w / 2), (w.h / 2)
|
||||
switch a {
|
||||
case AnchorTL:
|
||||
return p
|
||||
|
||||
case AnchorT:
|
||||
return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: 0})
|
||||
|
||||
case AnchorTR:
|
||||
return p.Add(Coord{X: w.w - wd.GetW(), Y: 0})
|
||||
|
||||
case AnchorL:
|
||||
return p.Add(Coord{X: 0, Y: midY - (wd.GetH() / 2)})
|
||||
|
||||
case AnchorC:
|
||||
return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: midY - (wd.GetH() / 2)})
|
||||
|
||||
case AnchorR:
|
||||
return p.Add(Coord{X: w.w - wd.GetW(), Y: midY - (wd.GetH() / 2)})
|
||||
|
||||
case AnchorBR:
|
||||
return p.Add(Coord{X: w.w - wd.GetW(), Y: w.h - wd.GetH()})
|
||||
|
||||
case AnchorB:
|
||||
return p.Add(Coord{X: midX - (wd.GetW() / 2), Y: w.h - wd.GetH()})
|
||||
|
||||
case AnchorBL:
|
||||
return p.Add(Coord{X: 0, Y: w.h - wd.GetH()})
|
||||
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (w *AbsoluteLayout) getAbsPos(wd Widget) Coord {
|
||||
rel := w.getRelPos(wd)
|
||||
return rel.Add(Coord{X: w.x, Y: w.y})
|
||||
}
|
||||
|
||||
105
alert.go
105
alert.go
@@ -37,10 +37,14 @@ type Alert struct {
|
||||
w, h int
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
layout *AbsoluteLayout
|
||||
title string
|
||||
message *Text
|
||||
btnOk, btnCancel *Button
|
||||
|
||||
keyMap KeyMap
|
||||
}
|
||||
|
||||
var _ Widget = (*Alert)(nil)
|
||||
@@ -54,23 +58,66 @@ func NewAlert(id string, style tcell.Style) *Alert {
|
||||
func (w *Alert) Init(id string, style tcell.Style) {
|
||||
w.id = id
|
||||
w.style = style
|
||||
|
||||
w.layout = NewAbsoluteLayout("alertlayout", tcell.StyleDefault)
|
||||
|
||||
w.message = NewText(fmt.Sprintf("%s-text", id), style)
|
||||
w.layout.AddAnchored(w.message, Coord{X: 0, Y: 0}, AnchorC)
|
||||
|
||||
w.btnOk = NewButton(fmt.Sprintf("%s-select", id), style)
|
||||
w.btnOk.SetLabel("Ok")
|
||||
w.btnOk.SetActive(true)
|
||||
w.layout.AddAnchored(w.btnOk, Coord{X: -2, Y: 0}, AnchorBR)
|
||||
|
||||
w.btnCancel = NewButton(fmt.Sprintf("%s-cancel", id), style)
|
||||
w.btnCancel.SetLabel("Cancel")
|
||||
w.layout.AddAnchored(w.btnCancel, Coord{X: 2, Y: 0}, AnchorBL)
|
||||
|
||||
w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
|
||||
tcell.KeyTab: w.SelectNext,
|
||||
tcell.KeyRight: w.SelectNext,
|
||||
tcell.KeyDown: w.SelectNext,
|
||||
tcell.KeyLeft: w.SelectNext,
|
||||
tcell.KeyUp: w.SelectNext,
|
||||
tcell.KeyEnter: w.Do,
|
||||
})
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *Alert) Id() string { return w.id }
|
||||
func (w *Alert) HandleResize(ev *tcell.EventResize) {
|
||||
w.btnOk.SetPos(Coord{X: w.x + w.w - w.btnOk.WantW(), Y: w.y + w.h - 1})
|
||||
w.btnCancel.SetPos(Coord{X: w.x + 1, Y: w.y + w.h - 1})
|
||||
w.w, w.h = ev.Size()
|
||||
// Trim space for the borders and pass on the size to the layout
|
||||
w.layout.HandleResize(tcell.NewEventResize(w.w-2, w.h-2))
|
||||
/*
|
||||
w.message.HandleResize(ev)
|
||||
w.message.SetPos(Coord{X: w.x + 1, Y: w.y + 1})
|
||||
msgWantH := w.message.WantH()
|
||||
if msgWantH > w.h {
|
||||
// TODO message won't fit in alert window
|
||||
}
|
||||
w.message.SetSize(Coord{X: w.w - 2, Y: msgWantH})
|
||||
|
||||
w.btnCancel.HandleResize(ev)
|
||||
w.btnCancel.SetPos(Coord{
|
||||
X: w.x + 2,
|
||||
Y: w.y + w.h - 3,
|
||||
})
|
||||
w.btnCancel.SetSize(Coord{X: 10, Y: 3})
|
||||
|
||||
w.btnOk.HandleResize(ev)
|
||||
w.btnOk.SetPos(Coord{
|
||||
X: w.x + w.w - 12,
|
||||
Y: w.y + w.h - 3,
|
||||
})
|
||||
w.btnOk.SetSize(Coord{X: 10, Y: 3})
|
||||
*/
|
||||
}
|
||||
|
||||
func (w *Alert) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
return w.keyMap.Handle(ev)
|
||||
}
|
||||
func (w *Alert) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *Alert) Draw(screen tcell.Screen) {
|
||||
@@ -81,10 +128,12 @@ func (w *Alert) Draw(screen tcell.Screen) {
|
||||
if !w.active {
|
||||
dS = dS.Dim(true)
|
||||
}
|
||||
|
||||
wh.TitledBorderFilled(w.x, w.y, w.x+w.w, w.y+w.h, w.title, wh.BRD_SIMPLE, w.style, screen)
|
||||
w.message.Draw(screen)
|
||||
w.btnOk.Draw(screen)
|
||||
w.btnCancel.Draw(screen)
|
||||
w.layout.Draw(screen)
|
||||
// w.message.Draw(screen)
|
||||
// w.btnOk.Draw(screen)
|
||||
// w.btnCancel.Draw(screen)
|
||||
}
|
||||
func (w *Alert) Active() bool { return w.active }
|
||||
func (w *Alert) SetActive(a bool) { w.active = a }
|
||||
@@ -94,6 +143,7 @@ func (w *Alert) SetX(x int) { w.x = x }
|
||||
func (w *Alert) SetY(y int) { w.y = y }
|
||||
func (w *Alert) GetX() int { return w.x }
|
||||
func (w *Alert) GetY() int { return w.y }
|
||||
func (w *Alert) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
func (w *Alert) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Alert) SetW(x int) { w.w = x }
|
||||
func (w *Alert) SetH(y int) { w.h = y }
|
||||
@@ -101,16 +151,45 @@ func (w *Alert) GetW() int { return w.w }
|
||||
func (w *Alert) GetH() int { return w.y }
|
||||
func (w *Alert) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Alert) Focusable() bool { return true }
|
||||
func (w *Alert) WantW() int {
|
||||
return w.btnOk.WantW() + w.btnCancel.WantW() + 4
|
||||
}
|
||||
|
||||
func (w *Alert) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Alert) Tabbable() bool { return w.tabbable }
|
||||
func (w *Alert) WantW() int { return w.btnOk.WantW() + w.btnCancel.WantW() + 4 }
|
||||
func (w *Alert) WantH() int {
|
||||
msg := len(strings.Split(wh.WrapText(w.message.GetText(), w.WantW()), "\n"))
|
||||
return 2 + w.btnOk.WantH() + msg
|
||||
}
|
||||
|
||||
func (w *Alert) SetTitle(ttl string) { w.title = ttl }
|
||||
func (w *Alert) SetMessage(msg string) {
|
||||
w.message.SetText(msg)
|
||||
// Borders + Buttons
|
||||
func (w *Alert) MinW() int {
|
||||
return 2 + w.message.MinW() + w.btnOk.MinW() + w.btnCancel.MinW()
|
||||
}
|
||||
|
||||
// Borders + Buttons + 2 lines for message
|
||||
func (w *Alert) MinH() int {
|
||||
return 2 + w.message.MinH() + w.btnOk.MinH()
|
||||
}
|
||||
|
||||
func (w *Alert) SetTitle(ttl string) { w.title = ttl }
|
||||
func (w *Alert) SetMessage(msg string) { w.message.SetText(msg) }
|
||||
|
||||
func (w *Alert) SetOkPressed(b func() bool) { w.btnOk.SetOnPressed(b) }
|
||||
func (w *Alert) SetCancelPressed(b func() bool) { w.btnCancel.SetOnPressed(b) }
|
||||
func (w *Alert) SelectNext(ev *tcell.EventKey) bool {
|
||||
if w.btnOk.Active() {
|
||||
w.btnOk.SetActive(false)
|
||||
w.btnCancel.SetActive(true)
|
||||
} else {
|
||||
w.btnOk.SetActive(true)
|
||||
w.btnCancel.SetActive(false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *Alert) Do(ev *tcell.EventKey) bool {
|
||||
if w.btnOk.Active() {
|
||||
return w.btnOk.HandleKey(ev)
|
||||
} else if w.btnCancel.Active() {
|
||||
return w.btnCancel.HandleKey(ev)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// TODO: Make sure this works right... I don't think it does.
|
||||
type BorderedWidget struct {
|
||||
id string
|
||||
style tcell.Style
|
||||
@@ -38,6 +39,7 @@ type BorderedWidget struct {
|
||||
title string
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
logger func(string)
|
||||
}
|
||||
@@ -62,11 +64,14 @@ func (w *BorderedWidget) Init(id string, s tcell.Style) {
|
||||
w.style = s
|
||||
w.visible = true
|
||||
w.border = h.BRD_CSIMPLE
|
||||
w.tabbable = true
|
||||
}
|
||||
|
||||
func (w *BorderedWidget) Id() string { return w.id }
|
||||
func (w *BorderedWidget) HandleResize(ev *tcell.EventResize) {
|
||||
w.widget.HandleResize(ev)
|
||||
// Trim space for border and pass the resize to the widget
|
||||
w.w, w.h = ev.Size()
|
||||
w.widget.HandleResize(tcell.NewEventResize(w.w-2, w.h-2))
|
||||
}
|
||||
|
||||
func (w *BorderedWidget) HandleKey(ev *tcell.EventKey) bool {
|
||||
@@ -94,10 +99,13 @@ 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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *BorderedWidget) Tabbable() bool { return w.tabbable }
|
||||
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) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -106,5 +114,8 @@ 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) MinW() int { return 2 + w.widget.MinW() }
|
||||
func (w *BorderedWidget) MinH() int { return 2 + w.widget.MinH() }
|
||||
|
||||
func (w *BorderedWidget) SetBorder(r []rune) { w.border = r }
|
||||
func (w *BorderedWidget) SetTitle(ttl string) { w.title = ttl }
|
||||
|
||||
16
button.go
16
button.go
@@ -38,6 +38,7 @@ type Button struct {
|
||||
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
onPressed func() bool
|
||||
}
|
||||
@@ -55,9 +56,11 @@ func (w *Button) Init(id string, style tcell.Style) {
|
||||
w.style = style
|
||||
w.visible = true
|
||||
w.onPressed = func() bool { return false }
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *Button) Id() string { return w.id }
|
||||
func (w *Button) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Button) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() }
|
||||
|
||||
func (w *Button) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
@@ -106,8 +109,8 @@ func (w *Button) Draw(screen tcell.Screen) {
|
||||
}
|
||||
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)
|
||||
h.DrawText(w.x, w.y+1, fmt.Sprintf("│%s│", h.Center(lbl, w.w-2)), dStyle, screen)
|
||||
h.DrawText(w.x, w.y+2, 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 }
|
||||
@@ -117,15 +120,20 @@ 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) GetPos() Coord { return Coord{X: w.x, Y: 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) WantW() int { return 4 + 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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Button) Tabbable() bool { return w.tabbable }
|
||||
func (w *Button) MinW() int { return len(w.label) + 2 }
|
||||
func (w *Button) MinH() int { return 1 }
|
||||
|
||||
func (w *Button) SetLabel(l string) { w.label = l }
|
||||
func (w *Button) SetOnPressed(p func() bool) { w.onPressed = p }
|
||||
|
||||
10
chat.go
10
chat.go
@@ -37,6 +37,7 @@ type Chat struct {
|
||||
w, h int
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
title string
|
||||
rawLog []string
|
||||
@@ -62,10 +63,12 @@ func (w *Chat) Init(id string, s tcell.Style) {
|
||||
w.id, w.style = id, s
|
||||
w.visible = true
|
||||
w.initKeyMap()
|
||||
w.tabbable = true
|
||||
}
|
||||
|
||||
func (w *Chat) Id() string { return w.id }
|
||||
func (w *Chat) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Chat) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() }
|
||||
|
||||
func (w *Chat) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
@@ -152,10 +155,13 @@ func (w *Chat) SetActive(a bool) { w.active = a }
|
||||
func (w *Chat) Visible() bool { return w.visible }
|
||||
func (w *Chat) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Chat) Focusable() bool { return true }
|
||||
func (w *Chat) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Chat) Tabbable() bool { return w.tabbable }
|
||||
func (w *Chat) SetX(x int) { w.x = x }
|
||||
func (w *Chat) SetY(y int) { w.y = y }
|
||||
func (w *Chat) GetX() int { return w.x }
|
||||
func (w *Chat) GetY() int { return w.y }
|
||||
func (w *Chat) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
func (w *Chat) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Chat) GetW() int { return w.w }
|
||||
func (w *Chat) GetH() int { return w.h }
|
||||
@@ -164,6 +170,8 @@ func (w *Chat) SetH(h int) { w.h = h }
|
||||
func (w *Chat) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Chat) WantW() int { return w.w }
|
||||
func (w *Chat) WantH() int { return w.h }
|
||||
func (w *Chat) MinW() int { return 2 + 20 }
|
||||
func (w *Chat) MinH() int { return 6 }
|
||||
|
||||
func (w *Chat) initKeyMap() {
|
||||
w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
|
||||
|
||||
13
checkbox.go
13
checkbox.go
@@ -40,6 +40,7 @@ type Checkbox struct {
|
||||
style tcell.Style
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
state int
|
||||
x, y int
|
||||
w, h int
|
||||
@@ -60,9 +61,11 @@ func (w *Checkbox) Init(id string, style tcell.Style) {
|
||||
w.style = style
|
||||
w.visible = true
|
||||
w.stateRunes = []rune{'X', ' ', '-'}
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *Checkbox) Id() string { return w.id }
|
||||
func (w *Checkbox) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Checkbox) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() }
|
||||
|
||||
func (w *Checkbox) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
@@ -96,6 +99,7 @@ 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) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -104,10 +108,15 @@ 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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Checkbox) Tabbable() bool { return w.tabbable }
|
||||
func (w *Checkbox) MinW() int {
|
||||
return len(fmt.Sprintf("[%s] %s", string(w.state), w.label))
|
||||
}
|
||||
func (w *Checkbox) MinH() int { return 1 }
|
||||
|
||||
func (w *Checkbox) SetLabel(l string) { w.label = l }
|
||||
func (w *Checkbox) SetChecked(v bool) {
|
||||
|
||||
9
cli.go
9
cli.go
@@ -38,6 +38,7 @@ type Cli struct {
|
||||
w, h int
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
title string
|
||||
rawLog []string
|
||||
@@ -67,10 +68,11 @@ func (w *Cli) Init(id string, s tcell.Style) {
|
||||
w.id, w.style = id, s
|
||||
w.visible = true
|
||||
w.initKeyMap()
|
||||
w.tabbable = true
|
||||
}
|
||||
|
||||
func (w *Cli) Id() string { return w.id }
|
||||
func (w *Cli) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Cli) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() }
|
||||
func (w *Cli) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
@@ -157,10 +159,13 @@ func (w *Cli) SetActive(a bool) { w.active = a }
|
||||
func (w *Cli) Visible() bool { return w.visible }
|
||||
func (w *Cli) SetVisible(a bool) { w.visible = a }
|
||||
func (w *Cli) Focusable() bool { return true }
|
||||
func (w *Cli) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Cli) Tabbable() bool { return w.tabbable }
|
||||
func (w *Cli) SetX(x int) { w.x = x }
|
||||
func (w *Cli) SetY(y int) { w.y = y }
|
||||
func (w *Cli) GetX() int { return w.x }
|
||||
func (w *Cli) GetY() int { return w.y }
|
||||
func (w *Cli) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
func (w *Cli) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Cli) GetW() int { return w.w }
|
||||
func (w *Cli) GetH() int { return w.h }
|
||||
@@ -169,6 +174,8 @@ func (w *Cli) SetH(h int) { w.h = h }
|
||||
func (w *Cli) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Cli) WantW() int { return w.w }
|
||||
func (w *Cli) WantH() int { return w.h }
|
||||
func (w *Cli) MinW() int { return 20 }
|
||||
func (w *Cli) MinH() int { return 6 }
|
||||
|
||||
func (w *Cli) initKeyMap() {
|
||||
w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
|
||||
|
||||
9
field.go
9
field.go
@@ -38,6 +38,7 @@ type Field struct {
|
||||
cursor int
|
||||
visible bool
|
||||
active bool
|
||||
tabbable bool
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
@@ -70,10 +71,11 @@ func (w *Field) Init(id string, style tcell.Style) {
|
||||
tcell.KeyEnd: w.handleEnd,
|
||||
tcell.KeyCtrlU: w.clearValueBeforeCursor,
|
||||
})
|
||||
w.tabbable = true
|
||||
}
|
||||
|
||||
func (w *Field) Id() string { return w.id }
|
||||
func (w *Field) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *Field) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size() }
|
||||
func (w *Field) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
@@ -137,6 +139,7 @@ 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) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -151,6 +154,10 @@ func (w *Field) WantH() int {
|
||||
}
|
||||
func (w *Field) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Field) Focusable() bool { return true }
|
||||
func (w *Field) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Field) Tabbable() bool { return w.tabbable }
|
||||
func (w *Field) MinW() int { return len(w.label) + 15 }
|
||||
func (w *Field) MinH() int { return 1 }
|
||||
|
||||
/* Non-Widget-Interface Functions */
|
||||
func (w *Field) handleBackspace(ev *tcell.EventKey) bool {
|
||||
|
||||
@@ -36,6 +36,7 @@ type FilePicker struct {
|
||||
active bool
|
||||
visible bool
|
||||
focusable bool
|
||||
tabbable bool
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
@@ -44,6 +45,8 @@ type FilePicker struct {
|
||||
path string
|
||||
wrkDir *os.File
|
||||
|
||||
layout *RelativeLayout
|
||||
|
||||
fileList *List
|
||||
btnSelect, btnCancel *Button
|
||||
}
|
||||
@@ -59,13 +62,21 @@ func NewFilePicker(id string, style tcell.Style) *FilePicker {
|
||||
func (w *FilePicker) Init(id string, style tcell.Style) {
|
||||
w.id = id
|
||||
w.style = style
|
||||
|
||||
w.layout = NewRelativeLayout(fmt.Sprintf("%s-layout", id), style)
|
||||
|
||||
w.btnSelect = NewButton(fmt.Sprintf("%s-select", id), style)
|
||||
w.btnSelect.SetLabel("Select")
|
||||
w.layout.Add(w.btnSelect, nil, RelAncBR)
|
||||
w.btnCancel = NewButton(fmt.Sprintf("%s-cancel", id), style)
|
||||
w.btnCancel.SetLabel("Cancel")
|
||||
w.layout.Add(w.btnCancel, nil, RelAncBL)
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *FilePicker) Id() string { return w.id }
|
||||
func (w *FilePicker) HandleResize(ev *tcell.EventResize) {
|
||||
w.w, w.h = ev.Size()
|
||||
// ww, wh := w.w-2, w.h-2 // Trim border space
|
||||
w.btnSelect.SetPos(Coord{X: w.x + w.w - w.btnSelect.WantW(), Y: w.y + w.h - 1})
|
||||
w.btnCancel.SetPos(Coord{X: w.x + 1, Y: w.y + w.h - 1})
|
||||
}
|
||||
@@ -99,6 +110,7 @@ func (w *FilePicker) SetX(x int) { w.x = x }
|
||||
func (w *FilePicker) SetY(y int) { w.y = y }
|
||||
func (w *FilePicker) GetX() int { return w.x }
|
||||
func (w *FilePicker) GetY() int { return w.y }
|
||||
func (w *FilePicker) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
func (w *FilePicker) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *FilePicker) SetW(x int) { w.w = x }
|
||||
func (w *FilePicker) SetH(y int) { w.h = y }
|
||||
@@ -106,6 +118,8 @@ func (w *FilePicker) GetW() int { return w.w }
|
||||
func (w *FilePicker) GetH() int { return w.y }
|
||||
func (w *FilePicker) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *FilePicker) Focusable() bool { return w.focusable }
|
||||
func (w *FilePicker) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *FilePicker) Tabbable() bool { return w.tabbable }
|
||||
func (w *FilePicker) WantW() int {
|
||||
// borders + the greater of the buttons next to each other or the list width
|
||||
return wh.Max((w.btnSelect.WantW()+w.btnCancel.WantW()), w.fileList.WantW()) + 2
|
||||
@@ -116,6 +130,14 @@ func (w *FilePicker) WantH() int {
|
||||
return 2 + w.fileList.WantH() + w.btnSelect.WantH()
|
||||
}
|
||||
|
||||
func (w *FilePicker) MinW() int {
|
||||
return 2 + w.fileList.MinW() + w.btnSelect.MinW() + w.btnCancel.MinW()
|
||||
}
|
||||
|
||||
func (w *FilePicker) MinH() int {
|
||||
return 2 + w.fileList.MinH() + w.btnSelect.MinH()
|
||||
}
|
||||
|
||||
func (w *FilePicker) SetTitle(ttl string) { w.title = ttl }
|
||||
func (w *FilePicker) SetPath(path string) error {
|
||||
var err error
|
||||
|
||||
264
linear_layout.go
Normal file
264
linear_layout.go
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
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/tcell-widgets/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// LinearLayout lays out all widgets added one after the other
|
||||
type LinearLayout struct {
|
||||
id string
|
||||
style tcell.Style
|
||||
|
||||
orientation LinearLayoutOrient
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
widgets []Widget
|
||||
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
disableTab bool
|
||||
|
||||
cursor int
|
||||
}
|
||||
|
||||
type LinearLayoutOrient int
|
||||
|
||||
const (
|
||||
LinLayV = LinearLayoutOrient(iota)
|
||||
LinLayH
|
||||
)
|
||||
|
||||
func NewLinearLayout(id string, s tcell.Style) *LinearLayout {
|
||||
ret := &LinearLayout{}
|
||||
ret.Init(id, s)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *LinearLayout) Init(id string, s tcell.Style) {
|
||||
w.id = id
|
||||
w.style = s
|
||||
w.visible = true
|
||||
}
|
||||
|
||||
func (w *LinearLayout) Id() string { return w.id }
|
||||
func (w *LinearLayout) HandleResize(ev *tcell.EventResize) {
|
||||
w.w, w.h = ev.Size()
|
||||
w.updateWidgetLayouts()
|
||||
}
|
||||
|
||||
func (w *LinearLayout) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.disableTab && ev.Key() == tcell.KeyTab {
|
||||
fndP := -1
|
||||
for i := w.cursor; i < len(w.widgets); i++ {
|
||||
if fndP == -1 {
|
||||
if w.widgets[i].Active() {
|
||||
fndP = i
|
||||
w.widgets[i].SetActive(false)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if w.widgets[i].Focusable() && w.widgets[i].Tabbable() {
|
||||
w.widgets[i].SetActive(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we're here, we hit the last widget, loop
|
||||
if fndP == -1 { // But didn't even find the active one
|
||||
return false
|
||||
}
|
||||
for i := 0; i < fndP; i++ {
|
||||
if w.widgets[i].Focusable() && w.widgets[i].Tabbable() {
|
||||
w.widgets[i].SetActive(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
for _, wi := range w.widgets {
|
||||
if wi.HandleKey(ev) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *LinearLayout) HandleTime(ev *tcell.EventTime) {
|
||||
for _, wi := range w.widgets {
|
||||
wi.HandleTime(ev)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LinearLayout) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
p := w.GetPos()
|
||||
for _, wd := range w.widgets {
|
||||
o := wd.GetPos()
|
||||
wd.SetPos(p.Add(o))
|
||||
wd.Draw(screen)
|
||||
wd.SetPos(o)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LinearLayout) Active() bool { return w.active }
|
||||
func (w *LinearLayout) SetActive(a bool) { w.active = a }
|
||||
func (w *LinearLayout) Visible() bool { return w.visible }
|
||||
func (w *LinearLayout) SetVisible(a bool) { w.visible = a }
|
||||
func (w *LinearLayout) Focusable() bool { return true }
|
||||
func (w *LinearLayout) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *LinearLayout) Tabbable() bool { return w.tabbable }
|
||||
func (w *LinearLayout) SetX(x int) { w.x = x }
|
||||
func (w *LinearLayout) SetY(y int) { w.y = y }
|
||||
func (w *LinearLayout) GetX() int { return w.x }
|
||||
func (w *LinearLayout) GetY() int { return w.y }
|
||||
func (w *LinearLayout) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
func (w *LinearLayout) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *LinearLayout) GetW() int { return w.w }
|
||||
func (w *LinearLayout) GetH() int { return w.h }
|
||||
func (w *LinearLayout) SetW(wd int) { w.w = wd }
|
||||
func (w *LinearLayout) SetH(h int) { w.h = h }
|
||||
func (w *LinearLayout) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *LinearLayout) WantW() int {
|
||||
var wantW int
|
||||
for _, wd := range w.widgets {
|
||||
if w.orientation == LinLayV {
|
||||
// Find the highest want of all widgets
|
||||
wantW = h.Max(wd.WantW(), wantW)
|
||||
} else if w.orientation == LinLayH {
|
||||
// Find the sum of all widget widgets wants
|
||||
wantW = wantW + wd.WantW()
|
||||
}
|
||||
}
|
||||
return wantW
|
||||
}
|
||||
|
||||
func (w *LinearLayout) WantH() int {
|
||||
var wantH int
|
||||
for _, wd := range w.widgets {
|
||||
if w.orientation == LinLayV {
|
||||
// Find the sum of all widget widgets wants
|
||||
wantH = wantH + wd.WantH()
|
||||
} else if w.orientation == LinLayH {
|
||||
// Find the highest want of all widgets
|
||||
wantH = h.Max(wd.WantH(), wantH)
|
||||
}
|
||||
}
|
||||
return wantH
|
||||
}
|
||||
|
||||
func (w *LinearLayout) MinW() int {
|
||||
var minW int
|
||||
for _, wd := range w.widgets {
|
||||
if w.orientation == LinLayV {
|
||||
// Find the highest minimum width of all widgets
|
||||
minW = h.Max(wd.MinW(), minW)
|
||||
} else if w.orientation == LinLayH {
|
||||
// Find the sum of all widget minimum widgets
|
||||
minW = minW + wd.MinW()
|
||||
}
|
||||
}
|
||||
return minW
|
||||
}
|
||||
|
||||
func (w *LinearLayout) MinH() int {
|
||||
var minH int
|
||||
for _, wd := range w.widgets {
|
||||
if w.orientation == LinLayV {
|
||||
minH = minH + wd.MinH()
|
||||
} else if w.orientation == LinLayH {
|
||||
minH = h.Max(wd.MinH(), minH)
|
||||
}
|
||||
}
|
||||
return minH
|
||||
}
|
||||
func (w *LinearLayout) Append(n Widget) { w.widgets = append(w.widgets, n) }
|
||||
func (w *LinearLayout) Delete(n Widget) {
|
||||
for i := 0; i < len(w.widgets); i++ {
|
||||
if w.widgets[i] == n {
|
||||
w.DeleteIndex(i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *LinearLayout) DeleteIndex(idx int) {
|
||||
w.widgets = append(w.widgets[:idx], w.widgets[idx+1:]...)
|
||||
}
|
||||
|
||||
func (w *LinearLayout) Insert(n Widget, idx int) {
|
||||
w.widgets = append(w.widgets[:idx], append([]Widget{n}, w.widgets[idx:]...)...)
|
||||
}
|
||||
|
||||
func (w *LinearLayout) updateWidgetLayouts() {
|
||||
for _, wd := range w.widgets {
|
||||
w.updateWidgetPos(wd)
|
||||
w.updateWidgetSize(wd)
|
||||
}
|
||||
}
|
||||
|
||||
// The Layout should have a static Size set at this point that we can use
|
||||
// For now we're centering all views in the Layout (on the cross-axis)
|
||||
//
|
||||
// The position and size of each widget before this should be correct
|
||||
// Find the position and size of the widget before this one
|
||||
func (w *LinearLayout) updateWidgetPos(wd Widget) {
|
||||
prevP, prevS := 0, 0
|
||||
for _, wrk := range w.widgets {
|
||||
if w.orientation == LinLayV {
|
||||
if wrk == wd {
|
||||
wd.SetPos(Coord{X: w.w - (wd.GetW() / 2), Y: prevP + prevS + 1})
|
||||
return
|
||||
}
|
||||
prevP, prevS = wrk.GetY(), wrk.GetH()
|
||||
} else if w.orientation == LinLayH {
|
||||
if wrk == wd {
|
||||
wd.SetPos(Coord{X: prevP + prevS + 1, Y: w.h - (wd.GetH() / 2)})
|
||||
return
|
||||
}
|
||||
prevP, prevS = wrk.GetX(), wrk.GetW()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The Layout should have a static Size set at this point that we can use
|
||||
// For now we're centering all views in the Layout (on the cross-axis)
|
||||
//
|
||||
// The position of this widget should be correct
|
||||
func (w *LinearLayout) updateWidgetSize(wd Widget) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (w *LinearLayout) getRelPos(wd Widget) Coord {
|
||||
return Coord{}
|
||||
}
|
||||
|
||||
func (w *LinearLayout) getAbsPos(wd Widget) Coord {
|
||||
rel := w.getRelPos(wd)
|
||||
return rel.Add(Coord{X: w.x, Y: w.y})
|
||||
}
|
||||
15
list.go
15
list.go
@@ -33,6 +33,7 @@ type List struct {
|
||||
active bool
|
||||
visible bool
|
||||
focusable bool
|
||||
tabbable bool
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
@@ -83,6 +84,7 @@ func (w *List) Init(id string, style tcell.Style) {
|
||||
return false
|
||||
})
|
||||
w.itemsStyle = make(map[int]tcell.Style)
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *List) Id() string { return w.id }
|
||||
func (w *List) HandleResize(ev *tcell.EventResize) {}
|
||||
@@ -136,6 +138,7 @@ func (w *List) SetX(x int) { w.x = x }
|
||||
func (w *List) SetY(y int) { w.y = y }
|
||||
func (w *List) GetX() int { return w.x }
|
||||
func (w *List) GetY() int { return w.y }
|
||||
func (w *List) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
func (w *List) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *List) SetW(x int) { w.w = x }
|
||||
func (w *List) SetH(y int) { w.h = y }
|
||||
@@ -143,6 +146,8 @@ func (w *List) GetW() int { return w.w }
|
||||
func (w *List) GetH() int { return w.y }
|
||||
func (w *List) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *List) Focusable() bool { return w.focusable }
|
||||
func (w *List) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *List) Tabbable() bool { return w.tabbable }
|
||||
func (w *List) WantW() int {
|
||||
lng := h.Longest(w.list)
|
||||
if len(w.border) > 0 {
|
||||
@@ -159,6 +164,16 @@ func (w *List) WantH() int {
|
||||
return lng
|
||||
}
|
||||
|
||||
func (w *List) MinW() int {
|
||||
lng := h.Longest(w.list)
|
||||
if lng > 80 {
|
||||
lng = 80
|
||||
}
|
||||
return 2 + lng
|
||||
}
|
||||
|
||||
func (w *List) MinH() int { return 4 }
|
||||
|
||||
func (w *List) SetFocusable(f bool) { w.focusable = f }
|
||||
|
||||
func (w *List) SetCursorWrap(b bool) { w.cursorWrap = b }
|
||||
|
||||
36
menu.go
36
menu.go
@@ -32,6 +32,7 @@ type Menu struct {
|
||||
style tcell.Style
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
@@ -76,6 +77,7 @@ func (w *Menu) Init(id string, style tcell.Style) {
|
||||
return false
|
||||
},
|
||||
})
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *Menu) Id() string { return w.id }
|
||||
func (w *Menu) HandleResize(ev *tcell.EventResize) {}
|
||||
@@ -154,6 +156,7 @@ 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) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -161,6 +164,8 @@ 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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Menu) Tabbable() bool { return w.tabbable }
|
||||
|
||||
func (w *Menu) WantW() int {
|
||||
var maxW int
|
||||
@@ -186,6 +191,37 @@ func (w *Menu) WantH() int {
|
||||
return ret + len(w.items)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Menu) MinW() int {
|
||||
labels := []string{w.label}
|
||||
for i := range w.items {
|
||||
labels = append(labels, w.items[i].label)
|
||||
}
|
||||
switch w.menuType {
|
||||
case MenuTypeH:
|
||||
wrk := 0
|
||||
for i := range labels {
|
||||
wrk += len(labels[i])
|
||||
}
|
||||
return wrk
|
||||
case MenuTypeV:
|
||||
return h.Longest(labels)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (w *Menu) MinH() int {
|
||||
switch w.menuType {
|
||||
case MenuTypeH:
|
||||
return 1
|
||||
case MenuTypeV:
|
||||
if len(w.label) > 0 {
|
||||
return 1 + len(w.items)
|
||||
}
|
||||
return len(w.items)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
func (w *Menu) SetType(tp MenuType) { w.menuType = tp }
|
||||
func (w *Menu) SetLabel(lbl string) { w.label = lbl }
|
||||
func (w *Menu) SetFocusable(f bool) {}
|
||||
|
||||
12
menu_item.go
12
menu_item.go
@@ -32,6 +32,7 @@ type MenuItem struct {
|
||||
style tcell.Style
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
x, y int
|
||||
w, h int
|
||||
|
||||
@@ -72,6 +73,7 @@ func (w *MenuItem) Init(id string, style tcell.Style) {
|
||||
for i := range w.items {
|
||||
w.items[i].SetActive(i == w.cursor)
|
||||
}
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *MenuItem) Id() string { return w.id }
|
||||
func (w *MenuItem) HandleResize(ev *tcell.EventResize) {}
|
||||
@@ -101,7 +103,10 @@ func (w *MenuItem) Draw(screen tcell.Screen) {
|
||||
h.DrawText(x, y, h.PadR(w.label, wd), st, screen)
|
||||
y += 1
|
||||
if w.expanded {
|
||||
x += 2
|
||||
if len(w.items) > 0 {
|
||||
h.TitledBorderFilled(w.x-1, w.y, w.x+w.WantW(), w.y+w.WantH(), w.label, h.BRD_CSIMPLE, w.style, screen)
|
||||
}
|
||||
x += 1
|
||||
for i := range w.items {
|
||||
w.items[i].SetPos(Coord{X: x, Y: y})
|
||||
w.items[i].Draw(screen)
|
||||
@@ -122,6 +127,7 @@ 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) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -132,7 +138,7 @@ func (w *MenuItem) WantW() int {
|
||||
if len(w.items) > 0 {
|
||||
for i := range w.items {
|
||||
if w.items[i].WantW() > ret {
|
||||
ret = w.items[i].WantW()
|
||||
ret = w.items[i].WantW() + 1
|
||||
// TODO: Figure offset of subitems
|
||||
}
|
||||
}
|
||||
@@ -151,6 +157,8 @@ func (w *MenuItem) WantH() int {
|
||||
}
|
||||
func (w *MenuItem) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *MenuItem) Focusable() bool { return !w.disabled }
|
||||
func (w *MenuItem) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *MenuItem) Tabbable() bool { return w.tabbable }
|
||||
|
||||
// How much width this item wants
|
||||
func (w *MenuItem) Expand(e bool) {
|
||||
|
||||
13
prompt.go
13
prompt.go
@@ -37,6 +37,7 @@ type Prompt struct {
|
||||
w, h int
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
title string
|
||||
message *Text
|
||||
@@ -62,6 +63,7 @@ func (w *Prompt) Init(id string, style tcell.Style) {
|
||||
w.btnOk.SetLabel("Ok")
|
||||
w.btnCancel = NewButton(fmt.Sprintf("%s-cancel", id), style)
|
||||
w.btnCancel.SetLabel("Cancel")
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *Prompt) Id() string { return w.id }
|
||||
func (w *Prompt) HandleResize(ev *tcell.EventResize) {
|
||||
@@ -99,6 +101,7 @@ func (w *Prompt) SetX(x int) { w.x = x }
|
||||
func (w *Prompt) SetY(y int) { w.y = y }
|
||||
func (w *Prompt) GetX() int { return w.x }
|
||||
func (w *Prompt) GetY() int { return w.y }
|
||||
func (w *Prompt) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
func (w *Prompt) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *Prompt) SetW(x int) { w.w = x }
|
||||
func (w *Prompt) SetH(y int) { w.h = y }
|
||||
@@ -106,6 +109,8 @@ func (w *Prompt) GetW() int { return w.w }
|
||||
func (w *Prompt) GetH() int { return w.y }
|
||||
func (w *Prompt) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *Prompt) Focusable() bool { return true }
|
||||
func (w *Prompt) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Prompt) Tabbable() bool { return w.tabbable }
|
||||
func (w *Prompt) WantW() int {
|
||||
return w.btnOk.WantW() + w.btnCancel.WantW() + 4
|
||||
}
|
||||
@@ -120,4 +125,12 @@ func (w *Prompt) SetMessage(msg string) {
|
||||
w.message.SetText(msg)
|
||||
}
|
||||
|
||||
func (w *Prompt) MinW() int {
|
||||
return 2 + w.field.MinW() + w.btnOk.MinW() + w.btnCancel.MinW() + w.message.MinW()
|
||||
}
|
||||
|
||||
func (w *Prompt) MinH() int {
|
||||
return 2 + w.field.MinH() + w.btnOk.MinH() + w.message.MinH()
|
||||
}
|
||||
|
||||
func (w *Prompt) SetOnOk(f func(string) bool) { w.onOk = f }
|
||||
|
||||
@@ -26,10 +26,13 @@ import "github.com/gdamore/tcell"
|
||||
type RelativeLayout struct {
|
||||
id string
|
||||
style tcell.Style
|
||||
widgetRelations map[Widget]widgetRelation
|
||||
widgetRelations map[Widget][]widgetRelation
|
||||
widgets []Widget
|
||||
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
}
|
||||
@@ -44,14 +47,19 @@ type widgetRelation struct {
|
||||
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
|
||||
RelRelAbove = iota // Above Widget
|
||||
RelRelToRightOf // To Right of Widget
|
||||
RelRelBelow // Below Widget
|
||||
RelRelToLeftOf // To Left of Widget
|
||||
RelAncTL // Anchored to parent Top-Left
|
||||
RelAncT // Anchored to parent Top
|
||||
RelAncTR // Anchored to parent Top-Right
|
||||
RelAncL // Anchored to parent Left
|
||||
RelAncC // Anchored to parent Center
|
||||
RelAncR // Anchored to parent Right
|
||||
RelAncBL // Anchored to parent Bottom-Left
|
||||
RelAncB // Anchored to parent Bottom
|
||||
RelAncBR // Anchored to parent Bottom-Right
|
||||
)
|
||||
|
||||
func NewRelativeLayout(id string, s tcell.Style) *RelativeLayout {
|
||||
@@ -64,6 +72,7 @@ func (w *RelativeLayout) Init(id string, style tcell.Style) {
|
||||
w.id = id
|
||||
w.style = style
|
||||
w.visible = true
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *RelativeLayout) Id() string { return w.id }
|
||||
func (w *RelativeLayout) HandleResize(ev *tcell.EventResize) {}
|
||||
@@ -73,6 +82,8 @@ func (w *RelativeLayout) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
// All widgets should have correct (relative) positions
|
||||
/*
|
||||
done := make(map[Widget]widgetRelation)
|
||||
rem := make(map[Widget]widgetRelation)
|
||||
for k, v := range w.widgetRelations {
|
||||
@@ -82,16 +93,21 @@ func (w *RelativeLayout) Draw(screen tcell.Screen) {
|
||||
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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *RelativeLayout) Tabbable() bool { return w.tabbable }
|
||||
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) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *RelativeLayout) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -99,7 +115,29 @@ 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) MinW() int {
|
||||
// Find the highest value for x in all widgets GetX() + MinW()
|
||||
var minW int
|
||||
for _, wd := range w.widgets {
|
||||
wrk := wd.GetX() + wd.MinW()
|
||||
if wrk > minW {
|
||||
minW = wrk
|
||||
}
|
||||
}
|
||||
return minW
|
||||
}
|
||||
|
||||
func (w *RelativeLayout) MinH() int {
|
||||
// Find the highest value for y in all widgets GetY() + MinH()
|
||||
var minH int
|
||||
for _, wd := range w.widgets {
|
||||
wrk := wd.GetY() + wd.MinH()
|
||||
if wrk > minH {
|
||||
minH = wrk
|
||||
}
|
||||
}
|
||||
return minH
|
||||
}
|
||||
|
||||
func (w *RelativeLayout) Add(n Widget, relTo Widget, relation RelativeRelation) {
|
||||
w.widgetRelations[n] = widgetRelation{
|
||||
@@ -107,3 +145,6 @@ func (w *RelativeLayout) Add(n Widget, relTo Widget, relation RelativeRelation)
|
||||
relTo: relTo,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *RelativeLayout) AddRelation(n Widget, relation RelativeRelation, relTo Widget) {
|
||||
}
|
||||
|
||||
12
searcher.go
12
searcher.go
@@ -37,6 +37,7 @@ type Searcher struct {
|
||||
w, h int
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
title string
|
||||
search *Field
|
||||
@@ -77,6 +78,7 @@ func (w *Searcher) Init(id string, style tcell.Style) {
|
||||
tcell.KeyPgDn: w.handleKeyPgDn,
|
||||
tcell.KeyEnter: w.handleKeyEnter,
|
||||
})
|
||||
w.tabbable = true
|
||||
}
|
||||
|
||||
func (w *Searcher) Id() string { return w.id }
|
||||
@@ -222,6 +224,7 @@ func (w *Searcher) WantH() int {
|
||||
return 2 + w.search.WantH() + len(w.filteredData) // Border + Field + Data
|
||||
}
|
||||
|
||||
func (w *Searcher) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
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 }
|
||||
@@ -229,6 +232,15 @@ 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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Searcher) Tabbable() bool { return w.tabbable }
|
||||
func (w *Searcher) MinW() int {
|
||||
return 2 + w.search.MinW()
|
||||
}
|
||||
|
||||
func (w *Searcher) MinH() int {
|
||||
return 2 + w.search.MinH() + 5
|
||||
}
|
||||
|
||||
func (w *Searcher) SetHideOnSelect(t bool) { w.hideOnSelect = t }
|
||||
func (w *Searcher) SetTitle(ttl string) { w.title = ttl }
|
||||
|
||||
50
table.go
50
table.go
@@ -36,6 +36,7 @@ type Table struct {
|
||||
active bool
|
||||
visible bool
|
||||
focusable bool
|
||||
tabbable bool
|
||||
|
||||
header []string
|
||||
footer []string
|
||||
@@ -82,6 +83,7 @@ func (w *Table) Init(id string, style tcell.Style) {
|
||||
w.style = style
|
||||
w.visible = true
|
||||
w.border = h.BRD_CSIMPLE
|
||||
w.tabbable = true
|
||||
}
|
||||
func (w *Table) Id() string { return w.id }
|
||||
func (w *Table) HandleResize(ev *tcell.EventResize) {}
|
||||
@@ -147,6 +149,7 @@ 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) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -198,8 +201,55 @@ func (w *Table) WantH() int {
|
||||
return datLen
|
||||
}
|
||||
|
||||
func (w *Table) MinW() 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) MinH() int {
|
||||
if w.minimized {
|
||||
return 1
|
||||
}
|
||||
datLen := h.Min(len(w.data)+2, 7) // 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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Table) Tabbable() bool { return w.tabbable }
|
||||
|
||||
func (w *Table) SetTitle(ttl string) { w.title = ttl }
|
||||
func (w *Table) SetFocusable(f bool) { w.focusable = f }
|
||||
|
||||
21
text.go
21
text.go
@@ -22,6 +22,8 @@ THE SOFTWARE.
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
h "git.bullercodeworks.com/brian/tcell-widgets/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
@@ -33,6 +35,7 @@ type Text struct {
|
||||
x, y int
|
||||
w, h int
|
||||
visible bool
|
||||
tabbable bool
|
||||
}
|
||||
|
||||
var _ Widget = (*Text)(nil)
|
||||
@@ -47,6 +50,7 @@ func (w *Text) Init(id string, style tcell.Style) {
|
||||
w.id = id
|
||||
w.style = style
|
||||
w.visible = true
|
||||
w.tabbable = false
|
||||
}
|
||||
func (w *Text) Id() string { return w.id }
|
||||
func (w *Text) HandleResize(ev *tcell.EventResize) {}
|
||||
@@ -56,7 +60,17 @@ func (w *Text) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
h.DrawText(w.x, w.y, w.text, w.style, screen)
|
||||
var pts []string
|
||||
if w.w < len(w.text) {
|
||||
pts = strings.Split(h.WrapText(w.text, w.w), "\n")
|
||||
} else {
|
||||
pts = []string{w.text}
|
||||
}
|
||||
y := w.y
|
||||
for i := range pts {
|
||||
h.DrawText(w.x, y, pts[i], w.style, screen)
|
||||
y++
|
||||
}
|
||||
}
|
||||
func (w *Text) Active() bool { return false }
|
||||
func (w *Text) SetActive(a bool) {}
|
||||
@@ -66,6 +80,7 @@ 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) GetPos() Coord { return Coord{X: w.x, Y: 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 }
|
||||
@@ -75,6 +90,10 @@ 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) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *Text) Tabbable() bool { return w.tabbable }
|
||||
func (w *Text) MinW() int { return len(w.text) }
|
||||
func (w *Text) MinH() int { return 1 }
|
||||
|
||||
func (w *Text) SetText(txt string) { w.text = txt }
|
||||
func (w *Text) GetText() string { return w.text }
|
||||
|
||||
272
timefield.go
Normal file
272
timefield.go
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
h "git.bullercodeworks.com/brian/tcell-widgets/helpers"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type TimeField struct {
|
||||
id string
|
||||
label string
|
||||
style tcell.Style
|
||||
active bool
|
||||
visible bool
|
||||
tabbable bool
|
||||
|
||||
x, y int
|
||||
w, h int
|
||||
wantW, wantH int
|
||||
|
||||
value time.Time
|
||||
hasDate bool
|
||||
hasTime bool
|
||||
hasSeconds bool
|
||||
|
||||
showNowBtn bool
|
||||
nowBtnActive bool
|
||||
|
||||
cursor int
|
||||
keyMap KeyMap
|
||||
}
|
||||
|
||||
// TODO: Allow changing the format.
|
||||
// For now it's just yyyy-mm-dd hh:mm:ss
|
||||
|
||||
var _ Widget = (*TimeField)(nil)
|
||||
|
||||
func NewTimeField(id string, style tcell.Style) *TimeField {
|
||||
ret := &TimeField{style: style}
|
||||
ret.Init(id, style)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (w *TimeField) Init(id string, style tcell.Style) {
|
||||
w.id = id
|
||||
w.style = style
|
||||
w.hasDate = true
|
||||
w.hasTime = true
|
||||
w.showNowBtn = true
|
||||
w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
|
||||
tcell.KeyLeft: w.handleLeft,
|
||||
tcell.KeyRight: w.handleRight,
|
||||
tcell.KeyHome: w.handleHome,
|
||||
tcell.KeyEnd: w.handleEnd,
|
||||
})
|
||||
w.tabbable = true
|
||||
}
|
||||
|
||||
func (w *TimeField) Id() string { return w.id }
|
||||
func (w *TimeField) HandleResize(ev *tcell.EventResize) {}
|
||||
func (w *TimeField) HandleKey(ev *tcell.EventKey) bool {
|
||||
if !w.active {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (w *TimeField) HandleTime(ev *tcell.EventTime) {}
|
||||
func (w *TimeField) Draw(screen tcell.Screen) {
|
||||
if !w.visible {
|
||||
return
|
||||
}
|
||||
ds := w.style
|
||||
if !w.active {
|
||||
ds = ds.Dim(true)
|
||||
}
|
||||
x := w.x
|
||||
labelW := len(w.label)
|
||||
if labelW > 0 {
|
||||
h.DrawText(w.x, w.y, w.label+": ", ds, screen)
|
||||
x = x + labelW + 2
|
||||
}
|
||||
if w.hasDate {
|
||||
yr, mo, dy := w.value.Year(), w.value.Month(), w.value.Day()
|
||||
for idx, vl := range fmt.Sprintf("%4d%2d%2d", yr, mo, dy) {
|
||||
if idx == 4 || idx == 7 {
|
||||
h.DrawText(x, w.y, "-", ds, screen)
|
||||
x++
|
||||
}
|
||||
if idx == w.cursor && !w.nowBtnActive {
|
||||
if w.active {
|
||||
h.DrawText(x, w.y, string(vl), ds.Reverse(true).Blink(true), screen)
|
||||
} else {
|
||||
h.DrawText(x, w.y, string(vl), ds, screen)
|
||||
}
|
||||
} else {
|
||||
h.DrawText(x, w.y, string(vl), ds, screen)
|
||||
}
|
||||
x++
|
||||
}
|
||||
}
|
||||
if w.hasTime {
|
||||
hr, mn, sc := w.value.Hour(), w.value.Minute(), w.value.Second()
|
||||
txt := fmt.Sprintf("%2d%2d", hr, mn)
|
||||
if w.hasSeconds {
|
||||
txt = fmt.Sprintf("%s%2d", txt, sc)
|
||||
}
|
||||
for idx, vl := range txt {
|
||||
if idx == 2 || idx == 5 {
|
||||
h.DrawText(x, w.y, ":", ds, screen)
|
||||
x++
|
||||
}
|
||||
if idx+8 == w.cursor && !w.nowBtnActive {
|
||||
if w.active {
|
||||
h.DrawText(x, w.y, string(vl), ds.Reverse(true).Blink(true), screen)
|
||||
} else {
|
||||
h.DrawText(x, w.y, string(vl), ds, screen)
|
||||
}
|
||||
} else {
|
||||
h.DrawText(x, w.y, string(vl), ds, screen)
|
||||
}
|
||||
x++
|
||||
}
|
||||
}
|
||||
if w.showNowBtn {
|
||||
if w.nowBtnActive {
|
||||
h.DrawText(x, w.y, "[ Now ]", ds.Reverse(true), screen)
|
||||
} else {
|
||||
h.DrawText(x, w.y, "[ Now ]", ds, screen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *TimeField) Active() bool { return w.active }
|
||||
func (w *TimeField) SetActive(a bool) { w.active = a }
|
||||
func (w *TimeField) Visible() bool { return w.visible }
|
||||
func (w *TimeField) SetVisible(a bool) { w.visible = a }
|
||||
func (w *TimeField) SetX(x int) { w.x = x }
|
||||
func (w *TimeField) SetY(y int) { w.y = y }
|
||||
func (w *TimeField) GetX() int { return w.x }
|
||||
func (w *TimeField) GetY() int { return w.y }
|
||||
func (w *TimeField) GetPos() Coord { return Coord{X: w.x, Y: w.y} }
|
||||
func (w *TimeField) SetPos(c Coord) { w.x, w.y = c.X, c.Y }
|
||||
func (w *TimeField) SetW(x int) { w.w = x }
|
||||
func (w *TimeField) SetH(y int) { w.h = y }
|
||||
func (w *TimeField) GetW() int { return w.w }
|
||||
func (w *TimeField) GetH() int { return w.y }
|
||||
func (w *TimeField) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
|
||||
func (w *TimeField) Focusable() bool { return true }
|
||||
func (w *TimeField) SetTabbable(b bool) { w.tabbable = b }
|
||||
func (w *TimeField) Tabbable() bool { return w.tabbable }
|
||||
func (w *TimeField) WantW() int {
|
||||
wdt := 0
|
||||
if w.hasDate {
|
||||
wdt = 10 // yyyy-mm-dd
|
||||
}
|
||||
if w.hasTime {
|
||||
if w.hasDate {
|
||||
wdt += 1 // space between date & time
|
||||
}
|
||||
if w.hasSeconds {
|
||||
wdt += 8 // hh:mm:ss
|
||||
} else {
|
||||
wdt += 5 // hh:mm
|
||||
}
|
||||
}
|
||||
return wdt
|
||||
}
|
||||
func (w *TimeField) WantH() int { return 1 }
|
||||
func (w *TimeField) MinW() int {
|
||||
wdt := 0
|
||||
if w.hasDate {
|
||||
wdt = 10 // yyyy-mm-dd
|
||||
}
|
||||
if w.hasTime {
|
||||
if w.hasDate {
|
||||
wdt += 1 // space between date & time
|
||||
}
|
||||
if w.hasSeconds {
|
||||
wdt += 8 // hh:mm:ss
|
||||
} else {
|
||||
wdt += 5 // hh:mm
|
||||
}
|
||||
}
|
||||
return wdt
|
||||
}
|
||||
func (w *TimeField) MinH() int { return 1 }
|
||||
|
||||
func (w *TimeField) SetLabel(lbl string) { w.label = lbl }
|
||||
func (w *TimeField) SetTime(tm time.Time) { w.value = tm }
|
||||
|
||||
func (w *TimeField) SetHasSeconds(b bool) { w.hasSeconds = b }
|
||||
func (w *TimeField) SetHasTime(b bool) { w.hasTime = b }
|
||||
func (w *TimeField) SetHasDate(b bool) { w.hasDate = b }
|
||||
|
||||
func (w *TimeField) SetValue(v time.Time) { w.value = v }
|
||||
func (w *TimeField) Value() time.Time { return w.value }
|
||||
|
||||
func (w *TimeField) handleLeft(ev *tcell.EventKey) bool {
|
||||
if w.nowBtnActive {
|
||||
w.cursor = w.cursorLength()
|
||||
return true
|
||||
} else if w.cursor > 0 {
|
||||
w.cursor--
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *TimeField) handleRight(ev *tcell.EventKey) bool {
|
||||
if w.cursor < w.cursorLength() {
|
||||
w.cursor++
|
||||
return true
|
||||
} else if !w.nowBtnActive {
|
||||
w.nowBtnActive = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *TimeField) handleHome(ev *tcell.EventKey) bool {
|
||||
if w.cursor != 0 {
|
||||
w.cursor = 0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *TimeField) handleEnd(ev *tcell.EventKey) bool {
|
||||
l := w.cursorLength()
|
||||
if w.cursor != l {
|
||||
w.cursor = w.cursorLength()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *TimeField) cursorLength() int {
|
||||
var ret int
|
||||
if w.hasDate {
|
||||
ret += 8
|
||||
}
|
||||
if w.hasTime {
|
||||
ret += 4
|
||||
if w.hasSeconds {
|
||||
ret += 2
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -37,10 +37,13 @@ type Widget interface {
|
||||
Visible() bool
|
||||
SetVisible(bool)
|
||||
Focusable() bool
|
||||
Tabbable() bool
|
||||
SetTabbable(bool)
|
||||
SetX(int)
|
||||
SetY(int)
|
||||
GetX() int
|
||||
GetY() int
|
||||
GetPos() Coord
|
||||
SetPos(Coord)
|
||||
// Whatever is managing this widget (parent widget, screen, etc) should
|
||||
// tell it exactly what the Width & Height are.
|
||||
@@ -52,6 +55,9 @@ type Widget interface {
|
||||
// Given infinite space, WantW & WantH are what this widget wants
|
||||
WantW() int
|
||||
WantH() int
|
||||
// MinW & MinH are what this widget must have to be functional.
|
||||
MinW() int
|
||||
MinH() int
|
||||
SetSize(Coord)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user