mirror of
https://github.com/gizak/termui.git
synced 2025-04-26 13:48:54 +08:00
feat: (widget) add Form widget
This commit is contained in:
parent
f976fe697a
commit
0b8d905cec
56
_examples/form.go
Normal file
56
_examples/form.go
Normal file
@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
ui "github.com/gizak/termui/v3"
|
||||
"github.com/gizak/termui/v3/widgets"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := ui.Init(); err != nil {
|
||||
log.Fatalf("failed to initialize termui: %v", err)
|
||||
}
|
||||
defer ui.Close()
|
||||
|
||||
f0 := widgets.NewForm(
|
||||
context.Background(),
|
||||
"Add user",
|
||||
func(ctx context.Context, validated bool, fields []*widgets.Field) {
|
||||
ui.Close()
|
||||
fmt.Printf("Form validated: %t. Values:\n", validated)
|
||||
for _, f := range fields {
|
||||
fmt.Printf("%s: %s\n", f.Name, f.Text)
|
||||
}
|
||||
os.Exit(0)
|
||||
},
|
||||
widgets.NewField("First name", ""),
|
||||
widgets.NewField("Surname", ""),
|
||||
widgets.NewField("Test", "Default value"),
|
||||
)
|
||||
f0.SetRect(0, 0, 50, 20)
|
||||
|
||||
p2 := widgets.NewParagraph()
|
||||
p2.Title = "Multiline"
|
||||
p2.Text = "Simple colored text\nwith label. It [can be](fg:red) multilined with \\n or [break automatically](fg:red,fg:bold)"
|
||||
p2.SetRect(0, 50, 35, 55)
|
||||
p2.BorderStyle.Fg = ui.ColorYellow
|
||||
|
||||
ui.Render(f0, p2)
|
||||
|
||||
uiEvents := ui.PollEvents()
|
||||
for {
|
||||
e := <-uiEvents
|
||||
switch e.ID {
|
||||
case "<C-c>":
|
||||
return
|
||||
default:
|
||||
if !f0.IsDone() {
|
||||
f0.Handle(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
140
widgets/form.go
Normal file
140
widgets/form.go
Normal file
@ -0,0 +1,140 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
. "github.com/gizak/termui/v3"
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
Name string
|
||||
Text string
|
||||
cursor int
|
||||
}
|
||||
|
||||
type Form struct {
|
||||
Paragraph
|
||||
|
||||
TextFgColor Style
|
||||
TextBgColor Style
|
||||
Fields []*Field
|
||||
Done bool
|
||||
|
||||
ctx context.Context
|
||||
onExitCallback func(ctx context.Context, validated bool, fields []*Field)
|
||||
fieldIdx int
|
||||
}
|
||||
|
||||
func NewField(name string, value string) *Field {
|
||||
f := &Field{
|
||||
Name: name,
|
||||
Text: value,
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func NewForm(ctx context.Context, name string, callback func(ctx context.Context, validated bool, fields []*Field), fields ...*Field) *Form {
|
||||
|
||||
f := &Form{
|
||||
Paragraph: *NewParagraph(),
|
||||
TextFgColor: Theme.Paragraph.Text,
|
||||
ctx: ctx,
|
||||
onExitCallback: callback,
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
f.Fields = append(f.Fields, field)
|
||||
}
|
||||
|
||||
f.Border = true
|
||||
f.Title = name
|
||||
f._draw()
|
||||
return f
|
||||
}
|
||||
|
||||
// Handle input event
|
||||
// <Up> or <Down> to switch field
|
||||
// <Enter> will switch to next field or exit scan if last field
|
||||
func (f *Form) Handle(e Event) {
|
||||
switch e.ID {
|
||||
case "<Down>":
|
||||
if f.fieldIdx < len(f.Fields)-1 {
|
||||
f.fieldIdx++
|
||||
}
|
||||
case "<Up>":
|
||||
if f.fieldIdx > 0 {
|
||||
f.fieldIdx--
|
||||
}
|
||||
case "<Enter>":
|
||||
if f.fieldIdx < len(f.Fields)-1 {
|
||||
f.fieldIdx++
|
||||
} else {
|
||||
f.Done = true
|
||||
f.onExitCallback(f.ctx, true, f.Fields)
|
||||
}
|
||||
case "<Esc>":
|
||||
f.Done = true
|
||||
f.onExitCallback(f.ctx, false, f.Fields)
|
||||
default:
|
||||
f.Fields[f.fieldIdx].Handle(e)
|
||||
}
|
||||
f._draw()
|
||||
Render(f)
|
||||
}
|
||||
|
||||
// Handle field input
|
||||
func (f *Field) Handle(e Event) {
|
||||
switch e.ID {
|
||||
case "<Backspace>":
|
||||
if f.cursor > 0 {
|
||||
f.Text = f.Text[:f.cursor-1] + f.Text[f.cursor:]
|
||||
} else if len(f.Text) > 1 {
|
||||
f.Text = f.Text[1:]
|
||||
} else {
|
||||
f.Text = ""
|
||||
}
|
||||
if f.cursor > 0 {
|
||||
f.cursor--
|
||||
}
|
||||
if f.cursor > len(f.Text) {
|
||||
f.cursor = len(f.Text)
|
||||
}
|
||||
case "<Right>":
|
||||
if f.cursor < len(f.Text) {
|
||||
f.cursor++
|
||||
}
|
||||
case "<Left>":
|
||||
if f.cursor > 0 {
|
||||
f.cursor--
|
||||
}
|
||||
case "<Space>":
|
||||
f.Text = f.Text[:f.cursor] + " " + f.Text[f.cursor:]
|
||||
f.cursor++
|
||||
default:
|
||||
f.Text = f.Text[:f.cursor] + e.ID + f.Text[f.cursor:]
|
||||
f.cursor++
|
||||
}
|
||||
}
|
||||
|
||||
// IsDone return wether user has entered <Esc> or <Enter>
|
||||
func (f *Form) IsDone() bool {
|
||||
return f.Done
|
||||
}
|
||||
|
||||
func (f *Form) _draw() {
|
||||
cursor := "[|](bg:white)"
|
||||
f.Text = ""
|
||||
for i, field := range f.Fields {
|
||||
txt := field.Text
|
||||
if i == f.fieldIdx && field.Text != "" {
|
||||
begin := field.Text[:field.cursor]
|
||||
end := field.Text[field.cursor:]
|
||||
txt = begin + cursor + end
|
||||
} else if i == f.fieldIdx {
|
||||
txt = cursor
|
||||
}
|
||||
f.Text += fmt.Sprintf("%s: %s\n", field.Name, txt)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user