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