diff --git a/application.go b/application.go index 03d5f0d..7098a23 100644 --- a/application.go +++ b/application.go @@ -228,6 +228,11 @@ EventLoop: a.Stop() } + // Allow a validation to short-circuit input handling + if !a.ValidatePrimitive(p, event) { + continue + } + // Pass other key events to the currently focused primitive. if p != nil { if handler := p.InputHandler(); handler != nil { @@ -503,3 +508,52 @@ func (a *Application) QueueEvent(event tcell.Event) *Application { a.events <- event return a } + +// ValidatePrimitive will call a validation function specific to +// a FormItem set using the SetValidateFunc() methods when one of +// the field exit keys — Enter, Tab, Backtab, or Escape — is used. +// If the validation function returns false the focus will stay on +// the non-valid form field. +func (a *Application) ValidatePrimitive(p Primitive, event *tcell.EventKey) bool { + switch event.Key() { + case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: + // We will check validation below + default: + // No need to check + return true + } + switch p.(type) { + case *InputField: + i := p.(*InputField) + if i.valid == nil { + break + } + return i.valid(i, event) + + case *DropDown: + dd := p.(*DropDown) + if dd.valid == nil { + break + } + return dd.valid(dd, event) + + case *Checkbox: + cb := p.(*Checkbox) + if cb.valid == nil { + break + } + return cb.valid(cb, event) + + default: + b, ok := p.(*Box) + if !ok { + break + } + if b.valid == nil { + break + } + return b.valid(p, event) + + } + return true +} diff --git a/box.go b/box.go index 6bcd6d2..d2b2388 100644 --- a/box.go +++ b/box.go @@ -58,6 +58,11 @@ type Box struct { // An optional function which is called before the box is drawn. draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) + + // A callback function to be called when one of the field exit keys — Enter, + // Tab, Backtab, or Escape — is used. If this callback returns false it will + // bypass the input handler and leave the focus on the non-valid field. + valid func(Primitive, *tcell.EventKey) bool } // NewBox returns a Box without a border. @@ -231,6 +236,14 @@ func (b *Box) SetTitleAlign(align int) *Box { return b } +// SetValidateFunc sets a callback to be called when one of the field exit keys — +// Enter, Tab, Backtab, or Escape — is used. If this callback returns false it +// will stop the input handler from moving focus to a different field. +func (b *Box) SetValidateFunc(handler func(Primitive, *tcell.EventKey) bool) *Box { + b.valid = handler + return b +} + // Draw draws this primitive onto the screen. func (b *Box) Draw(screen tcell.Screen) { // Don't draw anything if there is no space. diff --git a/checkbox.go b/checkbox.go index b684de7..57d0ec0 100644 --- a/checkbox.go +++ b/checkbox.go @@ -42,6 +42,11 @@ type Checkbox struct { // A callback function set by the Form class and called when the user leaves // this form item. finished func(tcell.Key) + + // A callback function to be called when one of the field exit keys — Enter, + // Tab, Backtab, or Escape — is used. If this callback returns false it will + // bypass the input handler and leave the focus on the non-valid field. + valid func(*Checkbox, *tcell.EventKey) bool } // NewCheckbox returns a new input field. @@ -142,6 +147,14 @@ func (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem { return c } +// SetValidateFunc sets a callback to be called when one of the field exit keys — +// Enter, Tab, Backtab, or Escape — is used. If this callback returns false it +// will stop the input handler from moving focus to a different field. +func (c *Checkbox) SetValidateFunc(handler func(*Checkbox, *tcell.EventKey) bool) *Checkbox { + c.valid = handler + return c +} + // Draw draws this primitive onto the screen. func (c *Checkbox) Draw(screen tcell.Screen) { c.Box.Draw(screen) @@ -254,11 +267,16 @@ type CheckboxArgs struct { // returns the event to be forwarded to the primitive's default // input handler (nil if nothing should be forwarded). InputCaptureFunc func(event *tcell.EventKey) *tcell.EventKey + + // A callback function to be called when one of the field exit keys — Enter, + // Tab, Backtab, or Escape — is used. If this callback returns false it will + // bypass the input handler and leave the focus on the non-valid field. + ValidateFunc func(*Checkbox, *tcell.EventKey) bool } // ApplyArgs applies the values from a CheckboxArgs{} struct to the // associated properties of the Checkbox. -func (c *Checkbox) ApplyArgs(args *CheckboxArgs) *Checkbox{ +func (c *Checkbox) ApplyArgs(args *CheckboxArgs) *Checkbox { c.SetLabel(args.Label) c.SetChecked(args.Checked) @@ -288,9 +306,11 @@ func (c *Checkbox) ApplyArgs(args *CheckboxArgs) *Checkbox{ if args.FinishedFunc != nil { c.SetFinishedFunc(args.FinishedFunc) } + if args.ValidateFunc != nil { + c.SetValidateFunc(args.ValidateFunc) + } if args.InputCaptureFunc != nil { c.SetInputCapture(args.InputCaptureFunc) } return c } - diff --git a/dropdown.go b/dropdown.go index 7842a68..610009f 100644 --- a/dropdown.go +++ b/dropdown.go @@ -70,6 +70,11 @@ type DropDown struct { // A callback function which is called when the user changes the drop-down's // selection. selected func(text string, index int) + + // A callback function to be called when one of the field exit keys — Enter, + // Tab, Backtab, or Escape — is used. If this callback returns false it will + // bypass the input handler and leave the focus on the non-valid field. + valid func(*DropDown, *tcell.EventKey) bool } // NewDropDown returns a new drop-down. @@ -256,6 +261,14 @@ func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem { return d } +// SetValidateFunc sets a callback to be called when one of the field exit keys — +// Enter, Tab, Backtab, or Escape — is used. If this callback returns false it +// will stop the input handler from moving focus to a different field. +func (d *DropDown) SetValidateFunc(handler func(*DropDown, *tcell.EventKey) bool) *DropDown { + d.valid = handler + return d +} + // Draw draws this primitive onto the screen. func (d *DropDown) Draw(screen tcell.Screen) { d.Box.Draw(screen) @@ -499,8 +512,10 @@ type DropDownArgs struct { // returns the event to be forwarded to the primitive's default // input handler (nil if nothing should be forwarded). InputCaptureFunc func(event *tcell.EventKey) *tcell.EventKey -} + // An optional function which is called before the box is drawn. + ValidateFunc func(*DropDown, *tcell.EventKey) bool +} // ApplyArgs applies the values from a DropDownArgs{} struct to the // associated properties of the DropDown. @@ -537,6 +552,9 @@ func (d *DropDown) ApplyArgs(args *DropDownArgs) *DropDown { if args.FinishedFunc != nil { d.SetFinishedFunc(args.FinishedFunc) } + if args.ValidateFunc != nil { + d.SetValidateFunc(args.ValidateFunc) + } if args.InputCaptureFunc != nil { d.SetInputCapture(args.InputCaptureFunc) } diff --git a/inputfield.go b/inputfield.go index 957e873..45320e3 100644 --- a/inputfield.go +++ b/inputfield.go @@ -85,6 +85,11 @@ type InputField struct { // A callback function set by the Form class and called when the user leaves // this form item. finished func(tcell.Key) + + // A callback function to be called when one of the field exit keys — Enter, + // Tab, Backtab, or Escape — is used. If this callback returns false it will + // bypass the input handler and leave the focus on the non-valid field. + valid func(*InputField, *tcell.EventKey) bool } // NewInputField returns a new input field. @@ -226,6 +231,14 @@ func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem { return i } +// SetValidateFunc sets a callback to be called when one of the field exit keys — +// Enter, Tab, Backtab, or Escape — is used. If this callback returns false it +// will stop the input handler from moving focus to a different field. +func (i *InputField) SetValidateFunc(handler func(*InputField, *tcell.EventKey) bool) *InputField { + i.valid = handler + return i +} + // Draw draws this primitive onto the screen. func (i *InputField) Draw(screen tcell.Screen) { i.Box.Draw(screen) @@ -507,6 +520,9 @@ type InputFieldArgs struct { // returns the event to be forwarded to the primitive's default // input handler (nil if nothing should be forwarded). InputCaptureFunc func(event *tcell.EventKey) *tcell.EventKey + + // An optional function which is called before the box is drawn. + ValidateFunc func(*InputField, *tcell.EventKey) bool } // ApplyArgs applies the values from a InputFieldArgs{} or PasswordFieldArgs{} @@ -563,10 +579,11 @@ func (i *InputField) applyInputFieldArgs(args *InputFieldArgs) *InputField { if args.FinishedFunc != nil { i.SetFinishedFunc(args.FinishedFunc) } + if args.ValidateFunc != nil { + i.SetValidateFunc(args.ValidateFunc) + } if args.InputCaptureFunc != nil { i.SetInputCapture(args.InputCaptureFunc) } return i } - - diff --git a/passwordfield.go b/passwordfield.go index 9a6a04c..743bd3e 100644 --- a/passwordfield.go +++ b/passwordfield.go @@ -64,6 +64,9 @@ type PasswordFieldArgs struct { // returns the event to be forwarded to the primitive's default // input handler (nil if nothing should be forwarded). InputCaptureFunc func(event *tcell.EventKey) *tcell.EventKey + + // An optional function which is called before the box is drawn. + ValidateFunc func(*InputField, *tcell.EventKey) bool } // applyPasswordFieldArgs applies the values from a PasswordFieldArgs{} @@ -99,9 +102,11 @@ func (i *InputField) applyPasswordFieldArgs(args *PasswordFieldArgs) *InputField if args.FinishedFunc != nil { i.SetFinishedFunc(args.FinishedFunc) } + if args.ValidateFunc != nil { + i.SetValidateFunc(args.ValidateFunc) + } if args.InputCaptureFunc != nil { i.SetInputCapture(args.InputCaptureFunc) } return i } - diff --git a/validation.go b/validation.go new file mode 100644 index 0000000..3052f0b --- /dev/null +++ b/validation.go @@ -0,0 +1,7 @@ +package tview + +import "github.com/gdamore/tcell" + +type PrimitiveValidationHandler interface { + Validate(p Primitive, key *tcell.EventKey) bool +}