Update upstream source from tag 'upstream/1.4.0'
Update to upstream version '1.4.0'
with Debian dir 80f57b0bd216bce8edac03f4955d896675d1c8c1
Stephen Gelman
1 year, 7 months ago
6 | 6 | runs-on: ubuntu-latest |
7 | 7 | steps: |
8 | 8 | |
9 | - name: Set up Go 1.12 | |
9 | - name: Set up Go 1.13 | |
10 | 10 | uses: actions/setup-go@v1 |
11 | 11 | with: |
12 | go-version: 1.12 | |
12 | go-version: 1.13 | |
13 | 13 | id: go |
14 | 14 | |
15 | 15 | - name: Check out code into the Go module directory |
2 | 2 | env: |
3 | 3 | - GO111MODULE=on |
4 | 4 | |
5 | arch: | |
6 | - amd64 | |
7 | - ppc64le | |
8 | ||
5 | 9 | go: |
6 | - 1.11.x | |
7 | - 1.12.x | |
10 | - 1.13.x | |
11 | - 1.14.x | |
12 | - 1.15.x | |
8 | 13 | |
9 | 14 | notifications: |
10 | 15 | email: true |
148 | 148 | - [tcell](https://github.com/gdamore/tcell) - a cell based view for text terminals, like xterm, inspired by termbox |
149 | 149 | - [asciigraph](https://github.com/guptarohit/asciigraph) - lightweight ASCII line-graphs for Go |
150 | 150 | - [logrus](https://github.com/sirupsen/logrus) - structured pluggable logging for Go |
151 | - [testify](github.com/stretchr/testify) - tools for testifying that your code will behave as you intend | |
151 | - [testify](https://github.com/stretchr/testify) - tools for testifying that your code will behave as you intend | |
152 | 152 | |
153 | 153 | ## Contact |
154 | 154 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
12 | 12 | "sync" |
13 | 13 | "time" |
14 | 14 | |
15 | "github.com/gdamore/tcell" | |
15 | tcell "github.com/gdamore/tcell/v2" | |
16 | 16 | log "github.com/sirupsen/logrus" |
17 | 17 | ) |
18 | 18 | |
81 | 81 | // provides services to a running gowid application, such as access to the |
82 | 82 | // palette, the screen and the state of the mouse. |
83 | 83 | type App struct { |
84 | IPalette // App holds an IPalette and provides it to each widget when rendering | |
85 | screen tcell.Screen // Each app has one screen | |
86 | TCellEvents chan tcell.Event // Events from tcell e.g. resize | |
87 | AfterRenderEvents chan IAfterRenderEvent // Functions intended to run on the widget goroutine | |
88 | closing bool // If true then app is in process of closing - it may be draining AfterRenderEvents. | |
89 | closingMtx sync.Mutex // Make sure an AfterRenderEvent and closing don't race. | |
90 | viewPlusMenus IWidget // The base widget that is displayed - includes registered menus | |
91 | view IWidget // The base widget that is displayed under registered menus | |
92 | colorMode ColorMode // The current color mode of the terminal - 256, 16, mono, etc | |
93 | inCopyMode bool // True if the app has been switched into "copy mode", for the user to copy a widget value | |
94 | copyClaimed int // True if a widget has "claimed" copy mode during this Render pass | |
95 | copyClaimedBy IIdentity | |
96 | copyLevel int | |
97 | refreshCopy bool | |
98 | prevWasMouseMove bool // True if we last processed simple mouse movement. We can optimize on slow | |
99 | // systems by discarding subsequent mouse movement events. | |
84 | IPalette // App holds an IPalette and provides it to each widget when rendering | |
85 | screen tcell.Screen // Each app has one screen | |
86 | TCellEvents chan tcell.Event // Events from tcell e.g. resize | |
87 | AfterRenderEvents chan IAfterRenderEvent // Functions intended to run on the widget goroutine | |
88 | closing bool // If true then app is in process of closing - it may be draining AfterRenderEvents. | |
89 | closingMtx sync.Mutex // Make sure an AfterRenderEvent and closing don't race. | |
90 | viewPlusMenus IWidget // The base widget that is displayed - includes registered menus | |
91 | view IWidget // The base widget that is displayed under registered menus | |
92 | colorMode ColorMode // The current color mode of the terminal - 256, 16, mono, etc | |
93 | inCopyMode bool // True if the app has been switched into "copy mode", for the user to copy a widget value | |
94 | copyClaimed int // True if a widget has "claimed" copy mode during this Render pass | |
95 | copyClaimedBy IIdentity | |
96 | copyLevel int | |
97 | refreshCopy bool | |
98 | prevWasMouseMove bool // True if we last processed simple mouse movement. We can optimize on slow | |
99 | enableMouseMotion bool | |
100 | enableBracketedPaste bool | |
101 | screenInited bool | |
102 | dontOwnScreen bool | |
103 | tty string | |
100 | 104 | |
101 | 105 | lastMouse MouseState // So I can tell if a button was previously clicked |
102 | 106 | MouseState // Track which mouse buttons are currently down |
108 | 112 | |
109 | 113 | // AppArgs is a helper struct, providing arguments for the initialization of App. |
110 | 114 | type AppArgs struct { |
111 | View IWidget | |
112 | Palette IPalette | |
113 | Log log.StdLogger | |
114 | DontActivate bool | |
115 | Screen tcell.Screen | |
116 | View IWidget | |
117 | Palette IPalette | |
118 | EnableMouseMotion bool | |
119 | EnableBracketedPaste bool | |
120 | Log log.StdLogger | |
121 | DontActivate bool | |
122 | Tty string | |
115 | 123 | } |
116 | 124 | |
117 | 125 | // IUnhandledInput is used as a handler for application user input that is not handled by any |
230 | 238 | } |
231 | 239 | |
232 | 240 | // NewAppSafe returns an initialized App struct, or an error on failure. It will |
233 | // initialize a tcell.Screen object behind the scenes, and enable mouse support | |
241 | // initialize a tcell.Screen object and enable mouse support if its not provided, | |
234 | 242 | // meaning that tcell will receive mouse events if the terminal supports them. |
235 | 243 | func newApp(args AppArgs) (rapp *App, rerr error) { |
236 | screen, err := tcell.NewScreen() | |
237 | if err != nil { | |
238 | rerr = WithKVs(err, map[string]interface{}{"TERM": os.Getenv("TERM")}) | |
239 | return | |
244 | screen := args.Screen | |
245 | if screen == nil { | |
246 | var err error | |
247 | screen, err = tcellScreen(args.Tty) | |
248 | if err != nil { | |
249 | rerr = WithKVs(err, map[string]interface{}{"TERM": os.Getenv("TERM")}) | |
250 | return | |
251 | } | |
240 | 252 | } |
241 | 253 | |
242 | 254 | var palette IPalette = args.Palette |
262 | 274 | } |
263 | 275 | |
264 | 276 | res := &App{ |
265 | IPalette: palette, | |
266 | screen: screen, | |
267 | TCellEvents: tch, | |
268 | AfterRenderEvents: wch, | |
269 | closing: false, | |
270 | view: args.View, | |
271 | viewPlusMenus: args.View, | |
272 | colorMode: Mode256Colors, | |
273 | ClickTargets: clicks, | |
274 | log: args.Log, | |
275 | } | |
276 | ||
277 | if !args.DontActivate { | |
278 | if err = res.initScreen(); err != nil { | |
277 | IPalette: palette, | |
278 | screen: screen, | |
279 | TCellEvents: tch, | |
280 | AfterRenderEvents: wch, | |
281 | closing: false, | |
282 | view: args.View, | |
283 | viewPlusMenus: args.View, | |
284 | colorMode: Mode256Colors, | |
285 | ClickTargets: clicks, | |
286 | log: args.Log, | |
287 | enableMouseMotion: args.EnableMouseMotion, | |
288 | enableBracketedPaste: args.EnableBracketedPaste, | |
289 | dontOwnScreen: args.Screen != nil, | |
290 | tty: args.Tty, | |
291 | } | |
292 | ||
293 | if !res.dontOwnScreen && !args.DontActivate { | |
294 | if err := res.initScreen(); err != nil { | |
279 | 295 | return nil, err |
280 | 296 | } |
281 | 297 | res.initColorMode() |
298 | if res.enableBracketedPaste { | |
299 | screen.EnablePaste() | |
300 | } | |
282 | 301 | } |
283 | 302 | |
284 | 303 | screen.Clear() |
460 | 479 | // internal state, like the size of the underlying terminal. |
461 | 480 | func (a *App) HandleTCellEvent(ev interface{}, unhandled IUnhandledInput) { |
462 | 481 | switch ev := ev.(type) { |
463 | case *tcell.EventKey: | |
482 | case *tcell.EventKey, *tcell.EventPaste: | |
464 | 483 | // This makes for a better experience on limited hardware like raspberry pi |
465 | 484 | debug.SetGCPercent(-1) |
466 | 485 | defer debug.SetGCPercent(100) |
478 | 497 | a.refreshCopy = false |
479 | 498 | } |
480 | 499 | a.RedrawTerminal() |
500 | ||
501 | //case *tcell.EventPaste: | |
502 | //log.Infof("GCLA: app.go tcell paste") | |
503 | ||
481 | 504 | case *tcell.EventMouse: |
482 | if !a.prevWasMouseMove || ev.Modifiers() != 0 || ev.Buttons() != 0 { | |
505 | if !a.prevWasMouseMove || a.enableMouseMotion || ev.Modifiers() != 0 || ev.Buttons() != 0 { | |
483 | 506 | switch ev.Buttons() { |
484 | 507 | case tcell.Button1: |
485 | 508 | a.MouseLeftClicked = true |
659 | 682 | // currentView's internal buffer is modified if currentView.Editable is true. |
660 | 683 | func (a *App) handleInputEvent(ev interface{}, unhandled IUnhandledInput) { |
661 | 684 | switch ev.(type) { |
662 | case *tcell.EventKey, *tcell.EventMouse: | |
685 | case *tcell.EventKey, *tcell.EventPaste, *tcell.EventMouse: | |
663 | 686 | x, y := a.TerminalSize() |
664 | 687 | handled := UserInputIfSelectable(a.viewPlusMenus, ev, RenderBox{C: x, R: y}, Focused, a) |
665 | 688 | if !handled { |
791 | 814 | // I can't make tcell claim and release the same screen successfully. Clients of |
792 | 815 | // the app struct shouldn't cache the screen object returned via GetScreen(). |
793 | 816 | // |
817 | // Assumes we own the screen... | |
794 | 818 | func (a *App) ActivateScreen() error { |
795 | screen, err := tcell.NewScreen() | |
819 | screen, err := tcellScreen(a.tty) | |
796 | 820 | if err != nil { |
797 | 821 | return WithKVs(err, map[string]interface{}{"TERM": os.Getenv("TERM")}) |
798 | 822 | } |
823 | a.DeactivateScreen() | |
799 | 824 | a.screen = screen |
800 | 825 | if err := a.initScreen(); err != nil { |
801 | 826 | return err |
802 | 827 | } |
828 | if a.enableBracketedPaste { | |
829 | a.screen.EnablePaste() | |
830 | } | |
803 | 831 | return nil |
804 | 832 | } |
805 | 833 | |
834 | // Assumes we own the screen | |
806 | 835 | func (a *App) DeactivateScreen() { |
807 | a.screen.Fini() | |
808 | a.screen = nil | |
836 | if a.screen != nil && a.screenInited { | |
837 | a.screen.Fini() | |
838 | a.screen = nil | |
839 | a.screenInited = false | |
840 | } | |
809 | 841 | } |
810 | 842 | |
811 | 843 | func (a *App) initScreen() error { |
813 | 845 | return WithKVs(err, map[string]interface{}{"TERM": os.Getenv("TERM")}) |
814 | 846 | } |
815 | 847 | |
848 | a.screenInited = true | |
816 | 849 | a.initColorMode() |
817 | 850 | |
818 | 851 | defFg := ColorDefault |
824 | 857 | defBg = IColorToTCell(bgCol, defBg, a.GetColorMode()) |
825 | 858 | defSt = defSt.MergeUnder(style) |
826 | 859 | } |
827 | defStyle := tcell.Style(defSt.OnOff).Background(defBg.ToTCell()).Foreground(defFg.ToTCell()) | |
860 | defStyle := tcell.Style{}.Attributes(defSt.OnOff).Background(defBg.ToTCell()).Foreground(defFg.ToTCell()) | |
828 | 861 | // Ask TCell to set the screen's default style according to the palette's "default" |
829 | 862 | // config, if one is provided. This might make every screen cell underlined, for example, |
830 | 863 | // in the absence of overriding styling from widgets. |
831 | 864 | a.screen.SetStyle(defStyle) |
832 | 865 | a.screen.EnableMouse() |
833 | ||
866 | if a.enableBracketedPaste { | |
867 | a.screen.EnablePaste() | |
868 | } | |
834 | 869 | return nil |
835 | 870 | } |
836 | 871 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package gowid |
111 | 111 | } |
112 | 112 | |
113 | 113 | func (c *Callbacks) RemoveCallback(name interface{}, cb IIdentity) bool { |
114 | if c == nil { | |
115 | return false | |
116 | } | |
114 | 117 | c.Lock() |
115 | 118 | defer c.Unlock() |
116 | 119 | cbs, ok := c.callbacks[name] |
119 | 122 | ok = false |
120 | 123 | for i, cb2 := range cbs { |
121 | 124 | if cb.ID() == cb2.ID() { |
122 | //delete(c.callbacks, name) | |
123 | 125 | // Append backwards for easier deletion later |
124 | 126 | idxs = append([]int{i}, idxs...) |
125 | 127 | } |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package gowid |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
10 | 10 | "unicode/utf8" |
11 | 11 | |
12 | 12 | "github.com/gcla/gowid/gwutil" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | "github.com/mattn/go-runewidth" |
15 | 15 | "github.com/pkg/errors" |
16 | 16 | ) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package gowid |
9 | 9 | "strconv" |
10 | 10 | |
11 | 11 | "github.com/gcla/gowid/gwutil" |
12 | "github.com/gdamore/tcell" | |
12 | tcell "github.com/gdamore/tcell/v2" | |
13 | 13 | lru "github.com/hashicorp/golang-lru" |
14 | 14 | "github.com/lucasb-eyer/go-colorful" |
15 | 15 | "github.com/pkg/errors" |
626 | 626 | } |
627 | 627 | |
628 | 628 | term256 = []TCellColor{ |
629 | MakeTCellColorExt(tcell.Color(0)), | |
630 | MakeTCellColorExt(tcell.Color(1)), | |
631 | MakeTCellColorExt(tcell.Color(2)), | |
632 | MakeTCellColorExt(tcell.Color(3)), | |
633 | MakeTCellColorExt(tcell.Color(4)), | |
634 | MakeTCellColorExt(tcell.Color(5)), | |
635 | MakeTCellColorExt(tcell.Color(6)), | |
636 | MakeTCellColorExt(tcell.Color(7)), | |
637 | MakeTCellColorExt(tcell.Color(8)), | |
638 | MakeTCellColorExt(tcell.Color(9)), | |
639 | MakeTCellColorExt(tcell.Color(10)), | |
640 | MakeTCellColorExt(tcell.Color(11)), | |
641 | MakeTCellColorExt(tcell.Color(12)), | |
642 | MakeTCellColorExt(tcell.Color(13)), | |
643 | MakeTCellColorExt(tcell.Color(14)), | |
644 | MakeTCellColorExt(tcell.Color(15)), | |
629 | MakeTCellColorExt(tcell.ColorBlack), | |
630 | MakeTCellColorExt(tcell.ColorMaroon), | |
631 | MakeTCellColorExt(tcell.ColorGreen), | |
632 | MakeTCellColorExt(tcell.ColorOlive), | |
633 | MakeTCellColorExt(tcell.ColorNavy), | |
634 | MakeTCellColorExt(tcell.ColorPurple), | |
635 | MakeTCellColorExt(tcell.ColorTeal), | |
636 | MakeTCellColorExt(tcell.ColorSilver), | |
637 | MakeTCellColorExt(tcell.ColorGray), | |
638 | MakeTCellColorExt(tcell.ColorRed), | |
639 | MakeTCellColorExt(tcell.ColorLime), | |
640 | MakeTCellColorExt(tcell.ColorYellow), | |
641 | MakeTCellColorExt(tcell.ColorBlue), | |
642 | MakeTCellColorExt(tcell.ColorFuchsia), | |
643 | MakeTCellColorExt(tcell.ColorAqua), | |
644 | MakeTCellColorExt(tcell.ColorWhite), | |
645 | 645 | // |
646 | MakeTCellColorExt(tcell.Color(16)), | |
647 | MakeTCellColorExt(tcell.Color(17)), | |
648 | MakeTCellColorExt(tcell.Color(18)), | |
649 | MakeTCellColorExt(tcell.Color(19)), | |
650 | MakeTCellColorExt(tcell.Color(20)), | |
651 | MakeTCellColorExt(tcell.Color(21)), | |
652 | MakeTCellColorExt(tcell.Color(22)), | |
653 | MakeTCellColorExt(tcell.Color(23)), | |
654 | MakeTCellColorExt(tcell.Color(24)), | |
655 | MakeTCellColorExt(tcell.Color(25)), | |
656 | MakeTCellColorExt(tcell.Color(26)), | |
657 | MakeTCellColorExt(tcell.Color(27)), | |
658 | MakeTCellColorExt(tcell.Color(28)), | |
659 | MakeTCellColorExt(tcell.Color(29)), | |
660 | MakeTCellColorExt(tcell.Color(30)), | |
661 | MakeTCellColorExt(tcell.Color(31)), | |
662 | MakeTCellColorExt(tcell.Color(32)), | |
663 | MakeTCellColorExt(tcell.Color(33)), | |
664 | MakeTCellColorExt(tcell.Color(34)), | |
665 | MakeTCellColorExt(tcell.Color(35)), | |
666 | MakeTCellColorExt(tcell.Color(36)), | |
667 | MakeTCellColorExt(tcell.Color(37)), | |
668 | MakeTCellColorExt(tcell.Color(38)), | |
669 | MakeTCellColorExt(tcell.Color(39)), | |
670 | MakeTCellColorExt(tcell.Color(40)), | |
671 | MakeTCellColorExt(tcell.Color(41)), | |
672 | MakeTCellColorExt(tcell.Color(42)), | |
673 | MakeTCellColorExt(tcell.Color(43)), | |
674 | MakeTCellColorExt(tcell.Color(44)), | |
675 | MakeTCellColorExt(tcell.Color(45)), | |
676 | MakeTCellColorExt(tcell.Color(46)), | |
677 | MakeTCellColorExt(tcell.Color(47)), | |
678 | MakeTCellColorExt(tcell.Color(48)), | |
679 | MakeTCellColorExt(tcell.Color(49)), | |
680 | MakeTCellColorExt(tcell.Color(50)), | |
681 | MakeTCellColorExt(tcell.Color(51)), | |
682 | MakeTCellColorExt(tcell.Color(52)), | |
683 | MakeTCellColorExt(tcell.Color(53)), | |
684 | MakeTCellColorExt(tcell.Color(54)), | |
685 | MakeTCellColorExt(tcell.Color(55)), | |
686 | MakeTCellColorExt(tcell.Color(56)), | |
687 | MakeTCellColorExt(tcell.Color(57)), | |
688 | MakeTCellColorExt(tcell.Color(58)), | |
689 | MakeTCellColorExt(tcell.Color(59)), | |
690 | MakeTCellColorExt(tcell.Color(60)), | |
691 | MakeTCellColorExt(tcell.Color(61)), | |
692 | MakeTCellColorExt(tcell.Color(62)), | |
693 | MakeTCellColorExt(tcell.Color(63)), | |
694 | MakeTCellColorExt(tcell.Color(64)), | |
695 | MakeTCellColorExt(tcell.Color(65)), | |
696 | MakeTCellColorExt(tcell.Color(66)), | |
697 | MakeTCellColorExt(tcell.Color(67)), | |
698 | MakeTCellColorExt(tcell.Color(68)), | |
699 | MakeTCellColorExt(tcell.Color(69)), | |
700 | MakeTCellColorExt(tcell.Color(70)), | |
701 | MakeTCellColorExt(tcell.Color(71)), | |
702 | MakeTCellColorExt(tcell.Color(72)), | |
703 | MakeTCellColorExt(tcell.Color(73)), | |
704 | MakeTCellColorExt(tcell.Color(74)), | |
705 | MakeTCellColorExt(tcell.Color(75)), | |
706 | MakeTCellColorExt(tcell.Color(76)), | |
707 | MakeTCellColorExt(tcell.Color(77)), | |
708 | MakeTCellColorExt(tcell.Color(78)), | |
709 | MakeTCellColorExt(tcell.Color(79)), | |
710 | MakeTCellColorExt(tcell.Color(80)), | |
711 | MakeTCellColorExt(tcell.Color(81)), | |
712 | MakeTCellColorExt(tcell.Color(82)), | |
713 | MakeTCellColorExt(tcell.Color(83)), | |
714 | MakeTCellColorExt(tcell.Color(84)), | |
715 | MakeTCellColorExt(tcell.Color(85)), | |
716 | MakeTCellColorExt(tcell.Color(86)), | |
717 | MakeTCellColorExt(tcell.Color(87)), | |
718 | MakeTCellColorExt(tcell.Color(88)), | |
719 | MakeTCellColorExt(tcell.Color(89)), | |
720 | MakeTCellColorExt(tcell.Color(90)), | |
721 | MakeTCellColorExt(tcell.Color(91)), | |
722 | MakeTCellColorExt(tcell.Color(92)), | |
723 | MakeTCellColorExt(tcell.Color(93)), | |
724 | MakeTCellColorExt(tcell.Color(94)), | |
725 | MakeTCellColorExt(tcell.Color(95)), | |
726 | MakeTCellColorExt(tcell.Color(96)), | |
727 | MakeTCellColorExt(tcell.Color(97)), | |
728 | MakeTCellColorExt(tcell.Color(98)), | |
729 | MakeTCellColorExt(tcell.Color(99)), | |
730 | MakeTCellColorExt(tcell.Color(100)), | |
731 | MakeTCellColorExt(tcell.Color(101)), | |
732 | MakeTCellColorExt(tcell.Color(102)), | |
733 | MakeTCellColorExt(tcell.Color(103)), | |
734 | MakeTCellColorExt(tcell.Color(104)), | |
735 | MakeTCellColorExt(tcell.Color(105)), | |
736 | MakeTCellColorExt(tcell.Color(106)), | |
737 | MakeTCellColorExt(tcell.Color(107)), | |
738 | MakeTCellColorExt(tcell.Color(108)), | |
739 | MakeTCellColorExt(tcell.Color(109)), | |
740 | MakeTCellColorExt(tcell.Color(110)), | |
741 | MakeTCellColorExt(tcell.Color(111)), | |
742 | MakeTCellColorExt(tcell.Color(112)), | |
743 | MakeTCellColorExt(tcell.Color(113)), | |
744 | MakeTCellColorExt(tcell.Color(114)), | |
745 | MakeTCellColorExt(tcell.Color(115)), | |
746 | MakeTCellColorExt(tcell.Color(116)), | |
747 | MakeTCellColorExt(tcell.Color(117)), | |
748 | MakeTCellColorExt(tcell.Color(118)), | |
749 | MakeTCellColorExt(tcell.Color(119)), | |
750 | MakeTCellColorExt(tcell.Color(120)), | |
751 | MakeTCellColorExt(tcell.Color(121)), | |
752 | MakeTCellColorExt(tcell.Color(122)), | |
753 | MakeTCellColorExt(tcell.Color(123)), | |
754 | MakeTCellColorExt(tcell.Color(124)), | |
755 | MakeTCellColorExt(tcell.Color(125)), | |
756 | MakeTCellColorExt(tcell.Color(126)), | |
757 | MakeTCellColorExt(tcell.Color(127)), | |
758 | MakeTCellColorExt(tcell.Color(128)), | |
759 | MakeTCellColorExt(tcell.Color(129)), | |
760 | MakeTCellColorExt(tcell.Color(130)), | |
761 | MakeTCellColorExt(tcell.Color(131)), | |
762 | MakeTCellColorExt(tcell.Color(132)), | |
763 | MakeTCellColorExt(tcell.Color(133)), | |
764 | MakeTCellColorExt(tcell.Color(134)), | |
765 | MakeTCellColorExt(tcell.Color(135)), | |
766 | MakeTCellColorExt(tcell.Color(136)), | |
767 | MakeTCellColorExt(tcell.Color(137)), | |
768 | MakeTCellColorExt(tcell.Color(138)), | |
769 | MakeTCellColorExt(tcell.Color(139)), | |
770 | MakeTCellColorExt(tcell.Color(140)), | |
771 | MakeTCellColorExt(tcell.Color(141)), | |
772 | MakeTCellColorExt(tcell.Color(142)), | |
773 | MakeTCellColorExt(tcell.Color(143)), | |
774 | MakeTCellColorExt(tcell.Color(144)), | |
775 | MakeTCellColorExt(tcell.Color(145)), | |
776 | MakeTCellColorExt(tcell.Color(146)), | |
777 | MakeTCellColorExt(tcell.Color(147)), | |
778 | MakeTCellColorExt(tcell.Color(148)), | |
779 | MakeTCellColorExt(tcell.Color(149)), | |
780 | MakeTCellColorExt(tcell.Color(150)), | |
781 | MakeTCellColorExt(tcell.Color(151)), | |
782 | MakeTCellColorExt(tcell.Color(152)), | |
783 | MakeTCellColorExt(tcell.Color(153)), | |
784 | MakeTCellColorExt(tcell.Color(154)), | |
785 | MakeTCellColorExt(tcell.Color(155)), | |
786 | MakeTCellColorExt(tcell.Color(156)), | |
787 | MakeTCellColorExt(tcell.Color(157)), | |
788 | MakeTCellColorExt(tcell.Color(158)), | |
789 | MakeTCellColorExt(tcell.Color(159)), | |
790 | MakeTCellColorExt(tcell.Color(160)), | |
791 | MakeTCellColorExt(tcell.Color(161)), | |
792 | MakeTCellColorExt(tcell.Color(162)), | |
793 | MakeTCellColorExt(tcell.Color(163)), | |
794 | MakeTCellColorExt(tcell.Color(164)), | |
795 | MakeTCellColorExt(tcell.Color(165)), | |
796 | MakeTCellColorExt(tcell.Color(166)), | |
797 | MakeTCellColorExt(tcell.Color(167)), | |
798 | MakeTCellColorExt(tcell.Color(168)), | |
799 | MakeTCellColorExt(tcell.Color(169)), | |
800 | MakeTCellColorExt(tcell.Color(170)), | |
801 | MakeTCellColorExt(tcell.Color(171)), | |
802 | MakeTCellColorExt(tcell.Color(172)), | |
803 | MakeTCellColorExt(tcell.Color(173)), | |
804 | MakeTCellColorExt(tcell.Color(174)), | |
805 | MakeTCellColorExt(tcell.Color(175)), | |
806 | MakeTCellColorExt(tcell.Color(176)), | |
807 | MakeTCellColorExt(tcell.Color(177)), | |
808 | MakeTCellColorExt(tcell.Color(178)), | |
809 | MakeTCellColorExt(tcell.Color(179)), | |
810 | MakeTCellColorExt(tcell.Color(180)), | |
811 | MakeTCellColorExt(tcell.Color(181)), | |
812 | MakeTCellColorExt(tcell.Color(182)), | |
813 | MakeTCellColorExt(tcell.Color(183)), | |
814 | MakeTCellColorExt(tcell.Color(184)), | |
815 | MakeTCellColorExt(tcell.Color(185)), | |
816 | MakeTCellColorExt(tcell.Color(186)), | |
817 | MakeTCellColorExt(tcell.Color(187)), | |
818 | MakeTCellColorExt(tcell.Color(188)), | |
819 | MakeTCellColorExt(tcell.Color(189)), | |
820 | MakeTCellColorExt(tcell.Color(190)), | |
821 | MakeTCellColorExt(tcell.Color(191)), | |
822 | MakeTCellColorExt(tcell.Color(192)), | |
823 | MakeTCellColorExt(tcell.Color(193)), | |
824 | MakeTCellColorExt(tcell.Color(194)), | |
825 | MakeTCellColorExt(tcell.Color(195)), | |
826 | MakeTCellColorExt(tcell.Color(196)), | |
827 | MakeTCellColorExt(tcell.Color(197)), | |
828 | MakeTCellColorExt(tcell.Color(198)), | |
829 | MakeTCellColorExt(tcell.Color(199)), | |
830 | MakeTCellColorExt(tcell.Color(200)), | |
831 | MakeTCellColorExt(tcell.Color(201)), | |
832 | MakeTCellColorExt(tcell.Color(202)), | |
833 | MakeTCellColorExt(tcell.Color(203)), | |
834 | MakeTCellColorExt(tcell.Color(204)), | |
835 | MakeTCellColorExt(tcell.Color(205)), | |
836 | MakeTCellColorExt(tcell.Color(206)), | |
837 | MakeTCellColorExt(tcell.Color(207)), | |
838 | MakeTCellColorExt(tcell.Color(208)), | |
839 | MakeTCellColorExt(tcell.Color(209)), | |
840 | MakeTCellColorExt(tcell.Color(210)), | |
841 | MakeTCellColorExt(tcell.Color(211)), | |
842 | MakeTCellColorExt(tcell.Color(212)), | |
843 | MakeTCellColorExt(tcell.Color(213)), | |
844 | MakeTCellColorExt(tcell.Color(214)), | |
845 | MakeTCellColorExt(tcell.Color(215)), | |
846 | MakeTCellColorExt(tcell.Color(216)), | |
847 | MakeTCellColorExt(tcell.Color(217)), | |
848 | MakeTCellColorExt(tcell.Color(218)), | |
849 | MakeTCellColorExt(tcell.Color(219)), | |
850 | MakeTCellColorExt(tcell.Color(220)), | |
851 | MakeTCellColorExt(tcell.Color(221)), | |
852 | MakeTCellColorExt(tcell.Color(222)), | |
853 | MakeTCellColorExt(tcell.Color(223)), | |
854 | MakeTCellColorExt(tcell.Color(224)), | |
855 | MakeTCellColorExt(tcell.Color(225)), | |
856 | MakeTCellColorExt(tcell.Color(226)), | |
857 | MakeTCellColorExt(tcell.Color(227)), | |
858 | MakeTCellColorExt(tcell.Color(228)), | |
859 | MakeTCellColorExt(tcell.Color(229)), | |
860 | MakeTCellColorExt(tcell.Color(230)), | |
861 | MakeTCellColorExt(tcell.Color(231)), | |
862 | MakeTCellColorExt(tcell.Color(232)), | |
863 | MakeTCellColorExt(tcell.Color(233)), | |
864 | MakeTCellColorExt(tcell.Color(234)), | |
865 | MakeTCellColorExt(tcell.Color(235)), | |
866 | MakeTCellColorExt(tcell.Color(236)), | |
867 | MakeTCellColorExt(tcell.Color(237)), | |
868 | MakeTCellColorExt(tcell.Color(238)), | |
869 | MakeTCellColorExt(tcell.Color(239)), | |
870 | MakeTCellColorExt(tcell.Color(240)), | |
871 | MakeTCellColorExt(tcell.Color(241)), | |
872 | MakeTCellColorExt(tcell.Color(242)), | |
873 | MakeTCellColorExt(tcell.Color(243)), | |
874 | MakeTCellColorExt(tcell.Color(244)), | |
875 | MakeTCellColorExt(tcell.Color(245)), | |
876 | MakeTCellColorExt(tcell.Color(246)), | |
877 | MakeTCellColorExt(tcell.Color(247)), | |
878 | MakeTCellColorExt(tcell.Color(248)), | |
879 | MakeTCellColorExt(tcell.Color(249)), | |
880 | MakeTCellColorExt(tcell.Color(250)), | |
881 | MakeTCellColorExt(tcell.Color(251)), | |
882 | MakeTCellColorExt(tcell.Color(252)), | |
883 | MakeTCellColorExt(tcell.Color(253)), | |
884 | MakeTCellColorExt(tcell.Color(254)), | |
885 | MakeTCellColorExt(tcell.Color(255)), | |
646 | MakeTCellColorExt(tcell.Color16), | |
647 | MakeTCellColorExt(tcell.Color17), | |
648 | MakeTCellColorExt(tcell.Color18), | |
649 | MakeTCellColorExt(tcell.Color19), | |
650 | MakeTCellColorExt(tcell.Color20), | |
651 | MakeTCellColorExt(tcell.Color21), | |
652 | MakeTCellColorExt(tcell.Color22), | |
653 | MakeTCellColorExt(tcell.Color23), | |
654 | MakeTCellColorExt(tcell.Color24), | |
655 | MakeTCellColorExt(tcell.Color25), | |
656 | MakeTCellColorExt(tcell.Color26), | |
657 | MakeTCellColorExt(tcell.Color27), | |
658 | MakeTCellColorExt(tcell.Color28), | |
659 | MakeTCellColorExt(tcell.Color29), | |
660 | MakeTCellColorExt(tcell.Color30), | |
661 | MakeTCellColorExt(tcell.Color31), | |
662 | MakeTCellColorExt(tcell.Color32), | |
663 | MakeTCellColorExt(tcell.Color33), | |
664 | MakeTCellColorExt(tcell.Color34), | |
665 | MakeTCellColorExt(tcell.Color35), | |
666 | MakeTCellColorExt(tcell.Color36), | |
667 | MakeTCellColorExt(tcell.Color37), | |
668 | MakeTCellColorExt(tcell.Color38), | |
669 | MakeTCellColorExt(tcell.Color39), | |
670 | MakeTCellColorExt(tcell.Color40), | |
671 | MakeTCellColorExt(tcell.Color41), | |
672 | MakeTCellColorExt(tcell.Color42), | |
673 | MakeTCellColorExt(tcell.Color43), | |
674 | MakeTCellColorExt(tcell.Color44), | |
675 | MakeTCellColorExt(tcell.Color45), | |
676 | MakeTCellColorExt(tcell.Color46), | |
677 | MakeTCellColorExt(tcell.Color47), | |
678 | MakeTCellColorExt(tcell.Color48), | |
679 | MakeTCellColorExt(tcell.Color49), | |
680 | MakeTCellColorExt(tcell.Color50), | |
681 | MakeTCellColorExt(tcell.Color51), | |
682 | MakeTCellColorExt(tcell.Color52), | |
683 | MakeTCellColorExt(tcell.Color53), | |
684 | MakeTCellColorExt(tcell.Color54), | |
685 | MakeTCellColorExt(tcell.Color55), | |
686 | MakeTCellColorExt(tcell.Color56), | |
687 | MakeTCellColorExt(tcell.Color57), | |
688 | MakeTCellColorExt(tcell.Color58), | |
689 | MakeTCellColorExt(tcell.Color59), | |
690 | MakeTCellColorExt(tcell.Color60), | |
691 | MakeTCellColorExt(tcell.Color61), | |
692 | MakeTCellColorExt(tcell.Color62), | |
693 | MakeTCellColorExt(tcell.Color63), | |
694 | MakeTCellColorExt(tcell.Color64), | |
695 | MakeTCellColorExt(tcell.Color65), | |
696 | MakeTCellColorExt(tcell.Color66), | |
697 | MakeTCellColorExt(tcell.Color67), | |
698 | MakeTCellColorExt(tcell.Color68), | |
699 | MakeTCellColorExt(tcell.Color69), | |
700 | MakeTCellColorExt(tcell.Color70), | |
701 | MakeTCellColorExt(tcell.Color71), | |
702 | MakeTCellColorExt(tcell.Color72), | |
703 | MakeTCellColorExt(tcell.Color73), | |
704 | MakeTCellColorExt(tcell.Color74), | |
705 | MakeTCellColorExt(tcell.Color75), | |
706 | MakeTCellColorExt(tcell.Color76), | |
707 | MakeTCellColorExt(tcell.Color77), | |
708 | MakeTCellColorExt(tcell.Color78), | |
709 | MakeTCellColorExt(tcell.Color79), | |
710 | MakeTCellColorExt(tcell.Color80), | |
711 | MakeTCellColorExt(tcell.Color81), | |
712 | MakeTCellColorExt(tcell.Color82), | |
713 | MakeTCellColorExt(tcell.Color83), | |
714 | MakeTCellColorExt(tcell.Color84), | |
715 | MakeTCellColorExt(tcell.Color85), | |
716 | MakeTCellColorExt(tcell.Color86), | |
717 | MakeTCellColorExt(tcell.Color87), | |
718 | MakeTCellColorExt(tcell.Color88), | |
719 | MakeTCellColorExt(tcell.Color89), | |
720 | MakeTCellColorExt(tcell.Color90), | |
721 | MakeTCellColorExt(tcell.Color91), | |
722 | MakeTCellColorExt(tcell.Color92), | |
723 | MakeTCellColorExt(tcell.Color93), | |
724 | MakeTCellColorExt(tcell.Color94), | |
725 | MakeTCellColorExt(tcell.Color95), | |
726 | MakeTCellColorExt(tcell.Color96), | |
727 | MakeTCellColorExt(tcell.Color97), | |
728 | MakeTCellColorExt(tcell.Color98), | |
729 | MakeTCellColorExt(tcell.Color99), | |
730 | MakeTCellColorExt(tcell.Color100), | |
731 | MakeTCellColorExt(tcell.Color101), | |
732 | MakeTCellColorExt(tcell.Color102), | |
733 | MakeTCellColorExt(tcell.Color103), | |
734 | MakeTCellColorExt(tcell.Color104), | |
735 | MakeTCellColorExt(tcell.Color105), | |
736 | MakeTCellColorExt(tcell.Color106), | |
737 | MakeTCellColorExt(tcell.Color107), | |
738 | MakeTCellColorExt(tcell.Color108), | |
739 | MakeTCellColorExt(tcell.Color109), | |
740 | MakeTCellColorExt(tcell.Color110), | |
741 | MakeTCellColorExt(tcell.Color111), | |
742 | MakeTCellColorExt(tcell.Color112), | |
743 | MakeTCellColorExt(tcell.Color113), | |
744 | MakeTCellColorExt(tcell.Color114), | |
745 | MakeTCellColorExt(tcell.Color115), | |
746 | MakeTCellColorExt(tcell.Color116), | |
747 | MakeTCellColorExt(tcell.Color117), | |
748 | MakeTCellColorExt(tcell.Color118), | |
749 | MakeTCellColorExt(tcell.Color119), | |
750 | MakeTCellColorExt(tcell.Color120), | |
751 | MakeTCellColorExt(tcell.Color121), | |
752 | MakeTCellColorExt(tcell.Color122), | |
753 | MakeTCellColorExt(tcell.Color123), | |
754 | MakeTCellColorExt(tcell.Color124), | |
755 | MakeTCellColorExt(tcell.Color125), | |
756 | MakeTCellColorExt(tcell.Color126), | |
757 | MakeTCellColorExt(tcell.Color127), | |
758 | MakeTCellColorExt(tcell.Color128), | |
759 | MakeTCellColorExt(tcell.Color129), | |
760 | MakeTCellColorExt(tcell.Color130), | |
761 | MakeTCellColorExt(tcell.Color131), | |
762 | MakeTCellColorExt(tcell.Color132), | |
763 | MakeTCellColorExt(tcell.Color133), | |
764 | MakeTCellColorExt(tcell.Color134), | |
765 | MakeTCellColorExt(tcell.Color135), | |
766 | MakeTCellColorExt(tcell.Color136), | |
767 | MakeTCellColorExt(tcell.Color137), | |
768 | MakeTCellColorExt(tcell.Color138), | |
769 | MakeTCellColorExt(tcell.Color139), | |
770 | MakeTCellColorExt(tcell.Color140), | |
771 | MakeTCellColorExt(tcell.Color141), | |
772 | MakeTCellColorExt(tcell.Color142), | |
773 | MakeTCellColorExt(tcell.Color143), | |
774 | MakeTCellColorExt(tcell.Color144), | |
775 | MakeTCellColorExt(tcell.Color145), | |
776 | MakeTCellColorExt(tcell.Color146), | |
777 | MakeTCellColorExt(tcell.Color147), | |
778 | MakeTCellColorExt(tcell.Color148), | |
779 | MakeTCellColorExt(tcell.Color149), | |
780 | MakeTCellColorExt(tcell.Color150), | |
781 | MakeTCellColorExt(tcell.Color151), | |
782 | MakeTCellColorExt(tcell.Color152), | |
783 | MakeTCellColorExt(tcell.Color153), | |
784 | MakeTCellColorExt(tcell.Color154), | |
785 | MakeTCellColorExt(tcell.Color155), | |
786 | MakeTCellColorExt(tcell.Color156), | |
787 | MakeTCellColorExt(tcell.Color157), | |
788 | MakeTCellColorExt(tcell.Color158), | |
789 | MakeTCellColorExt(tcell.Color159), | |
790 | MakeTCellColorExt(tcell.Color160), | |
791 | MakeTCellColorExt(tcell.Color161), | |
792 | MakeTCellColorExt(tcell.Color162), | |
793 | MakeTCellColorExt(tcell.Color163), | |
794 | MakeTCellColorExt(tcell.Color164), | |
795 | MakeTCellColorExt(tcell.Color165), | |
796 | MakeTCellColorExt(tcell.Color166), | |
797 | MakeTCellColorExt(tcell.Color167), | |
798 | MakeTCellColorExt(tcell.Color168), | |
799 | MakeTCellColorExt(tcell.Color169), | |
800 | MakeTCellColorExt(tcell.Color170), | |
801 | MakeTCellColorExt(tcell.Color171), | |
802 | MakeTCellColorExt(tcell.Color172), | |
803 | MakeTCellColorExt(tcell.Color173), | |
804 | MakeTCellColorExt(tcell.Color174), | |
805 | MakeTCellColorExt(tcell.Color175), | |
806 | MakeTCellColorExt(tcell.Color176), | |
807 | MakeTCellColorExt(tcell.Color177), | |
808 | MakeTCellColorExt(tcell.Color178), | |
809 | MakeTCellColorExt(tcell.Color179), | |
810 | MakeTCellColorExt(tcell.Color180), | |
811 | MakeTCellColorExt(tcell.Color181), | |
812 | MakeTCellColorExt(tcell.Color182), | |
813 | MakeTCellColorExt(tcell.Color183), | |
814 | MakeTCellColorExt(tcell.Color184), | |
815 | MakeTCellColorExt(tcell.Color185), | |
816 | MakeTCellColorExt(tcell.Color186), | |
817 | MakeTCellColorExt(tcell.Color187), | |
818 | MakeTCellColorExt(tcell.Color188), | |
819 | MakeTCellColorExt(tcell.Color189), | |
820 | MakeTCellColorExt(tcell.Color190), | |
821 | MakeTCellColorExt(tcell.Color191), | |
822 | MakeTCellColorExt(tcell.Color192), | |
823 | MakeTCellColorExt(tcell.Color193), | |
824 | MakeTCellColorExt(tcell.Color194), | |
825 | MakeTCellColorExt(tcell.Color195), | |
826 | MakeTCellColorExt(tcell.Color196), | |
827 | MakeTCellColorExt(tcell.Color197), | |
828 | MakeTCellColorExt(tcell.Color198), | |
829 | MakeTCellColorExt(tcell.Color199), | |
830 | MakeTCellColorExt(tcell.Color200), | |
831 | MakeTCellColorExt(tcell.Color201), | |
832 | MakeTCellColorExt(tcell.Color202), | |
833 | MakeTCellColorExt(tcell.Color203), | |
834 | MakeTCellColorExt(tcell.Color204), | |
835 | MakeTCellColorExt(tcell.Color205), | |
836 | MakeTCellColorExt(tcell.Color206), | |
837 | MakeTCellColorExt(tcell.Color207), | |
838 | MakeTCellColorExt(tcell.Color208), | |
839 | MakeTCellColorExt(tcell.Color209), | |
840 | MakeTCellColorExt(tcell.Color210), | |
841 | MakeTCellColorExt(tcell.Color211), | |
842 | MakeTCellColorExt(tcell.Color212), | |
843 | MakeTCellColorExt(tcell.Color213), | |
844 | MakeTCellColorExt(tcell.Color214), | |
845 | MakeTCellColorExt(tcell.Color215), | |
846 | MakeTCellColorExt(tcell.Color216), | |
847 | MakeTCellColorExt(tcell.Color217), | |
848 | MakeTCellColorExt(tcell.Color218), | |
849 | MakeTCellColorExt(tcell.Color219), | |
850 | MakeTCellColorExt(tcell.Color220), | |
851 | MakeTCellColorExt(tcell.Color221), | |
852 | MakeTCellColorExt(tcell.Color222), | |
853 | MakeTCellColorExt(tcell.Color223), | |
854 | MakeTCellColorExt(tcell.Color224), | |
855 | MakeTCellColorExt(tcell.Color225), | |
856 | MakeTCellColorExt(tcell.Color226), | |
857 | MakeTCellColorExt(tcell.Color227), | |
858 | MakeTCellColorExt(tcell.Color228), | |
859 | MakeTCellColorExt(tcell.Color229), | |
860 | MakeTCellColorExt(tcell.Color230), | |
861 | MakeTCellColorExt(tcell.Color231), | |
862 | MakeTCellColorExt(tcell.Color232), | |
863 | MakeTCellColorExt(tcell.Color233), | |
864 | MakeTCellColorExt(tcell.Color234), | |
865 | MakeTCellColorExt(tcell.Color235), | |
866 | MakeTCellColorExt(tcell.Color236), | |
867 | MakeTCellColorExt(tcell.Color237), | |
868 | MakeTCellColorExt(tcell.Color238), | |
869 | MakeTCellColorExt(tcell.Color239), | |
870 | MakeTCellColorExt(tcell.Color240), | |
871 | MakeTCellColorExt(tcell.Color241), | |
872 | MakeTCellColorExt(tcell.Color242), | |
873 | MakeTCellColorExt(tcell.Color243), | |
874 | MakeTCellColorExt(tcell.Color244), | |
875 | MakeTCellColorExt(tcell.Color245), | |
876 | MakeTCellColorExt(tcell.Color246), | |
877 | MakeTCellColorExt(tcell.Color247), | |
878 | MakeTCellColorExt(tcell.Color248), | |
879 | MakeTCellColorExt(tcell.Color249), | |
880 | MakeTCellColorExt(tcell.Color250), | |
881 | MakeTCellColorExt(tcell.Color251), | |
882 | MakeTCellColorExt(tcell.Color252), | |
883 | MakeTCellColorExt(tcell.Color253), | |
884 | MakeTCellColorExt(tcell.Color254), | |
885 | MakeTCellColorExt(tcell.Color255), | |
886 | 886 | } |
887 | 887 | |
888 | 888 | term2Cache *lru.Cache |
1012 | 1012 | bgt = bg.ToTCell() |
1013 | 1013 | } |
1014 | 1014 | st := StyleNone.MergeUnder(attr) |
1015 | return tcell.Style(st.OnOff).Foreground(fgt).Background(bgt) | |
1015 | return tcell.Style{}.Attributes(st.OnOff).Foreground(fgt).Background(bgt) | |
1016 | 1016 | } |
1017 | 1017 | |
1018 | 1018 | //====================================================================== |
1205 | 1205 | func (r RGBColor) ToTCellColor(mode ColorMode) (TCellColor, bool) { |
1206 | 1206 | switch mode { |
1207 | 1207 | case Mode24BitColors: |
1208 | c := tcell.Color((r.Red << 16) | (r.Green << 8) | (r.Blue << 0) | int(tcell.ColorIsRGB)) | |
1208 | c := tcell.NewRGBColor(int32(r.Red), int32(r.Green), int32(r.Blue)) | |
1209 | 1209 | return MakeTCellColorExt(c), true |
1210 | 1210 | case Mode256Colors: |
1211 | 1211 | if IgnoreBase16 { |
1217 | 1217 | rd := cubeLookup88_16[r.Red>>4] |
1218 | 1218 | g := cubeLookup88_16[r.Green>>4] |
1219 | 1219 | b := cubeLookup88_16[r.Blue>>4] |
1220 | c := tcell.Color((CubeStart + (((rd * cubeSize88) + g) * cubeSize88) + b) + 0) | |
1220 | c := tcell.Color((CubeStart + (((rd * cubeSize88) + g) * cubeSize88) + b) + 0) + tcell.ColorValid | |
1221 | 1221 | return MakeTCellColorExt(c), true |
1222 | 1222 | case Mode16Colors: |
1223 | 1223 | return r.findClosest(colorful16, term16, term16Cache), true |
1299 | 1299 | panic(errors.WithStack(InvalidColor{Color: s})) |
1300 | 1300 | } |
1301 | 1301 | |
1302 | idx = idx - 1 // offset for tcell, which stores default at -1 | |
1303 | ||
1304 | c := MakeTCellColorExt(tcell.Color(idx)) | |
1302 | col := tcell.ColorDefault | |
1303 | if idx > 0 { | |
1304 | idx = idx - 1 | |
1305 | col = tcell.ColorValid + tcell.Color(idx) | |
1306 | } | |
1307 | c := MakeTCellColorExt(col) | |
1305 | 1308 | |
1306 | 1309 | switch mode { |
1307 | 1310 | case Mode24BitColors, Mode256Colors, Mode88Colors, Mode16Colors: |
1391 | 1394 | switch mode { |
1392 | 1395 | case Mode24BitColors: |
1393 | 1396 | adj := intScale(s.Val, 101, 0x100) |
1394 | c := tcell.Color((adj << 16) | (adj << 8) | (adj << 0) | int(tcell.ColorIsRGB)) | |
1397 | c := tcell.NewRGBColor(int32(adj), int32(adj), int32(adj)) | |
1395 | 1398 | return MakeTCellColorExt(c), true |
1396 | 1399 | case Mode256Colors: |
1397 | x := tcell.Color(grayAdjustment256(grayLookup256_101[s.Val]) + 1) | |
1400 | x := tcell.Color(grayAdjustment256(grayLookup256_101[s.Val]) + 1) + tcell.ColorValid | |
1398 | 1401 | return MakeTCellColorExt(x), true |
1399 | 1402 | case Mode88Colors: |
1400 | x := tcell.Color(grayAdjustment88(grayLookup88_101[s.Val]) + 1) | |
1403 | x := tcell.Color(grayAdjustment88(grayLookup88_101[s.Val]) + 1) + tcell.ColorValid | |
1401 | 1404 | return MakeTCellColorExt(x), true |
1402 | 1405 | default: |
1403 | 1406 | panic(errors.WithStack(ColorModeMismatch{Color: s, Mode: mode})) |
1407 | 1410 | //====================================================================== |
1408 | 1411 | |
1409 | 1412 | // TCellColor is an IColor using tcell's color primitives. If you are not porting from urwid or translating |
1410 | // from urwid, this is the simplest approach to using color. Note that the underlying tcell.Color value is | |
1411 | // stored offset by 2 from the value tcell would use to actually render a colored cell. Tcell represents | |
1412 | // e.g. black by 0, maroon by 1, and so on - that means the default/empty/zero value for a tcell.Color object | |
1413 | // is the color black. Gowid's layering approach means that the empty value for a color should mean "no color | |
1414 | // preference" - so we want the zero value to mean that. A tcell.Color of -1 means "default color". So gowid | |
1415 | // coopts -2 to mean "no color preference". We store the tcell.Color offset by 2 so the empty value for a | |
1416 | // TCellColor means "no color preference". When we convert to a tcell.Color, we subtract 2 (but since a value | |
1417 | // of -2 is meaningless to tcell, the caller should check and not pass on a value of -2 to tcell APIs - see | |
1418 | // gowid.ColorNone) | |
1413 | // from urwid, this is the simplest approach to using color. Gowid's layering approach means that the empty | |
1414 | // value for a color should mean "no color preference" - so we want the zero value to mean that. A tcell.Color | |
1415 | // of 0 means "default color". So gowid coopts nil to mean "no color preference". | |
1419 | 1416 | type TCellColor struct { |
1420 | tc tcell.Color | |
1417 | tc *tcell.Color | |
1421 | 1418 | } |
1422 | 1419 | |
1423 | 1420 | var ( |
1433 | 1430 | match := tcellColorRE.FindStringSubmatch(val) // e.g. "Color00" |
1434 | 1431 | if len(match) == 2 { |
1435 | 1432 | n, _ := strconv.ParseUint(match[1], 16, 8) |
1436 | return MakeTCellColorExt(tcell.Color(n)), nil | |
1433 | return MakeTCellColorExt(tcell.Color(n) + tcell.ColorValid), nil | |
1437 | 1434 | } else if col, ok := tcell.ColorNames[val]; !ok { |
1438 | 1435 | return TCellColor{}, errors.WithStack(InvalidColor{Color: val}) |
1439 | 1436 | } else { |
1443 | 1440 | |
1444 | 1441 | // MakeTCellColor returns an initialized TCellColor given a tcell.Color input. The values that can be |
1445 | 1442 | // used are provided here: https://github.com/gdamore/tcell/blob/master/color.go#L41. |
1446 | func MakeTCellColorSafe(val tcell.Color) (TCellColor, error) { | |
1447 | return TCellColor{val + 2}, nil | |
1448 | } | |
1449 | ||
1450 | // MakeTCellColor returns an initialized TCellColor given a tcell.Color input. The values that can be | |
1451 | // used are provided here: https://github.com/gdamore/tcell/blob/master/color.go#L41. | |
1452 | 1443 | func MakeTCellColorExt(val tcell.Color) TCellColor { |
1453 | res, _ := MakeTCellColorSafe(val) | |
1454 | return res | |
1444 | return TCellColor{&val} | |
1455 | 1445 | } |
1456 | 1446 | |
1457 | 1447 | // MakeTCellNoColor returns an initialized TCellColor that represents "no color" - meaning if another |
1458 | 1448 | // color is rendered "under" this one, then the color underneath will be displayed. |
1459 | 1449 | func MakeTCellNoColor() TCellColor { |
1460 | res := MakeTCellColorExt(-2) | |
1461 | return res | |
1450 | return TCellColor{} | |
1462 | 1451 | } |
1463 | 1452 | |
1464 | 1453 | // String implements Stringer for '%v' support. |
1465 | 1454 | func (r TCellColor) String() string { |
1466 | c := r.tc - 2 | |
1467 | if c == -2 { | |
1455 | if r.tc == nil { | |
1468 | 1456 | return "[no-color]" |
1469 | 1457 | } else { |
1458 | c := *r.tc | |
1470 | 1459 | return fmt.Sprintf("TCellColor(%v)", tcell.Color(c)) |
1471 | 1460 | } |
1472 | 1461 | } |
1473 | 1462 | |
1474 | 1463 | // ToTCell converts a TCellColor back to a tcell.Color for passing to tcell APIs. |
1475 | 1464 | func (r TCellColor) ToTCell() tcell.Color { |
1476 | return r.tc - 2 | |
1465 | if r.tc == nil { | |
1466 | return tcell.ColorDefault | |
1467 | } | |
1468 | return *r.tc | |
1477 | 1469 | } |
1478 | 1470 | |
1479 | 1471 | // ToTCellColor is a no-op, and exists so that TCellColor conforms to the IColor interface. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package gowid |
5 | 5 | import ( |
6 | 6 | "testing" |
7 | 7 | |
8 | "github.com/gdamore/tcell" | |
8 | tcell "github.com/gdamore/tcell/v2" | |
9 | 9 | "github.com/go-test/deep" |
10 | 10 | "github.com/stretchr/testify/assert" |
11 | 11 | ) |
17 | 17 | i2 := i2a.ToTCell() |
18 | 18 | // See https://jonasjacek.github.io/colors/ - we are skipping |
19 | 19 | // colors 0-21 inclusive |
20 | if i2 != 232 { | |
20 | if i2 != tcell.Color232 { | |
21 | 21 | t.Errorf("Failed") |
22 | 22 | } |
23 | 23 | } |
27 | 27 | c, _ := MakeRGBColorExtSafe(0, 0, 0) |
28 | 28 | i2a, _ := c.ToTCellColor(Mode256Colors) |
29 | 29 | i2 := i2a.ToTCell() |
30 | if i2 != 0 { | |
30 | if i2 != tcell.ColorValid { | |
31 | 31 | t.Errorf("Failed") |
32 | 32 | } |
33 | 33 | } |
36 | 36 | c := NewUrwidColor("dark red") |
37 | 37 | i2a, _ := c.ToTCellColor(Mode256Colors) |
38 | 38 | i2 := i2a.ToTCell() |
39 | if i2 != 2-1 { | |
39 | if i2 != tcell.ColorMaroon { | |
40 | 40 | t.Errorf("Failed") |
41 | 41 | } |
42 | 42 | } |
60 | 60 | if c.Val != 100 { |
61 | 61 | t.Errorf("Failed") |
62 | 62 | } |
63 | if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != 232 { | |
63 | if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != tcell.Color232 { | |
64 | 64 | t.Errorf("Failed") |
65 | 65 | } |
66 | 66 | } |
70 | 70 | if c.Val != 3 { |
71 | 71 | t.Errorf("Failed") |
72 | 72 | } |
73 | if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != 233 { | |
73 | if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != tcell.Color233 { | |
74 | 74 | t.Errorf("Failed") |
75 | 75 | } |
76 | 76 | } |
80 | 80 | if c.Val != 0 { |
81 | 81 | t.Errorf("Failed") |
82 | 82 | } |
83 | if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != 17 { | |
83 | if v, _ := c.ToTCellColor(Mode256Colors); v.ToTCell() != tcell.Color17 { | |
84 | 84 | t.Errorf("Failed") |
85 | 85 | } |
86 | 86 | } |
133 | 133 | func TestGray881(t *testing.T) { |
134 | 134 | c := MakeGrayColor("g100") |
135 | 135 | v, _ := c.ToTCellColor(Mode88Colors) |
136 | assert.Equal(t, v.ToTCell(), tcell.Color(80)) | |
136 | assert.Equal(t, v.ToTCell(), tcell.Color80) | |
137 | 137 | } |
138 | 138 | |
139 | 139 | func TestDefault1(t *testing.T) { |
140 | 140 | c, _ := MakeColorSafe("default") |
141 | 141 | v, _ := c.ToTCellColor(Mode256Colors) |
142 | assert.Equal(t, v.ToTCell(), tcell.Color(-1)) | |
142 | assert.Equal(t, v.ToTCell(), tcell.ColorDefault) | |
143 | 143 | } |
144 | 144 | |
145 | 145 | func TestTCell1(t *testing.T) { |
149 | 149 | } |
150 | 150 | |
151 | 151 | func TestTCell2(t *testing.T) { |
152 | c, _ := MakeTCellColorSafe(tcell.ColorMaroon) | |
152 | c := MakeTCellColorExt(tcell.ColorMaroon) | |
153 | 153 | v, _ := c.ToTCellColor(Mode256Colors) |
154 | 154 | assert.Equal(t, v.ToTCell(), tcell.ColorMaroon) |
155 | 155 | } |
218 | 218 | case tcell.KeyEnter: |
219 | 219 | w.IWidget = text.New(fmt.Sprintf("Nice to meet you, %s.\n\nPress Q to exit.", w.IWidget.(*edit.Widget).Text())) |
220 | 220 | default: |
221 | res = gowid.UserInput(w.IWidget, ev, size, focus, app) | |
221 | res = w.IWidget.UserInput(w.IWidget, ev, size, focus, app) | |
222 | 222 | } |
223 | 223 | } |
224 | 224 | return res |
266 | 266 | sbtn := styled.New(btn, gowid.MakeStyledAs(gowid.StyleReverse)) |
267 | 267 | div := divider.NewBlank() |
268 | 268 | |
269 | btn.OnClick(gowid.WidgetChangedCallback{"cb", func(app gowid.IApp, w gowid.IWidget) { | |
269 | btn.OnClick(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) { | |
270 | 270 | app.Quit() |
271 | 271 | }}) |
272 | 272 | |
273 | ask.OnTextSet(gowid.WidgetChangedCallback{"cb", func(app gowid.IApp, w gowid.IWidget) { | |
273 | ask.OnTextSet(gowid.WidgetCallback{"cb", func(app gowid.IApp, w gowid.IWidget) { | |
274 | 274 | if ask.Text() == "" { |
275 | 275 | reply.SetText("", app) |
276 | 276 | } else { |
294 | 294 | } |
295 | 295 | ``` |
296 | 296 | - The bottom-most widget in the pile is a `button.Widget`. It itself wraps an inner widget, and when rendered will add characters on the left and right of the inner widget to create a button effect. |
297 | - `button.Widget` can call an interface method when it's clicked. `OnClick()` expects an `IWidgetChangedCallback`. You can use the `WidgetChangedCallback()` adapter to pass a simple function. | |
298 | - The first parameter of `WidgetChangedCallback` is an `interface{}`. It's meant to uniquely identify this callback instance so that if you later need to remove the callback, you can by passing the same `interface{}`. Here I've used a simple string, "cb". The callbacks are scoped to the widget, so you can use the same callback identifier when registering callbacks for other widgets. | |
297 | - `button.Widget` can call an interface method when it's clicked. `OnClick()` expects an `IWidgetChangedCallback`. You can use the `WidgetCallback()` adapter to pass a simple function. | |
298 | - The first parameter of `WidgetCallback` is an `interface{}`. It's meant to uniquely identify this callback instance so that if you later need to remove the callback, you can by passing the same `interface{}`. Here I've used a simple string, "cb". The callbacks are scoped to the widget, so you can use the same callback identifier when registering callbacks for other widgets. | |
299 | 299 | - `edit.Widget` can call an interface method when its text changes. In this example, every time the user enters a character, `ask` will update the `reply` widget so that it displays a message. |
300 | 300 | - The callback will be called with two arguments - the application `app` and the widget issuing the callback. But if it's more convenient, you can rely on Go's scope rules to capture the widgets that you need to modify in the callback. `ask`'s callback refers to `reply` and not the callback parameter `w`. |
301 | 301 | - The `<exit>` button is styled using `MakeStyleAs()`, which applies a text style like underline, bold or reverse-video. No colors are given, so the button will use the terminal's default colors. |
5 | 5 | |
6 | 6 | **Purpose:** The `asciigraph` widget renders line graphs. It uses the Go package `github.com/guptarohit/asciigraph`. |
7 | 7 | |
8 | ![desc](https://drive.google.com/uc?export=view&id=19bDRxGYjtL00c1y6StrSZG63AM86Zm7C) | |
8 | ![desc](https://user-images.githubusercontent.com/45680/118377433-35141900-b59b-11eb-818d-546dca83fd9e.png) | |
9 | 9 | |
10 | 10 | **Examples:** |
11 | 11 | |
34 | 34 | |
35 | 35 | **Purpose**: a clickable widget. The app can register callbacks to handle click events. |
36 | 36 | |
37 | ![desc](https://drive.google.com/uc?export=view&id=19kVB4t4c0dLwLusRaGj6oNrTWhNr02u2) | |
37 | ![desc](https://user-images.githubusercontent.com/45680/118377515-b1a6f780-b59b-11eb-9003-1c37c008db39.png) | |
38 | 38 | |
39 | 39 | **Examples:** |
40 | 40 | |
56 | 56 | |
57 | 57 | **Purpose**: a clickable widget with two states - selected and unselected. |
58 | 58 | |
59 | ![desc](https://drive.google.com/uc?export=view&id=1a7OBwLMzithJDwtylG1aLct0dLsyK2kn) | |
59 | ![desc](https://user-images.githubusercontent.com/45680/118377546-e61ab380-b59b-11eb-8d6c-54b88608269a.png) | |
60 | 60 | |
61 | 61 | **Examples:** |
62 | 62 | |
74 | 74 | |
75 | 75 | **Purpose**: arrange child widgets into vertical columns, with configurable column widths. |
76 | 76 | |
77 | ![desc](https://drive.google.com/uc?export=view&id=1kZI6n7wvO16PFu_-nTJ8t24WNsyasdU7) | |
77 | ![desc](https://user-images.githubusercontent.com/45680/118377593-25490480-b59c-11eb-845b-51baf1936faf.png) | |
78 | 78 | |
79 | 79 | **Examples:** |
80 | 80 | - `github.com/gcla/gowid/examples/gowid-widgets2` |
86 | 86 | |
87 | 87 | **Purpose**: a modal dialog box that can be opened on top of another widget and will process the user input preferentially. |
88 | 88 | |
89 | ![desc](https://drive.google.com/uc?export=view&id=1gq_HJLXdnr0KJ2gPK4nalJ56WyPuVkJh) | |
89 | ![desc](https://user-images.githubusercontent.com/45680/118377633-66411900-b59c-11eb-9c6f-74f7adb4c102.png) | |
90 | 90 | |
91 | 91 | **Examples:** |
92 | 92 | |
96 | 96 | |
97 | 97 | **Purpose**: a configurable horizontal line that can be used to separate widgets arranged vertically. Can render using ascii or unicode. |
98 | 98 | |
99 | ![desc](https://drive.google.com/uc?export=view&id=1YRHeQvckXIVPwPf-8sLAWd3ZuT_qTfuL) | |
99 | ![desc](https://user-images.githubusercontent.com/45680/118377670-b1f3c280-b59c-11eb-9e5a-10d5689f527f.png) | |
100 | 100 | |
101 | 101 | **Examples:** |
102 | 102 | |
108 | 108 | |
109 | 109 | **Purpose**: a text area that will display text typed in by the user, with an optional caption/prefix. |
110 | 110 | |
111 | ![desc](https://drive.google.com/uc?export=view&id=1Wr_nFrRawGN_FyGv8pqBEegSrwboe3Fm) | |
111 | ![desc](https://user-images.githubusercontent.com/45680/118377720-f8492180-b59c-11eb-918d-833fdd4a3586.png) | |
112 | 112 | |
113 | 113 | **Examples:** |
114 | 114 | |
120 | 120 | |
121 | 121 | **Purpose**: a widget that when rendered returns a canvas full of the same user-supplied `Cell`. |
122 | 122 | |
123 | ![desc](https://drive.google.com/uc?export=view&id=1_O4P4YlikeP6j0vK8A7VPCXwxcnOh9a4) | |
123 | ![desc](https://user-images.githubusercontent.com/45680/118377735-1f9fee80-b59d-11eb-8aef-2e6ea2b3fc6c.png) | |
124 | 124 | |
125 | 125 | **Examples:** |
126 | 126 | |
140 | 140 | |
141 | 141 | **Purpose**: surround a child widget with a configurable "frame", using unicode or ascii characters. |
142 | 142 | |
143 | ![desc](https://drive.google.com/uc?export=view&id=1EUU4DZxPb6B4-u0XPN9rTgDxmHB9Z0lw) | |
143 | ![desc](https://user-images.githubusercontent.com/45680/118377753-43633480-b59d-11eb-8fc2-4ca276376f26.png) | |
144 | 144 | |
145 | 145 | **Examples:** |
146 | 146 | |
153 | 153 | |
154 | 154 | **Purpose**: a way to arrange widgets in a grid, with configurable horizontal alignment. |
155 | 155 | |
156 | ![desc](https://drive.google.com/uc?export=view&id=1ngHp3pzFzw7qSM8uQ-UmKiijIE4b_ULq) | |
156 | ![desc](https://user-images.githubusercontent.com/45680/118377795-845b4900-b59d-11eb-82e5-b59e5b3d9619.png) | |
157 | 157 | |
158 | 158 | **Examples:** |
159 | 159 | |
183 | 183 | |
184 | 184 | **Purpose**: a flexible widget to navigate a vertical list of widgets rendered in flow mode. |
185 | 185 | |
186 | ![desc](https://drive.google.com/uc?export=view&id=1uJ3Muv5zEu8HHK5DlBU9NEB_v9aHyemi) | |
186 | ![desc](https://user-images.githubusercontent.com/45680/118377820-ad7bd980-b59d-11eb-8368-966567e626ff.png) | |
187 | 187 | |
188 | 188 | **Examples:** |
189 | 189 | |
196 | 196 | |
197 | 197 | **Purpose**: a drop-down menu supporting arbitrarily many sub-menus. |
198 | 198 | |
199 | ![desc](https://drive.google.com/uc?export=view&id=1kLrAyPAbRi37VjxoVikSfjvrfxKiDVtQ) | |
199 | ![desc](https://user-images.githubusercontent.com/45680/118377834-c4bac700-b59d-11eb-9884-fea03e543be8.png) | |
200 | 200 | |
201 | 201 | **Examples:** |
202 | 202 | |
206 | 206 | |
207 | 207 | **Purpose**: a widget to render one widget over another, only passing user input to the occluded widget if the input coordinates are outside the boundaries of the widget on top. |
208 | 208 | |
209 | ![desc](https://drive.google.com/uc?export=view&id=1q8LcHhl-ZTA9AEIEWmgRGeRTnwjGyX4u) | |
209 | ![desc](https://user-images.githubusercontent.com/45680/118377862-e2882c00-b59d-11eb-880b-5753239b92b0.png) | |
210 | 210 | |
211 | 211 | **Examples:** |
212 | 212 | |
241 | 241 | |
242 | 242 | **Purpose**: arrange child widgets into horizontal bands, with configurable heights. |
243 | 243 | |
244 | ![desc](https://drive.google.com/uc?export=view&id=1Bnzhu-hHsr0Ok3hFP5Q0LlaQMTOPEepD) | |
244 | ![desc](https://user-images.githubusercontent.com/45680/118377912-31ce5c80-b59e-11eb-84af-888729e98b25.png) | |
245 | 245 | |
246 | 246 | **Examples:** |
247 | 247 | |
254 | 254 | |
255 | 255 | **Purpose**: a simple progress monitor. |
256 | 256 | |
257 | ![desc](https://drive.google.com/uc?export=view&id=15GK6PIlh_CswM6OQ0WNy2O6LC-EwMvhh) | |
257 | ![desc](https://user-images.githubusercontent.com/45680/118377933-54f90c00-b59e-11eb-9589-200d829f2a80.png) | |
258 | 258 | |
259 | 259 | **Examples:** |
260 | 260 | |
292 | 292 | |
293 | 293 | **Purpose**: a widget that, as part of a group, can be in a selected state (if no others in the group are selected) or is otherwise unselected. |
294 | 294 | |
295 | ![desc](https://drive.google.com/uc?export=view&id=1cJa4m-DaDpbGwUWChASh-m9AE0sI6tk3) | |
295 | ![desc](https://user-images.githubusercontent.com/45680/118377974-8a055e80-b59e-11eb-834e-94080b1ff53b.png) | |
296 | 296 | |
297 | 297 | **Examples:** |
298 | 298 | |
315 | 315 | |
316 | 316 | **Purpose**: adds a drop-shadow effect to a widget. |
317 | 317 | |
318 | ![desc](https://drive.google.com/uc?export=view&id=1BtI1f0nbyxDUhgsgYaHhOqV3sQJwZnoy) | |
318 | ![desc](https://user-images.githubusercontent.com/45680/118377988-a1dce280-b59e-11eb-9fcd-1bfe57e7206b.png) | |
319 | 319 | |
320 | 320 | **Examples:** |
321 | 321 | |
336 | 336 | ## table |
337 | 337 | |
338 | 338 | **Purpose**: a widget to display tabular data in columns and rows. |
339 | ||
340 | ||
341 | ||
342 | ||
339 | 343 | |
340 | 344 | ![desc](https://drive.google.com/uc?export=view&id=1TZXfT_VVf5g2sNYi9hia2h36krcGUBI8) |
341 | 345 | |
395 | 399 | |
396 | 400 | **Purpose**: a VT-220 capable terminal emulator widget, heavily plagiarized from urwid's `vterm.py`. |
397 | 401 | |
398 | ![desc](https://drive.google.com/uc?export=view&id=1PNcMPwiybGBBu48Oot7_hm-uV55TJPTw) | |
402 | ![desc](https://user-images.githubusercontent.com/45680/118378264-92f72f80-b5a0-11eb-99d0-0e51b4108870.png) | |
399 | 403 | |
400 | 404 | **Examples:** |
401 | 405 | |
441 | 445 | |
442 | 446 | **Purpose**: a widget to render text, optionally styled and aligned. |
443 | 447 | |
444 | ![desc](https://drive.google.com/uc?export=view&id=1XJaSfqljC5ullPj5M9O2ld4OGtP9Nvji) | |
448 | ![desc](https://user-images.githubusercontent.com/45680/118378052-fed89880-b59e-11eb-8605-1ce32217b755.png) | |
445 | 449 | |
446 | 450 | **Examples:** |
447 | 451 | |
491 | 495 | |
492 | 496 | **Purpose**: a generalization of the `list` widget to render a tree structure. |
493 | 497 | |
494 | ![desc](https://drive.google.com/uc?export=view&id=1GDirTv-CeKH8CNBjSNrHG7WtE7ccU93P) | |
498 | ![desc](https://user-images.githubusercontent.com/45680/118378280-b02bfe00-b5a0-11eb-92e7-4178f718f844.png) | |
495 | 499 | |
496 | 500 | **Examples:** |
497 | 501 | |
512 | 516 | |
513 | 517 | **Purpose**: a vertical scroll bar with clickable arrows on either end. |
514 | 518 | |
515 | ![desc](https://drive.google.com/uc?export=view&id=1dUArLQ1KuzwQmthpTs-HXwxkHvTOVCuT) | |
519 | ![desc](https://user-images.githubusercontent.com/45680/118378077-292a5600-b59f-11eb-8dbb-c31fdf08337d.png) | |
516 | 520 | |
517 | 521 | **Examples:** |
518 | 522 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // An example of the gowid asciigraph widget which relies upon the |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A simple gowid directory browser. |
20 | 20 | "github.com/gcla/gowid/widgets/styled" |
21 | 21 | "github.com/gcla/gowid/widgets/text" |
22 | 22 | "github.com/gcla/gowid/widgets/tree" |
23 | "github.com/gdamore/tcell" | |
23 | tcell "github.com/gdamore/tcell/v2" | |
24 | 24 | log "github.com/sirupsen/logrus" |
25 | 25 | kingpin "gopkg.in/alecthomas/kingpin.v2" |
26 | 26 | ) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A poor-man's editor using gowid widgets - shows dialog, edit and vscroll. |
23 | 23 | "github.com/gcla/gowid/widgets/styled" |
24 | 24 | "github.com/gcla/gowid/widgets/text" |
25 | 25 | "github.com/gcla/gowid/widgets/vscroll" |
26 | "github.com/gdamore/tcell" | |
26 | tcell "github.com/gdamore/tcell/v2" | |
27 | 27 | log "github.com/sirupsen/logrus" |
28 | 28 | kingpin "gopkg.in/alecthomas/kingpin.v2" |
29 | 29 | ) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A port of urwid's fib.py example using gowid widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A port of urwid's graph.py example using gowid widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A port of urwid's "Hello World" example from the tutorial, using gowid widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A demonstration of gowid's menu, list and overlay widgets. |
19 | 19 | "github.com/gcla/gowid/widgets/styled" |
20 | 20 | "github.com/gcla/gowid/widgets/text" |
21 | 21 | "github.com/gcla/gowid/widgets/vpadding" |
22 | "github.com/gdamore/tcell" | |
22 | tcell "github.com/gdamore/tcell/v2" | |
23 | 23 | log "github.com/sirupsen/logrus" |
24 | 24 | ) |
25 | 25 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A demonstration of gowid's overlay and fill widgets. |
10 | 10 | "github.com/gcla/gowid/widgets/fill" |
11 | 11 | "github.com/gcla/gowid/widgets/overlay" |
12 | 12 | "github.com/gcla/gowid/widgets/styled" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | log "github.com/sirupsen/logrus" |
15 | 15 | ) |
16 | 16 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A demonstration of gowid's overlay, fill, asciigraph and radio widgets. |
21 | 21 | "github.com/gcla/gowid/widgets/styled" |
22 | 22 | "github.com/gcla/gowid/widgets/text" |
23 | 23 | "github.com/gcla/gowid/widgets/vpadding" |
24 | "github.com/gdamore/tcell" | |
24 | tcell "github.com/gdamore/tcell/v2" | |
25 | 25 | asc "github.com/guptarohit/asciigraph" |
26 | 26 | log "github.com/sirupsen/logrus" |
27 | 27 | ) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A demonstration of gowid's overlay and fill widgets. |
11 | 11 | "github.com/gcla/gowid/widgets/overlay" |
12 | 12 | "github.com/gcla/gowid/widgets/styled" |
13 | 13 | "github.com/gcla/gowid/widgets/text" |
14 | "github.com/gdamore/tcell" | |
14 | tcell "github.com/gdamore/tcell/v2" | |
15 | 15 | log "github.com/sirupsen/logrus" |
16 | 16 | ) |
17 | 17 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A port of urwid's palette_test.py example using gowid widgets. |
19 | 19 | "github.com/gcla/gowid/widgets/radio" |
20 | 20 | "github.com/gcla/gowid/widgets/styled" |
21 | 21 | "github.com/gcla/gowid/widgets/text" |
22 | "github.com/gdamore/tcell" | |
22 | tcell "github.com/gdamore/tcell/v2" | |
23 | 23 | log "github.com/sirupsen/logrus" |
24 | 24 | ) |
25 | 25 |
0 | 0 | //go:generate statik -src=data |
1 | 1 | |
2 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
2 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
3 | 3 | // code is governed by the MIT license that can be found in the LICENSE |
4 | 4 | // file. |
5 | 5 | |
17 | 17 | _ "github.com/gcla/gowid/examples/gowid-table/statik" |
18 | 18 | kingpin "gopkg.in/alecthomas/kingpin.v2" |
19 | 19 | |
20 | "github.com/gdamore/tcell" | |
20 | tcell "github.com/gdamore/tcell/v2" | |
21 | 21 | "github.com/rakyll/statik/fs" |
22 | 22 | log "github.com/sirupsen/logrus" |
23 | 23 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A very poor-man's tmux written using gowid's terminal widget. |
19 | 19 | "github.com/gcla/gowid/widgets/styled" |
20 | 20 | "github.com/gcla/gowid/widgets/terminal" |
21 | 21 | "github.com/gcla/gowid/widgets/text" |
22 | "github.com/gdamore/tcell" | |
22 | tcell "github.com/gdamore/tcell/v2" | |
23 | 23 | log "github.com/sirupsen/logrus" |
24 | 24 | ) |
25 | 25 | |
203 | 203 | defer f.Close() |
204 | 204 | |
205 | 205 | palette := gowid.Palette{ |
206 | "invred": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed), | |
207 | "line": gowid.MakeStyledPaletteEntry(gowid.NewUrwidColor("black"), gowid.NewUrwidColor("light gray"), gowid.StyleBold), | |
206 | "invred": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorRed), | |
207 | "invblue": gowid.MakePaletteEntry(gowid.ColorBlack, gowid.ColorCyan), | |
208 | "line": gowid.MakeStyledPaletteEntry(gowid.NewUrwidColor("black"), gowid.NewUrwidColor("light gray"), gowid.StyleBold), | |
208 | 209 | } |
209 | 210 | |
210 | 211 | hkDuration := terminal.HotKeyDuration{time.Second * 3} |
222 | 223 | } |
223 | 224 | |
224 | 225 | for _, cmd := range tcommands { |
225 | app, err := terminal.NewExt(terminal.Options{ | |
226 | Command: strings.Split(cmd, " "), | |
227 | HotKeyPersistence: &hkDuration, | |
228 | Scrollback: 100, | |
226 | tapp, err := terminal.NewExt(terminal.Options{ | |
227 | Command: strings.Split(cmd, " "), | |
228 | HotKeyPersistence: &hkDuration, | |
229 | Scrollback: 100, | |
230 | Scrollbar: true, | |
231 | EnableBracketedPaste: true, | |
232 | HotKeyFns: []terminal.HotKeyInputFn{ | |
233 | func(ev *tcell.EventKey, w terminal.IWidget, app gowid.IApp) bool { | |
234 | if w2, ok := w.(terminal.IScrollbar); ok { | |
235 | if ev.Key() == tcell.KeyRune && ev.Rune() == 's' { | |
236 | if w2.ScrollbarEnabled() { | |
237 | w2.DisableScrollbar(app) | |
238 | } else { | |
239 | w2.EnableScrollbar(app) | |
240 | } | |
241 | return true | |
242 | } | |
243 | } | |
244 | return false | |
245 | }, | |
246 | }, | |
229 | 247 | }) |
230 | 248 | if err != nil { |
231 | 249 | panic(err) |
232 | 250 | } |
233 | twidgets = append(twidgets, app) | |
251 | twidgets = append(twidgets, tapp) | |
234 | 252 | } |
235 | 253 | |
236 | 254 | tw := text.New(" Terminal Demo ") |
237 | twi := styled.New(tw, gowid.MakePaletteRef("invred")) | |
255 | twir := styled.New(tw, gowid.MakePaletteRef("invred")) | |
256 | twib := styled.New(tw, gowid.MakePaletteRef("invblue")) | |
238 | 257 | twp := holder.New(tw) |
239 | 258 | |
240 | 259 | vline := styled.New(fill.New('│'), gowid.MakePaletteRef("line")) |
265 | 284 | }) |
266 | 285 | t.OnBell(gowid.WidgetCallback{"cb", |
267 | 286 | func(app gowid.IApp, w gowid.IWidget) { |
268 | twp.SetSubWidget(twi, app) | |
287 | twp.SetSubWidget(twir, app) | |
269 | 288 | timer := time.NewTimer(time.Millisecond * 800) |
270 | 289 | go func() { |
271 | 290 | <-timer.C |
281 | 300 | tw.SetText(" "+w2.GetTitle()+" ", app) |
282 | 301 | }, |
283 | 302 | }) |
303 | t.OnHotKey(gowid.WidgetCallback{"cb", | |
304 | func(app gowid.IApp, w gowid.IWidget) { | |
305 | w2 := w.(*terminal.Widget) | |
306 | if w2.HotKeyActive() { | |
307 | twp.SetSubWidget(twib, app) | |
308 | } else { | |
309 | twp.SetSubWidget(tw, app) | |
310 | } | |
311 | }, | |
312 | }) | |
284 | 313 | } |
285 | 314 | |
286 | 315 | app, err = gowid.NewApp(gowid.AppArgs{ |
287 | View: view, | |
288 | Palette: &palette, | |
289 | Log: log.StandardLogger(), | |
316 | View: view, | |
317 | Palette: &palette, | |
318 | Log: log.StandardLogger(), | |
319 | EnableBracketedPaste: true, | |
290 | 320 | }) |
291 | 321 | examples.ExitOnErr(err) |
292 | 322 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
20 | 20 | "github.com/gcla/gowid/widgets/styled" |
21 | 21 | "github.com/gcla/gowid/widgets/text" |
22 | 22 | "github.com/gcla/gowid/widgets/tree" |
23 | "github.com/gdamore/tcell" | |
23 | tcell "github.com/gdamore/tcell/v2" | |
24 | 24 | log "github.com/sirupsen/logrus" |
25 | 25 | ) |
26 | 26 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // The first example from the gowid tutorial. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // The second example from the gowid tutorial. |
9 | 9 | "github.com/gcla/gowid" |
10 | 10 | "github.com/gcla/gowid/examples" |
11 | 11 | "github.com/gcla/gowid/widgets/text" |
12 | "github.com/gdamore/tcell" | |
12 | tcell "github.com/gdamore/tcell/v2" | |
13 | 13 | ) |
14 | 14 | |
15 | 15 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // The third example from the gowid tutorial. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // The fourth example from the gowid tutorial. |
10 | 10 | "github.com/gcla/gowid/examples" |
11 | 11 | "github.com/gcla/gowid/widgets/edit" |
12 | 12 | "github.com/gcla/gowid/widgets/text" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | ) |
15 | 15 | |
16 | 16 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // The fifth example from the gowid tutorial. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // The sixth example from the gowid tutorial. |
12 | 12 | "github.com/gcla/gowid/widgets/list" |
13 | 13 | "github.com/gcla/gowid/widgets/pile" |
14 | 14 | "github.com/gcla/gowid/widgets/text" |
15 | "github.com/gdamore/tcell" | |
15 | tcell "github.com/gdamore/tcell/v2" | |
16 | 16 | ) |
17 | 17 | |
18 | 18 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A gowid test app which exercises the pile, button, edit and progress widgets. |
20 | 20 | "github.com/gcla/gowid/widgets/styled" |
21 | 21 | "github.com/gcla/gowid/widgets/text" |
22 | 22 | "github.com/gcla/gowid/widgets/vpadding" |
23 | "github.com/gdamore/tcell" | |
23 | tcell "github.com/gdamore/tcell/v2" | |
24 | 24 | log "github.com/sirupsen/logrus" |
25 | 25 | ) |
26 | 26 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A gowid test app which exercises the columns, checkbox, edit and styled widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A gowid test app which exercises the button, grid, progress and radio widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A gowid test app which exercises the columns, list and framed widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A gowid test app which exercises the edit and vpadding widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A gowid test app which exercises the list, edit, columns and styled widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A gowid test app which exercises the list, edit and framed widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // A gowid test app which exercises the checkbox, columns and hpadding widgets. |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package examples |
0 | 0 | module github.com/gcla/gowid |
1 | ||
2 | go 1.13 | |
1 | 3 | |
2 | 4 | require ( |
3 | 5 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect |
4 | 6 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect |
5 | github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29 | |
7 | github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e | |
8 | github.com/creack/pty v1.1.15 | |
9 | github.com/gdamore/tcell/v2 v2.5.0 | |
6 | 10 | github.com/go-test/deep v1.0.1 |
7 | 11 | github.com/guptarohit/asciigraph v0.4.1 |
8 | 12 | github.com/hashicorp/golang-lru v0.5.1 |
9 | 13 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect |
10 | github.com/kr/pty v1.1.4 | |
11 | github.com/lucasb-eyer/go-colorful v1.0.3 | |
12 | github.com/mattn/go-runewidth v0.0.7 | |
14 | github.com/lucasb-eyer/go-colorful v1.2.0 | |
15 | github.com/mattn/go-runewidth v0.0.13 | |
13 | 16 | github.com/pkg/errors v0.8.1 |
14 | 17 | github.com/rakyll/statik v0.1.6 |
15 | 18 | github.com/sirupsen/logrus v1.4.2 |
16 | github.com/stretchr/testify v1.3.0 | |
17 | golang.org/x/text v0.3.2 | |
19 | github.com/stretchr/testify v1.7.0 | |
20 | golang.org/x/text v0.3.7 | |
18 | 21 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 |
19 | 22 | ) |
1 | 1 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= |
2 | 2 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= |
3 | 3 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
4 | github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e h1:OjdSMCht0ZVX7IH0nTdf00xEustvbtUGRgMh3gbdmOg= | |
5 | github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= | |
6 | github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc= | |
7 | github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | |
4 | 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
5 | 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
6 | 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
7 | 11 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= |
8 | 12 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= |
9 | github.com/gdamore/tcell v1.3.1-0.20200114053155-ef4e0a2e861f h1:9KnZ/v1noCLfM5yvKsq3TXwTCV8nH5Hwg0s/jrV/WS8= | |
10 | github.com/gdamore/tcell v1.3.1-0.20200114053155-ef4e0a2e861f/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= | |
11 | github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29 h1:kvzEHvL4/ORuWe6JN6WeaiRYIvVDUVaC2r0gpJIxJ6I= | |
12 | github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= | |
13 | github.com/gdamore/tcell/v2 v2.5.0 h1:/LA5f/wqTP5mWT79czngibKVVx5wOgdFTIXPQ68fMO8= | |
14 | github.com/gdamore/tcell/v2 v2.5.0/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo= | |
13 | 15 | github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= |
14 | 16 | github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= |
15 | 17 | github.com/guptarohit/asciigraph v0.4.1 h1:YHmCMN8VH81BIUIgTg2Fs3B52QDxNZw2RQ6j5pGoSxo= |
16 | 18 | github.com/guptarohit/asciigraph v0.4.1/go.mod h1:9fYEfE5IGJGxlP1B+w8wHFy7sNZMhPtn59f0RLtpRFM= |
17 | 19 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= |
18 | 20 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= |
19 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= | |
20 | 21 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
21 | 22 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= |
22 | 23 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
23 | github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= | |
24 | github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |
25 | github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= | |
26 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | |
27 | github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= | |
28 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= | |
24 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | |
25 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | |
26 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | |
27 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= | |
28 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | |
29 | 29 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= |
30 | 30 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
31 | 31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
32 | 32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
33 | 33 | github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= |
34 | 34 | github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= |
35 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | |
36 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | |
37 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | |
38 | github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= | |
35 | 39 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= |
36 | 40 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= |
37 | 41 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
38 | github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= | |
39 | 42 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
40 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | |
41 | 43 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
42 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= | |
43 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | |
44 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | |
45 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |
44 | 46 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
45 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= | |
46 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
47 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= | |
47 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
48 | golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 h1:saXMvIOKvRFwbOMicHXr0B1uwoxq9dGmLe5ExMES6c4= | |
49 | golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |
50 | golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= | |
51 | golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |
48 | 52 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
49 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= | |
50 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | |
53 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= | |
54 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | |
51 | 55 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
52 | 56 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= |
53 | 57 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= |
58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | |
59 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
60 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | |
61 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package gwtest |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
9 | 9 | "testing" |
10 | 10 | |
11 | 11 | "github.com/gcla/gowid" |
12 | "github.com/gdamore/tcell" | |
12 | tcell "github.com/gdamore/tcell/v2" | |
13 | 13 | log "github.com/sirupsen/logrus" |
14 | 14 | "github.com/stretchr/testify/assert" |
15 | 15 | ) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // Package gwutil provides general-purpose utilities that are not used by |
18 | 18 | |
19 | 19 | //====================================================================== |
20 | 20 | |
21 | // Min returns the smaller of two integer arguments. | |
22 | func Min(a, b int) int { | |
23 | if a < b { | |
24 | return a | |
25 | } | |
26 | return b | |
27 | } | |
28 | ||
29 | // Min returns the larger of two integer arguments. | |
30 | func Max(a, b int) int { | |
31 | if a > b { | |
32 | return a | |
33 | } | |
34 | return b | |
21 | // Min returns the smaller of >1 integer arguments. | |
22 | func Min(i int, js ...int) int { | |
23 | res := i | |
24 | for _, j := range js { | |
25 | if j < res { | |
26 | res = j | |
27 | } | |
28 | } | |
29 | return res | |
30 | } | |
31 | ||
32 | // Min returns the larger of >1 integer arguments. | |
33 | func Max(i int, js ...int) int { | |
34 | res := i | |
35 | for _, j := range js { | |
36 | if j > res { | |
37 | res = j | |
38 | } | |
39 | } | |
40 | return res | |
35 | 41 | } |
36 | 42 | |
37 | 43 | // LimitTo is a one-liner that uses Min and Max to bound a value. Assumes |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package gwutil |
43 | 43 | assert.Equal(t, "None", fmt.Sprintf("%v", opt1)) |
44 | 44 | } |
45 | 45 | |
46 | func TestMin1(t *testing.T) { | |
47 | assert.Equal(t, 1, Min(1, 2, 3)) | |
48 | assert.Equal(t, 1, Min(2, 1, 3)) | |
49 | assert.Equal(t, 1, Min(2, 3, 1)) | |
50 | } | |
51 | ||
52 | func TestMax1(t *testing.T) { | |
53 | assert.Equal(t, 3, Max(1, 2, 3)) | |
54 | assert.Equal(t, 3, Max(2, 1, 3)) | |
55 | assert.Equal(t, 3, Max(2, 3, 1)) | |
56 | } | |
57 | ||
46 | 58 | //====================================================================== |
47 | 59 | // Local Variables: |
48 | 60 | // mode: Go |
0 | // Copyright 2022 Graham Clark. All rights reserved. Use of this source | |
1 | // code is governed by the MIT license that can be found in the LICENSE | |
2 | // file. | |
3 | // | |
4 | // +build !windows | |
5 | ||
6 | package gowid | |
7 | ||
8 | import ( | |
9 | "os" | |
10 | ||
11 | tcell "github.com/gdamore/tcell/v2" | |
12 | ) | |
13 | ||
14 | //====================================================================== | |
15 | ||
16 | func tcellScreen(ttys string) (tcell.Screen, error) { | |
17 | var tty tcell.Tty | |
18 | var err error | |
19 | ||
20 | tty, err = tcell.NewDevTtyFromDev(bestTty(ttys)) | |
21 | if err != nil { | |
22 | return nil, WithKVs(err, map[string]interface{}{"tty": ttys}) | |
23 | } | |
24 | ||
25 | return tcell.NewTerminfoScreenFromTty(tty) | |
26 | } | |
27 | ||
28 | func bestTty(tty string) string { | |
29 | if tty != "" { | |
30 | return tty | |
31 | } | |
32 | gwtty := os.Getenv("GOWID_TTY") | |
33 | if gwtty != "" { | |
34 | return gwtty | |
35 | } | |
36 | return "/dev/tty" | |
37 | } | |
38 | ||
39 | //====================================================================== | |
40 | // Local Variables: | |
41 | // mode: Go | |
42 | // fill-column: 110 | |
43 | // End: |
0 | // Copyright 2022 Graham Clark. All rights reserved. Use of this source | |
1 | // code is governed by the MIT license that can be found in the LICENSE | |
2 | // file. | |
3 | // | |
4 | ||
5 | package gowid | |
6 | ||
7 | import ( | |
8 | tcell "github.com/gdamore/tcell/v2" | |
9 | ) | |
10 | ||
11 | //====================================================================== | |
12 | ||
13 | func tcellScreen(tty string) (tcell.Screen, error) { | |
14 | return tcell.NewScreen() | |
15 | } | |
16 | ||
17 | //====================================================================== | |
18 | // Local Variables: | |
19 | // mode: Go | |
20 | // fill-column: 110 | |
21 | // End: |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
9 | 9 | "strings" |
10 | 10 | |
11 | 11 | "github.com/gcla/gowid/gwutil" |
12 | "github.com/gdamore/tcell" | |
12 | tcell "github.com/gdamore/tcell/v2" | |
13 | 13 | "github.com/pkg/errors" |
14 | 14 | ) |
15 | 15 | |
1269 | 1269 | case IRenderFixed: |
1270 | 1270 | subSize = RenderFixed{} |
1271 | 1271 | case IRenderFlowWith: |
1272 | subSize = RenderFlowWith{C: d2.FlowColumns()} | |
1272 | subSize = RenderFlowWith{C: gwutil.Max(0, gwutil.Min(sz.BoxColumns(), d2.FlowColumns()))} | |
1273 | 1273 | case IRenderFlow: |
1274 | 1274 | subSize = RenderFlowWith{C: sz.BoxColumns()} |
1275 | 1275 | case IRenderWithUnits: |
1276 | subSize = RenderBox{C: sz.BoxColumns(), R: d2.Units()} | |
1276 | subSize = RenderBox{C: sz.BoxColumns(), R: gwutil.Max(0, gwutil.Min(sz.BoxRows(), d2.Units()))} | |
1277 | 1277 | case IRenderRelative: |
1278 | 1278 | subSize = RenderBox{C: sz.BoxColumns(), R: int((d2.Relative() * float64(sz.BoxRows())) + 0.5)} |
1279 | 1279 | case IRenderWithWeight: |
1347 | 1347 | case IRenderFixed: |
1348 | 1348 | subSize = RenderFixed{} |
1349 | 1349 | case IRenderBox: |
1350 | subSize = RenderBox{C: w2.BoxColumns(), R: w2.BoxRows()} | |
1350 | subSize = RenderBox{ | |
1351 | C: gwutil.Max(0, gwutil.Min(sz.BoxColumns(), w2.BoxColumns())), | |
1352 | R: gwutil.Max(0, gwutil.Min(sz.BoxRows(), w2.BoxRows())), | |
1353 | } | |
1351 | 1354 | case IRenderFlowWith: |
1352 | subSize = RenderFlowWith{C: w2.FlowColumns()} | |
1355 | subSize = RenderFlowWith{C: gwutil.Max(0, gwutil.Min(sz.BoxColumns(), w2.FlowColumns()))} | |
1353 | 1356 | case IRenderFlow: |
1354 | subSize = RenderFlowWith{C: sz.BoxColumns()} | |
1357 | subSize = RenderFlowWith{C: gwutil.Max(0, gwutil.Min(sz.BoxColumns(), sz.BoxColumns()))} | |
1355 | 1358 | case IRenderRelative: |
1356 | 1359 | subSize = RenderBox{C: int((w2.Relative() * float64(sz.BoxColumns())) + 0.5), R: sz.BoxRows()} |
1357 | 1360 | case IRenderWithUnits: |
1358 | subSize = RenderBox{C: w2.Units(), R: sz.BoxRows()} | |
1361 | subSize = RenderBox{ | |
1362 | C: gwutil.Max(0, gwutil.Min(sz.BoxColumns(), w2.Units())), | |
1363 | R: sz.BoxRows(), | |
1364 | } | |
1359 | 1365 | default: |
1360 | 1366 | return nil, errors.WithStack(DimensionError{Size: size, Dim: d}) |
1361 | 1367 | } |
1364 | 1370 | case IRenderFixed: |
1365 | 1371 | subSize = RenderFixed{} |
1366 | 1372 | case IRenderBox: |
1367 | subSize = RenderBox{C: w2.BoxColumns(), R: w2.BoxRows()} | |
1373 | subSize = RenderBox{ | |
1374 | C: gwutil.Max(0, gwutil.Min(sz.FlowColumns(), w2.BoxColumns())), | |
1375 | R: w2.BoxRows(), | |
1376 | } | |
1368 | 1377 | case IRenderFlowWith: |
1369 | subSize = RenderFlowWith{C: w2.FlowColumns()} | |
1378 | subSize = RenderFlowWith{C: gwutil.Max(0, gwutil.Min(sz.FlowColumns(), w2.FlowColumns()))} | |
1370 | 1379 | case IRenderFlow: |
1371 | 1380 | subSize = RenderFlowWith{C: sz.FlowColumns()} |
1372 | 1381 | case IRenderRelative: |
1373 | 1382 | subSize = RenderFlowWith{C: int((w2.Relative() * float64(sz.FlowColumns())) + 0.5)} |
1374 | 1383 | case IRenderWithUnits: |
1375 | subSize = RenderFlowWith{C: w2.Units()} | |
1384 | subSize = RenderFlowWith{C: gwutil.Max(0, gwutil.Min(sz.FlowColumns(), w2.Units()))} | |
1376 | 1385 | default: |
1377 | 1386 | return nil, errors.WithStack(DimensionError{Size: size, Dim: d}) |
1378 | 1387 | } |
1394 | 1403 | // TODO - doc |
1395 | 1404 | func ComputeSubSize(size IRenderSize, w IWidgetDimension, h IWidgetDimension) (IRenderSize, error) { |
1396 | 1405 | var subSize IRenderSize |
1406 | ||
1407 | maxh := 1000000 | |
1408 | if mh, ok := h.(IRenderMaxUnits); ok { | |
1409 | maxh = mh.MaxUnits() | |
1410 | } | |
1411 | maxw := 1000000 | |
1412 | if mw, ok := w.(IRenderMaxUnits); ok { | |
1413 | maxw = mw.MaxUnits() | |
1414 | } | |
1397 | 1415 | |
1398 | 1416 | switch sz := size.(type) { |
1399 | 1417 | case IRenderFixed: |
1401 | 1419 | case IRenderFixed: |
1402 | 1420 | subSize = RenderFixed{} |
1403 | 1421 | case IRenderBox: |
1404 | subSize = RenderBox{C: w2.BoxColumns(), R: w2.BoxRows()} | |
1422 | subSize = RenderBox{ | |
1423 | C: gwutil.Min(maxw, w2.BoxColumns()), | |
1424 | R: gwutil.Min(maxh, w2.BoxRows()), | |
1425 | } | |
1405 | 1426 | case IRenderFlowWith: |
1406 | subSize = RenderFlowWith{C: w2.FlowColumns()} | |
1427 | subSize = RenderFlowWith{ | |
1428 | C: gwutil.Min(maxw, w2.FlowColumns()), | |
1429 | } | |
1407 | 1430 | case IRenderWithUnits: |
1408 | 1431 | switch h2 := h.(type) { |
1409 | 1432 | case IRenderWithUnits: |
1410 | subSize = RenderBox{C: w2.Units(), R: h2.Units()} | |
1433 | subSize = RenderBox{ | |
1434 | C: gwutil.Min(maxw, w2.Units()), | |
1435 | R: gwutil.Min(maxh, h2.Units()), | |
1436 | } | |
1411 | 1437 | default: |
1412 | 1438 | subSize = RenderFixed{} |
1413 | 1439 | } |
1419 | 1445 | case IRenderFixed: |
1420 | 1446 | subSize = RenderFixed{} |
1421 | 1447 | case IRenderBox: |
1422 | subSize = RenderBox{C: w2.BoxColumns(), R: w2.BoxRows()} | |
1448 | subSize = RenderBox{ | |
1449 | C: gwutil.Max(0, gwutil.Min(maxw, sz.BoxColumns(), w2.BoxColumns())), | |
1450 | R: gwutil.Max(0, gwutil.Min(maxh, sz.BoxRows(), w2.BoxRows())), | |
1451 | } | |
1423 | 1452 | case IRenderFlowWith: |
1424 | subSize = RenderFlowWith{C: w2.FlowColumns()} | |
1453 | subSize = RenderFlowWith{ | |
1454 | C: gwutil.Max(0, gwutil.Min(maxw, sz.BoxColumns(), w2.FlowColumns())), | |
1455 | } | |
1425 | 1456 | case IRenderFlow: |
1426 | subSize = RenderFlowWith{C: sz.BoxColumns()} | |
1457 | subSize = RenderFlowWith{ | |
1458 | C: gwutil.Max(0, gwutil.Min(maxw, sz.BoxColumns(), sz.BoxColumns())), | |
1459 | } | |
1427 | 1460 | case IRenderRelative: |
1428 | 1461 | cols := int((w2.Relative() * float64(sz.BoxColumns())) + 0.5) |
1429 | 1462 | switch h2 := h.(type) { |
1430 | 1463 | case IRenderRelative: |
1431 | 1464 | rows := int((h2.Relative() * float64(sz.BoxRows())) + 0.5) |
1432 | subSize = RenderBox{C: cols, R: rows} | |
1465 | subSize = RenderBox{ | |
1466 | C: gwutil.Min(maxw, cols), | |
1467 | R: gwutil.Min(maxh, rows), | |
1468 | } | |
1433 | 1469 | case IRenderWithUnits: |
1434 | 1470 | rows := h2.Units() |
1435 | subSize = RenderBox{C: cols, R: rows} | |
1471 | subSize = RenderBox{ | |
1472 | C: gwutil.Min(maxw, cols), | |
1473 | R: gwutil.Min(maxh, sz.BoxRows(), rows), | |
1474 | } | |
1436 | 1475 | case IRenderFlow: |
1437 | subSize = RenderFlowWith{C: cols} | |
1476 | subSize = RenderFlowWith{ | |
1477 | C: gwutil.Min(maxw, cols), | |
1478 | } | |
1438 | 1479 | case IRenderFixed: |
1439 | subSize = RenderFlowWith{C: cols} | |
1480 | subSize = RenderFlowWith{ | |
1481 | C: gwutil.Min(maxw, cols), | |
1482 | } | |
1440 | 1483 | default: |
1441 | 1484 | return nil, errors.WithStack(DimensionError{Size: size, Dim: w}) |
1442 | 1485 | } |
1443 | 1486 | case IRenderWithUnits: |
1444 | cols := w2.Units() | |
1445 | switch h2 := h.(type) { | |
1487 | cols := gwutil.Min(sz.BoxColumns(), w2.Units()) | |
1488 | switch h := h.(type) { | |
1446 | 1489 | case IRenderRelative: |
1447 | rows := int((h2.Relative() * float64(sz.BoxRows())) + 0.5) | |
1448 | subSize = RenderBox{C: cols, R: rows} | |
1490 | rows := int((h.Relative() * float64(sz.BoxRows())) + 0.5) | |
1491 | subSize = RenderBox{ | |
1492 | C: gwutil.Min(maxw, cols), | |
1493 | R: gwutil.Min(maxh, rows), | |
1494 | } | |
1449 | 1495 | case IRenderWithUnits: |
1450 | rows := h2.Units() | |
1451 | subSize = RenderBox{C: cols, R: rows} | |
1496 | rows := h.Units() | |
1497 | subSize = RenderBox{ | |
1498 | C: gwutil.Min(maxw, cols), | |
1499 | R: gwutil.Max(0, gwutil.Min(maxh, sz.BoxRows(), rows)), | |
1500 | } | |
1452 | 1501 | case IRenderFlow: |
1453 | subSize = RenderFlowWith{C: cols} | |
1502 | subSize = RenderFlowWith{C: gwutil.Min(maxw, cols)} | |
1454 | 1503 | case IRenderFixed: |
1455 | subSize = RenderFlowWith{C: cols} | |
1504 | subSize = RenderFlowWith{C: gwutil.Min(maxw, cols)} | |
1456 | 1505 | default: |
1457 | 1506 | return nil, errors.WithStack(DimensionError{Size: size, Dim: w}) |
1458 | 1507 | } |
1464 | 1513 | case IRenderFixed: |
1465 | 1514 | subSize = RenderFixed{} |
1466 | 1515 | case IRenderBox: |
1467 | subSize = RenderBox{C: w2.BoxColumns(), R: w2.BoxRows()} | |
1516 | subSize = RenderBox{ | |
1517 | C: gwutil.Min(maxw, w2.BoxColumns()), | |
1518 | R: gwutil.Max(0, gwutil.Min(maxh, sz.FlowColumns(), w2.BoxRows())), | |
1519 | } | |
1468 | 1520 | case IRenderFlowWith: |
1469 | subSize = RenderFlowWith{C: w2.FlowColumns()} | |
1521 | subSize = RenderFlowWith{C: gwutil.Min(maxw, w2.FlowColumns())} | |
1470 | 1522 | case IRenderFlow: |
1471 | subSize = RenderFlowWith{C: sz.FlowColumns()} | |
1523 | subSize = RenderFlowWith{C: gwutil.Min(maxw, sz.FlowColumns())} | |
1472 | 1524 | case IRenderRelative: |
1473 | subSize = RenderFlowWith{C: int((w2.Relative() * float64(sz.FlowColumns())) + 0.5)} | |
1525 | subSize = RenderFlowWith{ | |
1526 | C: gwutil.Min(maxw, int((w2.Relative()*float64(sz.FlowColumns()))+0.5)), | |
1527 | } | |
1474 | 1528 | case IRenderWithUnits: |
1475 | subSize = RenderFlowWith{C: w2.Units()} | |
1529 | subSize = RenderFlowWith{ | |
1530 | C: gwutil.Max(0, gwutil.Min(maxw, sz.FlowColumns(), w2.Units())), | |
1531 | } | |
1476 | 1532 | default: |
1477 | 1533 | return nil, errors.WithStack(DimensionError{Size: size, Dim: w}) |
1478 | 1534 | } |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package gowid |
6 | 6 | "fmt" |
7 | 7 | "strings" |
8 | 8 | |
9 | "github.com/gdamore/tcell" | |
9 | tcell "github.com/gdamore/tcell/v2" | |
10 | 10 | ) |
11 | 11 | |
12 | 12 | //====================================================================== |
12 | 12 | "strings" |
13 | 13 | |
14 | 14 | "github.com/gcla/gowid" |
15 | "github.com/gdamore/tcell" | |
15 | tcell "github.com/gdamore/tcell/v2" | |
16 | 16 | ) |
17 | 17 | |
18 | 18 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
8 | 8 | "strings" |
9 | 9 | "testing" |
10 | 10 | |
11 | "github.com/gdamore/tcell" | |
11 | tcell "github.com/gdamore/tcell/v2" | |
12 | 12 | "github.com/stretchr/testify/assert" |
13 | 13 | ) |
14 | 14 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code | |
1 | 1 | // is governed by the MIT license that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // Package boxadapter provides a widget that will allow a box widget to be used |
8 | 8 | "fmt" |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | "github.com/gdamore/tcell" | |
11 | tcell "github.com/gdamore/tcell/v2" | |
12 | 12 | ) |
13 | 13 | |
14 | 14 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package boxadapter |
10 | 10 | "github.com/gcla/gowid/gwtest" |
11 | 11 | "github.com/gcla/gowid/widgets/edit" |
12 | 12 | "github.com/gcla/gowid/widgets/list" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | "github.com/stretchr/testify/assert" |
15 | 15 | ) |
16 | 16 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | 11 | "github.com/gcla/gowid/gwutil" |
12 | "github.com/gdamore/tcell" | |
12 | tcell "github.com/gdamore/tcell/v2" | |
13 | 13 | ) |
14 | 14 | |
15 | 15 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package button |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package checkbox |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
9 | 9 | "fmt" |
10 | 10 | |
11 | 11 | "github.com/gcla/gowid" |
12 | "github.com/gdamore/tcell" | |
12 | tcell "github.com/gdamore/tcell/v2" | |
13 | 13 | ) |
14 | 14 | |
15 | 15 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
12 | 12 | "github.com/gcla/gowid/gwutil" |
13 | 13 | "github.com/gcla/gowid/vim" |
14 | 14 | "github.com/gcla/gowid/widgets/fill" |
15 | "github.com/gdamore/tcell" | |
15 | tcell "github.com/gdamore/tcell/v2" | |
16 | 16 | ) |
17 | 17 | |
18 | 18 | //====================================================================== |
32 | 32 | } |
33 | 33 | |
34 | 34 | type Widget struct { |
35 | widgets []gowid.IContainerWidget | |
36 | focus int // -1 means nothing selectable | |
37 | prefCol int // caches the last set prefered col. Passes it on if widget hasn't changed focus | |
38 | opt Options | |
35 | widgets []gowid.IContainerWidget | |
36 | focus int // -1 means nothing selectable | |
37 | prefCol int // caches the last set prefered col. Passes it on if widget hasn't changed focus | |
38 | widthHelper []bool // optimizations to save frequent array allocations during use | |
39 | widthHelper2 []bool | |
40 | opt Options | |
39 | 41 | *gowid.Callbacks |
40 | 42 | gowid.AddressProvidesID |
41 | 43 | gowid.SubWidgetsCallbacks |
66 | 68 | opt.RightKeys = vim.AllRightKeys |
67 | 69 | } |
68 | 70 | res := &Widget{ |
69 | widgets: widgets, | |
70 | focus: -1, | |
71 | prefCol: -1, | |
72 | opt: opt, | |
71 | widgets: widgets, | |
72 | focus: -1, | |
73 | prefCol: -1, | |
74 | widthHelper: make([]bool, len(widgets)), | |
75 | widthHelper2: make([]bool, len(widgets)), | |
76 | opt: opt, | |
73 | 77 | } |
74 | 78 | res.SubWidgetsCallbacks = gowid.SubWidgetsCallbacks{CB: &res.Callbacks} |
75 | 79 | res.FocusCallbacks = gowid.FocusCallbacks{CB: &res.Callbacks} |
158 | 162 | ws[i] = &gowid.ContainerWidget{IWidget: iw, D: gowid.RenderFlow{}} |
159 | 163 | } |
160 | 164 | } |
165 | w.widthHelper = make([]bool, len(widgets)) | |
166 | w.widthHelper2 = make([]bool, len(widgets)) | |
161 | 167 | oldFocus := w.Focus() |
162 | 168 | w.widgets = ws |
163 | 169 | w.SetFocus(app, oldFocus) |
269 | 275 | return vim.KeyIn(evk, w.opt.RightKeys) |
270 | 276 | } |
271 | 277 | |
278 | type IWidthHelper interface { | |
279 | WidthHelpers() ([]bool, []bool) | |
280 | } | |
281 | ||
282 | var _ IWidthHelper = (*Widget)(nil) | |
283 | ||
284 | func (w *Widget) WidthHelpers() ([]bool, []bool) { | |
285 | return w.widthHelper, w.widthHelper2 | |
286 | } | |
287 | ||
272 | 288 | //'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' |
273 | 289 | |
274 | 290 | func SubWidgetSize(size gowid.IRenderSize, newX int, dim gowid.IWidgetDimension) gowid.IRenderSize { |
310 | 326 | } |
311 | 327 | |
312 | 328 | func UserInput(w IWidget, ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { |
313 | res := true | |
329 | res := false | |
314 | 330 | subfocus := w.Focus() |
315 | 331 | |
316 | 332 | subSizes := w.WidgetWidths(size, focus, subfocus, app) |
321 | 337 | forChild := false |
322 | 338 | |
323 | 339 | if subfocus != -1 { |
340 | res = true | |
324 | 341 | if evm, ok := ev.(*tcell.EventMouse); ok { |
325 | 342 | curX := 0 |
326 | 343 | mx, _ := evm.Position() |
417 | 434 | lenw := len(subs) |
418 | 435 | |
419 | 436 | res := make([]int, lenw) |
420 | helper := make([]bool, lenw) | |
437 | var widthHelper []bool | |
438 | var widthHelper2 []bool | |
439 | if w, ok := w.(IWidthHelper); ok { | |
440 | // Save some allocations | |
441 | widthHelper, widthHelper2 = w.WidthHelpers() | |
442 | defer func() { | |
443 | for i := 0; i < len(widthHelper); i++ { | |
444 | widthHelper[i] = false | |
445 | widthHelper2[i] = false | |
446 | } | |
447 | }() | |
448 | } else { | |
449 | widthHelper = make([]bool, lenw) | |
450 | widthHelper2 = make([]bool, lenw) | |
451 | } | |
421 | 452 | |
422 | 453 | haveColsTotal := false |
423 | 454 | var colsTotal int |
450 | 481 | res[i] = c.BoxColumns() |
451 | 482 | trunc(&res[i]) |
452 | 483 | colsUsed += res[i] |
453 | helper[i] = true | |
484 | widthHelper[i] = true | |
485 | widthHelper2[i] = true | |
454 | 486 | case gowid.IRenderBox: |
455 | 487 | res[i] = w2.BoxColumns() |
456 | 488 | trunc(&res[i]) |
457 | 489 | colsUsed += res[i] |
458 | helper[i] = true | |
490 | widthHelper[i] = true | |
491 | widthHelper2[i] = true | |
459 | 492 | case gowid.IRenderFlowWith: |
460 | 493 | res[i] = w2.FlowColumns() |
461 | 494 | trunc(&res[i]) |
462 | 495 | colsUsed += res[i] |
463 | helper[i] = true | |
496 | widthHelper[i] = true | |
497 | widthHelper2[i] = true | |
464 | 498 | case gowid.IRenderWithUnits: |
465 | 499 | res[i] = w2.Units() |
466 | 500 | trunc(&res[i]) |
467 | 501 | colsUsed += res[i] |
468 | helper[i] = true | |
502 | widthHelper[i] = true | |
503 | widthHelper2[i] = true | |
469 | 504 | case gowid.IRenderRelative: |
470 | 505 | cols, ok := size.(gowid.IColumns) |
471 | 506 | if !ok { |
474 | 509 | res[i] = int((w2.Relative() * float64(cols.Columns())) + 0.5) |
475 | 510 | trunc(&res[i]) |
476 | 511 | colsUsed += res[i] |
477 | helper[i] = true | |
512 | widthHelper[i] = true | |
513 | widthHelper2[i] = true | |
478 | 514 | case gowid.IRenderWithWeight: |
479 | 515 | // widget must be weighted |
480 | 516 | totalWeight += w2.Weight() |
481 | helper[i] = false | |
517 | widthHelper[i] = false | |
518 | widthHelper2[i] = false | |
482 | 519 | default: |
483 | 520 | panic(gowid.DimensionError{Size: size, Dim: w2}) |
484 | 521 | } |
493 | 530 | |
494 | 531 | // Now, divide up the remaining space among the weight columns |
495 | 532 | lasti := -1 |
533 | maxedOut := false | |
496 | 534 | for { |
497 | 535 | if colsLeft == 0 { |
498 | 536 | break |
500 | 538 | doneone := false |
501 | 539 | totalWeight = 0 |
502 | 540 | for i := 0; i < lenw; i++ { |
503 | if w2, ok := dims[i].(gowid.IRenderWithWeight); ok && !helper[i] { | |
541 | if w2, ok := dims[i].(gowid.IRenderWithWeight); ok && !widthHelper[i] { | |
504 | 542 | totalWeight += w2.Weight() |
505 | 543 | } |
506 | 544 | } |
507 | 545 | colsToDivideUp = colsLeft |
508 | 546 | for i := 0; i < lenw; i++ { |
509 | 547 | // Can only be weight here if !helper[i] ; but not sufficient for it to be eligible |
510 | if !helper[i] { | |
548 | if !widthHelper[i] { | |
511 | 549 | cols := int(((float32(dims[i].(gowid.IRenderWithWeight).Weight()) / float32(totalWeight)) * float32(colsToDivideUp)) + 0.5) |
512 | if max, ok := dims[i].(gowid.IRenderMaxUnits); ok { | |
513 | if cols > max.MaxUnits() { | |
514 | cols = max.MaxUnits() | |
515 | helper[i] = true // this one is done | |
550 | if !maxedOut { | |
551 | if max, ok := dims[i].(gowid.IRenderMaxUnits); ok { | |
552 | if cols >= max.MaxUnits() { | |
553 | cols = max.MaxUnits() | |
554 | widthHelper[i] = true // this one is done | |
555 | } | |
516 | 556 | } |
517 | 557 | } |
518 | 558 | if cols > colsLeft { |
524 | 564 | } |
525 | 565 | res[i] += cols |
526 | 566 | colsLeft -= cols |
527 | lasti = i | |
567 | lasti = gwutil.Max(i, lasti) | |
528 | 568 | doneone = true |
529 | 569 | } |
530 | 570 | } |
531 | 571 | } |
532 | 572 | if !doneone { |
533 | break | |
573 | // We used up all our extra space, after all weighted columns were maxed out. So | |
574 | // we're done. Any extra space (should be just 1 unit at most) goes on the last columns. | |
575 | if maxedOut { | |
576 | break | |
577 | } | |
578 | // All the weighted columns have been assigned, and all were maxed out. We still | |
579 | // have space to assign. So now grow the weighted columns, even though they don't need | |
580 | // any more space for a full render - what else to do with the space! | |
581 | maxedOut = true | |
582 | // Reset; all false indices will be indices of weighted columns again | |
583 | for i := 0; i < len(widthHelper); i++ { | |
584 | widthHelper[i] = widthHelper2[i] | |
585 | } | |
534 | 586 | } |
535 | 587 | } |
536 | 588 | if lasti != -1 && colsLeft > 0 { |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
8 | 8 | |
9 | 9 | "github.com/gcla/gowid" |
10 | 10 | "github.com/gcla/gowid/gwtest" |
11 | "github.com/gcla/gowid/widgets/button" | |
11 | 12 | "github.com/gcla/gowid/widgets/checkbox" |
12 | 13 | "github.com/gcla/gowid/widgets/fill" |
13 | 14 | "github.com/gcla/gowid/widgets/selectable" |
14 | 15 | "github.com/gcla/gowid/widgets/text" |
15 | "github.com/gdamore/tcell" | |
16 | tcell "github.com/gdamore/tcell/v2" | |
16 | 17 | "github.com/stretchr/testify/assert" |
17 | 18 | ) |
18 | 19 | |
151 | 152 | return renderWeightUpTo{gowid.RenderWithWeight{W: w}, max} |
152 | 153 | } |
153 | 154 | |
155 | func weight(w int) gowid.RenderWithWeight { | |
156 | return gowid.RenderWithWeight{W: w} | |
157 | } | |
158 | ||
154 | 159 | func makep(c rune) gowid.IWidget { |
155 | 160 | return selectable.New(fill.New(c)) |
156 | 161 | } |
168 | 173 | w = New(subs) |
169 | 174 | c = w.Render(gowid.RenderBox{C: 12, R: 1}, gowid.Focused, gwtest.D) |
170 | 175 | assert.Equal(t, "xxxxxyyyyyzz", c.String()) |
176 | } | |
177 | ||
178 | func TestColumns5(t *testing.T) { | |
179 | // None are selectable | |
180 | subs := []gowid.IContainerWidget{ | |
181 | &gowid.ContainerWidget{text.New("x"), gowid.RenderWithWeight{W: 1}}, | |
182 | &gowid.ContainerWidget{text.New("y"), gowid.RenderWithWeight{W: 1}}, | |
183 | } | |
184 | w := New(subs) | |
185 | sz := gowid.RenderBox{C: 2, R: 1} | |
186 | c := w.Render(sz, gowid.Focused, gwtest.D) | |
187 | assert.Equal(t, "xy", c.String()) | |
188 | ||
189 | evright := gwtest.CursorRight() | |
190 | acc := w.UserInput(evright, sz, gowid.Focused, gwtest.D) | |
191 | ||
192 | // Nothing in here should accept the input, so it should bubble back up | |
193 | assert.False(t, acc) | |
194 | } | |
195 | ||
196 | type renderWithUnitsMax struct { | |
197 | gowid.RenderWithUnits | |
198 | gowid.RenderMax | |
199 | } | |
200 | ||
201 | func TestColumns6(t *testing.T) { | |
202 | h := renderWithUnitsMax{ | |
203 | RenderWithUnits: gowid.RenderWithUnits{1}, | |
204 | } | |
205 | ||
206 | f := fill.New(' ') | |
207 | ||
208 | subs := []gowid.IContainerWidget{ | |
209 | &gowid.ContainerWidget{f, h}, | |
210 | &gowid.ContainerWidget{button.NewBare(text.New("1")), gowid.RenderWithWeight{W: 4}}, | |
211 | &gowid.ContainerWidget{f, h}, | |
212 | &gowid.ContainerWidget{button.NewBare(text.New("0.000000")), gowid.RenderWithWeight{W: 8}}, | |
213 | &gowid.ContainerWidget{f, h}, | |
214 | &gowid.ContainerWidget{button.NewBare(text.New("192.168.44.123")), gowid.RenderWithWeight{W: 14}}, | |
215 | &gowid.ContainerWidget{f, h}, | |
216 | &gowid.ContainerWidget{button.NewBare(text.New("192.168.44.213")), gowid.RenderWithWeight{W: 14}}, | |
217 | &gowid.ContainerWidget{f, h}, | |
218 | &gowid.ContainerWidget{button.NewBare(text.New("TFTP")), gowid.RenderWithWeight{W: 6}}, | |
219 | &gowid.ContainerWidget{f, h}, | |
220 | &gowid.ContainerWidget{button.NewBare(text.New("77")), gowid.RenderWithWeight{W: 7}}, | |
221 | &gowid.ContainerWidget{f, h}, | |
222 | &gowid.ContainerWidget{button.NewBare(text.New("Read Request, File: C:\\IBMTCPIP\\lccm.1, Transfer type: octet")), gowid.RenderWithWeight{W: 60}}, | |
223 | &gowid.ContainerWidget{f, h}, | |
224 | } | |
225 | w := New(subs) | |
226 | sz := gowid.RenderFlowWith{C: 158} | |
227 | c := w.Render(sz, gowid.Focused, gwtest.D) | |
228 | assert.Equal(t, " 1 0.000000 192.168.44.123 192.168.44.213 TFTP 77 Read Request, File: C:\\IBMTCPIP\\lccm.1, Transfer type: octet ", c.String()) | |
229 | ||
230 | } | |
231 | ||
232 | func TestColumns7(t *testing.T) { | |
233 | d2 := weightupto(1, 2) // weight 1, max 2 | |
234 | d3 := weightupto(1, 3) // weight 1, max 3 | |
235 | d4 := weightupto(1, 4) // weight 1, max 4 | |
236 | ||
237 | sz4 := gowid.RenderFlowWith{C: 4} | |
238 | sz6 := gowid.RenderFlowWith{C: 6} | |
239 | ||
240 | subs := []gowid.IContainerWidget{ | |
241 | &gowid.ContainerWidget{text.New("aa"), d2}, | |
242 | &gowid.ContainerWidget{text.New("bb"), d2}, | |
243 | } | |
244 | ||
245 | w := New(subs) | |
246 | c := w.Render(sz4, gowid.Focused, gwtest.D) | |
247 | assert.Equal(t, "aabb", c.String()) | |
248 | ||
249 | subs = []gowid.IContainerWidget{ | |
250 | &gowid.ContainerWidget{text.New("aa"), d3}, | |
251 | &gowid.ContainerWidget{text.New("bb"), d3}, | |
252 | } | |
253 | w = New(subs) | |
254 | ||
255 | c = w.Render(sz4, gowid.Focused, gwtest.D) | |
256 | assert.Equal(t, "aabb", c.String()) | |
257 | ||
258 | c = w.Render(sz6, gowid.Focused, gwtest.D) | |
259 | assert.Equal(t, "aa bb ", c.String()) | |
260 | ||
261 | subs = []gowid.IContainerWidget{ | |
262 | &gowid.ContainerWidget{text.New("aaaa"), d4}, | |
263 | &gowid.ContainerWidget{text.New("bb"), d2}, | |
264 | } | |
265 | w = New(subs) | |
266 | ||
267 | c = w.Render(sz6, gowid.Focused, gwtest.D) | |
268 | assert.Equal(t, "aaaabb", c.String()) | |
269 | ||
270 | subs = []gowid.IContainerWidget{ | |
271 | &gowid.ContainerWidget{text.New("aaaa"), weight(1)}, | |
272 | &gowid.ContainerWidget{text.New("bb"), weight(1)}, | |
273 | } | |
274 | w = New(subs) | |
275 | ||
276 | c = w.Render(sz6, gowid.Focused, gwtest.D) | |
277 | assert.Equal(t, "aaabb \na ", c.String()) | |
171 | 278 | } |
172 | 279 | |
173 | 280 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
19 | 19 | "github.com/gcla/gowid/widgets/shadow" |
20 | 20 | "github.com/gcla/gowid/widgets/styled" |
21 | 21 | "github.com/gcla/gowid/widgets/text" |
22 | "github.com/gdamore/tcell" | |
22 | tcell "github.com/gdamore/tcell/v2" | |
23 | 23 | ) |
24 | 24 | |
25 | 25 | //====================================================================== |
41 | 41 | SetHeight(gowid.IWidgetDimension, gowid.IApp) |
42 | 42 | } |
43 | 43 | |
44 | type ISwitchFocus interface { | |
45 | IsSwitchFocus() bool | |
46 | SwitchFocus(gowid.IApp) | |
47 | } | |
48 | ||
49 | type IModal interface { | |
50 | IsModal() bool | |
51 | } | |
52 | ||
44 | 53 | type IMaximizer interface { |
45 | 54 | IsMaxed() bool |
46 | 55 | Maximize(gowid.IApp) |
56 | 65 | Options Options |
57 | 66 | savedSubWidgetWidget gowid.IWidget |
58 | 67 | savedContainer gowid.ISettableComposite |
68 | content *pile.Widget | |
59 | 69 | contentWrapper *gowid.ContainerWidget |
60 | 70 | open bool |
61 | 71 | maxer Maximizer |
66 | 76 | var _ gowid.IWidget = (*Widget)(nil) |
67 | 77 | var _ IWidget = (*Widget)(nil) |
68 | 78 | var _ IMaximizer = (*Widget)(nil) |
79 | var _ ISwitchFocus = (*Widget)(nil) | |
80 | var _ IModal = (*Widget)(nil) | |
69 | 81 | |
70 | 82 | type Options struct { |
71 | 83 | Buttons []Button |
76 | 88 | BorderStyle gowid.ICellStyler |
77 | 89 | FocusOnWidget bool |
78 | 90 | NoFrame bool |
91 | Modal bool | |
92 | TabToButtons bool | |
93 | StartIdx int | |
79 | 94 | } |
80 | 95 | |
81 | 96 | type Button struct { |
82 | 97 | Msg string |
83 | Action gowid.WidgetChangedFunction | |
98 | Action gowid.IWidgetChangedCallback | |
84 | 99 | } |
85 | 100 | |
86 | 101 | var Quit, Exit, CloseD, Cancel Button |
89 | 104 | func init() { |
90 | 105 | Quit = Button{ |
91 | 106 | Msg: "Quit", |
92 | Action: gowid.QuitFn, | |
107 | Action: gowid.MakeWidgetCallback("quit", gowid.WidgetChangedFunction(gowid.QuitFn)), | |
93 | 108 | } |
94 | 109 | Exit = Button{ |
95 | 110 | Msg: "Exit", |
96 | Action: gowid.QuitFn, | |
111 | Action: gowid.MakeWidgetCallback("exit", gowid.WidgetChangedFunction(gowid.QuitFn)), | |
97 | 112 | } |
98 | 113 | CloseD = Button{ |
99 | 114 | Msg: "Close", |
104 | 119 | OkCancel = []Button{ |
105 | 120 | Button{ |
106 | 121 | Msg: "Ok", |
107 | Action: gowid.QuitFn, | |
122 | Action: gowid.MakeWidgetCallback("okcancel", gowid.WidgetChangedFunction(gowid.QuitFn)), | |
108 | 123 | }, |
109 | 124 | Cancel, |
110 | 125 | } |
141 | 156 | buttonStyle, backgroundStyle, borderStyle := opt.ButtonStyle, opt.BackgroundStyle, opt.BorderStyle |
142 | 157 | |
143 | 158 | if buttonStyle == nil { |
144 | buttonStyle = gowid.MakePaletteEntry(DefaultButtonText, DefaultButton) | |
159 | buttonStyle = gowid.MakeStyledPaletteEntry(DefaultButtonText, DefaultButton, gowid.StyleNone) | |
145 | 160 | } |
146 | 161 | if backgroundStyle == nil { |
147 | backgroundStyle = gowid.MakePaletteEntry(DefaultText, DefaultBackground) | |
162 | backgroundStyle = gowid.MakeStyledPaletteEntry(DefaultText, DefaultBackground, gowid.StyleNone) | |
148 | 163 | } |
149 | 164 | if borderStyle == nil { |
150 | borderStyle = gowid.MakePaletteEntry(DefaultButton, DefaultBackground) | |
165 | borderStyle = gowid.MakeStyledPaletteEntry(DefaultButton, DefaultBackground, gowid.StyleNone) | |
151 | 166 | } |
152 | 167 | |
153 | 168 | colsW := make([]gowid.IContainerWidget, 0) |
165 | 180 | res.Close(app) |
166 | 181 | }}) |
167 | 182 | } else { |
168 | bw.OnClick(gowid.WidgetCallback{fmt.Sprintf("cb-%d", i), b.Action}) | |
183 | bw.OnClick(b.Action) | |
169 | 184 | } |
170 | 185 | colsW = append(colsW, |
171 | 186 | &gowid.ContainerWidget{ |
181 | 196 | } |
182 | 197 | |
183 | 198 | if len(colsW) > 0 { |
184 | cols := columns.New(colsW) | |
199 | cols := columns.New(colsW, columns.Options{ | |
200 | StartColumn: opt.StartIdx * 2, | |
201 | }) | |
185 | 202 | pileW = append(pileW, |
186 | 203 | styled.New( |
187 | 204 | divider.NewUnicodeAlt(), |
192 | 209 | } |
193 | 210 | |
194 | 211 | dialogContent := pile.NewFlow(pileW...) |
195 | if !opt.FocusOnWidget { | |
196 | dialogContent.SetFocus(nil, len(pileW)-1) | |
197 | } | |
198 | 212 | |
199 | 213 | var d gowid.IWidget = dialogContent |
200 | 214 | if !opt.NoFrame { |
219 | 233 | res = &Widget{ |
220 | 234 | IWidget: d, |
221 | 235 | contentWrapper: wrapper, |
236 | content: dialogContent, | |
222 | 237 | Options: opt, |
223 | 238 | Callbacks: gowid.NewCallbacks(), |
239 | } | |
240 | ||
241 | if !opt.FocusOnWidget { | |
242 | res.FocusOnButtons(nil) | |
224 | 243 | } |
225 | 244 | |
226 | 245 | return res |
249 | 268 | return !w.Options.NoEscapeClose |
250 | 269 | } |
251 | 270 | |
271 | func (w *Widget) IsModal() bool { | |
272 | return w.Options.Modal | |
273 | } | |
274 | ||
275 | func (w *Widget) SwitchFocus(app gowid.IApp) { | |
276 | f := w.content.Focus() | |
277 | if f == 0 { | |
278 | w.FocusOnButtons(app) | |
279 | } else { | |
280 | w.FocusOnContent(app) | |
281 | } | |
282 | } | |
283 | ||
284 | func (w *Widget) IsSwitchFocus() bool { | |
285 | return w.Options.TabToButtons | |
286 | } | |
287 | ||
252 | 288 | func (w *Widget) IsOpen() bool { |
253 | 289 | return w.open |
254 | 290 | } |
336 | 372 | |
337 | 373 | func (w *Widget) SetContentWidth(d gowid.IWidgetDimension, app gowid.IApp) { |
338 | 374 | w.contentWrapper.D = d |
375 | } | |
376 | ||
377 | func (w *Widget) FocusOnButtons(app gowid.IApp) { | |
378 | w.content.SetFocus(app, len(w.content.SubWidgets())-1) | |
379 | } | |
380 | ||
381 | func (w *Widget) FocusOnContent(app gowid.IApp) { | |
382 | w.content.SetFocus(app, 0) | |
339 | 383 | } |
340 | 384 | |
341 | 385 | //'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' |
360 | 404 | func OpenExt(w IOpenExt, container gowid.ISettableComposite, width gowid.IWidgetDimension, height gowid.IWidgetDimension, app gowid.IApp) { |
361 | 405 | ov := overlay.New(w, container.SubWidget(), |
362 | 406 | gowid.VAlignMiddle{}, height, // Intended to mean use as much vertical space as you need |
363 | gowid.HAlignMiddle{}, width) | |
407 | gowid.HAlignMiddle{}, width, overlay.Options{ | |
408 | IgnoreLowerStyle: true, | |
409 | }) | |
364 | 410 | |
365 | 411 | if _, ok := width.(gowid.IRenderFixed); ok { |
366 | 412 | w.SetContentWidth(gowid.RenderFixed{}, app) // fixed or weight:1, ratio:0.5 |
376 | 422 | func UserInput(w IWidget, ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { |
377 | 423 | var res bool |
378 | 424 | if w.IsOpen() { |
379 | handled := false | |
380 | 425 | if evk, ok := ev.(*tcell.EventKey); ok { |
381 | 426 | switch { |
382 | 427 | case evk.Key() == tcell.KeyCtrlC || evk.Key() == tcell.KeyEsc: |
383 | 428 | if w.EscapeCloses() { |
384 | 429 | w.GetNoFunction().Changed(app, w) |
385 | handled = true | |
430 | res = true | |
386 | 431 | } |
387 | 432 | } |
388 | 433 | } |
389 | if !handled { | |
390 | handled = gowid.UserInputIfSelectable(w.SubWidget(), ev, size, focus, app) | |
434 | if !res { | |
435 | res = gowid.UserInputIfSelectable(w.SubWidget(), ev, size, focus, app) | |
391 | 436 | } |
392 | if !handled { | |
437 | if !res { | |
393 | 438 | if evk, ok := ev.(*tcell.EventKey); ok { |
394 | 439 | switch { |
395 | 440 | case evk.Key() == tcell.KeyRune && evk.Rune() == 'z': |
399 | 444 | } else { |
400 | 445 | w.Maximize(app) |
401 | 446 | } |
402 | handled = true | |
447 | res = true | |
448 | } | |
449 | case evk.Key() == tcell.KeyTab: | |
450 | if w, ok := w.(ISwitchFocus); ok { | |
451 | w.SwitchFocus(app) | |
452 | res = true | |
403 | 453 | } |
404 | 454 | } |
455 | ||
405 | 456 | } |
406 | 457 | } |
407 | res = true | |
458 | if w, ok := w.(IModal); ok { | |
459 | if w.IsModal() { | |
460 | res = true | |
461 | } | |
462 | } | |
408 | 463 | } else { |
409 | 464 | res = gowid.UserInputIfSelectable(w.SubWidget(), ev, size, focus, app) |
410 | 465 | } |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package divider |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
13 | 13 | "github.com/gcla/gowid" |
14 | 14 | "github.com/gcla/gowid/gwutil" |
15 | 15 | "github.com/gcla/gowid/widgets/text" |
16 | "github.com/gdamore/tcell" | |
16 | tcell "github.com/gdamore/tcell/v2" | |
17 | 17 | "github.com/pkg/errors" |
18 | 18 | ) |
19 | 19 | |
34 | 34 | MaskChr() rune |
35 | 35 | } |
36 | 36 | |
37 | type IPaste interface { | |
38 | PasteState(...bool) bool | |
39 | AddKey(*tcell.EventKey) | |
40 | GetKeys() []*tcell.EventKey | |
41 | } | |
42 | ||
37 | 43 | type Mask struct { |
38 | 44 | Chr rune |
39 | 45 | Enable bool |
69 | 75 | DownLines(size gowid.IRenderSize, doPage bool, app gowid.IApp) bool |
70 | 76 | } |
71 | 77 | |
78 | type IReadOnly interface { | |
79 | IsReadOnly() bool | |
80 | } | |
81 | ||
72 | 82 | type Widget struct { |
73 | 83 | IMask |
74 | 84 | caption string |
75 | 85 | text string |
86 | paste bool | |
87 | readonly bool | |
88 | pastedKeys []*tcell.EventKey | |
76 | 89 | cursorPos int |
77 | 90 | linesFromTop int |
78 | 91 | Callbacks *gowid.Callbacks |
82 | 95 | var _ fmt.Stringer = (*Widget)(nil) |
83 | 96 | var _ io.Reader = (*Widget)(nil) |
84 | 97 | var _ gowid.IWidget = (*Widget)(nil) |
98 | var _ IPaste = (*Widget)(nil) | |
99 | var _ IReadOnly = (*Widget)(nil) | |
85 | 100 | |
86 | 101 | // Writer embeds an EditWidget and provides the io.Writer interface. An gowid.IApp needs to |
87 | 102 | // be provided too because the widget's SetText() function requires it in order to issue |
92 | 107 | } |
93 | 108 | |
94 | 109 | type Options struct { |
95 | Caption string | |
96 | Text string | |
97 | Mask IMask | |
110 | Caption string | |
111 | Text string | |
112 | Mask IMask | |
113 | ReadOnly bool | |
98 | 114 | } |
99 | 115 | |
100 | 116 | func New(args ...Options) *Widget { |
109 | 125 | IMask: opt.Mask, |
110 | 126 | caption: opt.Caption, |
111 | 127 | text: opt.Text, |
128 | readonly: opt.ReadOnly, | |
112 | 129 | cursorPos: len(opt.Text), |
130 | pastedKeys: make([]*tcell.EventKey, 0, 100), | |
113 | 131 | linesFromTop: 0, |
114 | 132 | Callbacks: gowid.NewCallbacks(), |
115 | 133 | } |
118 | 136 | |
119 | 137 | func (w *Widget) String() string { |
120 | 138 | return fmt.Sprintf("edit") |
139 | } | |
140 | ||
141 | func (w *Widget) IsReadOnly() bool { | |
142 | return w.readonly | |
143 | } | |
144 | ||
145 | func (w *Widget) SetReadOnly(v bool, _ gowid.IApp) { | |
146 | w.readonly = v | |
121 | 147 | } |
122 | 148 | |
123 | 149 | // Set content from array |
187 | 213 | func (w *Widget) SetCaption(text string, app gowid.IApp) { |
188 | 214 | w.caption = text |
189 | 215 | gowid.RunWidgetCallbacks(w.Callbacks, Caption{}, app, w) |
216 | } | |
217 | ||
218 | //func (w *Widget) PasteState(b ...bool) []*tcell.EventKey { | |
219 | func (w *Widget) PasteState(b ...bool) bool { | |
220 | if len(b) > 0 { | |
221 | w.paste = b[0] | |
222 | //if w.paste { | |
223 | w.pastedKeys = w.pastedKeys[:0] | |
224 | //} | |
225 | } | |
226 | return w.paste | |
227 | } | |
228 | ||
229 | func (w *Widget) AddKey(ev *tcell.EventKey) { | |
230 | w.pastedKeys = append(w.pastedKeys, ev) | |
231 | } | |
232 | ||
233 | func (w *Widget) GetKeys() []*tcell.EventKey { | |
234 | return w.pastedKeys | |
190 | 235 | } |
191 | 236 | |
192 | 237 | func (w *Widget) CursorEnabled() bool { |
381 | 426 | } |
382 | 427 | } |
383 | 428 | |
429 | func keyIsPasteable(ev *tcell.EventKey) bool { | |
430 | switch ev.Key() { | |
431 | case tcell.KeyEnter, tcell.Key(' '), tcell.KeyRune: | |
432 | return true | |
433 | default: | |
434 | return false | |
435 | } | |
436 | } | |
437 | ||
438 | func isReadOnly(w interface{}) bool { | |
439 | readOnly := false | |
440 | if ro, ok := w.(IReadOnly); ok { | |
441 | readOnly = ro.IsReadOnly() | |
442 | } | |
443 | return readOnly | |
444 | } | |
445 | ||
446 | func pasteableKeyInput(w IWidget, ev *tcell.EventKey, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { | |
447 | if isReadOnly(w) { | |
448 | return false | |
449 | } | |
450 | ||
451 | handled := true | |
452 | switch ev.Key() { | |
453 | case tcell.KeyEnter: | |
454 | r := []rune(w.Text()) | |
455 | w.SetText(string(r[0:w.CursorPos()])+string('\n')+string(r[w.CursorPos():]), app) | |
456 | w.SetCursorPos(w.CursorPos()+1, app) | |
457 | case tcell.Key(' '): | |
458 | r := []rune(w.Text()) | |
459 | w.SetText(string(r[0:w.CursorPos()])+" "+string(r[w.CursorPos():]), app) | |
460 | w.SetCursorPos(w.CursorPos()+1, app) | |
461 | case tcell.KeyRune: | |
462 | // TODO: this is lame. Inserting a character is O(n) where n is length | |
463 | // of text. I should switch this to use the two stack model for edited | |
464 | // text. | |
465 | txt := w.Text() | |
466 | r := []rune(txt) | |
467 | cpos := w.CursorPos() | |
468 | rhs := make([]rune, len(r)-cpos) | |
469 | copy(rhs, r[cpos:]) | |
470 | w.SetText(string(append(append(r[:cpos], ev.Rune()), rhs...)), app) | |
471 | w.SetCursorPos(w.CursorPos()+1, app) | |
472 | ||
473 | default: | |
474 | handled = false | |
475 | } | |
476 | ||
477 | return handled | |
478 | } | |
479 | ||
384 | 480 | func UserInput(w IWidget, ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { |
385 | 481 | handled := true |
386 | 482 | doup := false |
387 | 483 | dodown := false |
388 | 484 | recalcLinesFromTop := false |
485 | readOnly := isReadOnly(w) | |
486 | ||
389 | 487 | switch ev := ev.(type) { |
390 | 488 | case *tcell.EventMouse: |
391 | 489 | switch ev.Buttons() { |
412 | 510 | handled = false |
413 | 511 | } |
414 | 512 | |
513 | case *tcell.EventPaste: | |
514 | if wp, ok := w.(IPaste); ok { | |
515 | if ev.Start() { | |
516 | wp.PasteState(true) | |
517 | } else { | |
518 | evs := wp.GetKeys() | |
519 | wp.PasteState(false) | |
520 | for _, ev := range evs { | |
521 | pasteableKeyInput(w, ev, size, focus, app) | |
522 | } | |
523 | } | |
524 | } | |
525 | ||
415 | 526 | case *tcell.EventKey: |
416 | switch ev.Key() { | |
417 | case tcell.KeyPgUp: | |
418 | handled = w.UpLines(size, true, app) | |
419 | case tcell.KeyUp, tcell.KeyCtrlP: | |
420 | doup = true | |
421 | case tcell.KeyDown, tcell.KeyCtrlN: | |
422 | dodown = true | |
423 | case tcell.KeyPgDn: | |
424 | handled = w.DownLines(size, true, app) | |
425 | case tcell.KeyLeft, tcell.KeyCtrlB: | |
426 | if w.CursorPos() > 0 { | |
427 | w.SetCursorPos(w.CursorPos()-1, app) | |
428 | } else { | |
527 | handled = false | |
528 | if wp, ok := w.(IPaste); ok { | |
529 | if wp.PasteState() && keyIsPasteable(ev) && !readOnly { | |
530 | wp.AddKey(ev) | |
531 | handled = true | |
532 | } | |
533 | } | |
534 | ||
535 | if !handled { | |
536 | handled = pasteableKeyInput(w, ev, size, focus, app) | |
537 | } | |
538 | ||
539 | if !handled { | |
540 | handled = true | |
541 | switch ev.Key() { | |
542 | case tcell.KeyPgUp: | |
543 | handled = w.UpLines(size, true, app) | |
544 | case tcell.KeyUp, tcell.KeyCtrlP: | |
545 | doup = true | |
546 | case tcell.KeyDown, tcell.KeyCtrlN: | |
547 | dodown = true | |
548 | case tcell.KeyPgDn: | |
549 | handled = w.DownLines(size, true, app) | |
550 | case tcell.KeyLeft, tcell.KeyCtrlB: | |
551 | if w.CursorPos() > 0 { | |
552 | w.SetCursorPos(w.CursorPos()-1, app) | |
553 | } else { | |
554 | handled = false | |
555 | } | |
556 | case tcell.KeyRight, tcell.KeyCtrlF: | |
557 | if w.CursorPos() < utf8.RuneCountInString(w.Text()) { | |
558 | w.SetCursorPos(w.CursorPos()+1, app) | |
559 | } else { | |
560 | handled = false | |
561 | } | |
562 | case tcell.KeyBackspace, tcell.KeyBackspace2: | |
563 | if !readOnly { | |
564 | if w.CursorPos() > 0 { | |
565 | pos := w.CursorPos() | |
566 | w.SetCursorPos(w.CursorPos()-1, app) | |
567 | r := []rune(w.Text()) | |
568 | w.SetText(string(r[0:pos-1])+string(r[pos:]), app) | |
569 | } | |
570 | } | |
571 | case tcell.KeyDelete, tcell.KeyCtrlD: | |
572 | if !readOnly { | |
573 | if w.CursorPos() < utf8.RuneCountInString(w.Text()) { | |
574 | r := []rune(w.Text()) | |
575 | w.SetText(string(r[0:w.CursorPos()])+string(r[w.CursorPos()+1:]), app) | |
576 | } | |
577 | } | |
578 | case tcell.KeyCtrlK: | |
579 | if !readOnly { | |
580 | r := []rune(w.Text()) | |
581 | w.SetText(string(r[0:w.CursorPos()]), app) | |
582 | } | |
583 | case tcell.KeyCtrlU: | |
584 | if !readOnly { | |
585 | r := []rune(w.Text()) | |
586 | w.SetText(string(r[w.CursorPos():]), app) | |
587 | w.SetCursorPos(0, app) | |
588 | } | |
589 | case tcell.KeyHome: | |
590 | w.SetCursorPos(0, app) | |
591 | w.SetLinesFromTop(0, app) | |
592 | case tcell.KeyCtrlW: | |
593 | if !readOnly { | |
594 | txt := []rune(w.Text()) | |
595 | origcp := w.CursorPos() | |
596 | cp := origcp | |
597 | for cp > 0 && unicode.IsSpace(txt[cp-1]) { | |
598 | cp-- | |
599 | } | |
600 | for cp > 0 && !unicode.IsSpace(txt[cp-1]) { | |
601 | cp-- | |
602 | } | |
603 | if cp != origcp { | |
604 | w.SetText(string(txt[0:cp])+string(txt[origcp:]), app) | |
605 | w.SetCursorPos(cp, app) | |
606 | } | |
607 | } | |
608 | case tcell.KeyCtrlA: | |
609 | // Would be nice to use a slice here, something that doesn't copy | |
610 | // TODO: terrible O(n) behavior :-( | |
611 | txt := w.Text() | |
612 | ||
613 | i := w.CursorPos() | |
614 | j := 0 | |
615 | lastnl := false | |
616 | curstart := 0 | |
617 | ||
618 | for _, ch := range txt { | |
619 | if lastnl { | |
620 | curstart = j | |
621 | } | |
622 | lastnl = (ch == '\n') | |
623 | ||
624 | if i == j { | |
625 | break | |
626 | } | |
627 | j += 1 | |
628 | } | |
629 | ||
630 | w.SetCursorPos(curstart, app) | |
631 | recalcLinesFromTop = true | |
632 | ||
633 | case tcell.KeyEnd: | |
634 | w.SetCursorPos(utf8.RuneCountInString(w.Text()), app) | |
635 | recalcLinesFromTop = true | |
636 | ||
637 | case tcell.KeyCtrlE: | |
638 | // TODO: terrible O(n) behavior :-( | |
639 | txt := w.Text() | |
640 | i := w.CursorPos() | |
641 | j := 0 | |
642 | checknl := false | |
643 | for _, ch := range txt { | |
644 | if i == j { | |
645 | checknl = true | |
646 | } | |
647 | j += 1 | |
648 | if checknl { | |
649 | if ch == '\n' { | |
650 | break | |
651 | } | |
652 | i += 1 | |
653 | } | |
654 | } | |
655 | w.SetCursorPos(i, app) | |
656 | recalcLinesFromTop = true | |
657 | ||
658 | default: | |
429 | 659 | handled = false |
430 | 660 | } |
431 | case tcell.KeyRight, tcell.KeyCtrlF: | |
432 | if w.CursorPos() < utf8.RuneCountInString(w.Text()) { | |
433 | w.SetCursorPos(w.CursorPos()+1, app) | |
434 | } else { | |
435 | handled = false | |
436 | } | |
437 | case tcell.KeyBackspace, tcell.KeyBackspace2: | |
438 | if w.CursorPos() > 0 { | |
439 | pos := w.CursorPos() | |
440 | w.SetCursorPos(w.CursorPos()-1, app) | |
441 | r := []rune(w.Text()) | |
442 | w.SetText(string(r[0:pos-1])+string(r[pos:]), app) | |
443 | } | |
444 | case tcell.KeyDelete, tcell.KeyCtrlD: | |
445 | if w.CursorPos() < utf8.RuneCountInString(w.Text()) { | |
446 | r := []rune(w.Text()) | |
447 | w.SetText(string(r[0:w.CursorPos()])+string(r[w.CursorPos()+1:]), app) | |
448 | } | |
449 | case tcell.KeyEnter: | |
450 | r := []rune(w.Text()) | |
451 | w.SetText(string(r[0:w.CursorPos()])+string('\n')+string(r[w.CursorPos():]), app) | |
452 | w.SetCursorPos(w.CursorPos()+1, app) | |
453 | case tcell.Key(' '): | |
454 | r := []rune(w.Text()) | |
455 | w.SetText(string(r[0:w.CursorPos()])+" "+string(r[w.CursorPos():]), app) | |
456 | w.SetCursorPos(w.CursorPos()+1, app) | |
457 | case tcell.KeyCtrlK: | |
458 | r := []rune(w.Text()) | |
459 | w.SetText(string(r[0:w.CursorPos()]), app) | |
460 | case tcell.KeyCtrlU: | |
461 | r := []rune(w.Text()) | |
462 | w.SetText(string(r[w.CursorPos():]), app) | |
463 | w.SetCursorPos(0, app) | |
464 | case tcell.KeyHome: | |
465 | w.SetCursorPos(0, app) | |
466 | w.SetLinesFromTop(0, app) | |
467 | case tcell.KeyCtrlW: | |
468 | txt := []rune(w.Text()) | |
469 | origcp := w.CursorPos() | |
470 | cp := origcp | |
471 | for cp > 0 && unicode.IsSpace(txt[cp-1]) { | |
472 | cp-- | |
473 | } | |
474 | for cp > 0 && !unicode.IsSpace(txt[cp-1]) { | |
475 | cp-- | |
476 | } | |
477 | if cp != origcp { | |
478 | w.SetText(string(txt[0:cp])+string(txt[origcp:]), app) | |
479 | w.SetCursorPos(cp, app) | |
480 | } | |
481 | case tcell.KeyCtrlA: | |
482 | // Would be nice to use a slice here, something that doesn't copy | |
483 | // TODO: terrible O(n) behavior :-( | |
484 | txt := w.Text() | |
485 | ||
486 | i := w.CursorPos() | |
487 | j := 0 | |
488 | lastnl := false | |
489 | curstart := 0 | |
490 | ||
491 | for _, ch := range txt { | |
492 | if lastnl { | |
493 | curstart = j | |
494 | } | |
495 | lastnl = (ch == '\n') | |
496 | ||
497 | if i == j { | |
498 | break | |
499 | } | |
500 | j += 1 | |
501 | } | |
502 | ||
503 | w.SetCursorPos(curstart, app) | |
504 | recalcLinesFromTop = true | |
505 | ||
506 | case tcell.KeyEnd: | |
507 | w.SetCursorPos(utf8.RuneCountInString(w.Text()), app) | |
508 | recalcLinesFromTop = true | |
509 | ||
510 | case tcell.KeyCtrlE: | |
511 | // TODO: terrible O(n) behavior :-( | |
512 | txt := w.Text() | |
513 | i := w.CursorPos() | |
514 | j := 0 | |
515 | checknl := false | |
516 | for _, ch := range txt { | |
517 | if i == j { | |
518 | checknl = true | |
519 | } | |
520 | j += 1 | |
521 | if checknl { | |
522 | if ch == '\n' { | |
523 | break | |
524 | } | |
525 | i += 1 | |
526 | } | |
527 | } | |
528 | w.SetCursorPos(i, app) | |
529 | recalcLinesFromTop = true | |
530 | ||
531 | case tcell.KeyRune: | |
532 | // TODO: this is lame. Inserting a character is O(n) where n is length | |
533 | // of text. I should switch this to use the two stack model for edited | |
534 | // text. | |
535 | txt := w.Text() | |
536 | r := []rune(txt) | |
537 | cpos := w.CursorPos() | |
538 | rhs := make([]rune, len(r)-cpos) | |
539 | copy(rhs, r[cpos:]) | |
540 | w.SetText(string(append(append(r[:cpos], ev.Rune()), rhs...)), app) | |
541 | w.SetCursorPos(w.CursorPos()+1, app) | |
542 | ||
543 | default: | |
544 | handled = false | |
545 | 661 | } |
546 | 662 | } |
547 | 663 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package edit |
10 | 10 | |
11 | 11 | "github.com/gcla/gowid" |
12 | 12 | "github.com/gcla/gowid/gwtest" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | "github.com/stretchr/testify/assert" |
15 | 15 | ) |
16 | 16 | |
29 | 29 | assert.Equal(t, "hi: 现在 abc ", c1.String()) |
30 | 30 | |
31 | 31 | evq := tcell.NewEventKey(tcell.KeyRune, 'q', tcell.ModNone) |
32 | evdel := tcell.NewEventKey(tcell.KeyDelete, 'X', tcell.ModNone) | |
32 | 33 | |
33 | 34 | w.SetCursorPos(0, gwtest.D) |
34 | 35 | w.UserInput(evq, sz, gowid.Focused, gwtest.D) |
39 | 40 | w.UserInput(evq, sz, gowid.Focused, gwtest.D) |
40 | 41 | c1 = w.Render(sz, gowid.Focused, gwtest.D) |
41 | 42 | assert.Equal(t, "qhi: 现q在 abc ", c1.String()) |
43 | w.UserInput(evq, sz, gowid.Focused, gwtest.D) | |
44 | c1 = w.Render(sz, gowid.Focused, gwtest.D) | |
45 | assert.Equal(t, "qhi: 现qq在 abc", c1.String()) | |
46 | w.UserInput(evdel, sz, gowid.Focused, gwtest.D) | |
47 | c1 = w.Render(sz, gowid.Focused, gwtest.D) | |
48 | assert.Equal(t, "qhi: 现qq abc ", c1.String()) | |
49 | ||
50 | w.SetReadOnly(true, gwtest.D) | |
51 | w.UserInput(evq, sz, gowid.Focused, gwtest.D) | |
52 | c1 = w.Render(sz, gowid.Focused, gwtest.D) | |
53 | assert.Equal(t, "qhi: 现qq abc ", c1.String()) | |
54 | w.UserInput(evdel, sz, gowid.Focused, gwtest.D) | |
55 | c1 = w.Render(sz, gowid.Focused, gwtest.D) | |
56 | assert.Equal(t, "qhi: 现qq abc ", c1.String()) | |
57 | ||
58 | w.SetReadOnly(false, gwtest.D) | |
59 | w.UserInput(evq, sz, gowid.Focused, gwtest.D) | |
60 | c1 = w.Render(sz, gowid.Focused, gwtest.D) | |
61 | assert.Equal(t, "qhi: 现qqq abc ", c1.String()) | |
42 | 62 | } |
43 | 63 | |
44 | 64 | func TestRender1(t *testing.T) { |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // Package fixedadaptor provides a widget that will render a fixed widget when |
8 | 8 | "fmt" |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | "github.com/gdamore/tcell" | |
11 | tcell "github.com/gdamore/tcell/v2" | |
12 | 12 | ) |
13 | 13 | |
14 | 14 | //====================================================================== |
15 | 15 | |
16 | 16 | // Wraps a Fixed widget and turns it into a Box widget. If rendered in a Fixed |
17 | // context, render as normal. If rendered in a Box context, render as a Flow | |
17 | // context, render as normal. If rendered in a Box context, render as a Fixed | |
18 | 18 | // widget, then either truncate or grow the resulting canvas to meet the |
19 | 19 | // box size requirement. |
20 | 20 | // |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package fixedadapter |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
11 | 11 | "github.com/gcla/gowid" |
12 | 12 | "github.com/gcla/gowid/gwutil" |
13 | 13 | "github.com/gcla/gowid/widgets/text" |
14 | "github.com/gdamore/tcell" | |
14 | tcell "github.com/gdamore/tcell/v2" | |
15 | 15 | "github.com/mattn/go-runewidth" |
16 | 16 | ) |
17 | 17 | |
28 | 28 | UnicodeAltFrame = FrameRunes{'▛', '▜', '▙', '▟', '▀', '▄', '▌', '▐'} |
29 | 29 | UnicodeAlt2Frame = FrameRunes{'╔', '╗', '╚', '╝', '═', '═', '║', '║'} |
30 | 30 | SpaceFrame = FrameRunes{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '} |
31 | nullFrame = FrameRunes{} | |
31 | 32 | ) |
32 | 33 | |
33 | 34 | func init() { |
74 | 75 | } |
75 | 76 | } else { |
76 | 77 | opt = opts[0] |
78 | } | |
79 | ||
80 | // Likely means nil value was used for 8-field struct - so this is a heuristic | |
81 | if opt.Frame == nullFrame { | |
82 | opt.Frame = AsciiFrame | |
77 | 83 | } |
78 | 84 | |
79 | 85 | res := &Widget{ |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package framed |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
17 | 17 | "github.com/gcla/gowid/widgets/pile" |
18 | 18 | "github.com/gcla/gowid/widgets/text" |
19 | 19 | "github.com/gcla/gowid/widgets/vpadding" |
20 | "github.com/gdamore/tcell" | |
20 | tcell "github.com/gdamore/tcell/v2" | |
21 | 21 | ) |
22 | 22 | |
23 | 23 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
10 | 10 | "github.com/gcla/gowid/gwtest" |
11 | 11 | "github.com/gcla/gowid/widgets/button" |
12 | 12 | "github.com/gcla/gowid/widgets/text" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | "github.com/stretchr/testify/assert" |
15 | 15 | ) |
16 | 16 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | 11 | "github.com/gcla/gowid/gwutil" |
12 | "github.com/gdamore/tcell" | |
12 | tcell "github.com/gdamore/tcell/v2" | |
13 | 13 | ) |
14 | 14 | |
15 | 15 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
12 | 12 | "github.com/gcla/gowid/widgets/checkbox" |
13 | 13 | "github.com/gcla/gowid/widgets/fill" |
14 | 14 | "github.com/gcla/gowid/widgets/text" |
15 | "github.com/gdamore/tcell" | |
15 | tcell "github.com/gdamore/tcell/v2" | |
16 | 16 | log "github.com/sirupsen/logrus" |
17 | 17 | "github.com/stretchr/testify/assert" |
18 | 18 | ) |
112 | 112 | assert.Equal(t, c.String(), "abcde ") |
113 | 113 | |
114 | 114 | c = w5.Render(gowid.RenderFlowWith{C: 3}, gowid.Focused, gwtest.D) |
115 | assert.Equal(t, c.String(), "abc") | |
115 | assert.Equal(t, c.String(), "abc\nde ") | |
116 | 116 | |
117 | 117 | c = w5.Render(gowid.RenderBox{C: 6, R: 1}, gowid.Focused, gwtest.D) |
118 | 118 | assert.Equal(t, c.String(), "abcde ") |
119 | 119 | |
120 | 120 | c = w5.Render(gowid.RenderBox{C: 3, R: 3}, gowid.Focused, gwtest.D) |
121 | assert.Equal(t, c.String(), "abc\n \n ") | |
121 | assert.Equal(t, c.String(), "abc\nde \n ") | |
122 | 122 | |
123 | 123 | c = w5.Render(gowid.RenderBox{C: 7, R: 3}, gowid.Focused, gwtest.D) |
124 | 124 | assert.Equal(t, c.String(), "abcde \n \n ") |
222 | 222 | |
223 | 223 | w10 := New(fill.New('x'), gowid.HAlignLeft{Margin: 1}, gowid.RenderWithUnits{U: 6}) |
224 | 224 | c10 := w10.Render(gowid.RenderBox{C: 5, R: 2}, gowid.Focused, gwtest.D) |
225 | assert.Equal(t, c10.String(), "xxxxx\nxxxxx") | |
225 | assert.Equal(t, c10.String(), " xxxx\n xxxx") | |
226 | 226 | |
227 | 227 | w11 := New(fill.New('x'), gowid.HAlignLeft{Margin: 1}, gowid.RenderWithUnits{U: 4}) |
228 | 228 | c11 := w11.Render(gowid.RenderBox{C: 5, R: 2}, gowid.Focused, gwtest.D) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
5 | 5 | package isselected |
6 | 6 | |
7 | 7 | import ( |
8 | "fmt" | |
9 | ||
8 | 10 | "github.com/gcla/gowid" |
9 | 11 | ) |
10 | 12 | |
63 | 65 | return w.Not.Selectable() |
64 | 66 | } |
65 | 67 | |
68 | func (w *Widget) String() string { | |
69 | return fmt.Sprintf("issel[%v#%v#%v]", w.Not, w.Selected, w.Focused) | |
70 | } | |
71 | ||
66 | 72 | //====================================================================== |
67 | 73 | |
68 | 74 | // For uses that require IComposite |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
8 | 8 | "fmt" |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | "github.com/gdamore/tcell" | |
11 | tcell "github.com/gdamore/tcell/v2" | |
12 | 12 | ) |
13 | 13 | |
14 | 14 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package keypress |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
10 | 10 | "github.com/gcla/gowid" |
11 | 11 | "github.com/gcla/gowid/gwutil" |
12 | 12 | "github.com/gcla/gowid/vim" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | "github.com/pkg/errors" |
15 | 15 | ) |
16 | 16 | |
212 | 212 | |
213 | 213 | type Options struct { |
214 | 214 | //SelectedStyle gowid.ICellStyler // apply a style to the selected widget - orthogonal to focus styling |
215 | DownKeys []vim.KeyPress | |
216 | UpKeys []vim.KeyPress | |
215 | DownKeys []vim.KeyPress | |
216 | UpKeys []vim.KeyPress | |
217 | DoNotSetSelected bool // Whether or not to set the focus.Selected field for the selected child | |
217 | 218 | } |
218 | 219 | |
219 | 220 | type IndexedWidget struct { |
338 | 339 | |
339 | 340 | func (w *Widget) CalculateOnScreen(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) (int, int, int, error) { |
340 | 341 | return CalculateOnScreen(w, size, focus, app) |
342 | } | |
343 | ||
344 | func (w *Widget) SelectChild(f gowid.Selector) bool { | |
345 | return !w.options.DoNotSetSelected && f.Selected | |
341 | 346 | } |
342 | 347 | |
343 | 348 | //'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' |
384 | 389 | //foobar := styled.New(curWidget, gowid.MakeStyledAs(gowid.StyleReverse)) |
385 | 390 | var curToRender gowid.IWidget = curWidget |
386 | 391 | if haveCols { |
387 | //c = gowid.Render(curWidget, gowid.RenderFlowWith{C: cols.Columns()}, focus, app) | |
388 | c = curToRender.Render(gowid.RenderFlowWith{C: cols.Columns()}, focus, app) | |
392 | c = curToRender.Render(gowid.RenderFlowWith{C: cols.Columns()}, focus.SelectIf(w.SelectChild(focus)), app) | |
389 | 393 | } else { |
390 | //c = gowid.Render(curWidget, gowid.RenderFixed{}, focus, app) | |
391 | c = curToRender.Render(gowid.RenderFixed{}, focus, app) | |
394 | c = curToRender.Render(gowid.RenderFixed{}, focus.SelectIf(w.SelectChild(focus)), app) | |
392 | 395 | } |
393 | 396 | creallines := c.BoxRows() |
394 | 397 | middle = SubRenders{curWidget, curPos, c, creallines} |
808 | 811 | break |
809 | 812 | } |
810 | 813 | if dirMoved > 0 { |
811 | _, curPosition = w.MoveToNextFocus(subRenderSize, focus, numLinesToUse, app) | |
814 | res, curPosition = w.MoveToNextFocus(subRenderSize, focus, numLinesToUse, app) | |
812 | 815 | } else if dirMoved < 0 { |
813 | _, curPosition = w.MoveToPreviousFocus(subRenderSize, focus, numLinesToUse, app) | |
816 | res, curPosition = w.MoveToPreviousFocus(subRenderSize, focus, numLinesToUse, app) | |
814 | 817 | } else { |
815 | 818 | panic(BadState) |
819 | } | |
820 | if !res { | |
821 | w.st = saveState | |
822 | w.Walker().SetFocus(startPosition, app) | |
823 | break | |
816 | 824 | } |
817 | 825 | } |
818 | 826 | //res = true |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package list |
16 | 16 | "github.com/gcla/gowid/widgets/pile" |
17 | 17 | "github.com/gcla/gowid/widgets/selectable" |
18 | 18 | "github.com/gcla/gowid/widgets/text" |
19 | "github.com/gdamore/tcell" | |
19 | tcell "github.com/gdamore/tcell/v2" | |
20 | 20 | log "github.com/sirupsen/logrus" |
21 | 21 | "github.com/stretchr/testify/assert" |
22 | 22 | ) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
11 | 11 | "github.com/gcla/gowid/widgets/holder" |
12 | 12 | "github.com/gcla/gowid/widgets/null" |
13 | 13 | "github.com/gcla/gowid/widgets/overlay" |
14 | "github.com/gdamore/tcell" | |
14 | tcell "github.com/gdamore/tcell/v2" | |
15 | 15 | ) |
16 | 16 | |
17 | 17 | //====================================================================== |
31 | 31 | Name() string |
32 | 32 | } |
33 | 33 | |
34 | type IOpener interface { | |
35 | OpenMenu(*Widget, ISite, gowid.IApp) bool | |
36 | CloseMenu(*Widget, gowid.IApp) | |
37 | } | |
38 | ||
34 | 39 | type Options struct { |
35 | 40 | CloseKeysProvided bool |
36 | 41 | CloseKeys []gowid.IKey |
38 | 43 | IgnoreKeys []gowid.IKey |
39 | 44 | NoAutoClose bool |
40 | 45 | Modal bool |
46 | OpenCloser IOpener | |
41 | 47 | } |
42 | 48 | |
43 | 49 | var ( |
85 | 91 | opt = opts[0] |
86 | 92 | } |
87 | 93 | |
94 | if opt.OpenCloser == nil { | |
95 | opt.OpenCloser = OpenerFunc(OpenSimple) | |
96 | } | |
97 | ||
88 | 98 | res := &Widget{ |
89 | 99 | name: name, |
90 | 100 | width: width, |
127 | 137 | return res |
128 | 138 | } |
129 | 139 | |
140 | func (w *Widget) String() string { | |
141 | return fmt.Sprintf("menu[%v]", w.Name()) | |
142 | } | |
143 | ||
130 | 144 | func (w *Widget) AutoClose() bool { |
131 | 145 | return w.autoClose |
132 | 146 | } |
143 | 157 | w.overlay.SetWidth(width, app) |
144 | 158 | } |
145 | 159 | |
160 | func (w *Widget) Height() gowid.IWidgetDimension { | |
161 | return w.overlay.Height() | |
162 | } | |
163 | ||
164 | func (w *Widget) SetHeight(width gowid.IWidgetDimension, app gowid.IApp) { | |
165 | w.overlay.SetHeight(width, app) | |
166 | } | |
167 | ||
146 | 168 | func (w *Widget) Name() string { |
147 | 169 | return w.name |
148 | 170 | } |
149 | 171 | |
150 | 172 | func (w *Widget) Open(site ISite, app gowid.IApp) { |
173 | w.opts.OpenCloser.OpenMenu(w, site, app) | |
174 | } | |
175 | ||
176 | func (w *Widget) OpenImpl(site ISite, app gowid.IApp) { | |
151 | 177 | w.site = site |
152 | 178 | site.SetNamer(w, app) |
153 | 179 | w.overlay.SetTop(w.top, app) |
157 | 183 | } |
158 | 184 | |
159 | 185 | func (w *Widget) Close(app gowid.IApp) { |
186 | w.opts.OpenCloser.CloseMenu(w, app) | |
187 | } | |
188 | ||
189 | func (w *Widget) CloseImpl(app gowid.IApp) { | |
160 | 190 | // protect against case where it's closed already |
161 | 191 | if w.site != nil { |
162 | 192 | w.site.SetNamer(nil, app) |
170 | 200 | return w.overlay |
171 | 201 | } |
172 | 202 | |
173 | func (w *Widget) String() string { | |
174 | return "menu" // TODO: should iterate over submenus | |
175 | } | |
176 | ||
177 | 203 | func (w *Widget) SetSubWidget(widget gowid.IWidget, app gowid.IApp) { |
178 | 204 | w.baseHolder.IWidget = widget |
179 | 205 | } |
224 | 250 | |
225 | 251 | func (w *rejectKeyInput) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { |
226 | 252 | if _, ok := ev.(*tcell.EventKey); ok && w.on { |
253 | return false | |
254 | } | |
255 | if _, ok := ev.(*tcell.EventPaste); ok && w.on { | |
227 | 256 | return false |
228 | 257 | } |
229 | 258 | return w.IWidget.UserInput(ev, size, focus, app) |
413 | 442 | } |
414 | 443 | |
415 | 444 | //====================================================================== |
445 | ||
446 | // Return false if it was already open | |
447 | type OpenerFunc func(bool, *Widget, ISite, gowid.IApp) bool | |
448 | ||
449 | func (m OpenerFunc) OpenMenu(mu *Widget, site ISite, app gowid.IApp) bool { | |
450 | return m(true, mu, site, app) | |
451 | } | |
452 | ||
453 | func (m OpenerFunc) CloseMenu(mu *Widget, app gowid.IApp) { | |
454 | m(false, mu, nil, app) | |
455 | } | |
456 | ||
457 | func OpenSimple(open bool, mu *Widget, site ISite, app gowid.IApp) bool { | |
458 | if open { | |
459 | mu.OpenImpl(site, app) | |
460 | return true | |
461 | } else { | |
462 | mu.CloseImpl(app) | |
463 | return true | |
464 | } | |
465 | } | |
466 | ||
467 | //====================================================================== | |
416 | 468 | // Local Variables: |
417 | 469 | // mode: Go |
418 | 470 | // fill-column: 110 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | 11 | "github.com/gcla/gowid/widgets/padding" |
12 | "github.com/gdamore/tcell" | |
12 | tcell "github.com/gdamore/tcell/v2" | |
13 | 13 | ) |
14 | 14 | |
15 | 15 | //====================================================================== |
67 | 67 | BottomGetsFocus() bool |
68 | 68 | TopGetsFocus() bool |
69 | 69 | BottomGetsCursor() bool |
70 | } | |
71 | ||
72 | type IIgnoreLowerStyle interface { | |
73 | IgnoreLowerStyle() bool | |
70 | 74 | } |
71 | 75 | |
72 | 76 | type IWidget interface { |
99 | 103 | Callbacks *gowid.Callbacks |
100 | 104 | } |
101 | 105 | |
106 | var _ IIgnoreLowerStyle = (*Widget)(nil) | |
107 | ||
102 | 108 | // For callback registration |
103 | 109 | type Top struct{} |
104 | 110 | type Bottom struct{} |
107 | 113 | BottomGetsFocus bool |
108 | 114 | TopGetsNoFocus bool |
109 | 115 | BottomGetsCursor bool |
116 | IgnoreLowerStyle bool | |
110 | 117 | } |
111 | 118 | |
112 | 119 | func New(top, bottom gowid.IWidget, |
234 | 241 | } |
235 | 242 | } |
236 | 243 | |
244 | func (w *Widget) IgnoreLowerStyle() bool { | |
245 | return w.opts.IgnoreLowerStyle | |
246 | } | |
247 | ||
237 | 248 | //====================================================================== |
238 | 249 | |
239 | 250 | func UserInput(w IOverlay, ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { |
252 | 263 | if !res { |
253 | 264 | _, ok1 := ev.(*tcell.EventKey) |
254 | 265 | _, ok2 := ev.(*tcell.EventMouse) |
255 | if notOccluded && (ok1 || ok2) { | |
266 | _, ok3 := ev.(*tcell.EventPaste) | |
267 | if notOccluded && (ok1 || ok2 || ok3) { | |
256 | 268 | res = gowid.UserInputIfSelectable(w.Bottom(), ev, size, focus, app) |
257 | 269 | } |
258 | 270 | } |
259 | 271 | } |
260 | 272 | return res |
273 | } | |
274 | ||
275 | // Merge cells as follows - use upper rune if set, use upper colors if set, | |
276 | // and use upper style only (don't let any lower run style bleed through) | |
277 | func mergeAllExceptUpperStyle(lower gowid.Cell, upper gowid.Cell) gowid.Cell { | |
278 | res := lower | |
279 | if upper.HasRune() { | |
280 | res = res.WithRune(upper.Rune()) | |
281 | } | |
282 | ||
283 | ufg, ubg, _ := upper.GetDisplayAttrs() | |
284 | if ubg != gowid.ColorNone { | |
285 | res = res.WithBackgroundColor(ubg) | |
286 | } | |
287 | if ufg != gowid.ColorNone { | |
288 | res = res.WithForegroundColor(ufg) | |
289 | } | |
290 | ||
291 | res = res.WithStyle(upper.Style()) | |
292 | return res | |
293 | } | |
294 | ||
295 | type iMergeWithFuncCanvas interface { | |
296 | MergeWithFunc(gowid.IMergeCanvas, int, int, gowid.CellMergeFunc, bool) | |
261 | 297 | } |
262 | 298 | |
263 | 299 | func Render(w IOverlay, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas { |
271 | 307 | bottomC2 := bottomC.Duplicate() |
272 | 308 | p2 := padding.New(w.Top(), w.VAlign(), w.Height(), w.HAlign(), w.Width()) |
273 | 309 | topC := p2.Render(size, tfocus, app) |
274 | bottomC2.MergeUnder(topC, 0, 0, w.BottomGetsCursor()) | |
310 | ||
311 | var bottomC2mc iMergeWithFuncCanvas | |
312 | ign := false | |
313 | if wIgn, ok := w.(IIgnoreLowerStyle); ok { | |
314 | if gc, ok := bottomC2.(iMergeWithFuncCanvas); ok { | |
315 | bottomC2mc = gc | |
316 | ign = wIgn.IgnoreLowerStyle() | |
317 | } | |
318 | } | |
319 | ||
320 | if ign { | |
321 | bottomC2mc.MergeWithFunc(topC, 0, 0, mergeAllExceptUpperStyle, w.BottomGetsCursor()) | |
322 | } else { | |
323 | bottomC2.MergeUnder(topC, 0, 0, w.BottomGetsCursor()) | |
324 | } | |
325 | ||
275 | 326 | return bottomC2 |
276 | 327 | } |
277 | 328 | } |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | // code is governed by the MIT license that can be found in the LICENSE | |
2 | // file. | |
3 | ||
4 | package overlay | |
5 | ||
6 | import ( | |
7 | "testing" | |
8 | ||
9 | "github.com/gcla/gowid" | |
10 | "github.com/gcla/gowid/gwtest" | |
11 | "github.com/gcla/gowid/widgets/styled" | |
12 | "github.com/gcla/gowid/widgets/text" | |
13 | "github.com/gdamore/tcell/v2" | |
14 | "github.com/stretchr/testify/assert" | |
15 | ) | |
16 | ||
17 | func TestOverlay1(t *testing.T) { | |
18 | tw := text.New("top") | |
19 | bw := text.New("bottom") | |
20 | ov := New(tw, bw, gowid.VAlignTop{}, gowid.RenderFixed{}, gowid.HAlignLeft{}, gowid.RenderFixed{}) | |
21 | c := ov.Render(gowid.RenderFlowWith{C: 6}, gowid.Focused, gwtest.D) | |
22 | assert.Equal(t, "toptom", c.String()) | |
23 | ||
24 | bwStyled := styled.New(bw, gowid.MakeStyledAs(gowid.StyleBold)) | |
25 | ||
26 | // When the widget is created this way, the style from the lower widget bleeds through | |
27 | ov = New(tw, bwStyled, gowid.VAlignTop{}, gowid.RenderFixed{}, gowid.HAlignLeft{}, gowid.RenderFixed{}) | |
28 | c = ov.Render(gowid.RenderFlowWith{C: 6}, gowid.Focused, gwtest.D) | |
29 | assert.Equal(t, "toptom", c.String()) | |
30 | assert.Equal(t, tcell.AttrBold, c.CellAt(0, 0).Style().OnOff&tcell.AttrBold) | |
31 | ||
32 | // When the widget is created this way, the style from the upper widget is set unilaterally | |
33 | ov = New(tw, bwStyled, gowid.VAlignTop{}, gowid.RenderFixed{}, gowid.HAlignLeft{}, gowid.RenderFixed{}, | |
34 | Options{ | |
35 | IgnoreLowerStyle: true, | |
36 | }) | |
37 | c = ov.Render(gowid.RenderFlowWith{C: 6}, gowid.Focused, gwtest.D) | |
38 | assert.Equal(t, "toptom", c.String()) | |
39 | assert.Equal(t, tcell.AttrMask(0), c.CellAt(0, 0).Style().OnOff&tcell.AttrBold) | |
40 | } |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
11 | 11 | "github.com/gcla/gowid" |
12 | 12 | "github.com/gcla/gowid/gwutil" |
13 | 13 | "github.com/gcla/gowid/widgets/fill" |
14 | "github.com/gdamore/tcell" | |
14 | tcell "github.com/gdamore/tcell/v2" | |
15 | 15 | ) |
16 | 16 | |
17 | 17 | //====================================================================== |
149 | 149 | default: |
150 | 150 | } |
151 | 151 | |
152 | switch al := w.VAlign().(type) { | |
153 | case gowid.VAlignTop: | |
154 | switch s := size2.(type) { | |
155 | case gowid.IRenderBox: | |
156 | size2 = gowid.RenderBox{C: s.BoxColumns(), R: s.BoxRows() - al.Margin} | |
157 | } | |
158 | } | |
159 | ||
152 | 160 | return gowid.ComputeSubSizeUnsafe(size2, w.Width(), w.Height()) |
153 | //return SubWidgetSize(w, size, focus, app) | |
154 | 161 | } |
155 | 162 | |
156 | 163 | func (w *Widget) RenderSize(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.IRenderBox { |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package padding |
11 | 11 | "github.com/gcla/gowid/widgets/fill" |
12 | 12 | "github.com/gcla/gowid/widgets/framed" |
13 | 13 | "github.com/gcla/gowid/widgets/text" |
14 | "github.com/gdamore/tcell" | |
14 | tcell "github.com/gdamore/tcell/v2" | |
15 | 15 | "github.com/stretchr/testify/assert" |
16 | 16 | ) |
17 | ||
18 | type renderRatioUpTo struct { | |
19 | gowid.RenderWithRatio | |
20 | max int | |
21 | } | |
22 | ||
23 | func (s renderRatioUpTo) MaxUnits() int { | |
24 | return s.max | |
25 | } | |
26 | ||
27 | func ratioupto(w float64, max int) renderRatioUpTo { | |
28 | return renderRatioUpTo{gowid.RenderWithRatio{R: w}, max} | |
29 | } | |
17 | 30 | |
18 | 31 | func TestPadding1(t *testing.T) { |
19 | 32 | var w gowid.IWidget |
22 | 35 | w = New(fill.New('x'), gowid.VAlignMiddle{}, gowid.RenderWithUnits{U: 2}, gowid.HAlignMiddle{}, gowid.RenderWithUnits{U: 2}) |
23 | 36 | c = w.Render(gowid.RenderBox{C: 4, R: 4}, gowid.Focused, gwtest.D) |
24 | 37 | assert.Equal(t, " \n xx \n xx \n ", c.String()) |
38 | ||
39 | w = New(fill.New('x'), gowid.VAlignMiddle{}, gowid.RenderWithUnits{U: 2}, gowid.HAlignMiddle{}, gowid.RenderWithRatio{R: 0.5}) | |
40 | c = w.Render(gowid.RenderBox{C: 4, R: 4}, gowid.Focused, gwtest.D) | |
41 | assert.Equal(t, " \n xx \n xx \n ", c.String()) | |
42 | ||
43 | w = New(fill.New('x'), gowid.VAlignMiddle{}, ratioupto(0.5, 1), gowid.HAlignMiddle{}, gowid.RenderWithUnits{U: 2}) | |
44 | c = w.Render(gowid.RenderBox{C: 4, R: 4}, gowid.Focused, gwtest.D) | |
45 | assert.Equal(t, " \n xx \n \n ", c.String()) | |
46 | ||
47 | w = New(fill.New('x'), gowid.VAlignMiddle{}, gowid.RenderWithUnits{U: 2}, gowid.HAlignMiddle{}, ratioupto(0.5, 1)) | |
48 | c = w.Render(gowid.RenderBox{C: 4, R: 4}, gowid.Focused, gwtest.D) | |
49 | assert.Equal(t, " \n x \n x \n ", c.String()) | |
25 | 50 | |
26 | 51 | w = New(text.New("foo"), gowid.VAlignMiddle{}, gowid.RenderFixed{}, gowid.HAlignMiddle{}, gowid.RenderFixed{}) |
27 | 52 | c = w.Render(gowid.RenderBox{C: 5, R: 3}, gowid.Focused, gwtest.D) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2021 Graham Clark. All rights reserved. Use of this source | |
1 | // code is governed by the MIT license that can be found in the LICENSE | |
2 | // file. | |
3 | ||
4 | // Package paragraph provides a simple text widget that neatly splits | |
5 | // over multiple lines. | |
6 | package paragraph | |
7 | ||
8 | import ( | |
9 | "strings" | |
10 | ||
11 | "github.com/gcla/gowid" | |
12 | "github.com/gcla/gowid/gwutil" | |
13 | ) | |
14 | ||
15 | //====================================================================== | |
16 | ||
17 | // Widget can be used to display text on the screen, with words broken | |
18 | // cleanly as the output width changes. | |
19 | type Widget struct { | |
20 | words []string | |
21 | lastcols int | |
22 | lastrows int | |
23 | gowid.RejectUserInput | |
24 | gowid.NotSelectable | |
25 | } | |
26 | ||
27 | var _ gowid.IWidget = (*Widget)(nil) | |
28 | ||
29 | func New(text string) *Widget { | |
30 | return &Widget{ | |
31 | words: strings.Fields(text), | |
32 | } | |
33 | } | |
34 | ||
35 | func NewWithWords(words ...string) *Widget { | |
36 | return &Widget{ | |
37 | words: words, | |
38 | } | |
39 | } | |
40 | ||
41 | func (w *Widget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas { | |
42 | switch size := size.(type) { | |
43 | case gowid.IRenderFlowWith: | |
44 | res := gowid.NewCanvas() | |
45 | var lc gowid.ICanvas | |
46 | var pos int | |
47 | var curWord string | |
48 | i := 0 | |
49 | space := false | |
50 | for { | |
51 | if i >= len(w.words) { | |
52 | if lc != nil { | |
53 | res.AppendBelow(lc, false, false) | |
54 | } | |
55 | break | |
56 | } | |
57 | if curWord == "" { | |
58 | curWord = w.words[i] | |
59 | } | |
60 | if lc == nil { | |
61 | lc = gowid.NewCanvasOfSize(size.FlowColumns(), 1) | |
62 | pos = 0 | |
63 | } | |
64 | if space { | |
65 | if pos < size.FlowColumns()-1 { | |
66 | pos++ | |
67 | } else { | |
68 | if lc != nil { | |
69 | res.AppendBelow(lc, false, false) | |
70 | lc = gowid.NewCanvasOfSize(size.FlowColumns(), 1) | |
71 | pos = 0 | |
72 | } | |
73 | } | |
74 | space = false | |
75 | } else { | |
76 | // No space left in current line | |
77 | if len(curWord) > size.FlowColumns()-pos { | |
78 | // No space on this line, but it will fit on next line. If it | |
79 | // doesn't even fit on a line by itself, well just split it | |
80 | if pos > 0 && lc != nil && len(curWord) <= size.FlowColumns() { | |
81 | res.AppendBelow(lc, false, false) | |
82 | lc = gowid.NewCanvasOfSize(size.FlowColumns(), 1) | |
83 | pos = 0 | |
84 | } | |
85 | take := gwutil.Min(size.FlowColumns()-pos, len(curWord)) | |
86 | for j := 0; j < take; j++ { | |
87 | lc.SetCellAt(pos, 0, gowid.CellFromRune(rune(curWord[j]))) | |
88 | pos++ | |
89 | } | |
90 | if pos >= size.FlowColumns() { | |
91 | res.AppendBelow(lc, false, false) | |
92 | lc = nil | |
93 | } | |
94 | if take == len(curWord) { | |
95 | i++ | |
96 | curWord = "" | |
97 | space = true | |
98 | } else { | |
99 | // Must be less | |
100 | curWord = curWord[take:] | |
101 | } | |
102 | } else { | |
103 | for j := 0; j < len(curWord); j++ { | |
104 | lc.SetCellAt(pos, 0, gowid.CellFromRune(rune(curWord[j]))) | |
105 | pos++ | |
106 | } | |
107 | i++ | |
108 | curWord = "" | |
109 | space = true | |
110 | } | |
111 | } | |
112 | ||
113 | } | |
114 | return res | |
115 | default: | |
116 | panic(gowid.WidgetSizeError{Widget: w, Size: size, Required: "gowid.IRenderFlow"}) | |
117 | } | |
118 | } | |
119 | ||
120 | func (w *Widget) RenderSize(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.IRenderBox { | |
121 | if w.lastcols != 0 { | |
122 | return gowid.RenderBox{C: w.lastcols, R: w.lastrows} | |
123 | } | |
124 | ||
125 | res := gowid.CalculateRenderSizeFallback(w, size, focus, app) | |
126 | w.lastcols = res.C | |
127 | w.lastrows = res.R | |
128 | return res | |
129 | } | |
130 | ||
131 | //====================================================================== | |
132 | // Local Variables: | |
133 | // mode: Go | |
134 | // fill-column: 110 | |
135 | // End: |
0 | // Copyright 2021 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | // that can be found in the LICENSE file. | |
2 | ||
3 | package paragraph | |
4 | ||
5 | import ( | |
6 | "strings" | |
7 | "testing" | |
8 | ||
9 | "github.com/gcla/gowid" | |
10 | "github.com/gcla/gowid/gwtest" | |
11 | "github.com/stretchr/testify/assert" | |
12 | ) | |
13 | ||
14 | //====================================================================== | |
15 | ||
16 | func Test1(t *testing.T) { | |
17 | w := New("hello world") | |
18 | c := w.Render(gowid.RenderFlowWith{C: 16}, gowid.NotSelected, gwtest.D) | |
19 | res := strings.Join([]string{ | |
20 | "hello world ", | |
21 | }, "\n") | |
22 | assert.Equal(t, res, c.String()) | |
23 | ||
24 | c = w.Render(gowid.RenderFlowWith{C: 6}, gowid.NotSelected, gwtest.D) | |
25 | res = strings.Join([]string{ | |
26 | "hello ", | |
27 | "world ", | |
28 | }, "\n") | |
29 | assert.Equal(t, res, c.String()) | |
30 | ||
31 | c = w.Render(gowid.RenderFlowWith{C: 9}, gowid.NotSelected, gwtest.D) | |
32 | res = strings.Join([]string{ | |
33 | "hello ", | |
34 | "world ", | |
35 | }, "\n") | |
36 | assert.Equal(t, res, c.String()) | |
37 | ||
38 | c = w.Render(gowid.RenderFlowWith{C: 5}, gowid.NotSelected, gwtest.D) | |
39 | res = strings.Join([]string{ | |
40 | "hello", | |
41 | "world", | |
42 | }, "\n") | |
43 | assert.Equal(t, res, c.String()) | |
44 | ||
45 | c = w.Render(gowid.RenderFlowWith{C: 4}, gowid.NotSelected, gwtest.D) | |
46 | res = strings.Join([]string{ | |
47 | "hell", | |
48 | "o wo", | |
49 | "rld ", | |
50 | }, "\n") | |
51 | assert.Equal(t, res, c.String()) | |
52 | ||
53 | w = New("hello worldatlarge") | |
54 | c = w.Render(gowid.RenderFlowWith{C: 6}, gowid.NotSelected, gwtest.D) | |
55 | res = strings.Join([]string{ | |
56 | "hello ", | |
57 | "worlda", | |
58 | "tlarge", | |
59 | }, "\n") | |
60 | assert.Equal(t, res, c.String()) | |
61 | ||
62 | c = w.Render(gowid.RenderFlowWith{C: 8}, gowid.NotSelected, gwtest.D) | |
63 | res = strings.Join([]string{ | |
64 | "hello wo", | |
65 | "rldatlar", | |
66 | "ge ", | |
67 | }, "\n") | |
68 | assert.Equal(t, res, c.String()) | |
69 | ||
70 | c = w.Render(gowid.RenderFlowWith{C: 12}, gowid.NotSelected, gwtest.D) | |
71 | res = strings.Join([]string{ | |
72 | "hello ", | |
73 | "worldatlarge", | |
74 | }, "\n") | |
75 | assert.Equal(t, res, c.String()) | |
76 | ||
77 | c = w.Render(gowid.RenderFlowWith{C: 13}, gowid.NotSelected, gwtest.D) | |
78 | res = strings.Join([]string{ | |
79 | "hello ", | |
80 | "worldatlarge ", | |
81 | }, "\n") | |
82 | assert.Equal(t, res, c.String()) | |
83 | ||
84 | c = w.Render(gowid.RenderFlowWith{C: 18}, gowid.NotSelected, gwtest.D) | |
85 | res = strings.Join([]string{ | |
86 | "hello worldatlarge", | |
87 | }, "\n") | |
88 | assert.Equal(t, res, c.String()) | |
89 | } | |
90 | ||
91 | //====================================================================== | |
92 | // Local Variables: | |
93 | // mode: Go | |
94 | // fill-column: 110 | |
95 | // End: |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
11 | 11 | "github.com/gcla/gowid" |
12 | 12 | "github.com/gcla/gowid/gwutil" |
13 | 13 | "github.com/gcla/gowid/vim" |
14 | "github.com/gdamore/tcell" | |
14 | tcell "github.com/gdamore/tcell/v2" | |
15 | 15 | ) |
16 | 16 | |
17 | 17 | //====================================================================== |
436 | 436 | // What if the Pile is rendered as a RenderFlow? Then you can't specify any weighted widgets |
437 | 437 | |
438 | 438 | func Render(w IWidget, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas { |
439 | ||
440 | 439 | subfocus := w.Focus() |
441 | 440 | // if !focus.Focus { |
442 | 441 | // subfocus = -1 |
456 | 455 | // a pile of width 20, and put it in a column, the next column starts at 21 |
457 | 456 | // TODO - remember which one has focus |
458 | 457 | res.AppendBelow(canvases[i], i == subfocus, false) |
459 | if haveMaxRow && res.BoxRows() >= rows.Rows() { | |
458 | if haveMaxRow && res.BoxRows() > rows.Rows() { | |
460 | 459 | trim = true |
461 | 460 | break |
462 | 461 | } |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package pile |
15 | 15 | "github.com/gcla/gowid/widgets/list" |
16 | 16 | "github.com/gcla/gowid/widgets/selectable" |
17 | 17 | "github.com/gcla/gowid/widgets/text" |
18 | "github.com/gdamore/tcell" | |
18 | tcell "github.com/gdamore/tcell/v2" | |
19 | 19 | "github.com/stretchr/testify/assert" |
20 | 20 | ) |
21 | 21 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package progress |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package radio |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
8 | 8 | "fmt" |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | "github.com/gdamore/tcell" | |
11 | tcell "github.com/gdamore/tcell/v2" | |
12 | 12 | ) |
13 | 13 | |
14 | 14 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | 0 | //go:generate statik -src=data |
1 | 1 | |
2 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
2 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
3 | 3 | // code is governed by the MIT license that can be found in the LICENSE |
4 | 4 | // file. |
5 | 5 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
8 | 8 | "fmt" |
9 | 9 | "strconv" |
10 | 10 | |
11 | "github.com/araddon/dateparse" | |
11 | 12 | "github.com/gcla/gowid" |
12 | 13 | "github.com/gcla/gowid/gwutil" |
13 | 14 | "github.com/gcla/gowid/widgets/columns" |
14 | 15 | "github.com/gcla/gowid/widgets/isselected" |
15 | 16 | "github.com/gcla/gowid/widgets/list" |
16 | 17 | "github.com/gcla/gowid/widgets/pile" |
17 | "github.com/gdamore/tcell" | |
18 | tcell "github.com/gdamore/tcell/v2" | |
18 | 19 | lru "github.com/hashicorp/golang-lru" |
19 | 20 | ) |
20 | 21 | |
108 | 109 | } |
109 | 110 | |
110 | 111 | var _ ICompare = FloatCompare{} |
112 | ||
113 | // DateTimeCompare is a unit type that satisfies ICompare, and can be used | |
114 | // for numerically comparing date/time values. | |
115 | type DateTimeCompare struct{} | |
116 | ||
117 | func (s DateTimeCompare) Less(i, j string) bool { | |
118 | x, err1 := dateparse.ParseAny(i) | |
119 | y, err2 := dateparse.ParseAny(j) | |
120 | if err1 == nil && err2 == nil { | |
121 | return x.Before(y) | |
122 | } else { | |
123 | return false | |
124 | } | |
125 | } | |
126 | ||
127 | var _ ICompare = DateTimeCompare{} | |
111 | 128 | |
112 | 129 | //====================================================================== |
113 | 130 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
14 | 14 | "github.com/gcla/gowid/widgets/fill" |
15 | 15 | "github.com/gcla/gowid/widgets/selectable" |
16 | 16 | "github.com/gcla/gowid/widgets/text" |
17 | "github.com/gdamore/tcell" | |
17 | tcell "github.com/gdamore/tcell/v2" | |
18 | 18 | "github.com/sirupsen/logrus" |
19 | 19 | "github.com/stretchr/testify/assert" |
20 | 20 | ) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
16 | 16 | |
17 | 17 | "github.com/gcla/gowid" |
18 | 18 | "github.com/gcla/gowid/gwutil" |
19 | "github.com/gdamore/tcell" | |
19 | tcell "github.com/gdamore/tcell/v2" | |
20 | 20 | "github.com/mattn/go-runewidth" |
21 | 21 | log "github.com/sirupsen/logrus" |
22 | 22 | "golang.org/x/text/encoding/charmap" |
308 | 308 | Height: height, |
309 | 309 | } |
310 | 310 | return res |
311 | } | |
312 | ||
313 | func (c *ViewPortCanvas) Duplicate() gowid.ICanvas { | |
314 | res := &ViewPortCanvas{ | |
315 | Canvas: c.Canvas.Duplicate().(*gowid.Canvas), | |
316 | Offset: c.Offset, | |
317 | Height: c.Height, | |
318 | } | |
319 | return res | |
320 | } | |
321 | ||
322 | func (c *ViewPortCanvas) MergeUnder(c2 gowid.IMergeCanvas, leftOffset, topOffset int, bottomGetsCursor bool) { | |
323 | c.Canvas.MergeUnder(c2, leftOffset, topOffset+c.Offset, bottomGetsCursor) | |
311 | 324 | } |
312 | 325 | |
313 | 326 | func (v *ViewPortCanvas) BoxRows() int { |
414 | 427 | } |
415 | 428 | |
416 | 429 | return len(p), nil |
430 | } | |
431 | ||
432 | func (c *Canvas) Duplicate() gowid.ICanvas { | |
433 | res := &Canvas{} | |
434 | *res = *c | |
435 | res.ViewPortCanvas = c.ViewPortCanvas.Duplicate().(*ViewPortCanvas) | |
436 | res.savedstyles = make(map[string]bool) | |
437 | for k, v := range c.savedstyles { | |
438 | res.savedstyles[k] = v | |
439 | } | |
440 | res.styles = make(map[string]bool) | |
441 | for k, v := range c.styles { | |
442 | res.styles[k] = v | |
443 | } | |
444 | res.tabstops = make([]int, len(c.tabstops)) | |
445 | for i, v := range res.tabstops { | |
446 | res.tabstops[i] = v | |
447 | } | |
448 | res.escbuf = make([]byte, len(c.escbuf)) | |
449 | for i, v := range res.escbuf { | |
450 | res.escbuf[i] = v | |
451 | } | |
452 | res.utf8Buffer = make([]byte, len(c.utf8Buffer)) | |
453 | for i, v := range res.utf8Buffer { | |
454 | res.utf8Buffer[i] = v | |
455 | } | |
456 | return res | |
417 | 457 | } |
418 | 458 | |
419 | 459 | func (c *Canvas) Reset() { |
1186 | 1226 | c.Offset -= height - c.Height |
1187 | 1227 | c.Height = height |
1188 | 1228 | if c.Height > c.Canvas.BoxRows() { |
1189 | c.Height = c.Canvas.BoxRows() | |
1229 | c.Canvas.AppendBelow(gowid.NewCanvasOfSize(width, c.Height-c.Canvas.BoxRows()), false, false) | |
1190 | 1230 | } else if c.Height < 1 { |
1191 | 1231 | c.Height = 1 |
1192 | 1232 | } |
1255 | 1295 | func (c *Canvas) MakeCellFrom(r rune) gowid.Cell { |
1256 | 1296 | var cell gowid.Cell = gowid.MakeCell(r, gowid.MakeTCellColorExt(tcell.ColorDefault), gowid.MakeTCellColorExt(tcell.ColorDefault), gowid.StyleNone) |
1257 | 1297 | if !c.fg.IsNone() { |
1258 | cell = cell.WithForegroundColor(gowid.MakeTCellColorExt(tcell.Color(c.fg.Val() - 1))) | |
1298 | cell = cell.WithForegroundColor(gowid.MakeTCellColorExt(tcell.Color(c.fg.Val() - 1) + tcell.ColorValid)) | |
1259 | 1299 | } |
1260 | 1300 | if !c.bg.IsNone() { |
1261 | cell = cell.WithBackgroundColor(gowid.MakeTCellColorExt(tcell.Color(c.bg.Val() - 1))) | |
1301 | cell = cell.WithBackgroundColor(gowid.MakeTCellColorExt(tcell.Color(c.bg.Val() - 1) + tcell.ColorValid)) | |
1262 | 1302 | } |
1263 | 1303 | if len(c.styles) > 0 { |
1264 | 1304 | for k, _ := range c.styles { |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
16 | 16 | "time" |
17 | 17 | "unsafe" |
18 | 18 | |
19 | "github.com/creack/pty" | |
19 | 20 | "github.com/gcla/gowid" |
20 | 21 | "github.com/gcla/gowid/gwutil" |
21 | "github.com/gdamore/tcell" | |
22 | "github.com/gdamore/tcell/terminfo" | |
23 | "github.com/gdamore/tcell/terminfo/dynamic" | |
24 | "github.com/kr/pty" | |
22 | "github.com/gcla/gowid/widgets/columns" | |
23 | "github.com/gcla/gowid/widgets/holder" | |
24 | "github.com/gcla/gowid/widgets/null" | |
25 | "github.com/gcla/gowid/widgets/vscroll" | |
26 | tcell "github.com/gdamore/tcell/v2" | |
27 | "github.com/gdamore/tcell/v2/terminfo" | |
28 | "github.com/gdamore/tcell/v2/terminfo/dynamic" | |
25 | 29 | log "github.com/sirupsen/logrus" |
26 | 30 | ) |
27 | 31 | |
53 | 57 | // IHotKeyPersistence determines how long a press of the hotkey will be in effect before |
54 | 58 | // keyboard user input is sent back to the underlying terminal. |
55 | 59 | IHotKeyPersistence |
60 | // IPaste tracks whether the paste start sequence has been seen wthout a matching | |
61 | // paste end sequence | |
62 | IPaste | |
56 | 63 | // HotKeyActive returns true if the hotkey is currently in effect. |
57 | 64 | HotKeyActive() bool |
58 | 65 | // SetHotKeyActive sets the state of HotKeyActive. |
67 | 74 | Scrolling() bool |
68 | 75 | } |
69 | 76 | |
77 | type IHotKeyFunctions interface { | |
78 | // Customized handling of hotkey sequences | |
79 | HotKeyFunctions() []HotKeyInputFn | |
80 | } | |
81 | ||
82 | type IScrollbar interface { | |
83 | ScrollbarEnabled() bool | |
84 | EnableScrollbar(app gowid.IApp) | |
85 | DisableScrollbar(app gowid.IApp) | |
86 | } | |
87 | ||
70 | 88 | type IHotKeyPersistence interface { |
71 | 89 | HotKeyDuration() time.Duration |
72 | 90 | } |
74 | 92 | type IHotKeyProvider interface { |
75 | 93 | HotKey() tcell.Key |
76 | 94 | } |
95 | ||
96 | type IPaste interface { | |
97 | PasteState(...bool) bool | |
98 | } | |
99 | ||
100 | type HotKeyInputFn func(ev *tcell.EventKey, w IWidget, app gowid.IApp) bool | |
77 | 101 | |
78 | 102 | type HotKeyDuration struct { |
79 | 103 | D time.Duration |
96 | 120 | type LEDs struct{} |
97 | 121 | type Title struct{} |
98 | 122 | type ProcessExited struct{} |
123 | type HotKeyCB struct{} | |
99 | 124 | |
100 | 125 | type bell struct{} |
101 | 126 | type leds struct{} |
102 | 127 | type title struct{} |
128 | type hotkey struct{} | |
103 | 129 | |
104 | 130 | type Options struct { |
105 | Command []string | |
106 | Env []string | |
107 | HotKey IHotKeyProvider | |
108 | HotKeyPersistence IHotKeyPersistence // the period of time a hotKey sticks after the first post-hotKey keypress | |
109 | Scrollback int | |
131 | Command []string | |
132 | Env []string | |
133 | HotKey IHotKeyProvider | |
134 | HotKeyPersistence IHotKeyPersistence // the period of time a hotKey sticks after the first post-hotKey keypress | |
135 | Scrollback int | |
136 | Scrollbar bool // disabled regardless of setting if there is no scrollback | |
137 | HotKeyFns []HotKeyInputFn // allow custom behavior after pressing the hotkey | |
138 | EnableBracketedPaste bool | |
139 | KeyPressToEndScrollMode bool // set to true to enable legacy behavior - when the user has scrolled | |
140 | // back to the prompt, still require a keypress (q or Q) to end scroll-mode. | |
110 | 141 | } |
111 | 142 | |
112 | 143 | // Widget is a widget that hosts a terminal-based application. The user provides the |
130 | 161 | hotKeyDownTime time.Time |
131 | 162 | hotKeyTimer *time.Timer |
132 | 163 | isScrolling bool |
164 | paste bool | |
165 | hold *holder.Widget // used if scrollbar is enabled | |
166 | cols *columns.Widget // used if scrollbar is enabled | |
167 | sbar *vscroll.Widget // used if scrollbar is enabled | |
168 | scrollbarTmpOff bool // a simple hack to help with UserInput and Render | |
133 | 169 | Callbacks *gowid.Callbacks |
134 | 170 | gowid.IsSelectable |
135 | 171 | } |
174 | 210 | opts.HotKey = HotKey{tcell.KeyCtrlB} |
175 | 211 | } |
176 | 212 | |
213 | if opts.Scrollback <= 0 { | |
214 | opts.Scrollbar = false | |
215 | } | |
216 | ||
217 | var persistence IHotKeyPersistence | |
218 | if opts.HotKeyPersistence != nil { | |
219 | persistence = opts.HotKeyPersistence | |
220 | } else { | |
221 | persistence = &HotKeyDuration{ | |
222 | D: 2 * time.Second, | |
223 | } | |
224 | } | |
225 | ||
226 | // Always allocate so the scrollbar can be turned on later | |
227 | sbar := vscroll.NewExt(vscroll.VerticalScrollbarUnicodeRunes) | |
228 | ||
229 | hold := holder.New(null.New()) | |
230 | ||
231 | cols := columns.New([]gowid.IContainerWidget{ | |
232 | &gowid.ContainerWidget{hold, gowid.RenderWithWeight{W: 1}}, | |
233 | &gowid.ContainerWidget{sbar, gowid.RenderWithUnits{U: 1}}, | |
234 | }) | |
235 | ||
177 | 236 | res := &Widget{ |
178 | 237 | params: opts, |
179 | 238 | IHotKeyProvider: opts.HotKey, |
180 | IHotKeyPersistence: opts.HotKeyPersistence, | |
239 | IHotKeyPersistence: persistence, | |
181 | 240 | terminfo: ti, |
241 | sbar: sbar, | |
242 | cols: cols, | |
243 | hold: hold, | |
182 | 244 | Callbacks: gowid.NewCallbacks(), |
183 | 245 | } |
246 | ||
247 | res.hold.SetSubWidget(res, nil) | |
248 | res.cols.SetFocus(nil, 0) | |
249 | ||
250 | sbar.OnClickAbove(gowid.WidgetCallback{"cb", res.clickUp}) | |
251 | sbar.OnClickBelow(gowid.WidgetCallback{"cb", res.clickDown}) | |
252 | sbar.OnClickUpArrow(gowid.WidgetCallback{"cb", res.clickUpArrow}) | |
253 | sbar.OnClickDownArrow(gowid.WidgetCallback{"cb", res.clickDownArrow}) | |
184 | 254 | |
185 | 255 | var _ gowid.IWidget = res |
186 | 256 | var _ ITerminal = res |
187 | 257 | var _ IWidget = res |
258 | var _ IHotKeyFunctions = res | |
259 | var _ IScrollbar = res | |
188 | 260 | var _ io.Writer = res |
189 | 261 | |
190 | 262 | return res, nil |
204 | 276 | |
205 | 277 | func (w *Widget) Terminfo() *terminfo.Terminfo { |
206 | 278 | return w.terminfo |
279 | } | |
280 | ||
281 | func (w *Widget) ScrollbarEnabled() bool { | |
282 | return w.params.Scrollbar | |
283 | } | |
284 | ||
285 | func (w *Widget) EnableScrollbar(app gowid.IApp) { | |
286 | w.params.Scrollbar = true | |
287 | } | |
288 | ||
289 | func (w *Widget) DisableScrollbar(app gowid.IApp) { | |
290 | w.params.Scrollbar = false | |
291 | } | |
292 | ||
293 | func (w *Widget) HotKeyFunctions() []HotKeyInputFn { | |
294 | return w.params.HotKeyFns | |
207 | 295 | } |
208 | 296 | |
209 | 297 | func (w *Widget) Bell(app gowid.IApp) { |
252 | 340 | gowid.RemoveWidgetCallback(w.Callbacks, Bell{}, f) |
253 | 341 | } |
254 | 342 | |
343 | func (w *Widget) OnHotKey(f gowid.IWidgetChangedCallback) { | |
344 | gowid.AddWidgetCallback(w.Callbacks, HotKeyCB{}, f) | |
345 | } | |
346 | ||
347 | func (w *Widget) RemoveOnHotKey(f gowid.IIdentity) { | |
348 | gowid.RemoveWidgetCallback(w.Callbacks, HotKeyCB{}, f) | |
349 | } | |
350 | ||
351 | func (w *Widget) PasteState(b ...bool) bool { | |
352 | if len(b) > 0 { | |
353 | w.paste = b[0] | |
354 | } | |
355 | return w.paste | |
356 | } | |
357 | ||
255 | 358 | func (w *Widget) HotKeyActive() bool { |
256 | 359 | return w.hotKeyDown |
257 | 360 | } |
262 | 365 | if w.hotKeyTimer != nil { |
263 | 366 | w.hotKeyTimer.Stop() |
264 | 367 | } |
368 | ||
369 | gowid.RunWidgetCallbacks(w.Callbacks, HotKeyCB{}, app, w) | |
265 | 370 | |
266 | 371 | if down { |
267 | 372 | w.hotKeyDownTime = time.Now() |
268 | 373 | w.hotKeyTimer = time.AfterFunc(w.HotKeyDuration(), func() { |
269 | 374 | app.Run(gowid.RunFunction(func(app gowid.IApp) { |
270 | 375 | w.SetHotKeyActive(app, false) |
376 | gowid.RunWidgetCallbacks(w.Callbacks, HotKeyCB{}, app, w) | |
271 | 377 | })) |
272 | 378 | }) |
273 | 379 | } |
283 | 389 | } else { |
284 | 390 | lines = w.canvas.ScrollBuffer(dir, false, gwutil.SomeInt(lines)) |
285 | 391 | } |
286 | // Scrolling is now true if it (a) was previously, or (b) wasn't, but we | |
287 | // scrolled more than one line | |
288 | w.isScrolling = w.isScrolling || lines != 0 | |
392 | ||
393 | wasScrolling := w.isScrolling | |
394 | if lines != 0 { | |
395 | w.isScrolling = true | |
396 | } else if !w.params.KeyPressToEndScrollMode && dir == ScrollDown { | |
397 | // Disable scroll if we are at the bottom and we tried to scroll down | |
398 | // Thanks @Peter2121 ! | |
399 | w.isScrolling = false | |
400 | } | |
401 | if wasScrolling && !w.isScrolling { | |
402 | w.ResetScroll() | |
403 | } | |
289 | 404 | } |
290 | 405 | |
291 | 406 | func (w *Widget) ResetScroll() { |
322 | 437 | } |
323 | 438 | |
324 | 439 | func (w *Widget) UserInput(ev interface{}, size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) bool { |
440 | if !w.scrollbarTmpOff && w.params.Scrollbar { | |
441 | w.scrollbarTmpOff = true | |
442 | res := w.cols.UserInput(ev, size, focus, app) | |
443 | w.scrollbarTmpOff = false | |
444 | w.cols.SetFocus(app, 0) | |
445 | return res | |
446 | } | |
325 | 447 | return UserInput(w, ev, size, focus, app) |
326 | 448 | } |
327 | 449 | |
331 | 453 | panic(gowid.WidgetSizeError{Widget: w, Size: size, Required: "gowid.IRenderBox"}) |
332 | 454 | } |
333 | 455 | |
456 | if !w.scrollbarTmpOff && w.params.Scrollbar { | |
457 | w.scrollbarTmpOff = true | |
458 | c := w.cols.Render(size, focus, app) | |
459 | w.scrollbarTmpOff = false | |
460 | return c | |
461 | } | |
462 | ||
334 | 463 | w.TouchTerminal(box.BoxColumns(), box.BoxRows(), app) |
464 | ||
465 | w.sbar.Top = w.canvas.Offset | |
466 | w.sbar.Middle = w.canvas.scrollRegionEnd | |
467 | w.sbar.Bottom = gwutil.Max(0, w.canvas.ViewPortCanvas.Canvas.BoxRows()-(box.BoxRows()+w.canvas.Offset)) | |
335 | 468 | |
336 | 469 | return w.canvas |
337 | 470 | } |
484 | 617 | })) |
485 | 618 | }}) |
486 | 619 | |
620 | if w.params.EnableBracketedPaste { | |
621 | app.Run(gowid.RunFunction(func(app gowid.IApp) { | |
622 | for _, b := range enablePaste(w.terminfo) { | |
623 | canvas.ProcessByte(b) | |
624 | } | |
625 | })) | |
626 | } | |
627 | ||
487 | 628 | go func() { |
488 | 629 | for { |
489 | 630 | data := make([]byte, 4096) |
518 | 659 | if w.master != nil { |
519 | 660 | w.master.Close() |
520 | 661 | } |
662 | } | |
663 | ||
664 | func (w *Widget) clickUp(app gowid.IApp, w2 gowid.IWidget) { | |
665 | w.Scroll(ScrollUp, true, 1) | |
666 | } | |
667 | ||
668 | func (w *Widget) clickDown(app gowid.IApp, w2 gowid.IWidget) { | |
669 | w.Scroll(ScrollDown, true, 1) | |
670 | } | |
671 | ||
672 | func (w *Widget) clickUpArrow(app gowid.IApp, w2 gowid.IWidget) { | |
673 | w.Scroll(ScrollUp, false, 1) | |
674 | } | |
675 | ||
676 | func (w *Widget) clickDownArrow(app gowid.IApp, w2 gowid.IWidget) { | |
677 | w.Scroll(ScrollDown, false, 1) | |
521 | 678 | } |
522 | 679 | |
523 | 680 | //'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' |
557 | 714 | // pressed) then the input will not go to the terminal - it's hotkey |
558 | 715 | // function processing. |
559 | 716 | passToTerminal = false |
560 | res = true | |
717 | res = false | |
561 | 718 | deactivate := false |
562 | switch evk.Key() { | |
563 | case w.HotKey(): | |
564 | deactivate = true | |
565 | case tcell.KeyPgUp: | |
566 | w.Scroll(ScrollUp, true, 0) | |
567 | deactivate = true | |
568 | case tcell.KeyPgDn: | |
569 | w.Scroll(ScrollDown, true, 0) | |
570 | deactivate = true | |
571 | case tcell.KeyUp: | |
572 | w.Scroll(ScrollUp, false, 1) | |
573 | deactivate = true | |
574 | case tcell.KeyDown: | |
575 | w.Scroll(ScrollDown, false, 1) | |
576 | deactivate = true | |
577 | default: | |
578 | res = false | |
719 | if whk, ok := w.(IHotKeyFunctions); ok { | |
720 | for _, fn := range whk.HotKeyFunctions() { | |
721 | res = fn(evk, w, app) | |
722 | if res { | |
723 | deactivate = true | |
724 | break | |
725 | } | |
726 | } | |
727 | } | |
728 | if !res { | |
729 | res = true | |
730 | switch evk.Key() { | |
731 | case w.HotKey(): | |
732 | deactivate = true | |
733 | case tcell.KeyPgUp: | |
734 | w.Scroll(ScrollUp, true, 0) | |
735 | deactivate = true | |
736 | case tcell.KeyPgDn: | |
737 | w.Scroll(ScrollDown, true, 0) | |
738 | deactivate = true | |
739 | case tcell.KeyUp: | |
740 | w.Scroll(ScrollUp, false, 1) | |
741 | deactivate = true | |
742 | case tcell.KeyDown: | |
743 | w.Scroll(ScrollDown, false, 1) | |
744 | deactivate = true | |
745 | default: | |
746 | res = false | |
747 | } | |
579 | 748 | } |
580 | 749 | if deactivate { |
581 | 750 | w.SetHotKeyActive(app, false) |
597 | 766 | } |
598 | 767 | } |
599 | 768 | if passToTerminal { |
600 | seq, parsed := TCellEventToBytes(ev, w.Modes(), app.GetLastMouseState(), w.Terminfo()) | |
769 | seq, parsed := TCellEventToBytes(ev, w.Modes(), app.GetLastMouseState(), w, w.Terminfo()) | |
601 | 770 | |
602 | 771 | if parsed { |
603 | 772 | _, err := w.Write(seq) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
11 | 11 | |
12 | 12 | "github.com/gcla/gowid" |
13 | 13 | "github.com/gcla/gowid/gwutil" |
14 | "github.com/gdamore/tcell" | |
15 | "github.com/gdamore/tcell/terminfo" | |
14 | tcell "github.com/gdamore/tcell/v2" | |
15 | "github.com/gdamore/tcell/v2/terminfo" | |
16 | 16 | "github.com/stretchr/testify/assert" |
17 | 17 | ) |
18 | 18 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | // Based heavily on vterm.py from urwid |
8 | 8 | "fmt" |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | "github.com/gdamore/tcell" | |
12 | "github.com/gdamore/tcell/terminfo" | |
11 | tcell "github.com/gdamore/tcell/v2" | |
12 | "github.com/gdamore/tcell/v2/terminfo" | |
13 | 13 | log "github.com/sirupsen/logrus" |
14 | 14 | ) |
15 | 15 | |
23 | 23 | |
24 | 24 | func (e EventNotSupported) Error() string { |
25 | 25 | return fmt.Sprintf("Terminal input event %v of type %T not supported yet", e.Event, e.Event) |
26 | } | |
27 | ||
28 | func pasteStart(ti *terminfo.Terminfo) []byte { | |
29 | if ti.PasteStart != "" { | |
30 | return []byte(ti.PasteStart) | |
31 | } else { | |
32 | return []byte("\x1b[200~") | |
33 | } | |
34 | } | |
35 | ||
36 | func pasteEnd(ti *terminfo.Terminfo) []byte { | |
37 | if ti.PasteEnd != "" { | |
38 | return []byte(ti.PasteEnd) | |
39 | } else { | |
40 | return []byte("\x1b[201~") | |
41 | } | |
42 | } | |
43 | ||
44 | func enablePaste(ti *terminfo.Terminfo) []byte { | |
45 | if ti.EnablePaste != "" { | |
46 | return []byte(ti.EnablePaste) | |
47 | } else { | |
48 | return []byte("\x1b[?2004h") | |
49 | } | |
50 | } | |
51 | ||
52 | func disablePaste(ti *terminfo.Terminfo) []byte { | |
53 | if ti.DisablePaste != "" { | |
54 | return []byte(ti.DisablePaste) | |
55 | } else { | |
56 | return []byte("\x1b[?2004l") | |
57 | } | |
26 | 58 | } |
27 | 59 | |
28 | 60 | // TCellEventToBytes converts TCell's representation of a terminal event to |
33 | 65 | // subprocess is connected to a tty controlled by gowid. Events from the |
34 | 66 | // user are parsed by gowid via TCell - they are then translated by this |
35 | 67 | // function before being written to the TerminalWidget subprocess's tty. |
36 | func TCellEventToBytes(ev interface{}, mouse IMouseSupport, last gowid.MouseState, ti *terminfo.Terminfo) ([]byte, bool) { | |
68 | func TCellEventToBytes(ev interface{}, mouse IMouseSupport, last gowid.MouseState, paster IPaste, ti *terminfo.Terminfo) ([]byte, bool) { | |
37 | 69 | res := make([]byte, 0) |
38 | 70 | res2 := false |
39 | 71 | |
40 | 72 | switch ev := ev.(type) { |
73 | case *tcell.EventPaste: | |
74 | res2 = true | |
75 | if paster.PasteState() { | |
76 | // Already saw start | |
77 | res = append(res, pasteEnd(ti)...) | |
78 | paster.PasteState(false) | |
79 | } else { | |
80 | res = append(res, pasteStart(ti)...) | |
81 | paster.PasteState(true) | |
82 | } | |
41 | 83 | case *tcell.EventKey: |
42 | 84 | if ev.Key() < ' ' { |
43 | 85 | str := []rune{rune(ev.Key())} |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
8 | 8 | "fmt" |
9 | 9 | |
10 | 10 | "github.com/gcla/gowid" |
11 | "github.com/gdamore/tcell" | |
11 | tcell "github.com/gdamore/tcell/v2" | |
12 | 12 | ) |
13 | 13 | |
14 | 14 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package text |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
518 | 518 | |
519 | 519 | //====================================================================== |
520 | 520 | |
521 | type ISearchPred interface { | |
522 | CheckNode(IModel, IPos) bool | |
523 | } | |
524 | ||
525 | type SearchPred func(IModel, IPos) bool | |
526 | ||
527 | func (s SearchPred) CheckNode(tree IModel, pos IPos) bool { | |
528 | return s(tree, pos) | |
529 | } | |
530 | ||
531 | func DepthFirstSearch(tree IModel, fn ISearchPred) IPos { | |
532 | pos := NewPos() | |
533 | return depthFirstSearchImpl(tree, pos, fn) | |
534 | } | |
535 | ||
536 | func depthFirstSearchImpl(tree IModel, pos *TreePos, fn ISearchPred) IPos { | |
537 | if tree == nil { | |
538 | return nil | |
539 | } | |
540 | if fn.CheckNode(tree, pos) { | |
541 | return pos | |
542 | } | |
543 | cs := tree.Children() | |
544 | tpos := pos.Copy().(*TreePos) | |
545 | tpos.Pos = append(tpos.Pos, 0) | |
546 | i := 0 | |
547 | for cs.Next() { | |
548 | tpos.Pos[len(tpos.Pos)-1] = i | |
549 | rpos := depthFirstSearchImpl(cs.Value(), tpos, fn) | |
550 | if rpos != nil { | |
551 | return rpos | |
552 | } | |
553 | i += 1 | |
554 | } | |
555 | return nil | |
556 | } | |
557 | ||
558 | //====================================================================== | |
559 | ||
521 | 560 | type IWidgetMaker interface { |
522 | 561 | MakeWidget(pos IPos, tree IModel) gowid.IWidget |
523 | 562 | } |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package tree |
68 | 68 | tt := tp.GetSubStructure(parent1) |
69 | 69 | |
70 | 70 | assert.Equal(t, leaf1, tt) |
71 | ||
72 | count := 0 | |
73 | var pos IPos | |
74 | ||
75 | dfs := DepthFirstSearch(parent1, SearchPred(func(t IModel, p IPos) bool { | |
76 | count += 1 | |
77 | t2 := t.(*Tree) | |
78 | pos = p | |
79 | return t2.theLeaf == "leaf2" | |
80 | })) | |
81 | ||
82 | pos2 := pos.(*TreePos) | |
83 | ||
84 | assert.NotNil(t, dfs) | |
85 | assert.Equal(t, 6, count) | |
86 | assert.Equal(t, []int{2}, pos2.Indices()) | |
87 | ||
88 | st := pos2.GetSubStructure(parent1).(*Tree) | |
89 | assert.Equal(t, "leaf2", st.theLeaf) | |
90 | ||
71 | 91 | } |
72 | 92 | |
73 | 93 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
10 | 10 | |
11 | 11 | "github.com/gcla/gowid" |
12 | 12 | "github.com/gcla/gowid/widgets/fill" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | ) |
15 | 15 | |
16 | 16 | //====================================================================== |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source code is governed by the MIT license | |
1 | 1 | // that can be found in the LICENSE file. |
2 | 2 | |
3 | 3 | package vpadding |
10 | 10 | "github.com/gcla/gowid/gwtest" |
11 | 11 | "github.com/gcla/gowid/widgets/checkbox" |
12 | 12 | "github.com/gcla/gowid/widgets/fill" |
13 | "github.com/gcla/gowid/widgets/pile" | |
13 | 14 | "github.com/gcla/gowid/widgets/text" |
14 | "github.com/gdamore/tcell" | |
15 | tcell "github.com/gdamore/tcell/v2" | |
15 | 16 | log "github.com/sirupsen/logrus" |
16 | 17 | "github.com/stretchr/testify/assert" |
17 | 18 | ) |
56 | 57 | w10 := New(fill.New('x'), gowid.VAlignTop{2}, gowid.RenderWithUnits{U: 4}) |
57 | 58 | c10 := w10.Render(gowid.RenderBox{C: 3, R: 8}, gowid.Focused, gwtest.D) |
58 | 59 | assert.Equal(t, c10.String(), " \n \nxxx\nxxx\nxxx\nxxx\n \n ") |
60 | ||
61 | w11 := New(fill.New('x'), gowid.VAlignBottom{}, gowid.RenderWithUnits{U: 3}) | |
62 | c11 := w11.Render(gowid.RenderBox{C: 3, R: 4}, gowid.Focused, gwtest.D) | |
63 | assert.Equal(t, c11.String(), " \nxxx\nxxx\nxxx") | |
64 | ||
65 | p1 := pile.New([]gowid.IContainerWidget{ | |
66 | &gowid.ContainerWidget{ | |
67 | IWidget: text.New("111"), | |
68 | D: gowid.RenderFixed{}, | |
69 | }, | |
70 | &gowid.ContainerWidget{ | |
71 | IWidget: text.New("222"), | |
72 | D: gowid.RenderFixed{}, | |
73 | }, | |
74 | &gowid.ContainerWidget{ | |
75 | IWidget: text.New("333"), | |
76 | D: gowid.RenderFixed{}, | |
77 | }, | |
78 | &gowid.ContainerWidget{ | |
79 | IWidget: text.New("444"), | |
80 | D: gowid.RenderFixed{}, | |
81 | }, | |
82 | }) | |
83 | ||
84 | w12 := New(p1, gowid.VAlignBottom{}, gowid.RenderWithUnits{U: 3}) | |
85 | c12 := w12.Render(gowid.RenderBox{C: 3, R: 4}, gowid.Focused, gwtest.D) | |
86 | assert.Equal(t, c12.String(), " \n111\n222\n333") | |
87 | ||
88 | w13 := New(p1, gowid.VAlignBottom{}, gowid.RenderWithUnits{U: 3}) | |
89 | c13 := w13.Render(gowid.RenderBox{C: 3, R: 2}, gowid.Focused, gwtest.D) | |
90 | assert.Equal(t, c13.String(), "111\n222") | |
59 | 91 | |
60 | 92 | for _, w := range []gowid.IWidget{w1, w2, w3, w4, w5, w6, w7, w8, w9, w10} { |
61 | 93 | gwtest.RenderBoxManyTimes(t, w, 0, 10, 0, 10) |
0 | // Copyright 2019 Graham Clark. All rights reserved. Use of this source | |
0 | // Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source | |
1 | 1 | // code is governed by the MIT license that can be found in the LICENSE |
2 | 2 | // file. |
3 | 3 | |
10 | 10 | |
11 | 11 | "github.com/gcla/gowid" |
12 | 12 | "github.com/gcla/gowid/gwutil" |
13 | "github.com/gdamore/tcell" | |
13 | tcell "github.com/gdamore/tcell/v2" | |
14 | 14 | ) |
15 | 15 | |
16 | 16 | //====================================================================== |