From 0b8d905cecdcb3634a258e89644d048a366159d7 Mon Sep 17 00:00:00 2001 From: Pierre Roullon Date: Wed, 15 Jun 2022 09:12:20 +0200 Subject: [PATCH] feat: (widget) add Form widget --- _examples/form.go | 56 +++++++++++++++++++ widgets/form.go | 140 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 _examples/form.go create mode 100644 widgets/form.go diff --git a/_examples/form.go b/_examples/form.go new file mode 100644 index 0000000..dcaff30 --- /dev/null +++ b/_examples/form.go @@ -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 "": + return + default: + if !f0.IsDone() { + f0.Handle(e) + } + } + } +} diff --git a/widgets/form.go b/widgets/form.go new file mode 100644 index 0000000..c65981e --- /dev/null +++ b/widgets/form.go @@ -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 +// or to switch field +// will switch to next field or exit scan if last field +func (f *Form) Handle(e Event) { + switch e.ID { + case "": + if f.fieldIdx < len(f.Fields)-1 { + f.fieldIdx++ + } + case "": + if f.fieldIdx > 0 { + f.fieldIdx-- + } + case "": + if f.fieldIdx < len(f.Fields)-1 { + f.fieldIdx++ + } else { + f.Done = true + f.onExitCallback(f.ctx, true, f.Fields) + } + case "": + 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 "": + 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 "": + if f.cursor < len(f.Text) { + f.cursor++ + } + case "": + if f.cursor > 0 { + f.cursor-- + } + case "": + 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 or +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) + } +}