Projects STRLCPY termdash Commits b14a8445
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■
    CHANGELOG.md
    skipped 34 lines
    35 35   
    36 36  ### Added
    37 37   
     38 +#### Text input form functionality with keyboard navigation
     39 + 
     40 +- added a new `formdemo` that demonstrates a text input form with keyboard
     41 + navigation.
     42 + 
    38 43  #### Infrastructure changes
    39 44   
    40 45  - `container` now allows users to configure keyboard keys that move focus to
    skipped 414 lines
  • ■ ■ ■ ■ ■ ■
    README.md
    skipped 54 lines
    55 55   
    56 56  ```go
    57 57  go get -u github.com/mum4k/termdash
     58 +cd github.com/mum4k/termdash
    58 59  ```
    59 60   
    60 61  # Usage
    skipped 2 lines
    63 64  [termdashdemo.go](termdashdemo/termdashdemo.go). To execute the demo:
    64 65   
    65 66  ```go
    66  -go run github.com/mum4k/termdash/termdashdemo/termdashdemo.go
     67 +go run termdashdemo/termdashdemo.go
    67 68  ```
    68 69   
    69 70  # Documentation
    skipped 10 lines
    80 81  [buttondemo](widgets/button/buttondemo/buttondemo.go).
    81 82   
    82 83  ```go
    83  -go run github.com/mum4k/termdash/widgets/button/buttondemo/buttondemo.go
     84 +go run widgets/button/buttondemo/buttondemo.go
    84 85  ```
    85 86   
    86 87  [<img src="./doc/images/buttondemo.gif" alt="buttondemo" type="image/gif" width="50%">](widgets/button/buttondemo/buttondemo.go)
    skipped 5 lines
    92 93  [textinputdemo](widgets/textinput/textinputdemo/textinputdemo.go).
    93 94   
    94 95  ```go
    95  -go run github.com/mum4k/termdash/widgets/textinput/textinputdemo/textinputdemo.go
     96 +go run widgets/textinput/textinputdemo/textinputdemo.go
    96 97  ```
    97 98   
    98 99  [<img src="./doc/images/textinputdemo.gif" alt="textinputdemo" type="image/gif" width="80%">](widgets/textinput/textinputdemo/textinputdemo.go)
     100 + 
     101 +Can be used to create text input forms that support keyboard navigation:
     102 + 
     103 +```go
     104 +go run widgets/textinput/formdemo/formdemo.go
     105 +```
     106 + 
     107 +[<img src="./doc/images/formdemo.gif" alt="formdemo" type="image/gif" width="50%">](widgets/textinput/formdemo/formdemo.go)
    99 108   
    100 109  ## The Gauge
    101 110   
    skipped 1 lines
    103 112  [gaugedemo](widgets/gauge/gaugedemo/gaugedemo.go).
    104 113   
    105 114  ```go
    106  -go run github.com/mum4k/termdash/widgets/gauge/gaugedemo/gaugedemo.go
     115 +go run widgets/gauge/gaugedemo/gaugedemo.go
    107 116  ```
    108 117   
    109 118  [<img src="./doc/images/gaugedemo.gif" alt="gaugedemo" type="image/gif">](widgets/gauge/gaugedemo/gaugedemo.go)
    skipped 4 lines
    114 123  [donutdemo](widgets/donut/donutdemo/donutdemo.go).
    115 124   
    116 125  ```go
    117  -go run github.com/mum4k/termdash/widgets/donut/donutdemo/donutdemo.go
     126 +go run widgets/donut/donutdemo/donutdemo.go
    118 127  ```
    119 128   
    120 129  [<img src="./doc/images/donutdemo.gif" alt="donutdemo" type="image/gif">](widgets/donut/donutdemo/donutdemo.go)
    skipped 4 lines
    125 134  [textdemo](widgets/text/textdemo/textdemo.go).
    126 135   
    127 136  ```go
    128  -go run github.com/mum4k/termdash/widgets/text/textdemo/textdemo.go
     137 +go run widgets/text/textdemo/textdemo.go
    129 138  ```
    130 139   
    131 140  [<img src="./doc/images/textdemo.gif" alt="textdemo" type="image/gif">](widgets/text/textdemo/textdemo.go)
    skipped 5 lines
    137 146  [sparklinedemo](widgets/sparkline/sparklinedemo/sparklinedemo.go).
    138 147   
    139 148  ```go
    140  -go run github.com/mum4k/termdash/widgets/sparkline/sparklinedemo/sparklinedemo.go
     149 +go run widgets/sparkline/sparklinedemo/sparklinedemo.go
    141 150  ```
    142 151   
    143 152  [<img src="./doc/images/sparklinedemo.gif" alt="sparklinedemo" type="image/gif" width="50%">](widgets/sparkline/sparklinedemo/sparklinedemo.go)
    skipped 4 lines
    148 157  [barchartdemo](widgets/barchart/barchartdemo/barchartdemo.go).
    149 158   
    150 159  ```go
    151  -go run github.com/mum4k/termdash/widgets/barchart/barchartdemo/barchartdemo.go
     160 +go run widgets/barchart/barchartdemo/barchartdemo.go
    152 161  ```
    153 162   
    154 163  [<img src="./doc/images/barchartdemo.gif" alt="barchartdemo" type="image/gif" width="50%">](widgets/barchart/barchartdemo/barchartdemo.go)
    skipped 5 lines
    160 169  [linechartdemo](widgets/linechart/linechartdemo/linechartdemo.go).
    161 170   
    162 171  ```go
    163  -go run github.com/mum4k/termdash/widgets/linechart/linechartdemo/linechartdemo.go
     172 +go run widgets/linechart/linechartdemo/linechartdemo.go
    164 173  ```
    165 174   
    166 175  [<img src="./doc/images/linechartdemo.gif" alt="linechartdemo" type="image/gif" width="70%">](widgets/linechart/linechartdemo/linechartdemo.go)
    skipped 4 lines
    171 180  [segmentdisplaydemo](widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go).
    172 181   
    173 182  ```go
    174  -go run github.com/mum4k/termdash/widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go
     183 +go run widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go
    175 184  ```
    176 185   
    177 186  [<img src="./doc/images/segmentdisplaydemo.gif" alt="segmentdisplaydemo" type="image/gif">](widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go)
    skipped 40 lines
  • doc/images/formdemo.gif
  • ■ ■ ■ ■ ■ ■
    widgets/textinput/formdemo/formdemo.go
     1 +// Copyright 2020 Google Inc.
     2 +//
     3 +// Licensed under the Apache License, Version 2.0 (the "License");
     4 +// you may not use this file except in compliance with the License.
     5 +// You may obtain a copy of the License at
     6 +//
     7 +// http://www.apache.org/licenses/LICENSE-2.0
     8 +//
     9 +// Unless required by applicable law or agreed to in writing, software
     10 +// distributed under the License is distributed on an "AS IS" BASIS,
     11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 +// See the License for the specific language governing permissions and
     13 +// limitations under the License.
     14 + 
     15 +// Binary formdemo creates a form that accepts text inputs and supports
     16 +// keyboard navigation.
     17 +package main
     18 + 
     19 +import (
     20 + "context"
     21 + "fmt"
     22 + "os/user"
     23 + "time"
     24 + 
     25 + "github.com/mum4k/termdash"
     26 + "github.com/mum4k/termdash/align"
     27 + "github.com/mum4k/termdash/cell"
     28 + "github.com/mum4k/termdash/container"
     29 + "github.com/mum4k/termdash/keyboard"
     30 + "github.com/mum4k/termdash/linestyle"
     31 + "github.com/mum4k/termdash/terminal/tcell"
     32 + "github.com/mum4k/termdash/widgets/button"
     33 + "github.com/mum4k/termdash/widgets/text"
     34 + "github.com/mum4k/termdash/widgets/textinput"
     35 +)
     36 + 
     37 +// buttonChunks creates the text chunks for a button from the provided text.
     38 +func buttonChunks(text string) []*button.TextChunk {
     39 + if len(text) == 0 {
     40 + return nil
     41 + }
     42 + first := string(text[0])
     43 + rest := string(text[1:])
     44 + 
     45 + return []*button.TextChunk{
     46 + button.NewChunk(
     47 + "<",
     48 + button.TextCellOpts(cell.FgColor(cell.ColorWhite)),
     49 + button.FocusedTextCellOpts(cell.FgColor(cell.ColorBlack)),
     50 + button.PressedTextCellOpts(cell.FgColor(cell.ColorBlack)),
     51 + ),
     52 + button.NewChunk(
     53 + first,
     54 + button.TextCellOpts(cell.FgColor(cell.ColorRed)),
     55 + ),
     56 + button.NewChunk(
     57 + rest,
     58 + button.TextCellOpts(cell.FgColor(cell.ColorWhite)),
     59 + button.FocusedTextCellOpts(cell.FgColor(cell.ColorBlack)),
     60 + button.PressedTextCellOpts(cell.FgColor(cell.ColorBlack)),
     61 + ),
     62 + button.NewChunk(
     63 + ">",
     64 + button.TextCellOpts(cell.FgColor(cell.ColorWhite)),
     65 + button.FocusedTextCellOpts(cell.FgColor(cell.ColorBlack)),
     66 + button.PressedTextCellOpts(cell.FgColor(cell.ColorBlack)),
     67 + ),
     68 + }
     69 +}
     70 + 
     71 +// form contains the elements of a text input form.
     72 +type form struct {
     73 + // userInput is a text input that accepts user name.
     74 + userInput *textinput.TextInput
     75 + // uidInput is a text input that accepts UID.
     76 + uidInput *textinput.TextInput
     77 + // gidInput is a text input that accepts GID.
     78 + gidInput *textinput.TextInput
     79 + // homeInput is a text input that accepts path to the home folder.
     80 + homeInput *textinput.TextInput
     81 + 
     82 + // submitB is a button that submits the form.
     83 + submitB *button.Button
     84 + // cancelB is a button that exist the application.
     85 + cancelB *button.Button
     86 +}
     87 + 
     88 +// newForm returns a new form instance.
     89 +// The cancel argument is a function that terminates the application when called.
     90 +func newForm(cancel context.CancelFunc) (*form, error) {
     91 + var username string
     92 + u, err := user.Current()
     93 + if err != nil {
     94 + username = "mum4k"
     95 + } else {
     96 + username = u.Username
     97 + }
     98 + 
     99 + userInput, err := textinput.New(
     100 + textinput.Label("Username: ", cell.FgColor(cell.ColorNumber(33))),
     101 + textinput.DefaultText(username),
     102 + textinput.MaxWidthCells(20),
     103 + textinput.ExclusiveKeyboardOnFocus(),
     104 + )
     105 + uidInput, err := textinput.New(
     106 + textinput.Label("UID: ", cell.FgColor(cell.ColorNumber(33))),
     107 + textinput.DefaultText("1000"),
     108 + textinput.MaxWidthCells(20),
     109 + textinput.ExclusiveKeyboardOnFocus(),
     110 + )
     111 + gidInput, err := textinput.New(
     112 + textinput.Label("GID: ", cell.FgColor(cell.ColorNumber(33))),
     113 + textinput.DefaultText("1000"),
     114 + textinput.MaxWidthCells(20),
     115 + textinput.ExclusiveKeyboardOnFocus(),
     116 + )
     117 + homeInput, err := textinput.New(
     118 + textinput.Label("Home: ", cell.FgColor(cell.ColorNumber(33))),
     119 + textinput.DefaultText(fmt.Sprintf("/home/%s", username)),
     120 + textinput.MaxWidthCells(20),
     121 + textinput.ExclusiveKeyboardOnFocus(),
     122 + )
     123 + 
     124 + submitB, err := button.NewFromChunks(buttonChunks("Submit"), nil,
     125 + button.Key(keyboard.KeyEnter),
     126 + button.GlobalKeys('s', 'S'),
     127 + button.DisableShadow(),
     128 + button.Height(1),
     129 + button.TextHorizontalPadding(0),
     130 + button.FillColor(cell.ColorBlack),
     131 + button.FocusedFillColor(cell.ColorNumber(117)),
     132 + button.PressedFillColor(cell.ColorNumber(220)),
     133 + )
     134 + if err != nil {
     135 + panic(err)
     136 + }
     137 + 
     138 + cancelB, err := button.NewFromChunks(buttonChunks("Cancel"), func() error {
     139 + cancel()
     140 + return nil
     141 + },
     142 + button.FillColor(cell.ColorNumber(220)),
     143 + button.Key(keyboard.KeyEnter),
     144 + button.GlobalKeys('c', 'C'),
     145 + button.DisableShadow(),
     146 + button.Height(1),
     147 + button.TextHorizontalPadding(0),
     148 + button.FillColor(cell.ColorBlack),
     149 + button.FocusedFillColor(cell.ColorNumber(117)),
     150 + button.PressedFillColor(cell.ColorNumber(220)),
     151 + )
     152 + if err != nil {
     153 + panic(err)
     154 + }
     155 + 
     156 + return &form{
     157 + userInput: userInput,
     158 + uidInput: uidInput,
     159 + gidInput: gidInput,
     160 + homeInput: homeInput,
     161 + submitB: submitB,
     162 + cancelB: cancelB,
     163 + }, nil
     164 +}
     165 + 
     166 +// formLayout updates the container into a layout with text inputs and buttons.
     167 +func formLayout(c *container.Container, f *form) error {
     168 + return c.Update("root",
     169 + container.KeyFocusNext(keyboard.KeyTab),
     170 + container.KeyFocusGroupsNext(keyboard.KeyArrowDown, 1),
     171 + container.KeyFocusGroupsPrevious(keyboard.KeyArrowUp, 1),
     172 + container.KeyFocusGroupsNext(keyboard.KeyArrowRight, 2),
     173 + container.KeyFocusGroupsPrevious(keyboard.KeyArrowLeft, 2),
     174 + container.SplitHorizontal(
     175 + container.Top(
     176 + container.Border(linestyle.Light),
     177 + container.SplitHorizontal(
     178 + container.Top(
     179 + container.SplitHorizontal(
     180 + container.Top(
     181 + container.Focused(),
     182 + container.KeyFocusGroups(1),
     183 + container.PlaceWidget(f.userInput),
     184 + ),
     185 + container.Bottom(
     186 + container.KeyFocusGroups(1),
     187 + container.KeyFocusSkip(),
     188 + container.PlaceWidget(f.uidInput),
     189 + ),
     190 + ),
     191 + ),
     192 + container.Bottom(
     193 + container.SplitHorizontal(
     194 + container.Top(
     195 + container.KeyFocusGroups(1),
     196 + container.KeyFocusSkip(),
     197 + container.PlaceWidget(f.gidInput),
     198 + ),
     199 + container.Bottom(
     200 + container.KeyFocusGroups(1),
     201 + container.KeyFocusSkip(),
     202 + container.PlaceWidget(f.homeInput),
     203 + ),
     204 + ),
     205 + ),
     206 + ),
     207 + ),
     208 + container.Bottom(
     209 + container.SplitHorizontal(
     210 + container.Top(
     211 + container.SplitVertical(
     212 + container.Left(
     213 + container.KeyFocusGroups(1, 2),
     214 + container.PlaceWidget(f.submitB),
     215 + container.AlignHorizontal(align.HorizontalRight),
     216 + container.PaddingRight(5),
     217 + ),
     218 + container.Right(
     219 + container.KeyFocusGroups(1, 2),
     220 + container.PlaceWidget(f.cancelB),
     221 + container.AlignHorizontal(align.HorizontalLeft),
     222 + container.PaddingLeft(5),
     223 + ),
     224 + ),
     225 + ),
     226 + container.Bottom(
     227 + container.KeyFocusSkip(),
     228 + ),
     229 + container.SplitFixed(3),
     230 + ),
     231 + ),
     232 + container.SplitFixed(6),
     233 + ),
     234 + )
     235 +}
     236 + 
     237 +// submitLayout updates the container into a layout that displays the submitted data.
     238 +// The cancel argument is a function that terminates Termdash when called.
     239 +func submitLayout(c *container.Container, f *form, cancel context.CancelFunc) error {
     240 + t, err := text.New()
     241 + if err != nil {
     242 + return err
     243 + }
     244 + 
     245 + if err := t.Write("Submitted data:\n\n"); err != nil {
     246 + return err
     247 + }
     248 + if err := t.Write(fmt.Sprintf("Username: %s\n", f.userInput.Read())); err != nil {
     249 + return err
     250 + }
     251 + if err := t.Write(fmt.Sprintf("UID: %s\n", f.uidInput.Read())); err != nil {
     252 + return err
     253 + }
     254 + if err := t.Write(fmt.Sprintf("GID: %s\n", f.gidInput.Read())); err != nil {
     255 + return err
     256 + }
     257 + if err := t.Write(fmt.Sprintf("Home: %s\n", f.homeInput.Read())); err != nil {
     258 + return err
     259 + }
     260 + 
     261 + okB, err := button.NewFromChunks(buttonChunks("OK"), func() error {
     262 + cancel()
     263 + return nil
     264 + },
     265 + button.FillColor(cell.ColorNumber(220)),
     266 + button.Key(keyboard.KeyEnter),
     267 + button.GlobalKeys('o', 'O'),
     268 + button.DisableShadow(),
     269 + button.Height(1),
     270 + button.TextHorizontalPadding(0),
     271 + button.FillColor(cell.ColorBlack),
     272 + button.FocusedFillColor(cell.ColorNumber(117)),
     273 + button.PressedFillColor(cell.ColorNumber(220)),
     274 + )
     275 + if err != nil {
     276 + return err
     277 + }
     278 + 
     279 + return c.Update("root",
     280 + container.SplitHorizontal(
     281 + container.Top(
     282 + container.SplitVertical(
     283 + container.Left(),
     284 + container.Right(
     285 + container.PlaceWidget(t),
     286 + ),
     287 + container.SplitPercent(33),
     288 + ),
     289 + ),
     290 + container.Bottom(
     291 + container.Focused(),
     292 + container.PlaceWidget(okB),
     293 + ),
     294 + container.SplitFixed(7),
     295 + ),
     296 + )
     297 +}
     298 + 
     299 +func main() {
     300 + t, err := tcell.New()
     301 + if err != nil {
     302 + panic(err)
     303 + }
     304 + defer t.Close()
     305 + 
     306 + ctx, cancel := context.WithCancel(context.Background())
     307 + c, err := container.New(t, container.ID("root"))
     308 + if err != nil {
     309 + panic(err)
     310 + }
     311 + 
     312 + f, err := newForm(cancel)
     313 + if err != nil {
     314 + panic(err)
     315 + }
     316 + f.submitB.SetCallback(func() error {
     317 + return submitLayout(c, f, cancel)
     318 + })
     319 + if err := formLayout(c, f); err != nil {
     320 + panic(err)
     321 + }
     322 + 
     323 + if err := termdash.Run(ctx, t, c, termdash.RedrawInterval(100*time.Millisecond)); err != nil {
     324 + panic(err)
     325 + }
     326 +}
     327 + 
Please wait...
Page is in error, reload to recover