mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-25 13:48:50 +08:00
Working proof-of-concept of the textinput field.
This commit is contained in:
parent
bda6223690
commit
c0c9727c80
@ -350,8 +350,21 @@ func (fe *fieldEditor) viewFor(width int) (string, int, error) {
|
||||
return fe.data.runesIn(fe.visible), cur, nil
|
||||
}
|
||||
|
||||
// content returns the string content in the field editor.
|
||||
func (fe *fieldEditor) content() string {
|
||||
return string(fe.data)
|
||||
}
|
||||
|
||||
// reset resets the content back to zero.
|
||||
func (fe *fieldEditor) reset() {
|
||||
*fe = *newFieldEditor()
|
||||
}
|
||||
|
||||
// insert inserts the rune at the current position of the cursor.
|
||||
func (fe *fieldEditor) insert(r rune) {
|
||||
if runewidth.RuneWidth(r) == 0 {
|
||||
return
|
||||
}
|
||||
fe.data.insertAt(fe.curDataPos, r)
|
||||
fe.curDataPos++
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ package textinput
|
||||
// options.go contains configurable options for TextInput.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
@ -45,7 +47,8 @@ type options struct {
|
||||
border linestyle.LineStyle
|
||||
borderColor cell.Color
|
||||
|
||||
textWidthPerc *int
|
||||
widthPerc *int
|
||||
maxWidthCells *int
|
||||
label string
|
||||
labelCellOpts []cell.Option
|
||||
labelAlign align.Horizontal
|
||||
@ -59,6 +62,12 @@ type options struct {
|
||||
|
||||
// validate validates the provided options.
|
||||
func (o *options) validate() error {
|
||||
if min, max, perc := 0, 100, o.widthPerc; perc != nil && (*perc <= min || *perc > max) {
|
||||
return fmt.Errorf("invalid WidthPerc(%d), must be value in range %d < value <= %d", *perc, min, max)
|
||||
}
|
||||
if min, cells := 4, o.maxWidthCells; cells != nil && *cells < min {
|
||||
return fmt.Errorf("invalid MaxWidthCells(%d), must be value in range %d <= value", *cells, min)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -104,7 +113,7 @@ func HighlightedColor(c cell.Color) Option {
|
||||
|
||||
// DefaultCursorColorNumber is the default color number for the CursorColor
|
||||
// option.
|
||||
const DefaultCursorColorNumber = 235
|
||||
const DefaultCursorColorNumber = 250
|
||||
|
||||
// CursorColor sets the color of the cursor.
|
||||
// Defaults to DefaultCursorColorNumber.
|
||||
@ -129,12 +138,23 @@ func BorderColor(c cell.Color) Option {
|
||||
})
|
||||
}
|
||||
|
||||
// TextWidthPerc sets the width for the text input field as a percentage of the
|
||||
// container width. Must be a value in the range 0 < perc < 100.
|
||||
// WidthPerc sets the width for the text input field as a percentage of the
|
||||
// container width. Must be a value in the range 0 < perc <= 100.
|
||||
// Defaults to the width adjusted automatically base on the label length.
|
||||
func TextWidthPerc(perc int) Option {
|
||||
func WidthPerc(perc int) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.textWidthPerc = &perc
|
||||
opts.widthPerc = &perc
|
||||
})
|
||||
}
|
||||
|
||||
// MaxWidthCells sets the maximum width of the text input field as an absolute value
|
||||
// in cells. Must be a value in the range 4 <= cells.
|
||||
// This doesn't limit the text that the user can input, if the text overflows
|
||||
// the width of the input field, it scrolls to the left.
|
||||
// Defaults to using all available width in the container.
|
||||
func MaxWidthCells(cells int) Option {
|
||||
return option(func(opts *options) {
|
||||
opts.maxWidthCells = &cells
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,17 @@ package textinput
|
||||
import (
|
||||
"image"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/internal/alignfor"
|
||||
"github.com/mum4k/termdash/internal/area"
|
||||
"github.com/mum4k/termdash/internal/canvas"
|
||||
"github.com/mum4k/termdash/internal/draw"
|
||||
"github.com/mum4k/termdash/internal/runewidth"
|
||||
"github.com/mum4k/termdash/internal/wrap"
|
||||
"github.com/mum4k/termdash/keyboard"
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgetapi"
|
||||
@ -52,6 +59,9 @@ type TextInput struct {
|
||||
// mu protects the widget.
|
||||
mu sync.Mutex
|
||||
|
||||
// editor tracks the edits and the state of the text input field.
|
||||
editor *fieldEditor
|
||||
|
||||
// opts are the provided options.
|
||||
opts *options
|
||||
}
|
||||
@ -66,7 +76,8 @@ func New(opts ...Option) (*TextInput, error) {
|
||||
return nil, err
|
||||
}
|
||||
return &TextInput{
|
||||
opts: opt,
|
||||
editor: newFieldEditor(),
|
||||
opts: opt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -81,13 +92,97 @@ var (
|
||||
cursorRune rune = 0
|
||||
)
|
||||
|
||||
// Read reads the content of the text input field.
|
||||
func (ti *TextInput) Read() string {
|
||||
ti.mu.Lock()
|
||||
defer ti.mu.Unlock()
|
||||
|
||||
return ti.editor.content()
|
||||
}
|
||||
|
||||
// ReadAndClear reads the content of the text input field and clears it.
|
||||
func (ti *TextInput) ReadAndClear() string {
|
||||
ti.mu.Lock()
|
||||
defer ti.mu.Unlock()
|
||||
|
||||
c := ti.editor.content()
|
||||
ti.editor.reset()
|
||||
return c
|
||||
}
|
||||
|
||||
// Draw draws the TextInput widget onto the canvas.
|
||||
// Implements widgetapi.Widget.Draw.
|
||||
func (ti *TextInput) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error {
|
||||
ti.mu.Lock()
|
||||
defer ti.mu.Unlock()
|
||||
|
||||
// Ensure 4 available for text field.
|
||||
labelAr, textAr, err := split(cvs.Area(), ti.opts.label, ti.opts.widthPerc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var forField image.Rectangle
|
||||
if ti.opts.border != linestyle.None {
|
||||
forField = area.ExcludeBorder(textAr)
|
||||
} else {
|
||||
forField = textAr
|
||||
}
|
||||
|
||||
if forField.Dx() < minFieldWidth {
|
||||
return draw.ResizeNeeded(cvs)
|
||||
}
|
||||
|
||||
if !labelAr.Eq(image.ZR) {
|
||||
start, err := alignfor.Text(labelAr, ti.opts.label, ti.opts.labelAlign, align.VerticalMiddle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := draw.Text(
|
||||
cvs, ti.opts.label, start,
|
||||
draw.TextOverrunMode(draw.OverrunModeThreeDot),
|
||||
draw.TextMaxX(labelAr.Max.X),
|
||||
draw.TextCellOpts(ti.opts.labelCellOpts...),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if ti.opts.border != linestyle.None {
|
||||
if err := draw.Border(cvs, textAr, draw.BorderCellOpts(cell.FgColor(ti.opts.borderColor))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := cvs.SetAreaCellOpts(forField, cell.BgColor(ti.opts.fillColor)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
text, curPos, err := ti.editor.viewFor(forField.Dx())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := draw.Text(
|
||||
cvs, text, forField.Min,
|
||||
draw.TextMaxX(forField.Max.X),
|
||||
draw.TextCellOpts(cell.FgColor(ti.opts.textColor)),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if meta.Focused {
|
||||
p := image.Point{
|
||||
curPos + forField.Min.X,
|
||||
forField.Min.Y,
|
||||
}
|
||||
if err := cvs.SetCellOpts(
|
||||
p,
|
||||
cell.FgColor(ti.opts.highlightedColor),
|
||||
cell.BgColor(ti.opts.cursorColor),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -97,6 +192,36 @@ func (ti *TextInput) Keyboard(k *terminalapi.Keyboard) error {
|
||||
ti.mu.Lock()
|
||||
defer ti.mu.Unlock()
|
||||
|
||||
switch k.Key {
|
||||
case keyboard.KeyBackspace, keyboard.KeyBackspace2:
|
||||
ti.editor.deleteBefore()
|
||||
|
||||
case keyboard.KeyDelete:
|
||||
ti.editor.delete()
|
||||
|
||||
case keyboard.KeyArrowLeft:
|
||||
ti.editor.cursorLeft()
|
||||
|
||||
case keyboard.KeyArrowRight:
|
||||
ti.editor.cursorRight()
|
||||
|
||||
case keyboard.KeyHome, keyboard.KeyCtrlA:
|
||||
ti.editor.cursorStart()
|
||||
|
||||
case keyboard.KeyEnd, keyboard.KeyCtrlE:
|
||||
ti.editor.cursorEnd()
|
||||
|
||||
default:
|
||||
if err := wrap.ValidText(string(k.Key)); err != nil {
|
||||
// Ignore unsupported runes.
|
||||
return nil
|
||||
}
|
||||
if !unicode.IsPrint(rune(k.Key)) {
|
||||
return nil
|
||||
}
|
||||
ti.editor.insert(rune(k.Key))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -124,13 +249,20 @@ func (ti *TextInput) Options() widgetapi.Options {
|
||||
needWidth += 2
|
||||
needHeight += 2
|
||||
}
|
||||
|
||||
maxWidth := 0
|
||||
if ti.opts.maxWidthCells != nil {
|
||||
additional := *ti.opts.maxWidthCells - minFieldWidth
|
||||
maxWidth = needWidth + additional
|
||||
}
|
||||
|
||||
return widgetapi.Options{
|
||||
MinimumSize: image.Point{
|
||||
needWidth,
|
||||
needHeight,
|
||||
},
|
||||
MaximumSize: image.Point{
|
||||
0, // Any width.
|
||||
maxWidth,
|
||||
needHeight,
|
||||
},
|
||||
WantKeyboard: widgetapi.KeyScopeFocused,
|
||||
@ -141,10 +273,10 @@ func (ti *TextInput) Options() widgetapi.Options {
|
||||
// split splits the available area into label and text input areas according to
|
||||
// configuration. The returned labelAr might be image.ZR if no label was
|
||||
// configured.
|
||||
func split(cvsAr image.Rectangle, label string, textWidthPerc *int) (labelAr, textAr image.Rectangle, err error) {
|
||||
func split(cvsAr image.Rectangle, label string, widthPerc *int) (labelAr, textAr image.Rectangle, err error) {
|
||||
switch {
|
||||
case textWidthPerc != nil:
|
||||
splitP := 100 - *textWidthPerc
|
||||
case widthPerc != nil:
|
||||
splitP := 100 - *widthPerc
|
||||
labelAr, textAr, err := area.VSplit(cvsAr, splitP)
|
||||
if err != nil {
|
||||
return image.ZR, image.ZR, err
|
||||
|
@ -60,7 +60,6 @@ func (ct *callbackTracker) submit(text string) error {
|
||||
func TestTextInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
text string
|
||||
callback *callbackTracker
|
||||
opts []Option
|
||||
events []terminalapi.Event
|
||||
@ -71,9 +70,32 @@ func TestTextInput(t *testing.T) {
|
||||
wantNewErr bool
|
||||
wantDrawErr bool
|
||||
wantEventErr bool
|
||||
}{}
|
||||
}{
|
||||
{
|
||||
desc: "fails on WidthPerc too low",
|
||||
opts: []Option{
|
||||
WidthPerc(0),
|
||||
},
|
||||
wantNewErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails on WidthPerc too high",
|
||||
opts: []Option{
|
||||
WidthPerc(101),
|
||||
},
|
||||
wantNewErr: true,
|
||||
},
|
||||
{
|
||||
desc: "fails on MaxWidthCells too low",
|
||||
opts: []Option{
|
||||
MaxWidthCells(3),
|
||||
},
|
||||
wantNewErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
textFieldRune = 'x'
|
||||
textFieldRune = '_'
|
||||
cursorRune = '█'
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
gotCallback := tc.callback
|
||||
@ -171,6 +193,18 @@ func TestOptions(t *testing.T) {
|
||||
WantMouse: widgetapi.MouseScopeWidget,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no label and no border, max width specified",
|
||||
opts: []Option{
|
||||
MaxWidthCells(5),
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{4, 1},
|
||||
MaximumSize: image.Point{5, 1},
|
||||
WantKeyboard: widgetapi.KeyScopeFocused,
|
||||
WantMouse: widgetapi.MouseScopeWidget,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no label, has border",
|
||||
opts: []Option{
|
||||
@ -183,6 +217,19 @@ func TestOptions(t *testing.T) {
|
||||
WantMouse: widgetapi.MouseScopeWidget,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no label, has border, max width specified",
|
||||
opts: []Option{
|
||||
Border(linestyle.Light),
|
||||
MaxWidthCells(5),
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{6, 3},
|
||||
MaximumSize: image.Point{7, 3},
|
||||
WantKeyboard: widgetapi.KeyScopeFocused,
|
||||
WantMouse: widgetapi.MouseScopeWidget,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "has label and no border",
|
||||
opts: []Option{
|
||||
@ -195,6 +242,19 @@ func TestOptions(t *testing.T) {
|
||||
WantMouse: widgetapi.MouseScopeWidget,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "has label and no border, max width specified",
|
||||
opts: []Option{
|
||||
Label("hello"),
|
||||
MaxWidthCells(5),
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{9, 1},
|
||||
MaximumSize: image.Point{10, 1},
|
||||
WantKeyboard: widgetapi.KeyScopeFocused,
|
||||
WantMouse: widgetapi.MouseScopeWidget,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "has label with full-width runes and no border",
|
||||
opts: []Option{
|
||||
@ -220,6 +280,20 @@ func TestOptions(t *testing.T) {
|
||||
WantMouse: widgetapi.MouseScopeWidget,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "has label and border, max width specified",
|
||||
opts: []Option{
|
||||
Label("hello"),
|
||||
Border(linestyle.Light),
|
||||
MaxWidthCells(5),
|
||||
},
|
||||
want: widgetapi.Options{
|
||||
MinimumSize: image.Point{11, 3},
|
||||
MaximumSize: image.Point{12, 3},
|
||||
WantKeyboard: widgetapi.KeyScopeFocused,
|
||||
WantMouse: widgetapi.MouseScopeWidget,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@ -239,33 +313,33 @@ func TestOptions(t *testing.T) {
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
cvsAr image.Rectangle
|
||||
label string
|
||||
textWidthPerc *int
|
||||
wantLabelAr image.Rectangle
|
||||
wantTextAr image.Rectangle
|
||||
wantErr bool
|
||||
desc string
|
||||
cvsAr image.Rectangle
|
||||
label string
|
||||
widthPerc *int
|
||||
wantLabelAr image.Rectangle
|
||||
wantTextAr image.Rectangle
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "fails on invalid textWidthPerc",
|
||||
desc: "fails on invalid widthPerc",
|
||||
cvsAr: image.Rect(0, 0, 10, 1),
|
||||
textWidthPerc: func() *int {
|
||||
widthPerc: func() *int {
|
||||
i := -1
|
||||
return &i
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
desc: "no label and no textWidthPerc, full area for text input field",
|
||||
desc: "no label and no widthPerc, full area for text input field",
|
||||
cvsAr: image.Rect(0, 0, 5, 1),
|
||||
wantLabelAr: image.ZR,
|
||||
wantTextAr: image.Rect(0, 0, 5, 1),
|
||||
},
|
||||
{
|
||||
desc: "textWidthPerc set, splits canvas area",
|
||||
desc: "widthPerc set, splits canvas area",
|
||||
cvsAr: image.Rect(0, 0, 10, 1),
|
||||
textWidthPerc: func() *int {
|
||||
widthPerc: func() *int {
|
||||
i := 30
|
||||
return &i
|
||||
}(),
|
||||
@ -273,9 +347,9 @@ func TestSplit(t *testing.T) {
|
||||
wantTextAr: image.Rect(7, 0, 10, 1),
|
||||
},
|
||||
{
|
||||
desc: "textWidthPerc and label set",
|
||||
desc: "widthPerc and label set",
|
||||
cvsAr: image.Rect(0, 0, 10, 1),
|
||||
textWidthPerc: func() *int {
|
||||
widthPerc: func() *int {
|
||||
i := 30
|
||||
return &i
|
||||
}(),
|
||||
@ -285,9 +359,9 @@ func TestSplit(t *testing.T) {
|
||||
},
|
||||
|
||||
{
|
||||
desc: "textWidthPerc set to 100, splits canvas area",
|
||||
desc: "widthPerc set to 100, splits canvas area",
|
||||
cvsAr: image.Rect(0, 0, 10, 1),
|
||||
textWidthPerc: func() *int {
|
||||
widthPerc: func() *int {
|
||||
i := 100
|
||||
return &i
|
||||
}(),
|
||||
@ -295,9 +369,9 @@ func TestSplit(t *testing.T) {
|
||||
wantTextAr: image.Rect(0, 0, 10, 1),
|
||||
},
|
||||
{
|
||||
desc: "textWidthPerc set to 1, splits canvas area",
|
||||
desc: "widthPerc set to 1, splits canvas area",
|
||||
cvsAr: image.Rect(0, 0, 10, 1),
|
||||
textWidthPerc: func() *int {
|
||||
widthPerc: func() *int {
|
||||
i := 1
|
||||
return &i
|
||||
}(),
|
||||
@ -329,7 +403,7 @@ func TestSplit(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
gotLabelAr, gotTextAr, err := split(tc.cvsAr, tc.label, tc.textWidthPerc)
|
||||
gotLabelAr, gotTextAr, err := split(tc.cvsAr, tc.label, tc.widthPerc)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("split => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||
}
|
||||
|
@ -23,10 +23,10 @@ import (
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/container"
|
||||
"github.com/mum4k/termdash/container/grid"
|
||||
"github.com/mum4k/termdash/keyboard"
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/terminal/termbox"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
"github.com/mum4k/termdash/widgets/button"
|
||||
"github.com/mum4k/termdash/widgets/segmentdisplay"
|
||||
"github.com/mum4k/termdash/widgets/textinput"
|
||||
@ -126,72 +126,95 @@ func main() {
|
||||
updateText := make(chan string)
|
||||
go rollText(ctx, rollingSD, updateText)
|
||||
|
||||
input, err := textinput.New(
|
||||
textinput.Label("New text:", cell.FgColor(cell.ColorBlue)),
|
||||
textinput.MaxWidthCells(20),
|
||||
textinput.Border(linestyle.Light),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
submitB, err := button.New("Submit", func() error {
|
||||
updateText <- "Hello World"
|
||||
updateText <- input.ReadAndClear()
|
||||
return nil
|
||||
},
|
||||
button.GlobalKey(keyboard.KeyEnter),
|
||||
button.FillColor(cell.ColorNumber(220)),
|
||||
)
|
||||
clearB, err := button.New("Clear", func() error {
|
||||
input.ReadAndClear()
|
||||
updateText <- ""
|
||||
return nil
|
||||
},
|
||||
button.WidthFor("Submit"),
|
||||
button.FillColor(cell.ColorNumber(220)),
|
||||
)
|
||||
quitB, err := button.New("Quit", func() error {
|
||||
cancel()
|
||||
return nil
|
||||
},
|
||||
button.WidthFor("Submit"),
|
||||
button.FillColor(cell.ColorNumber(196)),
|
||||
)
|
||||
|
||||
input, err := textinput.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c, err := container.New(
|
||||
t,
|
||||
container.Border(linestyle.Light),
|
||||
container.BorderTitle("PRESS Q TO QUIT"),
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.PlaceWidget(rollingSD),
|
||||
builder := grid.New()
|
||||
builder.Add(
|
||||
grid.RowHeightPerc(40,
|
||||
grid.Widget(
|
||||
rollingSD,
|
||||
),
|
||||
container.Bottom(
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.PlaceWidget(input),
|
||||
),
|
||||
container.Bottom(
|
||||
container.SplitVertical(
|
||||
container.Left(
|
||||
container.AlignVertical(align.VerticalTop),
|
||||
container.AlignHorizontal(align.HorizontalRight),
|
||||
container.PaddingRight(1),
|
||||
container.PlaceWidget(submitB),
|
||||
),
|
||||
container.Right(
|
||||
container.AlignVertical(align.VerticalTop),
|
||||
container.AlignHorizontal(align.HorizontalLeft),
|
||||
container.PaddingLeft(1),
|
||||
container.PlaceWidget(clearB),
|
||||
),
|
||||
),
|
||||
),
|
||||
container.SplitPercent(30),
|
||||
),
|
||||
),
|
||||
container.SplitPercent(40),
|
||||
),
|
||||
)
|
||||
builder.Add(
|
||||
grid.RowHeightPerc(20,
|
||||
grid.Widget(
|
||||
input,
|
||||
container.AlignHorizontal(align.HorizontalCenter),
|
||||
container.AlignVertical(align.VerticalBottom),
|
||||
container.MarginBottom(1),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
builder.Add(
|
||||
grid.RowHeightPerc(40,
|
||||
grid.ColWidthPerc(20),
|
||||
grid.ColWidthPerc(20,
|
||||
grid.Widget(
|
||||
submitB,
|
||||
container.AlignVertical(align.VerticalTop),
|
||||
container.AlignHorizontal(align.HorizontalRight),
|
||||
),
|
||||
),
|
||||
grid.ColWidthPerc(20,
|
||||
grid.Widget(
|
||||
clearB,
|
||||
container.AlignVertical(align.VerticalTop),
|
||||
container.AlignHorizontal(align.HorizontalCenter),
|
||||
),
|
||||
),
|
||||
grid.ColWidthPerc(20,
|
||||
grid.Widget(
|
||||
quitB,
|
||||
container.AlignVertical(align.VerticalTop),
|
||||
container.AlignHorizontal(align.HorizontalLeft),
|
||||
),
|
||||
),
|
||||
grid.ColWidthPerc(20),
|
||||
),
|
||||
)
|
||||
|
||||
gridOpts, err := builder.Build()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c, err := container.New(t, gridOpts...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
quitter := func(k *terminalapi.Keyboard) {
|
||||
if k.Key == 'q' || k.Key == 'Q' {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
if err := termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(quitter), termdash.RedrawInterval(500*time.Millisecond)); err != nil {
|
||||
if err := termdash.Run(ctx, t, c, termdash.RedrawInterval(500*time.Millisecond)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user