diff --git a/_examples/form.go b/_examples/form.go new file mode 100644 index 00000000..dcaff302 --- /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/canvas.go b/canvas.go index 9001d980..e6dbaf07 100644 --- a/canvas.go +++ b/canvas.go @@ -3,7 +3,7 @@ package termui import ( "image" - "github.com/gizak/termui/v3/drawille" + "github.com/proullon/termui/v3/drawille" ) type Canvas struct { diff --git a/go.mod b/go.mod index 499a4a87..1a982b1e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module github.com/gizak/termui/v3 +module github.com/proullon/termui/v3 -go 1.15 +go 1.22 require ( github.com/mattn/go-runewidth v0.0.2 diff --git a/widgets/barchart.go b/widgets/barchart.go index d76de3bb..1cf0e9b6 100644 --- a/widgets/barchart.go +++ b/widgets/barchart.go @@ -10,7 +10,7 @@ import ( rw "github.com/mattn/go-runewidth" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) type BarChart struct { diff --git a/widgets/form.go b/widgets/form.go new file mode 100644 index 00000000..b7d7a166 --- /dev/null +++ b/widgets/form.go @@ -0,0 +1,140 @@ +package widgets + +import ( + "context" + "fmt" + + . "github.com/proullon/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) + } +} diff --git a/widgets/gauge.go b/widgets/gauge.go index 3dd921ba..e7c7a90c 100644 --- a/widgets/gauge.go +++ b/widgets/gauge.go @@ -8,7 +8,7 @@ import ( "fmt" "image" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) type Gauge struct { diff --git a/widgets/image.go b/widgets/image.go index 280802bc..f66a9f7b 100644 --- a/widgets/image.go +++ b/widgets/image.go @@ -8,7 +8,7 @@ import ( "image" "image/color" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) type Image struct { diff --git a/widgets/list.go b/widgets/list.go index a4a6cb1b..cff5024a 100644 --- a/widgets/list.go +++ b/widgets/list.go @@ -9,7 +9,7 @@ import ( rw "github.com/mattn/go-runewidth" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) type List struct { diff --git a/widgets/paragraph.go b/widgets/paragraph.go index 5b54dd8c..ae952f51 100644 --- a/widgets/paragraph.go +++ b/widgets/paragraph.go @@ -7,7 +7,7 @@ package widgets import ( "image" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) type Paragraph struct { diff --git a/widgets/piechart.go b/widgets/piechart.go index 24d0fb56..0b4f5efc 100644 --- a/widgets/piechart.go +++ b/widgets/piechart.go @@ -4,7 +4,7 @@ import ( "image" "math" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) const ( diff --git a/widgets/plot.go b/widgets/plot.go index f4870de8..87d2c88f 100644 --- a/widgets/plot.go +++ b/widgets/plot.go @@ -8,7 +8,7 @@ import ( "fmt" "image" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) // Plot has two modes: line(default) and scatter. diff --git a/widgets/sparkline.go b/widgets/sparkline.go index 840a8a7d..da1c98be 100644 --- a/widgets/sparkline.go +++ b/widgets/sparkline.go @@ -7,7 +7,7 @@ package widgets import ( "image" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) // Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers. diff --git a/widgets/stacked_barchart.go b/widgets/stacked_barchart.go index 255bb4ac..2177419f 100644 --- a/widgets/stacked_barchart.go +++ b/widgets/stacked_barchart.go @@ -10,7 +10,7 @@ import ( rw "github.com/mattn/go-runewidth" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) type StackedBarChart struct { diff --git a/widgets/table.go b/widgets/table.go index 05391ad5..059ffbad 100644 --- a/widgets/table.go +++ b/widgets/table.go @@ -7,10 +7,11 @@ package widgets import ( "image" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) -/*Table is like: +/* +Table is like: ┌ Awesome Table ───────────────────────────────────────────────┐ │ Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 | │──────────────────────────────────────────────────────────────│ diff --git a/widgets/tabs.go b/widgets/tabs.go index 8cbc92ce..47be9004 100644 --- a/widgets/tabs.go +++ b/widgets/tabs.go @@ -7,7 +7,7 @@ package widgets import ( "image" - . "github.com/gizak/termui/v3" + . "github.com/proullon/termui/v3" ) // TabPane is a renderable widget which can be used to conditionally render certain tabs/views. diff --git a/widgets/tree.go b/widgets/tree.go index ae1d3058..53c9ec6a 100644 --- a/widgets/tree.go +++ b/widgets/tree.go @@ -5,8 +5,8 @@ import ( "image" "strings" - . "github.com/gizak/termui/v3" rw "github.com/mattn/go-runewidth" + . "github.com/proullon/termui/v3" ) const treeIndent = " "