mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-25 13:48:50 +08:00
Merge pull request #287 from mum4k/243-formdemo
A demonstration of text input form functionality with keyboard navigation.
This commit is contained in:
commit
b14a8445b8
@ -35,6 +35,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
#### Text input form functionality with keyboard navigation
|
||||
|
||||
- added a new `formdemo` that demonstrates a text input form with keyboard
|
||||
navigation.
|
||||
|
||||
#### Infrastructure changes
|
||||
|
||||
- `container` now allows users to configure keyboard keys that move focus to
|
||||
|
29
README.md
29
README.md
@ -55,6 +55,7 @@ To install this library, run the following:
|
||||
|
||||
```go
|
||||
go get -u github.com/mum4k/termdash
|
||||
cd github.com/mum4k/termdash
|
||||
```
|
||||
|
||||
# Usage
|
||||
@ -63,7 +64,7 @@ The usage of most of these elements is demonstrated in
|
||||
[termdashdemo.go](termdashdemo/termdashdemo.go). To execute the demo:
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/termdashdemo/termdashdemo.go
|
||||
go run termdashdemo/termdashdemo.go
|
||||
```
|
||||
|
||||
# Documentation
|
||||
@ -80,7 +81,7 @@ Run the
|
||||
[buttondemo](widgets/button/buttondemo/buttondemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/button/buttondemo/buttondemo.go
|
||||
go run widgets/button/buttondemo/buttondemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/buttondemo.gif" alt="buttondemo" type="image/gif" width="50%">](widgets/button/buttondemo/buttondemo.go)
|
||||
@ -92,18 +93,26 @@ submitting text data. Run the
|
||||
[textinputdemo](widgets/textinput/textinputdemo/textinputdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/textinput/textinputdemo/textinputdemo.go
|
||||
go run widgets/textinput/textinputdemo/textinputdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/textinputdemo.gif" alt="textinputdemo" type="image/gif" width="80%">](widgets/textinput/textinputdemo/textinputdemo.go)
|
||||
|
||||
Can be used to create text input forms that support keyboard navigation:
|
||||
|
||||
```go
|
||||
go run widgets/textinput/formdemo/formdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/formdemo.gif" alt="formdemo" type="image/gif" width="50%">](widgets/textinput/formdemo/formdemo.go)
|
||||
|
||||
## The Gauge
|
||||
|
||||
Displays the progress of an operation. Run the
|
||||
[gaugedemo](widgets/gauge/gaugedemo/gaugedemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/gauge/gaugedemo/gaugedemo.go
|
||||
go run widgets/gauge/gaugedemo/gaugedemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/gaugedemo.gif" alt="gaugedemo" type="image/gif">](widgets/gauge/gaugedemo/gaugedemo.go)
|
||||
@ -114,7 +123,7 @@ Visualizes progress of an operation as a partial or a complete donut. Run the
|
||||
[donutdemo](widgets/donut/donutdemo/donutdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/donut/donutdemo/donutdemo.go
|
||||
go run widgets/donut/donutdemo/donutdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/donutdemo.gif" alt="donutdemo" type="image/gif">](widgets/donut/donutdemo/donutdemo.go)
|
||||
@ -125,7 +134,7 @@ Displays text content, supports trimming and scrolling of content. Run the
|
||||
[textdemo](widgets/text/textdemo/textdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/text/textdemo/textdemo.go
|
||||
go run widgets/text/textdemo/textdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/textdemo.gif" alt="textdemo" type="image/gif">](widgets/text/textdemo/textdemo.go)
|
||||
@ -137,7 +146,7 @@ sub-cell height. Run the
|
||||
[sparklinedemo](widgets/sparkline/sparklinedemo/sparklinedemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/sparkline/sparklinedemo/sparklinedemo.go
|
||||
go run widgets/sparkline/sparklinedemo/sparklinedemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/sparklinedemo.gif" alt="sparklinedemo" type="image/gif" width="50%">](widgets/sparkline/sparklinedemo/sparklinedemo.go)
|
||||
@ -148,7 +157,7 @@ Displays multiple bars showing relative ratios of values. Run the
|
||||
[barchartdemo](widgets/barchart/barchartdemo/barchartdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/barchart/barchartdemo/barchartdemo.go
|
||||
go run widgets/barchart/barchartdemo/barchartdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/barchartdemo.gif" alt="barchartdemo" type="image/gif" width="50%">](widgets/barchart/barchartdemo/barchartdemo.go)
|
||||
@ -160,7 +169,7 @@ events. Run the
|
||||
[linechartdemo](widgets/linechart/linechartdemo/linechartdemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/linechart/linechartdemo/linechartdemo.go
|
||||
go run widgets/linechart/linechartdemo/linechartdemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/linechartdemo.gif" alt="linechartdemo" type="image/gif" width="70%">](widgets/linechart/linechartdemo/linechartdemo.go)
|
||||
@ -171,7 +180,7 @@ Displays text by simulating a 16-segment display. Run the
|
||||
[segmentdisplaydemo](widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go).
|
||||
|
||||
```go
|
||||
go run github.com/mum4k/termdash/widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go
|
||||
go run widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go
|
||||
```
|
||||
|
||||
[<img src="./doc/images/segmentdisplaydemo.gif" alt="segmentdisplaydemo" type="image/gif">](widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go)
|
||||
|
BIN
doc/images/formdemo.gif
Normal file
BIN
doc/images/formdemo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 379 KiB |
326
widgets/textinput/formdemo/formdemo.go
Normal file
326
widgets/textinput/formdemo/formdemo.go
Normal file
@ -0,0 +1,326 @@
|
||||
// Copyright 2020 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Binary formdemo creates a form that accepts text inputs and supports
|
||||
// keyboard navigation.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/user"
|
||||
"time"
|
||||
|
||||
"github.com/mum4k/termdash"
|
||||
"github.com/mum4k/termdash/align"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/container"
|
||||
"github.com/mum4k/termdash/keyboard"
|
||||
"github.com/mum4k/termdash/linestyle"
|
||||
"github.com/mum4k/termdash/terminal/tcell"
|
||||
"github.com/mum4k/termdash/widgets/button"
|
||||
"github.com/mum4k/termdash/widgets/text"
|
||||
"github.com/mum4k/termdash/widgets/textinput"
|
||||
)
|
||||
|
||||
// buttonChunks creates the text chunks for a button from the provided text.
|
||||
func buttonChunks(text string) []*button.TextChunk {
|
||||
if len(text) == 0 {
|
||||
return nil
|
||||
}
|
||||
first := string(text[0])
|
||||
rest := string(text[1:])
|
||||
|
||||
return []*button.TextChunk{
|
||||
button.NewChunk(
|
||||
"<",
|
||||
button.TextCellOpts(cell.FgColor(cell.ColorWhite)),
|
||||
button.FocusedTextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
button.PressedTextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
),
|
||||
button.NewChunk(
|
||||
first,
|
||||
button.TextCellOpts(cell.FgColor(cell.ColorRed)),
|
||||
),
|
||||
button.NewChunk(
|
||||
rest,
|
||||
button.TextCellOpts(cell.FgColor(cell.ColorWhite)),
|
||||
button.FocusedTextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
button.PressedTextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
),
|
||||
button.NewChunk(
|
||||
">",
|
||||
button.TextCellOpts(cell.FgColor(cell.ColorWhite)),
|
||||
button.FocusedTextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
button.PressedTextCellOpts(cell.FgColor(cell.ColorBlack)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// form contains the elements of a text input form.
|
||||
type form struct {
|
||||
// userInput is a text input that accepts user name.
|
||||
userInput *textinput.TextInput
|
||||
// uidInput is a text input that accepts UID.
|
||||
uidInput *textinput.TextInput
|
||||
// gidInput is a text input that accepts GID.
|
||||
gidInput *textinput.TextInput
|
||||
// homeInput is a text input that accepts path to the home folder.
|
||||
homeInput *textinput.TextInput
|
||||
|
||||
// submitB is a button that submits the form.
|
||||
submitB *button.Button
|
||||
// cancelB is a button that exist the application.
|
||||
cancelB *button.Button
|
||||
}
|
||||
|
||||
// newForm returns a new form instance.
|
||||
// The cancel argument is a function that terminates the application when called.
|
||||
func newForm(cancel context.CancelFunc) (*form, error) {
|
||||
var username string
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
username = "mum4k"
|
||||
} else {
|
||||
username = u.Username
|
||||
}
|
||||
|
||||
userInput, err := textinput.New(
|
||||
textinput.Label("Username: ", cell.FgColor(cell.ColorNumber(33))),
|
||||
textinput.DefaultText(username),
|
||||
textinput.MaxWidthCells(20),
|
||||
textinput.ExclusiveKeyboardOnFocus(),
|
||||
)
|
||||
uidInput, err := textinput.New(
|
||||
textinput.Label("UID: ", cell.FgColor(cell.ColorNumber(33))),
|
||||
textinput.DefaultText("1000"),
|
||||
textinput.MaxWidthCells(20),
|
||||
textinput.ExclusiveKeyboardOnFocus(),
|
||||
)
|
||||
gidInput, err := textinput.New(
|
||||
textinput.Label("GID: ", cell.FgColor(cell.ColorNumber(33))),
|
||||
textinput.DefaultText("1000"),
|
||||
textinput.MaxWidthCells(20),
|
||||
textinput.ExclusiveKeyboardOnFocus(),
|
||||
)
|
||||
homeInput, err := textinput.New(
|
||||
textinput.Label("Home: ", cell.FgColor(cell.ColorNumber(33))),
|
||||
textinput.DefaultText(fmt.Sprintf("/home/%s", username)),
|
||||
textinput.MaxWidthCells(20),
|
||||
textinput.ExclusiveKeyboardOnFocus(),
|
||||
)
|
||||
|
||||
submitB, err := button.NewFromChunks(buttonChunks("Submit"), nil,
|
||||
button.Key(keyboard.KeyEnter),
|
||||
button.GlobalKeys('s', 'S'),
|
||||
button.DisableShadow(),
|
||||
button.Height(1),
|
||||
button.TextHorizontalPadding(0),
|
||||
button.FillColor(cell.ColorBlack),
|
||||
button.FocusedFillColor(cell.ColorNumber(117)),
|
||||
button.PressedFillColor(cell.ColorNumber(220)),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cancelB, err := button.NewFromChunks(buttonChunks("Cancel"), func() error {
|
||||
cancel()
|
||||
return nil
|
||||
},
|
||||
button.FillColor(cell.ColorNumber(220)),
|
||||
button.Key(keyboard.KeyEnter),
|
||||
button.GlobalKeys('c', 'C'),
|
||||
button.DisableShadow(),
|
||||
button.Height(1),
|
||||
button.TextHorizontalPadding(0),
|
||||
button.FillColor(cell.ColorBlack),
|
||||
button.FocusedFillColor(cell.ColorNumber(117)),
|
||||
button.PressedFillColor(cell.ColorNumber(220)),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &form{
|
||||
userInput: userInput,
|
||||
uidInput: uidInput,
|
||||
gidInput: gidInput,
|
||||
homeInput: homeInput,
|
||||
submitB: submitB,
|
||||
cancelB: cancelB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// formLayout updates the container into a layout with text inputs and buttons.
|
||||
func formLayout(c *container.Container, f *form) error {
|
||||
return c.Update("root",
|
||||
container.KeyFocusNext(keyboard.KeyTab),
|
||||
container.KeyFocusGroupsNext(keyboard.KeyArrowDown, 1),
|
||||
container.KeyFocusGroupsPrevious(keyboard.KeyArrowUp, 1),
|
||||
container.KeyFocusGroupsNext(keyboard.KeyArrowRight, 2),
|
||||
container.KeyFocusGroupsPrevious(keyboard.KeyArrowLeft, 2),
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.Border(linestyle.Light),
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.Focused(),
|
||||
container.KeyFocusGroups(1),
|
||||
container.PlaceWidget(f.userInput),
|
||||
),
|
||||
container.Bottom(
|
||||
container.KeyFocusGroups(1),
|
||||
container.KeyFocusSkip(),
|
||||
container.PlaceWidget(f.uidInput),
|
||||
),
|
||||
),
|
||||
),
|
||||
container.Bottom(
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.KeyFocusGroups(1),
|
||||
container.KeyFocusSkip(),
|
||||
container.PlaceWidget(f.gidInput),
|
||||
),
|
||||
container.Bottom(
|
||||
container.KeyFocusGroups(1),
|
||||
container.KeyFocusSkip(),
|
||||
container.PlaceWidget(f.homeInput),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
container.Bottom(
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.SplitVertical(
|
||||
container.Left(
|
||||
container.KeyFocusGroups(1, 2),
|
||||
container.PlaceWidget(f.submitB),
|
||||
container.AlignHorizontal(align.HorizontalRight),
|
||||
container.PaddingRight(5),
|
||||
),
|
||||
container.Right(
|
||||
container.KeyFocusGroups(1, 2),
|
||||
container.PlaceWidget(f.cancelB),
|
||||
container.AlignHorizontal(align.HorizontalLeft),
|
||||
container.PaddingLeft(5),
|
||||
),
|
||||
),
|
||||
),
|
||||
container.Bottom(
|
||||
container.KeyFocusSkip(),
|
||||
),
|
||||
container.SplitFixed(3),
|
||||
),
|
||||
),
|
||||
container.SplitFixed(6),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// submitLayout updates the container into a layout that displays the submitted data.
|
||||
// The cancel argument is a function that terminates Termdash when called.
|
||||
func submitLayout(c *container.Container, f *form, cancel context.CancelFunc) error {
|
||||
t, err := text.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := t.Write("Submitted data:\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Write(fmt.Sprintf("Username: %s\n", f.userInput.Read())); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Write(fmt.Sprintf("UID: %s\n", f.uidInput.Read())); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Write(fmt.Sprintf("GID: %s\n", f.gidInput.Read())); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Write(fmt.Sprintf("Home: %s\n", f.homeInput.Read())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
okB, err := button.NewFromChunks(buttonChunks("OK"), func() error {
|
||||
cancel()
|
||||
return nil
|
||||
},
|
||||
button.FillColor(cell.ColorNumber(220)),
|
||||
button.Key(keyboard.KeyEnter),
|
||||
button.GlobalKeys('o', 'O'),
|
||||
button.DisableShadow(),
|
||||
button.Height(1),
|
||||
button.TextHorizontalPadding(0),
|
||||
button.FillColor(cell.ColorBlack),
|
||||
button.FocusedFillColor(cell.ColorNumber(117)),
|
||||
button.PressedFillColor(cell.ColorNumber(220)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Update("root",
|
||||
container.SplitHorizontal(
|
||||
container.Top(
|
||||
container.SplitVertical(
|
||||
container.Left(),
|
||||
container.Right(
|
||||
container.PlaceWidget(t),
|
||||
),
|
||||
container.SplitPercent(33),
|
||||
),
|
||||
),
|
||||
container.Bottom(
|
||||
container.Focused(),
|
||||
container.PlaceWidget(okB),
|
||||
),
|
||||
container.SplitFixed(7),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
t, err := tcell.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer t.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c, err := container.New(t, container.ID("root"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f, err := newForm(cancel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.submitB.SetCallback(func() error {
|
||||
return submitLayout(c, f, cancel)
|
||||
})
|
||||
if err := formLayout(c, f); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := termdash.Run(ctx, t, c, termdash.RedrawInterval(100*time.Millisecond)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user