config.go raw
1 package cfg
2
3 import (
4 "sort"
5
6 "golang.org/x/exp/shiny/materialdesign/icons"
7
8 "github.com/p9c/p9/pkg/gel/gio/text"
9
10 l "github.com/p9c/p9/pkg/gel/gio/layout"
11
12 "github.com/p9c/p9/pkg/gel"
13 )
14
15 type Item struct {
16 slug string
17 typ string
18 label string
19 description string
20 widget string
21 dataType string
22 options []string
23 Slot interface{}
24 }
25
26 func (it *Item) Item(ng *Config) l.Widget {
27 return func(gtx l.Context) l.Dimensions {
28 return ng.Theme.VFlex().Rigid(
29 ng.H6(it.label).Fn,
30 ).Fn(gtx)
31 }
32 }
33
34 type ItemMap map[string]*Item
35
36 type GroupsMap map[string]ItemMap
37
38 type ListItem struct {
39 name string
40 widget func() []l.Widget
41 }
42
43 type ListItems []ListItem
44
45 func (l ListItems) Len() int {
46 return len(l)
47 }
48
49 func (l ListItems) Less(i, j int) bool {
50 return l[i].name < l[j].name
51 }
52
53 func (l ListItems) Swap(i, j int) {
54 l[i], l[j] = l[j], l[i]
55 }
56
57 type List struct {
58 name string
59 items ListItems
60 }
61
62 type Lists []List
63
64 func (l Lists) Len() int {
65 return len(l)
66 }
67
68 func (l Lists) Less(i, j int) bool {
69 return l[i].name < l[j].name
70 }
71
72 func (l Lists) Swap(i, j int) {
73 l[i], l[j] = l[j], l[i]
74 }
75
76 func (c *Config) Config() GroupsMap {
77 // schema := podcfg.GetConfigSchema(c.cx.Config)
78 tabNames := make(GroupsMap)
79 // // tabs := make(p9.WidgetMap)
80 // for i := range schema.Groups {
81 // for j := range schema.Groups[i].Fields {
82 // sgf := schema.Groups[i].Fields[j]
83 // if _, ok := tabNames[sgf.Group]; !ok {
84 // tabNames[sgf.Group] = make(ItemMap)
85 // }
86 // tabNames[sgf.Group][sgf.Slug] = &Item{
87 // slug: sgf.Slug,
88 // typ: sgf.Type,
89 // label: sgf.Label,
90 // description: sgf.Title,
91 // widget: sgf.Widget,
92 // dataType: sgf.Datatype,
93 // options: sgf.Options,
94 // Slot: c.cx.ConfigMap[sgf.Slug],
95 // }
96 // // D.S(sgf)
97 // // create all the necessary widgets required before display
98 // tgs := tabNames[sgf.Group][sgf.Slug]
99 // switch sgf.Widget {
100 // case "toggle":
101 // c.Bools[sgf.Slug] = c.Bool(*tgs.Slot.(*bool)).SetOnChange(
102 // func(b bool) {
103 // D.Ln(sgf.Slug, "submitted", b)
104 // bb := c.cx.ConfigMap[sgf.Slug].(*bool)
105 // *bb = b
106 // podcfg.Save(c.cx.Config)
107 // if sgf.Slug == "DarkTheme" {
108 // c.Theme.Colors.SetTheme(b)
109 // }
110 // },
111 // )
112 // case "integer":
113 // c.inputs[sgf.Slug] = c.Input(
114 // fmt.Sprint(*tgs.Slot.(*int)), sgf.Slug, "DocText", "DocBg", "PanelBg", func(txt string) {
115 // D.Ln(sgf.Slug, "submitted", txt)
116 // i := c.cx.ConfigMap[sgf.Slug].(*int)
117 // if n, e := strconv.Atoi(txt); !E.Chk(e) {
118 // *i = n
119 // }
120 // podcfg.Save(c.cx.Config)
121 // }, nil,
122 // )
123 // case "time":
124 // c.inputs[sgf.Slug] = c.Input(
125 // fmt.Sprint(
126 // *tgs.Slot.(*time.
127 // Duration),
128 // ), sgf.Slug, "DocText", "DocBg", "PanelBg", func(txt string) {
129 // D.Ln(sgf.Slug, "submitted", txt)
130 // tt := c.cx.ConfigMap[sgf.Slug].(*time.Duration)
131 // if d, e := time.ParseDuration(txt); !E.Chk(e) {
132 // *tt = d
133 // }
134 // podcfg.Save(c.cx.Config)
135 // }, nil,
136 // )
137 // case "float":
138 // c.inputs[sgf.Slug] = c.Input(
139 // strconv.FormatFloat(
140 // *tgs.Slot.(
141 // *float64), 'f', -1, 64,
142 // ), sgf.Slug, "DocText", "DocBg", "PanelBg", func(txt string) {
143 // D.Ln(sgf.Slug, "submitted", txt)
144 // ff := c.cx.ConfigMap[sgf.Slug].(*float64)
145 // if f, e := strconv.ParseFloat(txt, 64); !E.Chk(e) {
146 // *ff = f
147 // }
148 // podcfg.Save(c.cx.Config)
149 // }, nil,
150 // )
151 // case "string":
152 // c.inputs[sgf.Slug] = c.Input(
153 // *tgs.Slot.(*string), sgf.Slug, "DocText", "DocBg", "PanelBg", func(txt string) {
154 // D.Ln(sgf.Slug, "submitted", txt)
155 // ss := c.cx.ConfigMap[sgf.Slug].(*string)
156 // *ss = txt
157 // podcfg.Save(c.cx.Config)
158 // }, nil,
159 // )
160 // case "password":
161 // c.passwords[sgf.Slug] = c.Password(
162 // "password",
163 // tgs.Slot.(*string), "DocText", "DocBg", "PanelBg",
164 // func(txt string) {
165 // D.Ln(sgf.Slug, "submitted", txt)
166 // pp := c.cx.ConfigMap[sgf.Slug].(*string)
167 // *pp = txt
168 // podcfg.Save(c.cx.Config)
169 // },
170 // )
171 // case "multi":
172 // c.multis[sgf.Slug] = c.Multiline(
173 // tgs.Slot.(*cli.StringSlice), "DocText", "DocBg", "PanelBg", 30, func(txt []string) {
174 // D.Ln(sgf.Slug, "submitted", txt)
175 // sss := c.cx.ConfigMap[sgf.Slug].(*cli.StringSlice)
176 // *sss = txt
177 // podcfg.Save(c.cx.Config)
178 // },
179 // )
180 // // c.multis[sgf.Slug]
181 // case "radio":
182 // c.checkables[sgf.Slug] = c.Checkable()
183 // for i := range sgf.Options {
184 // c.checkables[sgf.Slug+sgf.Options[i]] = c.Checkable()
185 // }
186 // txt := *tabNames[sgf.Group][sgf.Slug].Slot.(*string)
187 // c.enums[sgf.Slug] = c.Enum().SetValue(txt).SetOnChange(
188 // func(value string) {
189 // rr := c.cx.ConfigMap[sgf.Slug].(*string)
190 // *rr = value
191 // podcfg.Save(c.cx.Config)
192 // },
193 // )
194 // c.lists[sgf.Slug] = c.List()
195 // }
196 // }
197 // }
198
199 // D.S(tabNames)
200 return tabNames // .Widget(c)
201 // return func(gtx l.Context) l.Dimensions {
202 // return l.Dimensions{}
203 // }
204 }
205
206 func (gm GroupsMap) Widget(ng *Config) l.Widget {
207 // _, file, line, _ := runtime.Caller(2)
208 // D.F("%s:%d", file, line)
209 var groups Lists
210 for i := range gm {
211 var li ListItems
212 gmi := gm[i]
213 for j := range gmi {
214 gmij := gmi[j]
215 li = append(
216 li, ListItem{
217 name: j,
218 widget: func() []l.Widget {
219 return ng.RenderConfigItem(gmij, len(li))
220 },
221 // },
222 },
223 )
224 }
225 sort.Sort(li)
226 groups = append(groups, List{name: i, items: li})
227 }
228 sort.Sort(groups)
229 var out []l.Widget
230 first := true
231 for i := range groups {
232 // D.Ln(groups[i].name)
233 g := groups[i]
234 if !first {
235 // put a space between the sections
236 out = append(
237 out, func(gtx l.Context) l.Dimensions {
238 dims := ng.VFlex().
239 // Rigid(
240 // // ng.Inset(0.25,
241 // ng.Fill("DocBg", l.Center, ng.TextSize.True, l.S, ng.Inset(0.25,
242 // gel.EmptyMaxWidth()).Fn,
243 // ).Fn,
244 // // ).Fn,
245 // ).
246 Rigid(ng.Inset(0.25, gel.EmptyMaxWidth()).Fn).
247 // Rigid(
248 // // ng.Inset(0.25,
249 // ng.Fill("DocBg", l.Center, ng.TextSize.True, l.N, ng.Inset(0.25,
250 // gel.EmptyMaxWidth()).Fn,
251 // ).Fn,
252 // // ).Fn,
253 // ).
254 Fn(gtx)
255 // ng.Fill("PanelBg", gel.EmptySpace(gtx.Constraints.Max.X, gtx.Constraints.Max.Y), l.Center, 0).Fn(gtx)
256 return dims
257 },
258 )
259 // out = append(out, func(gtx l.Context) l.Dimensions {
260 // return ng.th.ButtonInset(0.25, p9.EmptySpace(0, 0)).Fn(gtx)
261 // })
262 } else {
263 first = false
264 }
265 // put in the header
266 out = append(
267 out,
268 ng.Fill(
269 "Primary", l.Center, ng.TextSize.V*2, 0, ng.Flex().Flexed(
270 1,
271 ng.Inset(
272 0.75,
273 ng.H3(g.name).
274 Color("DocText").
275 Alignment(text.Start).
276 Fn,
277 ).Fn,
278 ).Fn,
279 ).Fn,
280 )
281 // out = append(out, func(gtx l.Context) l.Dimensions {
282 // return ng.th.Fill("PanelBg",
283 // ng.th.ButtonInset(0.25,
284 // ng.th.Flex().Flexed(1,
285 // p9.EmptyMaxWidth(),
286 // ).Fn,
287 // ).Fn,
288 // ).Fn(gtx)
289 // })
290 // add the widgets
291 for j := range groups[i].items {
292 gi := groups[i].items[j]
293 for x := range gi.widget() {
294 k := x
295 out = append(
296 out, func(gtx l.Context) l.Dimensions {
297 if k < len(gi.widget()) {
298 return ng.Fill(
299 "DocBg", l.Center, ng.TextSize.V, 0, ng.Flex().
300 // Rigid(
301 // ng.Inset(0.25, gel.EmptySpace(0, 0)).Fn,
302 // ).
303 Rigid(
304 ng.Inset(
305 0.25,
306 gi.widget()[k],
307 ).Fn,
308 ).Fn,
309 ).Fn(gtx)
310 }
311 return l.Dimensions{}
312 },
313 )
314 }
315 }
316 }
317 le := func(gtx l.Context, index int) l.Dimensions {
318 return out[index](gtx)
319 }
320 return func(gtx l.Context) l.Dimensions {
321 // clip.UniformRRect(f32.Rectangle{
322 // Max: f32.Pt(float32(gtx.Constraints.Max.X), float32(gtx.Constraints.Max.Y)),
323 // }, ng.TextSize.True/2).Add(gtx.Ops)
324 return ng.Fill(
325 "DocBg", l.Center, ng.TextSize.V, 0, ng.Inset(
326 0.25,
327 ng.lists["settings"].
328 Vertical().
329 Length(len(out)).
330 // Background("PanelBg").
331 // Color("DocBg").
332 // Active("Primary").
333 ListElement(le).
334 Fn,
335 ).Fn,
336 ).Fn(gtx)
337 }
338 }
339
340 // RenderConfigItem renders a config item. It takes a position variable which tells it which index it begins on
341 // the bigger config widget list, with this and its current data set the multi can insert and delete elements above
342 // its add button without rerendering the config item or worse, the whole config widget
343 func (c *Config) RenderConfigItem(item *Item, position int) []l.Widget {
344 switch item.widget {
345 case "toggle":
346 return c.RenderToggle(item)
347 case "integer":
348 return c.RenderInteger(item)
349 case "time":
350 return c.RenderTime(item)
351 case "float":
352 return c.RenderFloat(item)
353 case "string":
354 return c.RenderString(item)
355 case "password":
356 return c.RenderPassword(item)
357 case "multi":
358 return c.RenderMulti(item, position)
359 case "radio":
360 return c.RenderRadio(item)
361 }
362 D.Ln("fallthrough", item.widget)
363 return []l.Widget{func(l.Context) l.Dimensions { return l.Dimensions{} }}
364 }
365
366 func (c *Config) RenderToggle(item *Item) []l.Widget {
367 return []l.Widget{
368 func(gtx l.Context) l.Dimensions {
369 return c.Inset(
370 0.25,
371 c.Flex().
372 Rigid(
373 c.Switch(c.Bools[item.slug]).DisabledColor("Light").Fn,
374 ).
375 Flexed(
376 1,
377 c.VFlex().
378 Rigid(
379 c.Body1(item.label).Fn,
380 ).
381 Rigid(
382 c.Caption(item.description).Fn,
383 ).
384 Fn,
385 ).Fn,
386 ).Fn(gtx)
387 },
388 }
389 }
390
391 func (c *Config) RenderInteger(item *Item) []l.Widget {
392 return []l.Widget{
393 func(gtx l.Context) l.Dimensions {
394 return c.Inset(
395 0.25,
396 c.Flex().Flexed(
397 1,
398 c.VFlex().
399 Rigid(
400 c.Body1(item.label).Fn,
401 ).
402 Rigid(
403 c.inputs[item.slug].Fn,
404 ).
405 Rigid(
406 c.Caption(item.description).Fn,
407 ).
408 Fn,
409 ).Fn,
410 ).Fn(gtx)
411 },
412 }
413 }
414
415 func (c *Config) RenderTime(item *Item) []l.Widget {
416 return []l.Widget{
417 func(gtx l.Context) l.Dimensions {
418 return c.Inset(
419 0.25,
420 c.Flex().Flexed(
421 1,
422 c.VFlex().
423 Rigid(
424 c.Body1(item.label).Fn,
425 ).
426 Rigid(
427 c.inputs[item.slug].Fn,
428 ).
429 Rigid(
430 c.Caption(item.description).Fn,
431 ).
432 Fn,
433 ).Fn,
434 ).
435 Fn(gtx)
436 },
437 }
438 }
439
440 func (c *Config) RenderFloat(item *Item) []l.Widget {
441 return []l.Widget{
442 func(gtx l.Context) l.Dimensions {
443 return c.Inset(
444 0.25,
445 c.Flex().Flexed(
446 1,
447 c.VFlex().
448 Rigid(
449 c.Body1(item.label).Fn,
450 ).
451 Rigid(
452 c.inputs[item.slug].Fn,
453 ).
454 Rigid(
455 c.Caption(item.description).Fn,
456 ).
457 Fn,
458 ).Fn,
459 ).
460 Fn(gtx)
461 },
462 }
463 }
464
465 func (c *Config) RenderString(item *Item) []l.Widget {
466 return []l.Widget{
467 c.Inset(
468 0.25,
469 c.Flex().Flexed(
470 1,
471 c.VFlex().
472 Rigid(
473 c.Body1(item.label).Fn,
474 ).
475 Rigid(
476 c.inputs[item.slug].Fn,
477 ).
478 Rigid(
479 c.Caption(item.description).Fn,
480 ).
481 Fn,
482 ).Fn,
483 ).
484 Fn,
485 }
486 }
487
488 func (c *Config) RenderPassword(item *Item) []l.Widget {
489 return []l.Widget{
490 c.Inset(
491 0.25,
492 c.Flex().Flexed(
493 1,
494 c.VFlex().
495 Rigid(
496 c.Body1(item.label).Fn,
497 ).
498 Rigid(
499 c.passwords[item.slug].Fn,
500 ).
501 Rigid(
502 c.Caption(item.description).Fn,
503 ).
504 Fn,
505 ).Fn,
506 ).
507 Fn,
508 }
509 }
510
511 func (c *Config) RenderMulti(item *Item, position int) []l.Widget {
512 // D.Ln("rendering multi")
513 // c.multis[item.slug].
514 w := []l.Widget{
515 func(gtx l.Context) l.Dimensions {
516 return c.Inset(
517 0.25,
518 c.Flex().Flexed(
519 1,
520 c.VFlex().
521 Rigid(
522 c.Body1(item.label).Fn,
523 ).
524 Rigid(
525 c.Caption(item.description).Fn,
526 ).Fn,
527 ).Fn,
528 ).
529 Fn(gtx)
530 },
531 }
532 widgets := c.multis[item.slug].Widgets()
533 // D.Ln(widgets)
534 w = append(w, widgets...)
535 return w
536 }
537
538 func (c *Config) RenderRadio(item *Item) []l.Widget {
539 out := func(gtx l.Context) l.Dimensions {
540 var options []l.Widget
541 for i := range item.options {
542 var color string
543 color = "PanelBg"
544 if c.enums[item.slug].Value() == item.options[i] {
545 color = "Primary"
546 }
547 options = append(
548 options,
549 c.RadioButton(
550 c.checkables[item.slug+item.options[i]].
551 Color("DocText").
552 IconColor(color).
553 CheckedStateIcon(&icons.ToggleRadioButtonChecked).
554 UncheckedStateIcon(&icons.ToggleRadioButtonUnchecked),
555 c.enums[item.slug], item.options[i], item.options[i],
556 ).Fn,
557 )
558 }
559 return c.Inset(
560 0.25,
561 c.VFlex().
562 Rigid(
563 c.Body1(item.label).Fn,
564 ).
565 Rigid(
566 c.Flex().
567 Rigid(
568 func(gtx l.Context) l.Dimensions {
569 gtx.Constraints.Max.X = int(c.Theme.TextSize.Scale(10).V)
570 return c.lists[item.slug].DisableScroll(true).Slice(gtx, options...)(gtx)
571 // // return c.lists[item.slug].Length(len(options)).Vertical().ListElement(func(gtx l.Context, index int) l.Dimensions {
572 // // return options[index](gtx)
573 // // }).Fn(gtx)
574 // return c.lists[item.slug].Slice(gtx, options...)(gtx)
575 // // return l.Dimensions{}
576 },
577 ).
578 Flexed(
579 1,
580 c.Caption(item.description).Fn,
581 ).
582 Fn,
583 ).Fn,
584 ).
585 Fn(gtx)
586 }
587 return []l.Widget{out}
588 }
589