-
Notifications
You must be signed in to change notification settings - Fork 31
/
support.go
1822 lines (1564 loc) · 54.8 KB
/
support.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
// Package gowid provides widgets and tools for constructing compositional terminal user interfaces.
package gowid
import (
"fmt"
"strings"
"time"
"github.com/gcla/gowid/gwutil"
tcell "github.com/gdamore/tcell/v2"
"github.com/pkg/errors"
)
//======================================================================
type IRows interface {
Rows() int
}
type IColumns interface {
Columns() int
}
// IRenderSize is the type of objects that can specify how a widget is to be rendered.
// This is the empty interface, and only serves as a placeholder at the moment. In
// practise, actual rendering sizes will be determined by an IFlowDimension, IBoxDimension
// or an IFixedDimension
type IRenderSize interface{}
//======================================================================
// Widgets that are used in containers such as Pile or Columns must implement
// this interface. It specifies how each subwidget of the container should be
// rendered.
//
type IWidgetDimension interface {
ImplementsWidgetDimension() // This exists as a marker so that IWidgetDimension is not empty, meaning satisfied by any struct.
}
type IRenderFixed interface {
IWidgetDimension
Fixed() // dummy
}
type IRenderFlowWith interface {
IWidgetDimension
FlowColumns() int
}
type IRenderFlow interface {
IWidgetDimension
Flow() // dummy
}
type IBox interface {
BoxColumns() int
BoxRows() int
}
type IRenderBox interface {
IWidgetDimension
IBox
}
type IRenderWithWeight interface {
IWidgetDimension
Weight() int
}
type IRenderRelative interface {
IWidgetDimension
Relative() float64
}
type IRenderWithUnits interface {
IWidgetDimension
Units() int
}
// Used in widgets laid out side-by-side - intended to have the effect that these widgets are
// rendered last and provided a height that corresponds to the max of the height of those
// widgets already rendered.
type IRenderMax interface {
MaxHeight() // dummy
}
// Used in widgets laid out side-by-side - intended to limit the width of a widget
// which may otherwise be specified to be dimensioned in relation to the width available.
// This can let the layout algorithm give more space (e.g. maximized terminal) to widgets
// that can use it by constraining those that don't need it.
type IRenderMaxUnits interface {
MaxUnits() int
}
//======================================================================
// RenderFlowWith is an object passed to a widget's Render function that specifies that
// it should be rendered with a set number of columns, but using as many rows as
// the widget itself determines it needs.
type RenderFlowWith struct {
C int
}
func MakeRenderFlow(columns int) RenderFlowWith {
return RenderFlowWith{C: columns}
}
func (r RenderFlowWith) FlowColumns() int {
return r.C
}
// For IColumns
func (r RenderFlowWith) Columns() int {
return r.FlowColumns()
}
func (r RenderFlowWith) String() string {
return fmt.Sprintf("flowwith(c:%d)", r.C)
}
func (r RenderFlowWith) ImplementsWidgetDimension() {}
//======================================================================
// RenderBox is an object passed to a widget's Render function that specifies that
// it should be rendered with a set number of columns and rows.
type RenderBox struct {
C int
R int
}
func MakeRenderBox(columns, rows int) RenderBox {
return RenderBox{C: columns, R: rows}
}
func (r RenderBox) BoxColumns() int {
return r.C
}
func (r RenderBox) BoxRows() int {
return r.R
}
// For IColumns
func (r RenderBox) Columns() int {
return r.BoxColumns()
}
// For IRows
func (r RenderBox) Rows() int {
return r.BoxRows()
}
func (r RenderBox) ImplementsWidgetDimension() {}
func (r RenderBox) String() string {
return fmt.Sprintf("box(c:%d,r:%d)", r.C, r.R)
}
//======================================================================
// RenderFixed is an object passed to a widget's Render function that specifies that
// the widget itself will determine its own size.
type RenderFixed struct{}
func MakeRenderFixed() RenderFixed {
return RenderFixed{}
}
func (f RenderFixed) String() string {
return "fixed"
}
func (f RenderFixed) Fixed() {}
func (r RenderFixed) ImplementsWidgetDimension() {}
//======================================================================
type RenderWithWeight struct {
W int
}
func (f RenderWithWeight) String() string {
return fmt.Sprintf("weight(%d)", f.W)
}
func (f RenderWithWeight) Weight() int {
return f.W
}
func (r RenderWithWeight) ImplementsWidgetDimension() {}
//======================================================================
// RenderFlow is used by widgets that embed an inner widget, like hpadding.Widget.
// It directs the outer widget how it should render the inner widget. If the outer
// widget is rendered in box mode, the inner widget should be rendered in flow mode,
// using the box's number of columns. If the outer widget is rendered in flow mode,
// the inner widget should be rendered in flow mode with the same number of columns.
type RenderFlow struct{}
func (s RenderFlow) Flow() {}
func (f RenderFlow) String() string {
return "flow"
}
func (r RenderFlow) ImplementsWidgetDimension() {}
//======================================================================
// RenderMax is used in widgets laid out side-by-side - it's intended to
// have the effect that these widgets are rendered last and provided a
// height/width that corresponds to the max of the height/width of those widgets
// already rendered.
type RenderMax struct{}
func (s RenderMax) MaxHeight() {}
func (f RenderMax) String() string {
return "maxheight"
}
//======================================================================
// RenderWithUnits is used by widgets within a container. It specifies the number
// of columns or rows to use when rendering.
type RenderWithUnits struct {
U int
}
func (f RenderWithUnits) Units() int {
return f.U
}
func (f RenderWithUnits) String() string {
return fmt.Sprintf("units(%d)", f.U)
}
func (r RenderWithUnits) ImplementsWidgetDimension() {}
//======================================================================
// RenderWithRatio is used by widgets within a container
type RenderWithRatio struct {
R float64
}
func (f RenderWithRatio) Relative() float64 {
return f.R
}
func (f RenderWithRatio) String() string {
return fmt.Sprintf("ratio(%f)", f.R)
}
func (r RenderWithRatio) ImplementsWidgetDimension() {}
//======================================================================
type DimensionError struct {
Size IRenderSize
Dim IWidgetDimension
Row int
}
var _ error = DimensionError{}
func (e DimensionError) Error() string {
if e.Row == -1 {
return fmt.Sprintf("Dimension spec %v of type %T cannot be used with render size %v of type %T", e.Dim, e.Dim, e.Size, e.Size)
} else {
return fmt.Sprintf("Dimension spec %v of type %T cannot be used with render size %v of type %T and advised row %d", e.Dim, e.Dim, e.Size, e.Size, e.Row)
}
}
//======================================================================
type WidgetSizeError struct {
Widget interface{}
Size IRenderSize
Required string // in case I only need an interface - not sure how to capture it and not concrete type
}
var _ error = WidgetSizeError{}
func (e WidgetSizeError) Error() string {
if e.Required == "" {
return fmt.Sprintf("Widget %v cannot be rendered with %v of type %T", e.Widget, e.Size, e.Size)
} else {
return fmt.Sprintf("Widget %v cannot be rendered with %v of type %T - requires %s", e.Widget, e.Size, e.Size, e.Required)
}
}
//======================================================================
// IContainerWidget is the type of an object that contains a widget and
// a render object that determines how it is rendered within a container of
// widgets. Note that it itself is an IWidget.
type IContainerWidget interface {
IWidget
IComposite
Dimension() IWidgetDimension
SetDimension(IWidgetDimension)
}
//======================================================================
// ContainerWidget is a simple implementation that conforms to
// IContainerWidget. It can be used to pass widgets to containers like
// pile.Widget and columns.Widget.
type ContainerWidget struct {
IWidget
D IWidgetDimension
}
func (ww ContainerWidget) Dimension() IWidgetDimension {
return ww.D
}
func (ww *ContainerWidget) SetDimension(d IWidgetDimension) {
ww.D = d
}
func (w *ContainerWidget) SubWidget() IWidget {
return w.IWidget
}
func (w *ContainerWidget) SetSubWidget(wi IWidget, app IApp) {
w.IWidget = wi
}
func (w *ContainerWidget) String() string {
return fmt.Sprintf("container[%v,%v]", w.D, w.IWidget)
}
var _ IContainerWidget = (*ContainerWidget)(nil)
//======================================================================
// Three states - false+false, false+true, true+true
type Selector struct {
Focus bool
Selected bool
}
var Focused = Selector{
Focus: true,
Selected: true,
}
var Selected = Selector{
Focus: false,
Selected: true,
}
var NotSelected = Selector{
Focus: false,
Selected: false,
}
// SelectIf returns a Selector with the Selected field set dependent on the
// supplied condition only. The Focus field is set based on the supplied condition
// AND the receiver's Focus field. Used by composite widgets with multiple children
// to allow children to change their state dependent on whether they are selected
// but independent of whether the widget is currently in focus.
func (s Selector) SelectIf(cond bool) Selector {
return Selector{
Focus: s.Focus && cond,
Selected: cond,
}
}
// And returns a Selector with both Selected and Focus set dependent on the
// supplied condition AND the receiver. Used to propagate Selected and Focus
// state to sub widgets for input and rendering.
func (s Selector) And(cond bool) Selector {
return Selector{
Focus: s.Focus && cond,
Selected: s.Selected && cond,
}
}
func (s Selector) String() string {
return fmt.Sprintf("[Focus:%v,Selected:%v]", s.Focus, s.Selected)
}
// ISelectChild is implemented by any type that controls whether or not it
// will set focus.Selected on its currently "selected" child. For example, a
// columns widget will have a notion of a child widget that will take focus.
// The user may want to render that widget in a way that highlights the
// selected child, even when the columns widget itself does not have
// focus. The columns widget will set focus.Selected on Render() and
// UserInput() calls depending on the result of SelectChild() - if
// focus.Selected is set, then a styling widget can change the look of the
// widget appropriately.
//
type ISelectChild interface {
SelectChild(Selector) bool // Whether or not this widget will set focus.Selected for its selected child
}
// IWidget is the interface of any object acting as a gowid widget.
//
// Render() is provided a size (cols, maybe rows), whether or not the widget
// is in focus, and a context (palette, etc). It must return an object conforming
// to gowid's ICanvas, which is a representation of what can be displayed in the
// terminal.
//
// RenderSize() is used by clients that need to know only how big the widget
// will be when rendered. It is expected to be cheaper to compute in some cases
// than Render(), but a fallback is to run Render() then return the size of the
// canvas.
//
// Selectable() should return true if this widget is designed for interaction e.g.
// a Button would return true, but a Text widget would return false. Note that,
// like urwid, returning false does not guarantee the widget will never have
// focus - it might be given focus if there is no other option (no other
// selectable widgets in the container, for example).
//
// UserInput() is provided the TCell event (mouse or keyboard action),
// the size spec that would be given to Render(), whether or not the widget
// has focus, and access to the application, useful for effecting changes
// like changing colors, running a function, or quitting. The render size is
// needed because the widget might have to pass the event down to children
// widgets, and the correct one may depend on the coordinates of a mouse
// click relative to the dimensions of the widget itself.
//
type IWidget interface {
Render(size IRenderSize, focus Selector, app IApp) ICanvas
RenderSize(size IRenderSize, focus Selector, app IApp) IRenderBox
UserInput(ev interface{}, size IRenderSize, focus Selector, app IApp) bool
Selectable() bool
}
// IIdentity is used for widgets that support being a click target - so it
// is possible to link the widget that is the target of MouseReleased with
// the one that was the target of MouseLeft/Right/Middle when they might
// not necessarily be the same object (i.e. rebuilt widget hierarchy in
// between). Also used to name callbacks so they can be removed (since
// function objects can't be compared)
//
type IIdentity interface {
ID() interface{}
}
// IComposite is an interface for anything that has a concept of a single
// "inner" widget. This applies to certain widgets themselves
// (e.g. ButtonWidget) and also to the App object which holds the top-level
// view.
//
type IComposite interface {
SubWidget() IWidget
}
// IComposite is an interface for anything that has a concept of a single
// settable "inner" widget. This applies to certain widgets themselves
// (e.g. ButtonWidget) and also to the App object which holds the top-level
// view.
//
type ISettableComposite interface {
IComposite
SetSubWidget(IWidget, IApp)
}
// ISubWidgetSize returns the size argument that should be provided to
// render the inner widget based on the size argument provided to the
// containing widget.
type ISubWidgetSize interface {
SubWidgetSize(size IRenderSize, focus Selector, app IApp) IRenderSize
}
// ICompositeWidget is an interface implemented by widgets that contain one
// subwidget. Further implented methods could make it an IButtonWidget for
// example, which then means the RenderButton() function can be exploited
// to implement Render(). If you make a new Button by embedding
// ButtonWidget, you may be able to implement Render() by simply calling
// RenderButton().
//
type ICompositeWidget interface {
IWidget
IComposite
ISubWidgetSize
}
// ICompositeMultiple is an interface for widget containers that have multiple
// children and that support specifying how the children are laid out relative
// to each other.
//
type ICompositeMultiple interface {
SubWidgets() []IWidget
}
// ISettableSubWidgetsis implemented by a type that maintains a collection of child
// widgets (like pile, columns) and that allows them to be changed.
type ISettableSubWidgets interface {
SetSubWidgets([]IWidget, IApp)
}
// ICompositeMultipleDimensions is an interface for collections of widget dimensions,
// used in laying out some container widgets.
//
type ICompositeMultipleDimensions interface {
ICompositeMultiple
Dimensions() []IWidgetDimension
}
// ISettableDimensions is implemented by types that maintain a collection of
// dimensions - to be used by containers that use these dimensions to layout
// their children widgets.
//
type ISettableDimensions interface {
SetDimensions([]IWidgetDimension, IApp)
}
// IFocus is a container widget concept that describes which widget will be
// the target of keyboard input.
//
type IFocus interface {
Focus() int
SetFocus(app IApp, i int)
}
// IFindNextSelectable is for any object that can iterate to its next or
// previous object
type IFindNextSelectable interface {
FindNextSelectable(dir Direction, wrap bool) (int, bool)
}
// ICompositeMultipleWidget is a widget that implements ICompositeMultiple. The
// widget must support computing the render-time size of any of its children
// and setting focus.
//
type ICompositeMultipleWidget interface {
IWidget
ICompositeMultipleDimensions
IFocus
// SubWidgetSize should return the IRenderSize value that will be used to render
// an inner widget given the size used to render the outer widget and an
// IWidgetDimension (such as units, weight, etc)
SubWidgetSize(size IRenderSize, val int, sub IWidget, dim IWidgetDimension) IRenderSize
// RenderSubWidgets should return an array of canvases representing each inner
// widget, rendered in the context of the containing widget with the supplied
// size argument.
RenderSubWidgets(size IRenderSize, focus Selector, focusIdx int, app IApp) []ICanvas
// RenderedSubWidgetsSizes should return a bounding box for each inner widget
// when the containing widget is rendered with the provided size. Note that this
// is not the same as rendering each inner widget separately, because the
// container context might result in size adjustments e.g. adjusting the
// height of inner widgets to make sure they're aligned vertically.
RenderedSubWidgetsSizes(size IRenderSize, focus Selector, focusIdx int, app IApp) []IRenderBox
}
// IClickable is implemented by any type that implements a Click()
// method, intended to be run in response to a user interaction with the
// type such as left mouse click or hitting enter.
//
type IClickable interface {
Click(app IApp)
}
// IDoubleClickable is implemented by any type that implements a DoubleClick()
// method, intended to be run in response to a user interaction with the
// type such as two left mouse clicks close together in time.
//
type IDoubleClickable interface {
DoubleClick(app IApp) bool // Return true if action was taken; to suppress Click()
DoubleClickDelay() time.Duration
}
// IKeyPress is implemented by any type that implements a KeyPress()
// method, intended to be run in response to a user interaction with the
// type such as hitting the escape key.
//
type IKeyPress interface {
KeyPress(key IKey, app IApp)
}
// IClickTracker is implemented by any type that can track the state of whether it
// was clicked. This is trivial, and may just be a boolean flag. It's intended for
// widgets that want to change their look when a mouse button is clicked when they are
// in focus, but before the button is released - to indicate that the widget is about
// to be activated. Of course if the user moves the cursor off the widget then
// releases the mouse button, the widget will not be activated.
type IClickTracker interface {
SetClickPending(bool)
}
// IClickableWidget is implemented by any widget that implements a Click()
// method, intended to be run in response to a user interaction with the
// widget such as left mouse click or hitting enter. A widget implementing
// Click() and ID() may be able to run UserInputCheckedWidget() for its
// UserInput() implementation.
//
type IClickableWidget interface {
IWidget
IClickable
}
// IIdentityWidget is implemented by any widget that provides an ID()
// function that identifies itself and allows itself to be compared against
// other IIdentity implementers. This is intended be to used to check
// whether or not the widget that was in focus when a mouse click was
// issued is the same widget in focus when the mouse is released. If so
// then the widget was "clicked". This allows gowid to run the action on
// release rather than on click, which is more forgiving of mistaken
// clicks. The widget in focus on release may be logically the same widget
// as the one clicked, but possibly a different object, if the widget
// hierarchy was somehow rebuilt in response to the first click - so to
// receive the click event, make sure the newly built widget has the same
// ID() as the original (e.g. a serialized representation of a position in
// a ListWalker)
//
type IIdentityWidget interface {
IWidget
IIdentity
}
// IPreferedPosition is implemented by any widget that supports a prefered
// column or row (position in a dimension), meaning it understands what
// subwidget is at the current dimensional coordinate, and can move its focus
// widget to a new position. This is modeled on Urwid's get_pref_col()
// feature, which tries to provide a sensible switch of focus widget when
// moving the cursor vertically around the screen - instead of having it hop
// left and right depending on which widget happens to be in focus at the
// current y coordinate.
//
type IPreferedPosition interface {
GetPreferedPosition() gwutil.IntOption
SetPreferedPosition(col int, app IApp)
}
// IMenuCompatible is implemented by any widget that can set a subwidget.
// It's used by widgets like menus that need to inject themselves into
// the widget hierarchy close to the root (to be rendered over the main
// "view") i.e. the current root is made a child of the new menu widget,
// whuch becomes the new root.
type IMenuCompatible interface {
IWidget
ISettableComposite
}
//======================================================================
type ICopyResult interface {
ClipName() string
ClipValue() string
}
type CopyResult struct {
Name string
Val string
}
var _ ICopyResult = CopyResult{}
func (c CopyResult) ClipName() string {
return c.Name
}
func (c CopyResult) ClipValue() string {
return c.Val
}
type IClipboard interface {
Clips(app IApp) []ICopyResult
}
// IClipboardSelected is implemented by widgets that support changing their
// look when they have been "selected" in some application-level copy-mode,
// the idea being to provide the user with the information that this widget's
// contents will be copied.
type IClipboardSelected interface {
AlterWidget(w IWidget, app IApp) IWidget
}
//======================================================================
// AddressProvidesID is a convenience struct that can be embedded in widgets.
// It provides an ID() function by simply returning the pointer of its
// caller argument. The ID() function is for widgets that want to implement
// IIdentity, which is needed by containers that want to compare widgets.
// For example, if the user clicks on a button.Widget, the app can be used
// to save that widget. When the click is released, the button's UserInput
// function tries to determine whether the mouse was released over the
// same widget that was clicked. It can do this by comparing the widgets'
// ID() values. Note that this will not work if new button widgets are
// created each time Render/UserInput is called (because the caller
// will change).
type AddressProvidesID struct{}
func (a *AddressProvidesID) ID() interface{} {
return a
}
//======================================================================
// RejectUserInput is a convenience struct that can be embedded in widgets
// that don't accept any user input.
//
type RejectUserInput struct{}
func (r RejectUserInput) UserInput(ev interface{}, size IRenderSize, focus Selector, app IApp) bool {
return false
}
//======================================================================
// NotSelectable is a convenience struct that can be embedded in widgets. It provides
// a function that simply return false to the call to Selectable()
//
type NotSelectable struct{}
func (r *NotSelectable) Selectable() bool {
return false
}
//======================================================================
// IsSelectable is a convenience struct that can be embedded in widgets. It provides
// a function that simply return true to the call to Selectable()
//
type IsSelectable struct{}
func (r *IsSelectable) Selectable() bool {
return true
}
//======================================================================
// SelectableIfAnySubWidgetsAre is useful for various container widgets.
//
func SelectableIfAnySubWidgetsAre(w ICompositeMultipleDimensions) bool {
for _, widget := range w.SubWidgets() {
if widget.Selectable() {
return true
}
}
return false
}
//======================================================================
type IHAlignment interface {
ImplementsHAlignment()
}
type HAlignRight struct{}
type HAlignMiddle struct{}
type HAlignLeft struct {
Margin int
MarginRight int
}
func (h HAlignRight) ImplementsHAlignment() {}
func (h HAlignMiddle) ImplementsHAlignment() {}
func (h HAlignLeft) ImplementsHAlignment() {}
type IVAlignment interface {
ImplementsVAlignment()
}
type VAlignBottom struct {
Margin int
}
type VAlignMiddle struct{}
type VAlignTop struct {
Margin int
}
func (v VAlignBottom) ImplementsVAlignment() {}
func (v VAlignMiddle) ImplementsVAlignment() {}
func (v VAlignTop) ImplementsVAlignment() {}
//======================================================================
// CalculateRenderSizeFallback can be used by widgets that cannot easily compute a value
// for RenderSize without actually rendering the widget and measuring the bounding box.
// It assumes that if IRenderBox size is provided, then the widget's canvas when rendered
// will be that large, and simply returns the box. If an IRenderFlow is provided, then
// the widget is rendered, and the bounding box is returned.
func CalculateRenderSizeFallback(w IWidget, size IRenderSize, focus Selector, app IApp) RenderBox {
var res RenderBox
switch sz := size.(type) {
case IRenderBox:
res.R = sz.BoxRows()
res.C = sz.BoxColumns()
default:
c := w.Render(size, focus, app)
res.R = c.BoxRows()
res.C = c.BoxColumns()
}
return res
}
// UserInputIfSelectable will return false if the widget is not selectable; otherwise it will
// try the widget's UserInput function.
func UserInputIfSelectable(w IWidget, ev interface{}, size IRenderSize, focus Selector, app IApp) bool {
res := false
if w.Selectable() {
res = w.UserInput(ev, size, focus, app)
}
return res
}
// RenderSize currently passes control through to the widget's RenderSize
// method. Having this function allows for easier instrumentation of the
// RenderSize path. RenderSize is intended to compute the size of the canvas
// that will be generated when the widget is rendered. Some parent widgets
// need this value from their children, and it might be possible to compute
// it much more cheaply than rendering the widget in order to determine
// the canvas size only.
func RenderSize(w IWidget, size IRenderSize, focus Selector, app IApp) IRenderBox {
return w.RenderSize(size, focus, app)
}
// SubWidgetSize currently passes control through to the widget's SubWidgetSize
// method. Having this function allows for easier instrumentation of the
// SubWidgetSize path. The function should compute the size that it will itself
// use to render its child widget; for example, a framing widget rendered
// with IRenderBox might return a RenderBox value that is 2 units smaller
// in both height and width.
func SubWidgetSize(w ICompositeWidget, size IRenderSize, focus Selector, app IApp) IRenderSize {
return w.SubWidgetSize(size, focus, app)
}
// RenderRoot is called from the App application object when beginning the
// widget rendering process. It starts at the root of the widget hierarchy
// with an IRenderBox size argument equal to the size of the current terminal.
func RenderRoot(w IWidget, t *App) {
maxX, maxY := t.TerminalSize()
canvas := w.Render(RenderBox{C: maxX, R: maxY}, Focused, t)
// tcell will apply its default style to empty cells. But because gowid's model
// is to layer styles, here we explicitly merge each canvas cell on top of a cell
// constructed with the tcell default style. Therefore if the tcell default applies
// an underline, for example, then each canvas cell will be merged on top of a cell
// with an underline. If the upper cell masks out underline, then it won't show. But
// if the upper cell doesn't mask out the underline, it will show.
if paletteDefault, ok := t.CellStyler("default"); ok {
defFg := ColorDefault
defBg := ColorDefault
fgCol, bgCol, style := paletteDefault.GetStyle(t)
mode := t.GetColorMode()
defFg = IColorToTCell(fgCol, defFg, mode)
defBg = IColorToTCell(bgCol, defBg, mode)
RangeOverCanvas(canvas, CellRangeFunc(func(c Cell) Cell {
return MakeCell(c.codePoint, defFg, defBg, style).MergeDisplayAttrsUnder(c)
}))
}
Draw(canvas, t, t.GetScreen())
}
func FindNextSelectableFrom(w ICompositeMultipleDimensions, start int, dir Direction, wrap bool) (int, bool) {
dup := CopyWidgets(w.SubWidgets())
return FindNextSelectableWidget(dup, start, dir, wrap)
}
func FindNextSelectableWidget(w []IWidget, pos int, dir Direction, wrap bool) (int, bool) {
if len(w) == 0 {
return -1, false
}
if pos == -1 {
if dir <= 0 {
pos = len(w)
}
}
start := pos
for {
pos = pos + int(dir)
if pos == -1 {
if wrap {
pos = len(w) - 1
} else {
return -1, false
}
} else if pos == len(w) {
if wrap {
pos = 0
} else {
return -1, false
}
}
if w[pos] != nil && w[pos].Selectable() {
return pos, true
} else if pos == start {
return -1, false
}
}
}
func FixCanvasHeight(c ICanvas, size IRenderSize) {
// Make sure that if we're rendered as a box, we have enough rows.
if box, ok := size.(IRenderBox); ok {
if c.BoxRows() < box.BoxRows() {
AppendBlankLines(c, box.BoxRows()-c.BoxRows())
} else if c.BoxRows() > box.BoxRows() {
c.Truncate(0, c.BoxRows()-box.BoxRows())
}
}
}
type IAppendBlankLines interface {
BoxColumns() int
AppendBelow(c IAppendCanvas, doCursor bool, makeCopy bool)
}
func AppendBlankLines(c IAppendBlankLines, iters int) {
for i := 0; i < iters; i++ {
line := make([]Cell, c.BoxColumns())
c.AppendBelow(LineCanvas(line), false, false)
}
}
//======================================================================
type ICallbackRunner interface {
RunWidgetCallbacks(name interface{}, app IApp, w IWidget)
}
// IWidgetChangedCallback defines the types that can be used as callbacks
// that are issued when widget properties change. It expects a function
// Changed() that is called with the current app and the widget that is
// issuing the callback. It also expects to conform to IIdentity, so that
// one callback instance can be compared to another - this is to allow
// callbacks to be removed correctly, if that is required.
type IWidgetChangedCallback interface {
IIdentity
Changed(app IApp, widget IWidget, data ...interface{})
}
// WidgetChangedFunction meets the IWidgetChangedCallback interface, for simpler
// usage.
type WidgetChangedFunction func(app IApp, widget IWidget)
func (f WidgetChangedFunction) Changed(app IApp, widget IWidget, data ...interface{}) {
f(app, widget)
}
type WidgetChangedFunctionExt func(app IApp, widget IWidget, data ...interface{})
func (f WidgetChangedFunctionExt) Changed(app IApp, widget IWidget, data ...interface{}) {
f(app, widget, data...)
}
// WidgetCallback is a simple struct with a name field for IIdentity and
// that embeds a WidgetChangedFunction to be issued as a callback when a widget
// property changes.
type WidgetCallback struct {
Name interface{}
WidgetChangedFunction
}
func MakeWidgetCallback(name interface{}, fn WidgetChangedFunction) WidgetCallback {
return WidgetCallback{
Name: name,
WidgetChangedFunction: fn,
}
}
func (f WidgetCallback) ID() interface{} {
return f.Name
}
// WidgetCallbackExt is a simple struct with a name field for IIdentity and
// that embeds a WidgetChangedFunction to be issued as a callback when a widget
// property changes.
type WidgetCallbackExt struct {
Name interface{}
WidgetChangedFunctionExt
}
func MakeWidgetCallbackExt(name interface{}, fn WidgetChangedFunctionExt) WidgetCallbackExt {
return WidgetCallbackExt{
Name: name,
WidgetChangedFunctionExt: fn,
}
}
func (f WidgetCallbackExt) ID() interface{} {
return f.Name
}
func RunWidgetCallbacks(c ICallbacks, name interface{}, app IApp, data ...interface{}) {
if c != nil {
data2 := append([]interface{}{app}, data...)
c.RunCallbacks(name, data2...)
}
}
type widgetChangedCallbackProxy struct {
IWidgetChangedCallback
}
func (p widgetChangedCallbackProxy) Call(args ...interface{}) {
t := args[0].(IApp)