/
value.go
891 lines (780 loc) · 26.2 KB
/
value.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
// Copyright (c) 2018, Cogent Core. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package views allows you to view and edit Go data in the GUI using reflection.
package views
//go:generate core generate
import (
"fmt"
"reflect"
"strconv"
"cogentcore.org/core/base/reflectx"
"cogentcore.org/core/base/strcase"
"cogentcore.org/core/core"
"cogentcore.org/core/enums"
"cogentcore.org/core/events"
"cogentcore.org/core/events/key"
"cogentcore.org/core/styles"
"cogentcore.org/core/styles/states"
"cogentcore.org/core/tree"
"cogentcore.org/core/types"
)
// NewValue makes and returns a new [Value] from the given value and creates
// the widget for it with the given parent and optional tags (only the first
// argument is used). It is the main way that end-user code should interact
// with [Value]s. The given value needs to be a pointer for it to be modified.
//
// NewValue is not appropriate for internal code configuring
// non-solo values (for example, in StructView), but it should be fine
// for end-user code.
func NewValue(parent tree.Node, val any, tags ...string) Value {
t := ""
if len(tags) > 0 {
t = tags[0]
}
v := ToValue(val, t)
v.SetSoloValue(reflect.ValueOf(val))
w := parent.NewChild(v.WidgetType()).(core.Widget)
Config(v, w)
return v
}
// A Value is a bridge between Go values like strings, integers, and structs
// and GUI widgets. It allows you to represent simple and complicated values
// of any kind with automatic, editable, and user-friendly widgets. The most
// common pathway for making [Value]s is [NewValue].
type Value interface {
fmt.Stringer
// AsValueData gives access to the basic data fields so that the
// interface doesn't need to provide accessors for them.
AsValueData() *ValueData
// AsWidget returns the widget associated with the value.
AsWidget() core.Widget
// AsWidgetBase returns the widget base associated with the value.
AsWidgetBase() *core.WidgetBase
// WidgetType returns the type of widget associated with this value.
WidgetType() *types.Type
// SetWidget sets the widget used to represent the value.
// It is typically only used internally in [Config].
SetWidget(w core.Widget)
// Config configures the widget to represent the value, including setting up
// the OnChange event listener to set the value when the user edits it
// (values are always set immediately when the widget is updated).
// You should typically call the global [Config] function instead of this;
// this is the method that values implement, which is called in the global
// Config helper function.
Config()
// Update updates the widget representation to reflect the current value.
Update()
// Name returns the name of the value
Name() string
// SetName sets the name of the value
SetName(name string)
// Label returns the label for the value
Label() string
// SetLabel sets the label for the value
SetLabel(label string) *ValueData
// Doc returns the documentation for the value
Doc() string
// SetDoc sets the documentation for the value
SetDoc(doc string) *ValueData
// Is checks if flag is set, using atomic, safe for concurrent access
Is(f enums.BitFlag) bool
// SetFlag sets the given flag(s) to given state
// using atomic, safe for concurrent access
SetFlag(on bool, f ...enums.BitFlag)
// SetStructValue sets the value, owner and field information for a struct field.
SetStructValue(val reflect.Value, owner any, field *reflect.StructField, viewPath string)
// SetMapKey sets the key value and owner for a map key.
SetMapKey(val reflect.Value, owner any)
// SetMapValue sets the value, owner and map key information for a map
// element -- needs pointer to Value representation of key to track
// current key value.
SetMapValue(val reflect.Value, owner any, key any, keyView Value, viewPath string)
// SetSliceValue sets the value, owner and index information for a slice element.
SetSliceValue(val reflect.Value, owner any, idx int, viewPath string)
// SetSoloValue sets the value for a singleton standalone value
// (e.g., for arg values).
SetSoloValue(val reflect.Value)
// OwnerKind returns the reflect.Kind of the owner: Struct, Map, or Slice
// (or Invalid for standalone values such as args).
OwnerKind() reflect.Kind
// IsReadOnly returns whether the value is ReadOnly, which prevents modification
// of the underlying Value. Can be flagged by container views, or
// Map owners have ReadOnly values, and fields can be marked
// as ReadOnly using a struct tag.
IsReadOnly() bool
// SetReadOnly marks this value as ReadOnly or not
SetReadOnly(ro bool)
// Val returns the reflect.Value representation for this item.
Val() reflect.Value
// SetValue assigns the given value to this item (if not ReadOnly),
// using [xreflect.SetRobust] and emitting a change event.
SetValue(val any) bool
// SendChange sends events.Change event to all listeners registered on this view.
// This is the primary notification event for all Value elements.
// It takes an optional original event to base the event on.
SendChange(orig ...events.Event)
// OnChange registers given listener function for Change events on Value.
// This is the primary notification event for all Value elements.
OnChange(fun func(e events.Event))
// SetTags sets tags for this valueview, for non-struct values, to
// influence interface for this value -- see
// https://cogentcore.org/core/wiki/Tags for valid options. Adds to
// existing tags if some are already set.
SetTags(tags map[string]string)
// SetTag sets given tag to given value for this valueview, for non-struct
// values, to influence interface for this value -- see
// https://cogentcore.org/core/wiki/Tags for valid options.
SetTag(tag, value string)
// Tag returns value for given tag -- looks first at tags set by
// SetTag(s) methods, and then at field tags if this is a field in a
// struct -- returns false if tag was not set.
Tag(tag string) (string, bool)
// AllTags returns all the tags for this Value, from struct field or set
// specifically using SetTag* methods
AllTags() map[string]string
}
// ConfigDialoger is an optional interface that [Value]s may implement to
// indicate that they have a dialog associated with them that is configured
// with the ConfigDialog method. The dialog body itself is constructed and run
// using [OpenDialog].
type ConfigDialoger interface {
// ConfigDialog adds content to the given dialog body for this value.
// The bool return is false if the value does not use this method
// (e.g., for simple menu choosers).
// The returned function is an optional closure to be called
// in the Ok case, for cases where extra logic is required.
ConfigDialog(d *core.Body) (bool, func())
}
// OpenDialoger is an optional interface that [Value]s may implement to
// indicate that they have a dialog associated with them that is created,
// configured, and run with the OpenDialog method. This method typically
// calls a separate ConfigDialog method. If the [Value] does not implement
// [OpenDialoger] but does implement [ConfigDialoger], then [OpenDialogBase]
// will be used to create and run the dialog, and [ConfigDialoger.ConfigDialog]
// will be used to configure it.
type OpenDialoger interface {
// OpenDialog opens the dialog for this Value.
// Given function closure is called for the Ok action, after value
// has been updated, if using the dialog as part of another control flow.
// Note that some cases just pop up a menu chooser, not a full dialog.
OpenDialog(ctx core.Widget, fun func())
}
// ValueBase is the base type that all [Value] objects extend. It contains both
// [ValueData] and a generically parameterized [core.Widget].
type ValueBase[W core.Widget] struct {
ValueData
// Widget is the GUI widget used to display and edit the value in the GUI.
Widget W
}
func (v *ValueBase[W]) AsWidget() core.Widget {
return v.Widget
}
func (v *ValueBase[W]) AsWidgetBase() *core.WidgetBase {
return v.Widget.AsWidget()
}
func (v *ValueBase[W]) WidgetType() *types.Type {
var w W
return w.NodeType()
}
func (v *ValueBase[W]) SetWidget(w core.Widget) {
v.Widget = w.(W)
}
// Config configures the given [core.Widget] to represent the given [Value].
func Config(v Value, w core.Widget) {
if w == v.AsWidget() {
v.Update()
return
}
v.SetWidget(w)
ConfigBase(v, w)
v.Config()
v.Update()
}
// ConfigBase does the base configuration for the given [core.Widget]
// to represent the given [Value].
func ConfigBase(v Value, w core.Widget) {
w.AsWidget().SetTooltip(v.Doc())
w.SetState(v.IsReadOnly(), states.ReadOnly) // do right away
w.Style(func(s *styles.Style) {
s.SetState(v.IsReadOnly(), states.ReadOnly) // and in style
if tv, ok := v.Tag("width"); ok {
v, err := reflectx.ToFloat32(tv)
if err == nil {
s.Min.X.Ch(v)
}
}
if tv, ok := v.Tag("max-width"); ok {
v, err := reflectx.ToFloat32(tv)
if err == nil {
if v < 0 {
s.Grow.X = 1 // support legacy
} else {
s.Max.X.Ch(v)
}
}
}
if tv, ok := v.Tag("height"); ok {
v, err := reflectx.ToFloat32(tv)
if err == nil {
s.Min.Y.Em(v)
}
}
if tv, ok := v.Tag("max-height"); ok {
v, err := reflectx.ToFloat32(tv)
if err == nil {
if v < 0 {
s.Grow.Y = 1
} else {
s.Max.Y.Em(v)
}
}
}
if tv, ok := v.Tag("grow"); ok {
v, err := reflectx.ToFloat32(tv)
if err == nil {
s.Grow.X = v
}
}
if tv, ok := v.Tag("grow-y"); ok {
v, err := reflectx.ToFloat32(tv)
if err == nil {
s.Grow.Y = v
}
}
})
}
// OpenDialog opens any applicable dialog for the given value in the
// context of the given widget. It first tries [OpenDialoger], then
// [ConfigDialoger] with [OpenDialogBase]. If both of those fail, it
// returns false. It calls the given beforeFunc before opening any dialog.
func OpenDialog(v Value, ctx core.Widget, fun, beforeFunc func()) bool {
if od, ok := v.(OpenDialoger); ok {
if beforeFunc != nil {
beforeFunc()
}
od.OpenDialog(ctx, fun)
return true
}
if cd, ok := v.(ConfigDialoger); ok {
if beforeFunc != nil {
beforeFunc()
}
OpenDialogBase(v, cd, ctx, fun)
return true
}
return false
}
// OpenDialogBase is a helper for [OpenDialog] for cases that
// do not implement [OpenDialoger] but do implement [ConfigDialoger]
// to configure the dialog contents.
func OpenDialogBase(v Value, cd ConfigDialoger, ctx core.Widget, fun func()) {
vd := v.AsValueData()
opv := reflectx.OnePointerUnderlyingValue(vd.Value)
if !opv.IsValid() {
return
}
title, _, _ := vd.GetTitle()
obj := opv.Interface()
if core.RecycleDialog(obj) {
return
}
d := core.NewBody().AddTitle(title).AddText(v.Doc())
ok, okfun := cd.ConfigDialog(d)
if !ok {
return
}
// if we don't have anything specific for ok events,
// we just register an OnClose event and skip the
// OK and Cancel buttons
if okfun == nil && fun == nil {
d.OnClose(func(e events.Event) {
v.Update()
v.SendChange()
})
} else {
// otherwise, we have to make the bottom bar
d.AddBottomBar(func(parent core.Widget) {
d.AddCancel(parent)
d.AddOK(parent).OnClick(func(e events.Event) {
if okfun != nil {
okfun()
}
v.Update()
v.SendChange()
if fun != nil {
fun()
}
})
})
}
ds := d.NewFullDialog(ctx)
if v.Is(ValueDialogNewWindow) {
ds.SetNewWindow(true)
}
ds.Run()
}
// SetValue updates the underlying value representation of the Value to the given value.
// It also sends a change event. It does nothing if the value is read-only. It returns
// whether the value was successfully set.
func (v *ValueBase[W]) SetValue(value any) bool {
if v.IsReadOnly() {
return false
}
var err error
wasSet := false
if v.Owner != nil {
switch v.OwnKind {
case reflect.Struct:
err = reflectx.SetRobust(reflectx.PointerValue(v.Value).Interface(), value)
wasSet = true
case reflect.Map:
wasSet, err = v.SetValueMap(value)
case reflect.Slice:
err = reflectx.SetRobust(reflectx.PointerValue(v.Value).Interface(), value)
}
if updtr, ok := v.Owner.(core.Updater); ok {
updtr.Update()
}
} else {
err = reflectx.SetRobust(reflectx.PointerValue(v.Value).Interface(), value)
wasSet = true
}
v.SendChange()
if err != nil {
core.ErrorSnackbar(v.Widget, err, "Error setting value")
return false
}
return wasSet
}
func (v *ValueBase[W]) SetValueMap(val any) (bool, error) {
ov := reflectx.NonPointerValue(reflect.ValueOf(v.Owner))
wasSet := false
var err error
if v.Is(ValueMapKey) {
nv := reflectx.NonPointerValue(reflect.ValueOf(val)) // new key value
kv := reflectx.NonPointerValue(v.Value)
cv := ov.MapIndex(kv) // get current value
curnv := ov.MapIndex(nv) // see if new value there already
if val != kv.Interface() && curnv.IsValid() && !curnv.IsZero() {
// actually new key and current exists
d := core.NewBody().AddTitle("Map key conflict").
AddText(fmt.Sprintf("The map key value: %v already exists in the map; are you sure you want to overwrite the current value?", val))
d.AddBottomBar(func(parent core.Widget) {
d.AddCancel(parent).SetText("Cancel change")
d.AddOK(parent).SetText("Overwrite").OnClick(func(e events.Event) {
cv := ov.MapIndex(kv) // get current value
ov.SetMapIndex(kv, reflect.Value{}) // delete old key
ov.SetMapIndex(nv, cv) // set new key to current value
v.Value = nv // update value to new key
v.SendChange()
})
})
d.RunDialog(v.Widget)
return false, nil // abort this action right now
}
ov.SetMapIndex(kv, reflect.Value{}) // delete old key
ov.SetMapIndex(nv, cv) // set new key to current value
v.Value = nv // update value to new key
wasSet = true
} else {
v.Value = reflectx.NonPointerValue(reflect.ValueOf(val))
if v.KeyView != nil {
ck := reflectx.NonPointerValue(v.KeyView.Val()) // current key value
wasSet = reflectx.SetMapRobust(ov, ck, reflect.ValueOf(val)) // todo: error
} else { // static, key not editable?
wasSet = reflectx.SetMapRobust(ov, reflectx.NonPointerValue(reflect.ValueOf(v.Key)), v.Value) // todo: error
}
// wasSet = true
}
return wasSet, err
}
// note: could have a more efficient way to represent the different owner type
// data (Key vs. Field vs. Index), instead of just having everything for
// everything. However, Value itself gets customized for different target
// value types, and those are orthogonal to the owner type, so need a separate
// ValueOwner class that encodes these options more efficiently -- but
// that introduces another struct alloc and pointer -- not clear if it is
// worth it..
// ValueData contains the base data common to all [Value] objects.
// [Value] objects should extend [ValueBase], not ValueData.
type ValueData struct {
// Nm is locally unique name of Value
Nm string
// SavedLabel is the label for the Value
SavedLabel string
// SavedDoc is the saved documentation for the Value, if any
// (only valid if [ValueHasSaveDoc] is true)
SavedDoc string
// Flags are atomic bit flags for Value state
Flags ValueFlags
// the reflect.Value representation of the value
Value reflect.Value `set:"-"`
// kind of owner that we have -- reflect.Struct, .Map, .Slice are supported
OwnKind reflect.Kind
// a record of parent View names that have led up to this view -- displayed as extra contextual information in view dialog windows
ViewPath string
// the object that owns this value, either a struct, slice, or map, if non-nil
Owner any
// if Owner is a struct, this is the reflect.StructField associated with the value
Field *reflect.StructField
// set of tags that can be set to customize interface for different types of values -- only source for non-structfield values
Tags map[string]string `set:"-"`
// if Owner is a map, and this is a value, this is the key for this value in the map
Key any `set:"-" edit:"-"`
// if Owner is a map, and this is a value, this is the [Value] representing the key -- its value has the *current* value of the key, which can be edited
KeyView Value `set:"-" edit:"-"`
// if Owner is a slice, this is the index for the value in the slice
Index int `set:"-" edit:"-"`
// Listeners are event listener functions for processing events on this widget.
// type specific Listeners are added in OnInit when the widget is initialized.
Listeners events.Listeners `set:"-" view:"-"`
}
// ValueFlags for Value bool state
type ValueFlags int64 //enums:bitflag -trim-prefix Value
const (
// ValueReadOnly flagged after first configuration
ValueReadOnly ValueFlags = iota
// ValueMapKey for OwnKind = Map, this value represents the Key -- otherwise the Value
ValueMapKey
// ValueHasSavedLabel is whether the value has a saved version of its
// label, which can be set either automatically or explicitly
ValueHasSavedLabel
// ValueHasSavedDoc is whether the value has a saved version of its
// documentation, which can be set either automatically or explicitly
ValueHasSavedDoc
// ValueDialogNewWindow indicates that the dialog should be opened with
// in a new window, instead of a typical FullWindow in same current window.
// this is triggered by holding down any modifier key while clicking on a
// button that opens the window.
ValueDialogNewWindow
)
func (v *ValueData) AsValueData() *ValueData {
return v
}
func (v *ValueData) Name() string {
return v.Nm
}
func (v *ValueData) SetName(name string) {
v.Nm = name
}
func (v *ValueData) Label() string {
if v.Is(ValueHasSavedLabel) {
return v.SavedLabel
}
lbl := ""
lbltag, has := v.Tag("label")
// whether to sentence case
sc := true
if v.Owner != nil && len(NoSentenceCaseFor) > 0 {
sc = !NoSentenceCaseForType(types.TypeNameObj(v.Owner))
}
switch {
case has:
lbl = lbltag
case v.Field != nil:
lbl = v.Field.Name
if sc {
lbl = strcase.ToSentence(lbl)
}
default:
lbl = v.Nm
if sc {
lbl = strcase.ToSentence(lbl)
}
}
v.SavedLabel = lbl
v.SetFlag(true, ValueHasSavedLabel)
return v.SavedLabel
}
func (v *ValueData) SetLabel(label string) *ValueData {
v.SavedLabel = label
v.SetFlag(true, ValueHasSavedLabel)
return v
}
func (v *ValueData) Doc() string {
if v.Is(ValueHasSavedDoc) {
return v.SavedDoc
}
doc, _ := types.GetDoc(v.Value, reflect.ValueOf(v.Owner), v.Field, v.Label())
v.SavedDoc = doc
v.SetFlag(true, ValueHasSavedDoc)
return v.SavedDoc
}
func (v *ValueData) SetDoc(doc string) *ValueData {
v.SavedDoc = doc
v.SetFlag(true, ValueHasSavedDoc)
return v
}
func (v *ValueData) String() string {
return v.Nm + ": " + v.Value.String()
}
// Is checks if flag is set, using atomic, safe for concurrent access
func (v *ValueData) Is(f enums.BitFlag) bool {
return v.Flags.HasFlag(f)
}
// SetFlag sets the given flag(s) to given state
// using atomic, safe for concurrent access
func (v *ValueData) SetFlag(on bool, f ...enums.BitFlag) {
v.Flags.SetFlag(on, f...)
}
func (v *ValueData) SetReadOnly(ro bool) {
v.SetFlag(ro, ValueReadOnly)
}
// JoinViewPath returns a view path composed of two elements,
// with a • path separator, handling the cases where either or
// both can be empty.
func JoinViewPath(a, b string) string {
switch {
case a == "" && b == "":
return ""
case a == "":
return b
case b == "":
return a
default:
return a + " • " + b
}
}
func (v *ValueData) SetStructValue(val reflect.Value, owner any, field *reflect.StructField, viewPath string) {
v.OwnKind = reflect.Struct
v.Value = val
v.Owner = owner
v.Field = field
v.ViewPath = viewPath
v.SetName(field.Name)
}
func (v *ValueData) SetMapKey(key reflect.Value, owner any) {
v.OwnKind = reflect.Map
v.SetFlag(true, ValueMapKey)
v.Value = key
v.Owner = owner
v.SetName(reflectx.ToString(key.Interface()))
}
func (v *ValueData) SetMapValue(val reflect.Value, owner any, key any, keyView Value, viewPath string) {
v.OwnKind = reflect.Map
v.Value = val
v.Owner = owner
v.Key = key
v.KeyView = keyView
keystr := reflectx.ToString(key)
v.ViewPath = JoinViewPath(viewPath, keystr)
v.SetName(keystr)
}
func (v *ValueData) SetSliceValue(val reflect.Value, owner any, idx int, viewPath string) {
v.OwnKind = reflect.Slice
v.Value = val
v.Owner = owner
v.Index = idx
idxstr := fmt.Sprintf("%v", idx)
vpath := viewPath + "[" + idxstr + "]"
if v.Owner != nil {
if lblr, ok := v.Owner.(core.SliceLabeler); ok {
slbl := lblr.ElemLabel(idx)
if slbl != "" {
vpath = JoinViewPath(viewPath, slbl)
}
}
}
v.ViewPath = vpath
v.SetName(idxstr)
}
// SetSoloValue sets the value for a singleton standalone value
// (e.g., for arg values).
func (v *ValueData) SetSoloValue(val reflect.Value) {
v.OwnKind = reflect.Invalid
// we must ensure that it is a pointer value so that it has
// an underlying value that updates when changes occur
v.Value = reflectx.PointerValue(val)
}
// OwnerKind we have this one accessor b/c it is more useful for outside consumers vs. internal usage
func (v *ValueData) OwnerKind() reflect.Kind {
return v.OwnKind
}
func (v *ValueData) IsReadOnly() bool {
if v.Is(ValueReadOnly) {
return true
}
if v.OwnKind == reflect.Struct {
if et, has := v.Tag("edit"); has && et == "-" {
v.SetReadOnly(true) // cache
return true
}
}
npv := reflectx.NonPointerValue(v.Value)
if npv.Kind() == reflect.Interface && npv.IsZero() {
v.SetReadOnly(true) // cache
return true
}
return false
}
func (v *ValueData) Val() reflect.Value {
return v.Value
}
// OnChange registers given listener function for Change events on Value.
// This is the primary notification event for all Value elements.
func (v *ValueData) OnChange(fun func(e events.Event)) {
v.On(events.Change, fun)
}
// On adds an event listener function for the given event type
func (v *ValueData) On(etype events.Types, fun func(e events.Event)) {
v.Listeners.Add(etype, fun)
}
// SendChange sends events.Change event to all listeners registered on this view.
// This is the primary notification event for all Value elements. It takes
// an optional original event to base the event on.
func (v *ValueData) SendChange(orig ...events.Event) {
v.Send(events.Change, orig...)
}
// Send sends an NEW event of given type to this widget,
// optionally starting from values in the given original event
// (recommended to include where possible).
// Do NOT send an existing event using this method if you
// want the Handled state to persist throughout the call chain;
// call HandleEvent directly for any existing events.
func (v *ValueData) Send(typ events.Types, orig ...events.Event) {
var e events.Event
if len(orig) > 0 && orig[0] != nil {
e = orig[0].Clone()
e.AsBase().Typ = typ
} else {
e = &events.Base{Typ: typ}
}
v.HandleEvent(e)
}
// HandleEvent sends the given event to all Listeners for that event type.
// It also checks if the State has changed and calls ApplyStyle if so.
// If more significant Config level changes are needed due to an event,
// the event handler must do this itself.
func (v *ValueData) HandleEvent(ev events.Event) {
if core.DebugSettings.EventTrace {
fmt.Println("Event to Value:", v.String(), ev.String())
}
v.Listeners.Call(ev)
}
func (v *ValueData) SetTags(tags map[string]string) {
if v.Tags == nil {
v.Tags = make(map[string]string, len(tags))
}
for tag, val := range tags {
v.Tags[tag] = val
}
}
func (v *ValueData) SetTag(tag, value string) {
if v.Tags == nil {
v.Tags = make(map[string]string, 10)
}
v.Tags[tag] = value
}
func (v *ValueData) Tag(tag string) (string, bool) {
if v.Tags != nil {
if tv, ok := v.Tags[tag]; ok {
return tv, ok
}
}
if !(v.Owner != nil && v.OwnKind == reflect.Struct) {
return "", false
}
return v.Field.Tag.Lookup(tag)
}
func (v *ValueData) AllTags() map[string]string {
rvt := make(map[string]string)
if v.Tags != nil {
for key, val := range v.Tags {
rvt[key] = val
}
}
if !(v.Owner != nil && v.OwnKind == reflect.Struct) {
return rvt
}
smap := reflectx.StructTags(v.Field.Tag)
for key, val := range smap {
rvt[key] = val
}
return rvt
}
// OwnerLabel returns some extra info about the owner of this Value
// which is useful to put in title of our object
func (v *ValueData) OwnerLabel() string {
if v.Owner == nil {
return ""
}
switch v.OwnKind {
case reflect.Struct:
return strcase.ToSentence(v.Field.Name)
case reflect.Map:
kystr := ""
if v.Is(ValueMapKey) {
kv := reflectx.NonPointerValue(v.Value)
kystr = reflectx.ToString(kv.Interface())
} else {
if v.KeyView != nil {
ck := reflectx.NonPointerValue(v.KeyView.Val()) // current key value
kystr = reflectx.ToString(ck.Interface())
} else {
kystr = reflectx.ToString(v.Key)
}
}
if kystr != "" {
return kystr
}
case reflect.Slice:
if lblr, ok := v.Owner.(core.SliceLabeler); ok {
slbl := lblr.ElemLabel(v.Index)
if slbl != "" {
return slbl
}
}
return strconv.Itoa(v.Index)
}
return ""
}
// GetTitle returns a title for this item suitable for a window title etc,
// based on the underlying value type name, owner label, and ViewPath.
// newPath returns just what should be added to a ViewPath
// also includes zero value check reported in the isZero bool, which
// can be used for not proceeding in case of non-value-based types.
func (v *ValueData) GetTitle() (label, newPath string, isZero bool) {
var npt reflect.Type
if v.Value.IsZero() || !reflectx.NonPointerValue(v.Value).IsValid() || reflectx.NonPointerValue(v.Value).IsZero() {
npt = reflectx.NonPointerType(v.Value.Type())
isZero = true
} else {
opv := reflectx.OnePointerUnderlyingValue(v.Value)
npt = reflectx.NonPointerType(opv.Type())
}
newPath = reflectx.FriendlyTypeName(npt)
olbl := v.OwnerLabel()
if olbl != "" && olbl != newPath {
label = olbl + " (" + newPath + ")"
} else {
label = newPath
}
if v.ViewPath != "" {
label += " (" + v.ViewPath + ")"
}
return
}
// ConfigDialogWidget configures the widget for the given value to open the dialog for
// the given value when clicked and have the appropriate tooltip for that.
// If allowReadOnly is false, the dialog will not be opened if the value
// is read only.
func ConfigDialogWidget(v Value, allowReadOnly bool) {
doc := v.Doc()
tip := ""
// windows are never new on mobile
if !core.TheApp.Platform().IsMobile() {
tip += "[Shift: new window]"
if doc != "" {
tip += " "
}
}
tip += doc
v.AsWidgetBase().SetTooltip(tip)
v.AsWidget().OnClick(func(e events.Event) {
if allowReadOnly || !v.IsReadOnly() {
v.SetFlag(e.HasAnyModifier(key.Shift), ValueDialogNewWindow)
OpenDialog(v, v.AsWidget(), nil, nil)
}
})
}