2021-07-03 17:30:08 +00:00
|
|
|
package ui
|
|
|
|
|
|
|
|
import (
|
2021-07-07 01:40:12 +00:00
|
|
|
"strconv"
|
2021-07-03 17:30:08 +00:00
|
|
|
"strings"
|
|
|
|
|
2022-03-05 15:58:15 +00:00
|
|
|
"git.yetaga.in/alazyreader/library/media"
|
2023-04-08 02:42:54 +00:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2021-07-03 17:30:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Drawable interface {
|
|
|
|
Draw(tcell.Screen)
|
|
|
|
SetSize(x, y, h, w int)
|
2021-07-13 22:32:01 +00:00
|
|
|
SetStyle(tcell.Style)
|
2021-07-29 01:39:37 +00:00
|
|
|
SetVisible(bool)
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Offsets struct {
|
2021-07-04 17:49:53 +00:00
|
|
|
Top int
|
|
|
|
Bottom int
|
|
|
|
Left int
|
|
|
|
Right int
|
|
|
|
Percent int
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Contents []struct {
|
|
|
|
Offsets Offsets
|
|
|
|
Container Drawable
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
LayoutUnmanaged = iota
|
|
|
|
LayoutHorizontalEven
|
|
|
|
LayoutVerticalEven
|
2021-07-04 17:49:53 +00:00
|
|
|
LayoutHorizontalPercent
|
|
|
|
LayoutVerticalPercent
|
2021-07-03 17:30:08 +00:00
|
|
|
)
|
|
|
|
|
2021-07-13 22:32:01 +00:00
|
|
|
var (
|
2023-04-08 02:42:54 +00:00
|
|
|
StyleActive = tcell.Style{}.Foreground(tcell.ColorWhite).Background(tcell.ColorBlack)
|
|
|
|
StyleInactive = tcell.Style{}.Foreground(tcell.ColorGray).Background(tcell.ColorBlack)
|
2021-07-13 22:32:01 +00:00
|
|
|
)
|
|
|
|
|
2021-07-03 17:30:08 +00:00
|
|
|
// A Container has no visible UI of its own, but arranges sub-components on the screen.
|
|
|
|
// layoutMethod manages how subcomponents are organized. If `LayoutUnmanaged`, it just uses the offsets
|
|
|
|
// in contents to paint on the screen. Otherwise, `LayoutHorizontalEven` and `LayoutVerticalEven` will
|
|
|
|
// have it compute even distributions of space for all components either horizontally or vertically,
|
|
|
|
// filling the container.
|
|
|
|
type Container struct {
|
|
|
|
x, y int
|
|
|
|
h, w int
|
|
|
|
layoutMethod int
|
|
|
|
contents Contents
|
2021-07-29 01:39:37 +00:00
|
|
|
visible bool
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewContainer(contents Contents, layoutMethod int) *Container {
|
|
|
|
return &Container{
|
|
|
|
layoutMethod: layoutMethod,
|
|
|
|
contents: contents,
|
2021-07-29 01:39:37 +00:00
|
|
|
visible: true,
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Container) Draw(s tcell.Screen) {
|
2021-07-29 01:39:37 +00:00
|
|
|
if !c.visible {
|
|
|
|
return
|
|
|
|
}
|
2021-07-03 17:30:08 +00:00
|
|
|
for i := range c.contents {
|
|
|
|
c.contents[i].Container.Draw(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Container) SetSize(x, y, h, w int) {
|
|
|
|
c.x, c.y, c.h, c.w = x, y, h, w
|
2021-07-04 13:41:22 +00:00
|
|
|
carry := 0
|
2021-07-03 17:30:08 +00:00
|
|
|
if c.layoutMethod == LayoutVerticalEven {
|
|
|
|
num := len(c.contents)
|
2021-07-04 13:41:22 +00:00
|
|
|
extra := c.h % num
|
2021-07-03 17:30:08 +00:00
|
|
|
for r := range c.contents {
|
|
|
|
w := c.w
|
2021-07-04 01:25:32 +00:00
|
|
|
h := c.h / num
|
|
|
|
x := c.x
|
2021-07-04 13:41:22 +00:00
|
|
|
y := c.y + (h * r) + carry
|
|
|
|
if extra > 0 { // distribute "extra" space to containers as we have some left
|
|
|
|
h++
|
|
|
|
extra--
|
|
|
|
carry++
|
|
|
|
}
|
2021-07-03 17:30:08 +00:00
|
|
|
c.contents[r].Container.SetSize(x, y, h, w)
|
|
|
|
}
|
|
|
|
} else if c.layoutMethod == LayoutHorizontalEven {
|
|
|
|
num := len(c.contents)
|
2021-07-04 13:41:22 +00:00
|
|
|
extra := c.w % num
|
2021-07-03 17:30:08 +00:00
|
|
|
for r := range c.contents {
|
|
|
|
w := c.w / num
|
|
|
|
h := c.h
|
2021-07-04 13:41:22 +00:00
|
|
|
x := c.x + (w * r) + carry
|
2021-07-03 17:30:08 +00:00
|
|
|
y := c.y
|
2021-07-04 13:41:22 +00:00
|
|
|
if extra > 0 { // distribute "extra" space to containers as we have some left
|
|
|
|
w++
|
|
|
|
extra--
|
|
|
|
carry++
|
|
|
|
}
|
2021-07-03 17:30:08 +00:00
|
|
|
c.contents[r].Container.SetSize(x, y, h, w)
|
|
|
|
}
|
2021-07-04 17:49:53 +00:00
|
|
|
} else if c.layoutMethod == LayoutHorizontalPercent {
|
|
|
|
// first, work out overall distribution
|
|
|
|
total := 0
|
|
|
|
for r := range c.contents {
|
|
|
|
// `0` or negatives are set as minimum
|
|
|
|
if c.contents[r].Offsets.Percent < 1 {
|
|
|
|
total += 1
|
|
|
|
} else {
|
|
|
|
total += c.contents[r].Offsets.Percent
|
|
|
|
}
|
|
|
|
}
|
|
|
|
carry := 0
|
|
|
|
// push around containers
|
|
|
|
for r := range c.contents {
|
|
|
|
ratio := (float64(c.contents[r].Offsets.Percent) / float64(total))
|
|
|
|
w := int(float64(c.w) * ratio)
|
|
|
|
h := c.h
|
|
|
|
x := c.x + carry
|
|
|
|
y := c.y
|
|
|
|
carry += w
|
|
|
|
// and add any remaining space to the last container
|
|
|
|
if r == len(c.contents)-1 {
|
|
|
|
w += (c.w - carry)
|
|
|
|
}
|
|
|
|
c.contents[r].Container.SetSize(x, y, h, w)
|
|
|
|
}
|
|
|
|
} else if c.layoutMethod == LayoutVerticalPercent {
|
|
|
|
// first, work out overall distribution
|
|
|
|
total := 0
|
|
|
|
for r := range c.contents {
|
|
|
|
// `0` or negatives are set as minimum
|
|
|
|
if c.contents[r].Offsets.Percent < 1 {
|
|
|
|
total += 1
|
|
|
|
} else {
|
|
|
|
total += c.contents[r].Offsets.Percent
|
|
|
|
}
|
|
|
|
}
|
|
|
|
carry := 0
|
|
|
|
// push around containers
|
|
|
|
for r := range c.contents {
|
|
|
|
ratio := (float64(c.contents[r].Offsets.Percent) / float64(total))
|
|
|
|
w := c.w
|
|
|
|
h := int(float64(c.h) * ratio)
|
|
|
|
x := c.x
|
|
|
|
y := c.y + carry
|
|
|
|
carry += h
|
|
|
|
// and add any remaining space to the last container
|
|
|
|
if r == len(c.contents)-1 {
|
|
|
|
h += (c.h - carry)
|
|
|
|
}
|
|
|
|
c.contents[r].Container.SetSize(x, y, h, w)
|
|
|
|
}
|
2021-07-03 17:30:08 +00:00
|
|
|
} else {
|
|
|
|
for r := range c.contents {
|
|
|
|
x := c.x + c.contents[r].Offsets.Left
|
|
|
|
y := c.y + c.contents[r].Offsets.Top
|
|
|
|
h := c.h - c.contents[r].Offsets.Bottom
|
|
|
|
w := c.w - c.contents[r].Offsets.Right
|
|
|
|
c.contents[r].Container.SetSize(x, y, h, w)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-13 22:32:01 +00:00
|
|
|
func (c *Container) SetStyle(s tcell.Style) {
|
|
|
|
// containers have no visible elements to style
|
|
|
|
}
|
|
|
|
|
2021-07-29 01:39:37 +00:00
|
|
|
func (c *Container) SetVisible(b bool) {
|
|
|
|
c.visible = b
|
|
|
|
}
|
|
|
|
|
2021-07-03 17:30:08 +00:00
|
|
|
func (c *Container) Contents() Contents {
|
|
|
|
return c.contents
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Container) SetContents(con Contents) {
|
|
|
|
c.contents = con
|
|
|
|
}
|
|
|
|
|
|
|
|
// A Box draws a ASCII box around its contents, with an optional title and footer.
|
|
|
|
type Box struct {
|
2021-08-01 15:05:51 +00:00
|
|
|
x, y int
|
|
|
|
h, w int
|
|
|
|
title Drawable
|
|
|
|
menuItems Drawable
|
|
|
|
contents Contents
|
|
|
|
style tcell.Style
|
|
|
|
cascade bool
|
|
|
|
visible bool
|
|
|
|
transparent bool
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 22:32:01 +00:00
|
|
|
func NewBox(title string, menuItems []string, contents Contents, initialStyle tcell.Style, cascade bool) *Box {
|
2021-07-03 17:30:08 +00:00
|
|
|
return &Box{
|
2021-08-01 15:05:51 +00:00
|
|
|
title: NewPaddedText(title),
|
|
|
|
menuItems: NewPaddedText(strings.Join(menuItems, " ")),
|
|
|
|
contents: contents,
|
|
|
|
style: initialStyle,
|
|
|
|
cascade: cascade,
|
|
|
|
visible: true,
|
|
|
|
transparent: false,
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Box) SetSize(x, y, h, w int) {
|
|
|
|
b.x, b.y, b.h, b.w = x, y, h, w
|
|
|
|
b.title.SetSize(b.x+2, b.y, 0, 0)
|
|
|
|
b.menuItems.SetSize(b.x+2, b.y+b.h-1, 0, 0)
|
|
|
|
for c := range b.contents {
|
|
|
|
x := b.x + b.contents[c].Offsets.Left
|
|
|
|
y := b.y + b.contents[c].Offsets.Top
|
|
|
|
h := b.h - b.contents[c].Offsets.Bottom
|
|
|
|
w := b.w - b.contents[c].Offsets.Right
|
|
|
|
b.contents[c].Container.SetSize(x, y, h, w)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Box) Draw(s tcell.Screen) {
|
2021-07-29 01:39:37 +00:00
|
|
|
if !b.visible {
|
|
|
|
return
|
|
|
|
}
|
2021-08-01 15:05:51 +00:00
|
|
|
// blank out inner area
|
|
|
|
if !b.transparent {
|
|
|
|
for m := b.x + 1; m < b.x+b.w-1; m++ {
|
|
|
|
for n := b.y + 1; n < b.y+b.h-1; n++ {
|
|
|
|
s.SetContent(m, n, ' ', nil, b.style)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// draw outside bars
|
2021-07-04 01:25:32 +00:00
|
|
|
for m := b.x + 1; m < b.x+b.w-1; m++ {
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(m, b.y, tcell.RuneHLine, nil, b.style)
|
|
|
|
s.SetContent(m, b.y+b.h-1, tcell.RuneHLine, nil, b.style)
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
2021-07-04 01:25:32 +00:00
|
|
|
for m := b.y + 1; m < b.y+b.h-1; m++ {
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(b.x, m, tcell.RuneVLine, nil, b.style)
|
|
|
|
s.SetContent(b.x+b.w-1, m, tcell.RuneVLine, nil, b.style)
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(b.x, b.y, tcell.RuneULCorner, nil, b.style)
|
|
|
|
s.SetContent(b.x+b.w-1, b.y, tcell.RuneURCorner, nil, b.style)
|
|
|
|
s.SetContent(b.x, b.y+b.h-1, tcell.RuneLLCorner, nil, b.style)
|
|
|
|
s.SetContent(b.x+b.w-1, b.y+b.h-1, tcell.RuneLRCorner, nil, b.style)
|
2021-07-03 17:30:08 +00:00
|
|
|
|
|
|
|
if b.title != nil {
|
|
|
|
b.title.Draw(s)
|
|
|
|
}
|
|
|
|
if b.menuItems != nil {
|
|
|
|
b.menuItems.Draw(s)
|
|
|
|
}
|
|
|
|
for c := range b.contents {
|
|
|
|
b.contents[c].Container.Draw(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-13 22:32:01 +00:00
|
|
|
func (b *Box) SetStyle(s tcell.Style) {
|
|
|
|
b.style = s
|
|
|
|
b.title.SetStyle(s)
|
|
|
|
b.menuItems.SetStyle(s)
|
|
|
|
if b.cascade {
|
|
|
|
for c := range b.contents {
|
|
|
|
b.contents[c].Container.SetStyle(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-29 01:39:37 +00:00
|
|
|
func (b *Box) SetVisible(v bool) {
|
|
|
|
b.visible = v
|
|
|
|
}
|
|
|
|
|
2021-08-01 15:05:51 +00:00
|
|
|
func (b *Box) SetTransparent(v bool) {
|
|
|
|
b.transparent = v
|
|
|
|
}
|
|
|
|
|
2021-07-03 17:30:08 +00:00
|
|
|
func (b *Box) Contents() Contents {
|
|
|
|
return b.contents
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Box) SetContents(c Contents) {
|
|
|
|
b.contents = c
|
|
|
|
}
|
|
|
|
|
|
|
|
// A List is a scrollable, pageable list with a selector token.
|
|
|
|
type List struct {
|
|
|
|
x, y int
|
|
|
|
h, w int
|
|
|
|
selected int
|
2021-07-07 01:40:12 +00:00
|
|
|
listItems []ListKeyValue
|
2021-07-13 22:32:01 +00:00
|
|
|
style tcell.Style
|
2021-07-29 01:39:37 +00:00
|
|
|
visible bool
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
type ListKeyValue struct {
|
|
|
|
Key int
|
|
|
|
Value string
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewList(listItems []ListKeyValue, initialSelected int) *List {
|
2021-07-03 17:30:08 +00:00
|
|
|
return &List{
|
|
|
|
listItems: listItems,
|
|
|
|
selected: initialSelected,
|
2021-07-29 01:39:37 +00:00
|
|
|
visible: true,
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *List) SetSize(x, y, h, w int) {
|
|
|
|
l.x, l.y, l.h, l.w = x, y, h, w
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *List) Draw(s tcell.Screen) {
|
2021-07-29 01:39:37 +00:00
|
|
|
if !l.visible {
|
|
|
|
return
|
|
|
|
}
|
2021-07-03 17:30:08 +00:00
|
|
|
for i := range l.listItems {
|
2021-07-07 01:40:12 +00:00
|
|
|
for j, r := range l.listItems[i].Value {
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(l.x+j, l.y+i, r, nil, l.style)
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
if i == l.selected {
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(l.x+len(l.listItems[i].Value)+1, l.y+i, '<', nil, l.style)
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-29 01:39:37 +00:00
|
|
|
func (l *List) SetVisible(b bool) {
|
|
|
|
l.visible = b
|
|
|
|
}
|
|
|
|
|
2021-07-13 22:32:01 +00:00
|
|
|
func (l *List) SetStyle(s tcell.Style) {
|
|
|
|
l.style = s
|
|
|
|
}
|
|
|
|
|
2021-07-03 17:30:08 +00:00
|
|
|
func (l *List) Selected() int {
|
|
|
|
return l.selected
|
|
|
|
}
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
func (l *List) SelectedID() int {
|
2021-07-29 01:06:49 +00:00
|
|
|
if l.listItems == nil || len(l.listItems) == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
2021-07-07 01:40:12 +00:00
|
|
|
return l.listItems[l.selected].Key
|
|
|
|
}
|
|
|
|
|
2021-07-03 17:30:08 +00:00
|
|
|
func (l *List) SetSelected(i int) {
|
|
|
|
l.selected = i
|
|
|
|
}
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
func (l *List) ListMembers() []ListKeyValue {
|
2021-07-03 17:30:08 +00:00
|
|
|
return l.listItems
|
|
|
|
}
|
|
|
|
|
2021-08-01 22:50:53 +00:00
|
|
|
func (l *List) SetMembers(lkv []ListKeyValue) {
|
|
|
|
l.listItems = lkv
|
|
|
|
}
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
// BookDetails displays an editable list of book details
|
2021-07-04 18:49:36 +00:00
|
|
|
type BookDetails struct {
|
2021-07-29 01:39:37 +00:00
|
|
|
x, y int
|
|
|
|
h, w int
|
2022-03-05 15:58:15 +00:00
|
|
|
book *media.Book
|
2021-07-29 01:39:37 +00:00
|
|
|
style tcell.Style
|
|
|
|
visible bool
|
2021-07-04 18:49:36 +00:00
|
|
|
}
|
|
|
|
|
2022-03-05 15:58:15 +00:00
|
|
|
func NewBookDetails(b *media.Book) *BookDetails {
|
2021-07-07 01:40:12 +00:00
|
|
|
return &BookDetails{
|
2021-07-29 01:39:37 +00:00
|
|
|
book: b,
|
|
|
|
visible: true,
|
2021-07-07 01:40:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-05 15:58:15 +00:00
|
|
|
func (l *BookDetails) SetBook(b *media.Book) {
|
2021-07-07 01:40:12 +00:00
|
|
|
l.book = b
|
2021-07-04 18:49:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *BookDetails) SetSize(x, y, h, w int) {
|
|
|
|
l.x, l.y, l.h, l.w = x, y, h, w
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *BookDetails) Draw(s tcell.Screen) {
|
2021-07-07 01:40:12 +00:00
|
|
|
if l.book == nil {
|
|
|
|
return
|
|
|
|
}
|
2021-07-29 01:39:37 +00:00
|
|
|
if !l.visible {
|
|
|
|
return
|
|
|
|
}
|
2021-07-07 01:40:12 +00:00
|
|
|
items := []struct {
|
|
|
|
label string
|
|
|
|
value string
|
|
|
|
}{
|
|
|
|
{"Title", l.book.Title},
|
|
|
|
{"Authors", strings.Join(l.book.Authors, ", ")},
|
|
|
|
{"Sort Author", l.book.SortAuthor},
|
|
|
|
{"ISBN-10", l.book.ISBN10},
|
|
|
|
{"ISBN-13", l.book.ISBN13},
|
|
|
|
{"Format", l.book.Format},
|
|
|
|
{"Genre", l.book.Genre},
|
|
|
|
{"Publisher", l.book.Publisher},
|
|
|
|
{"Series", l.book.Series},
|
|
|
|
{"Volume", l.book.Volume},
|
|
|
|
{"Year", l.book.Year},
|
|
|
|
{"Signed", strconv.FormatBool(l.book.Signed)},
|
|
|
|
{"Cover URL", l.book.CoverURL},
|
|
|
|
{"Notes", l.book.Notes},
|
|
|
|
{"Description", l.book.Description},
|
|
|
|
}
|
2021-07-04 18:49:36 +00:00
|
|
|
for i := range items {
|
2021-07-07 01:40:12 +00:00
|
|
|
if i < l.h-2 {
|
|
|
|
kv := NewKeyValue(items[i].label, ": ", items[i].value)
|
|
|
|
kv.SetSize(l.x, l.y+i, 0, 0)
|
2021-07-13 22:32:01 +00:00
|
|
|
kv.SetStyle(l.style)
|
2021-07-07 01:40:12 +00:00
|
|
|
kv.Draw(s)
|
2021-07-04 18:49:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-29 01:39:37 +00:00
|
|
|
func (l *BookDetails) SetVisible(b bool) {
|
|
|
|
l.visible = b
|
|
|
|
}
|
|
|
|
|
2021-07-13 22:32:01 +00:00
|
|
|
func (l *BookDetails) SetStyle(s tcell.Style) {
|
|
|
|
l.style = s
|
|
|
|
}
|
|
|
|
|
2021-07-03 17:30:08 +00:00
|
|
|
// PaddedText outputs strings with a space on both sides.
|
|
|
|
// Useful for generating headings, footers, etc. Used by Box.
|
|
|
|
type PaddedText struct {
|
2021-07-29 01:39:37 +00:00
|
|
|
x, y int
|
|
|
|
h, w int
|
|
|
|
text string
|
|
|
|
style tcell.Style
|
|
|
|
visible bool
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewPaddedText(text string) *PaddedText {
|
2021-07-29 01:39:37 +00:00
|
|
|
return &PaddedText{text: text, visible: true}
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PaddedText) SetSize(x, y, _, _ int) {
|
|
|
|
p.x, p.y, p.h, p.w = x, y, 1, len(p.text)+2
|
|
|
|
}
|
|
|
|
|
2021-07-13 22:32:01 +00:00
|
|
|
func (p *PaddedText) SetStyle(s tcell.Style) {
|
|
|
|
p.style = s
|
|
|
|
}
|
|
|
|
|
2021-07-03 17:30:08 +00:00
|
|
|
func (p *PaddedText) Draw(s tcell.Screen) {
|
2021-07-04 01:25:32 +00:00
|
|
|
if p.text == "" {
|
|
|
|
return
|
|
|
|
}
|
2021-07-29 01:39:37 +00:00
|
|
|
if !p.visible {
|
|
|
|
return
|
|
|
|
}
|
2021-07-03 17:30:08 +00:00
|
|
|
t := p.x
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(t, p.y, ' ', nil, p.style)
|
2021-07-03 17:30:08 +00:00
|
|
|
t++
|
|
|
|
for _, r := range p.text {
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(t, p.y, r, nil, p.style)
|
2021-07-03 17:30:08 +00:00
|
|
|
t++
|
|
|
|
}
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(t, p.y, ' ', nil, p.style)
|
2021-07-03 17:30:08 +00:00
|
|
|
}
|
2021-07-07 01:40:12 +00:00
|
|
|
|
2021-07-29 01:39:37 +00:00
|
|
|
func (p *PaddedText) SetVisible(b bool) {
|
|
|
|
p.visible = b
|
|
|
|
}
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
type KeyValue struct {
|
|
|
|
x, y int
|
|
|
|
h, w int
|
|
|
|
key string
|
|
|
|
value string
|
|
|
|
separator string
|
2021-07-13 22:32:01 +00:00
|
|
|
style tcell.Style
|
2021-07-29 01:39:37 +00:00
|
|
|
visible bool
|
2021-07-07 01:40:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewKeyValue(key, separator, value string) *KeyValue {
|
|
|
|
return &KeyValue{
|
|
|
|
key: key,
|
|
|
|
separator: separator,
|
|
|
|
value: value,
|
2021-07-29 01:39:37 +00:00
|
|
|
visible: true,
|
2021-07-07 01:40:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *KeyValue) SetSize(x, y, _, _ int) {
|
|
|
|
p.x, p.y, p.h, p.w = x, y, 1, len(p.key)+len(p.separator)+len(p.value)
|
|
|
|
}
|
|
|
|
|
2021-07-13 22:32:01 +00:00
|
|
|
func (p *KeyValue) SetStyle(s tcell.Style) {
|
|
|
|
p.style = s
|
|
|
|
}
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
func (p *KeyValue) Draw(s tcell.Screen) {
|
2021-07-29 01:39:37 +00:00
|
|
|
if !p.visible {
|
|
|
|
return
|
|
|
|
}
|
2021-07-07 01:40:12 +00:00
|
|
|
for j, r := range p.key {
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(p.x+j, p.y, r, nil, p.style)
|
2021-07-07 01:40:12 +00:00
|
|
|
}
|
|
|
|
for j, r := range p.separator {
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(p.x+len(p.key)+j, p.y, r, nil, p.style)
|
2021-07-07 01:40:12 +00:00
|
|
|
}
|
|
|
|
for j, r := range p.value {
|
2021-07-13 22:32:01 +00:00
|
|
|
s.SetContent(p.x+len(p.key)+len(p.separator)+j, p.y, r, nil, p.style)
|
2021-07-07 01:40:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-29 01:39:37 +00:00
|
|
|
func (p *KeyValue) SetVisible(b bool) {
|
|
|
|
p.visible = b
|
|
|
|
}
|
|
|
|
|
2021-07-07 01:40:12 +00:00
|
|
|
func (p *KeyValue) GetValue() string {
|
|
|
|
return p.value
|
|
|
|
}
|
2021-08-01 15:41:02 +00:00
|
|
|
|
|
|
|
type EditableTextLine struct {
|
2021-08-01 19:49:35 +00:00
|
|
|
x, y int
|
|
|
|
h, w int
|
|
|
|
text string
|
|
|
|
style tcell.Style
|
|
|
|
visible bool
|
|
|
|
cursorPos int
|
|
|
|
showCursor bool
|
2021-08-01 15:41:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewEditableTextLine(initialText string) *EditableTextLine {
|
|
|
|
return &EditableTextLine{
|
2021-08-01 19:49:35 +00:00
|
|
|
text: initialText,
|
|
|
|
visible: true,
|
|
|
|
showCursor: true,
|
2021-08-01 15:41:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) SetSize(x, y, _, _ int) {
|
|
|
|
p.x, p.y, p.h, p.w = x, y, 1, len(p.text)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) SetStyle(s tcell.Style) {
|
|
|
|
p.style = s
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) Draw(s tcell.Screen) {
|
|
|
|
if !p.visible {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for j, r := range p.text {
|
|
|
|
s.SetContent(p.x+j, p.y, r, nil, p.style)
|
|
|
|
}
|
2021-08-01 19:49:35 +00:00
|
|
|
s.ShowCursor(p.x+p.cursorPos, p.y)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) SetVisible(b bool) {
|
|
|
|
p.visible = b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) SetCursorVisible(b bool) {
|
|
|
|
p.showCursor = b
|
2021-08-01 15:41:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) SetText(t string) {
|
|
|
|
p.text = t
|
|
|
|
if len(p.text) == 0 {
|
|
|
|
p.ResetCursor(true)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.ResetCursor(false)
|
|
|
|
}
|
|
|
|
|
2021-08-01 19:49:35 +00:00
|
|
|
func (p *EditableTextLine) Text() string {
|
|
|
|
return p.text
|
|
|
|
}
|
|
|
|
|
2021-08-01 15:41:02 +00:00
|
|
|
func (p *EditableTextLine) ResetCursor(beginning bool) {
|
|
|
|
if beginning {
|
|
|
|
p.cursorPos = 0
|
|
|
|
} else {
|
|
|
|
p.cursorPos = len(p.text)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) InsertAtCursor(r rune) {
|
|
|
|
if len(p.text) == 0 {
|
|
|
|
p.text = string(r)
|
|
|
|
p.cursorPos = 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.text = p.text[0:p.cursorPos] + string(r) + p.text[p.cursorPos:len(p.text)]
|
|
|
|
p.cursorPos = p.cursorPos + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) MoveCursor(i int) {
|
|
|
|
if p.cursorPos+i < 0 {
|
|
|
|
p.cursorPos = 0
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if p.cursorPos+i > len(p.text) {
|
|
|
|
p.cursorPos = len(p.text)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.cursorPos = p.cursorPos + i
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *EditableTextLine) DeleteAtCursor() {
|
|
|
|
if len(p.text) == 0 {
|
|
|
|
p.cursorPos = 0
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.text = p.text[0:p.cursorPos-1] + p.text[p.cursorPos:len(p.text)]
|
|
|
|
p.cursorPos = p.cursorPos - 1
|
|
|
|
}
|