app.go raw
1 package gel
2
3 import (
4 "fmt"
5
6 "go.uber.org/atomic"
7 "golang.org/x/exp/shiny/materialdesign/icons"
8
9 l "github.com/p9c/gio/layout"
10 "github.com/p9c/gio/text"
11 "github.com/p9c/gio/unit"
12 )
13
14 // App defines an application with a header, sidebar/menu, right side button bar, changeable body page widget and
15 // pop-over layers
16 type App struct {
17 *Window
18 activePage *atomic.String
19 invalidate chan struct{}
20 bodyBackground string
21 bodyColor string
22 pageBackground string
23 pageColor string
24 cardBackground string
25 cardColor string
26 buttonBar []l.Widget
27 hideSideBar bool
28 hideTitleBar bool
29 layers []l.Widget
30 Logo *[]byte
31 LogoClickable *Clickable
32 menuBackground string
33 menuButton *IconButton
34 menuClickable *Clickable
35 menuColor string
36 menuIcon *[]byte
37 MenuOpen bool
38 pages WidgetMap
39 root *Stack
40 sideBar []l.Widget
41 sideBarBackground string
42 sideBarColor string
43 SideBarSize *unit.Value
44 sideBarList *List
45 Size *atomic.Int32
46 statusBar []l.Widget
47 statusBarRight []l.Widget
48 statusBarBackground string
49 statusBarColor string
50 title string
51 titleBarBackground string
52 titleBarColor string
53 titleFont string
54 mainDirection l.Direction
55 PreRendering bool
56 Break1 float32
57 }
58
59 type WidgetMap map[string]l.Widget
60
61 func (w *Window) App(size *atomic.Int32, activePage *atomic.String, Break1 float32, ) *App {
62 // mc := w.Clickable()
63 a := &App{
64 Window: w,
65 activePage: activePage,
66 bodyBackground: "DocBg",
67 bodyColor: "DocText",
68 pageBackground: "PanelBg",
69 pageColor: "PanelText",
70 cardBackground: "DocBg",
71 cardColor: "DocText",
72 buttonBar: nil,
73 hideSideBar: false,
74 hideTitleBar: false,
75 layers: nil,
76 pages: make(WidgetMap),
77 root: w.Stack(),
78 sideBarBackground: "DocBg",
79 sideBarColor: "DocText",
80 statusBarBackground: "DocBg",
81 statusBarColor: "DocText",
82 Logo: &icons.ActionSettingsApplications,
83 title: "gio elements application",
84 titleBarBackground: "Primary",
85 titleBarColor: "DocBg",
86 titleFont: "plan9",
87 menuIcon: &icons.NavigationMenu,
88 menuColor: "DocText",
89 MenuOpen: false,
90 Size: size,
91 mainDirection: l.Center + 1,
92 Break1: Break1,
93 LogoClickable: w.WidgetPool.GetClickable(),
94 sideBarList: w.WidgetPool.GetList(),
95 }
96 a.SideBarSize = &unit.Value{}
97 return a
98 }
99
100 func (a *App) SetAppTitleText(title string) *App {
101 a.title = title
102 return a
103 }
104
105 func (a *App) AppTitleText() string {
106 return a.title
107 }
108
109 func (a *App) SetLogo(logo *[]byte) *App {
110 a.Logo = logo
111 return a
112 }
113
114 func (a *App) GetLogo() string {
115 return a.title
116 }
117
118 func (a *App) SetMainDirection(direction l.Direction) *App {
119 a.mainDirection = direction
120 return a
121 }
122
123 func (a *App) MainDirection() l.Direction {
124 return a.mainDirection
125 }
126
127 // Fn renders the node widget
128 func (a *App) Fn() func(gtx l.Context) l.Dimensions {
129 return func(gtx l.Context) l.Dimensions {
130 return a.Fill(a.bodyBackground, l.Center, 0, 0,
131 a.VFlex().
132 Rigid(
133 a.RenderHeader,
134 ).
135 Flexed(
136 1,
137 a.MainFrame,
138 ).
139 Rigid(
140 a.RenderStatusBar,
141 ).
142 Fn,
143 ).
144 Fn(gtx)
145 }
146 }
147
148 func (a *App) RenderStatusBar(gtx l.Context) l.Dimensions {
149 bar := a.Flex()
150 for x := range a.statusBar {
151 i := x
152 bar.Rigid(a.statusBar[i])
153 }
154 bar.Flexed(1, EmptyMaxWidth())
155 for x := range a.statusBarRight {
156 i := x
157 bar.Rigid(a.statusBarRight[i])
158 }
159 return bar.Fn(gtx)
160 }
161
162 func (a *App) RenderHeader(gtx l.Context) l.Dimensions {
163 return a.Flex().AlignMiddle().
164 Rigid(
165 a.Theme.Responsive(
166 a.Size.Load(),
167 Widgets{
168 {Widget: If(len(a.sideBar) > 0, a.MenuButton, a.NoMenuButton)},
169 {Size: a.Break1, Widget: a.NoMenuButton},
170 },
171 ).
172 Fn,
173 ).
174 Flexed(
175 1, If(
176 float32(a.Width.Load()) >= a.TextSize.Scale(a.Break1).V,
177 a.Direction().W().Embed(a.LogoAndTitle).Fn,
178 a.Direction().Center().Embed(a.LogoAndTitle).Fn,
179 ),
180 ).
181 Rigid(
182 a.RenderButtonBar,
183 ).Fn(gtx)
184 }
185
186 func (a *App) RenderButtonBar(gtx l.Context) l.Dimensions {
187 out := a.Theme.Flex()
188 for i := range a.buttonBar {
189 out.Rigid(a.buttonBar[i])
190 }
191 dims := out.Fn(gtx)
192 gtx.Constraints.Min = dims.Size
193 gtx.Constraints.Max = dims.Size
194 return dims
195 }
196
197 func (a *App) MainFrame(gtx l.Context) l.Dimensions {
198 return a.Flex().
199 Rigid(
200 a.VFlex().
201 Flexed(
202 1,
203 a.Responsive(
204 a.Size.Load(), Widgets{
205 {
206 Widget: func(gtx l.Context) l.Dimensions {
207 return If(
208 a.MenuOpen,
209 a.renderSideBar(),
210 EmptySpace(0, 0),
211 )(gtx)
212 },
213 },
214 {
215 Size: a.Break1,
216 Widget:
217 a.renderSideBar(),
218 },
219 },
220 ).Fn,
221 ).Fn,
222 ).
223 Flexed(
224 1,
225 a.RenderPage,
226 ).
227 Fn(gtx)
228 }
229
230 func (a *App) MenuButton(gtx l.Context) l.Dimensions {
231 bg := "Transparent"
232 color := a.menuColor
233 if a.MenuOpen {
234 color = "DocText"
235 bg = a.sideBarBackground
236 }
237 return a.Theme.Flex().SpaceEvenly().AlignEnd().
238 Rigid(
239 a.ButtonLayout(a.menuClickable).
240 CornerRadius(0).
241 Embed(
242 a.Inset(
243 0.375,
244 a.Icon().
245 Scale(Scales["H5"]).
246 Color(color).
247 Src(&icons.NavigationMenu).
248 Fn,
249 ).Fn,
250 ).
251 Background(bg).
252 SetClick(
253 func() {
254 a.MenuOpen = !a.MenuOpen
255 },
256 ).
257 Fn,
258 ).Fn(gtx)
259 }
260
261 func (a *App) NoMenuButton(_ l.Context) l.Dimensions {
262 a.MenuOpen = false
263 return l.Dimensions{}
264 }
265
266 func (a *App) LogoAndTitle(gtx l.Context) l.Dimensions {
267 return a.Theme.Responsive(
268 a.Size.Load(), Widgets{
269 {
270 Widget: a.Theme.Flex().AlignMiddle().
271 Rigid(
272 a.
273 Inset(
274 0.25, a.
275 IconButton(
276 a.LogoClickable.
277 SetClick(
278 func() {
279 D.Ln("clicked logo")
280 a.Theme.Dark.Flip()
281 a.Theme.Colors.SetDarkTheme(a.Theme.Dark.True())
282 },
283 ),
284 ).
285 Icon(
286 a.Icon().
287 Scale(Scales["H6"]).
288 Color("DocText").
289 Src(a.Logo),
290 ).
291 Background("Transparent").
292 Color("DocText").
293 ButtonInset(0.25).
294 Corners(0).
295 Fn,
296 ).
297 Fn,
298 ).
299 Rigid(
300 a.H5(a.ActivePageGet()).
301 Color("DocText").Fn,
302 ).
303 Fn,
304 },
305 {
306 Size: a.Break1,
307 Widget: a.Theme.Flex().AlignMiddle().
308 Rigid(
309 a.
310 Inset(
311 0.25, a.
312 IconButton(
313 a.LogoClickable.
314 SetClick(
315 func() {
316 D.Ln("clicked logo")
317 a.Theme.Dark.Flip()
318 a.Theme.Colors.SetDarkTheme(a.Theme.Dark.True())
319 },
320 ),
321 ).
322 Icon(
323 a.Icon().
324 Scale(Scales["H6"]).
325 Color("DocText").
326 Src(a.Logo),
327 ).
328 Background("Transparent").Color("DocText").
329 ButtonInset(0.25).
330 Corners(0).
331 Fn,
332 ).
333 Fn,
334 ).
335 Rigid(
336 a.H5(a.title).Color("DocText").Fn,
337 ).
338 Fn,
339 },
340 },
341 ).Fn(gtx)
342 }
343
344 func (a *App) RenderPage(gtx l.Context) l.Dimensions {
345 return a.Fill(
346 a.pageBackground, l.Center, 0, 0, a.Inset(
347 0.25,
348 func(gtx l.Context) l.Dimensions {
349 if page, ok := a.pages[a.activePage.Load()]; !ok {
350 return a.Flex().
351 Flexed(
352 1,
353 a.VFlex().SpaceEvenly().
354 Rigid(
355 a.H1("404").
356 Alignment(text.Middle).
357 Fn,
358 ).
359 Rigid(
360 a.Body1("page "+a.activePage.Load()+" not found").
361 Alignment(text.Middle).
362 Fn,
363 ).
364 Fn,
365 ).Fn(gtx)
366 } else {
367 return page(gtx)
368 }
369 },
370 ).Fn,
371 ).Fn(gtx)
372 }
373
374 func (a *App) DimensionCaption(gtx l.Context) l.Dimensions {
375 return a.Caption(fmt.Sprintf("%dx%d", gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Fn(gtx)
376 }
377
378 func (a *App) renderSideBar() l.Widget {
379 if len(a.sideBar) > 0 {
380 le := func(gtx l.Context, index int) l.Dimensions {
381 dims := a.sideBar[index](gtx)
382 return dims
383 }
384 return func(gtx l.Context) l.Dimensions {
385 a.PreRendering = true
386 gtx1 := CopyContextDimensionsWithMaxAxis(gtx, l.Horizontal)
387 // generate the dimensions for all the list elements
388 allDims := GetDimensionList(gtx1, len(a.sideBar), le)
389 a.PreRendering = false
390 max := 0
391 for _, i := range allDims {
392 if i.Size.X > max {
393 max = i.Size.X
394 }
395 }
396 a.SideBarSize.V = float32(max)
397 gtx.Constraints.Max.X = max
398 gtx.Constraints.Min.X = max
399 out := a.VFlex().
400 Rigid(
401 a.sideBarList.
402 Length(len(a.sideBar)).
403 LeftSide(true).
404 Vertical().
405 ListElement(le).
406 Fn,
407 )
408 return out.Fn(gtx)
409 }
410 } else {
411 return EmptySpace(0, 0)
412 }
413 }
414
415 func (a *App) ActivePage(activePage string) *App {
416 a.Invalidate()
417 a.activePage.Store(activePage)
418 return a
419 }
420 func (a *App) ActivePageGet() string {
421 return a.activePage.Load()
422 }
423 func (a *App) ActivePageGetAtomic() *atomic.String {
424 return a.activePage
425 }
426
427 func (a *App) BodyBackground(bodyBackground string) *App {
428 a.bodyBackground = bodyBackground
429 return a
430 }
431 func (a *App) BodyBackgroundGet() string {
432 return a.bodyBackground
433 }
434
435 func (a *App) BodyColor(bodyColor string) *App {
436 a.bodyColor = bodyColor
437 return a
438 }
439 func (a *App) BodyColorGet() string {
440 return a.bodyColor
441 }
442
443 func (a *App) CardBackground(cardBackground string) *App {
444 a.cardBackground = cardBackground
445 return a
446 }
447 func (a *App) CardBackgroundGet() string {
448 return a.cardBackground
449 }
450
451 func (a *App) CardColor(cardColor string) *App {
452 a.cardColor = cardColor
453 return a
454 }
455 func (a *App) CardColorGet() string {
456 return a.cardColor
457 }
458
459 func (a *App) ButtonBar(bar []l.Widget) *App {
460 a.buttonBar = bar
461 return a
462 }
463 func (a *App) ButtonBarGet() (bar []l.Widget) {
464 return a.buttonBar
465 }
466
467 func (a *App) HideSideBar(hideSideBar bool) *App {
468 a.hideSideBar = hideSideBar
469 return a
470 }
471 func (a *App) HideSideBarGet() bool {
472 return a.hideSideBar
473 }
474
475 func (a *App) HideTitleBar(hideTitleBar bool) *App {
476 a.hideTitleBar = hideTitleBar
477 return a
478 }
479 func (a *App) HideTitleBarGet() bool {
480 return a.hideTitleBar
481 }
482
483 func (a *App) Layers(widgets []l.Widget) *App {
484 a.layers = widgets
485 return a
486 }
487 func (a *App) LayersGet() []l.Widget {
488 return a.layers
489 }
490
491 func (a *App) MenuBackground(menuBackground string) *App {
492 a.menuBackground = menuBackground
493 return a
494 }
495 func (a *App) MenuBackgroundGet() string {
496 return a.menuBackground
497 }
498
499 func (a *App) MenuColor(menuColor string) *App {
500 a.menuColor = menuColor
501 return a
502 }
503 func (a *App) MenuColorGet() string {
504 return a.menuColor
505 }
506
507 func (a *App) MenuIcon(menuIcon *[]byte) *App {
508 a.menuIcon = menuIcon
509 return a
510 }
511 func (a *App) MenuIconGet() *[]byte {
512 return a.menuIcon
513 }
514
515 func (a *App) Pages(widgets WidgetMap) *App {
516 a.pages = widgets
517 return a
518 }
519 func (a *App) PagesGet() WidgetMap {
520 return a.pages
521 }
522
523 func (a *App) Root(root *Stack) *App {
524 a.root = root
525 return a
526 }
527 func (a *App) RootGet() *Stack {
528 return a.root
529 }
530
531 func (a *App) SideBar(widgets []l.Widget) *App {
532 a.sideBar = widgets
533 return a
534 }
535 func (a *App) SideBarBackground(sideBarBackground string) *App {
536 a.sideBarBackground = sideBarBackground
537 return a
538 }
539 func (a *App) SideBarBackgroundGet() string {
540 return a.sideBarBackground
541 }
542
543 func (a *App) SideBarColor(sideBarColor string) *App {
544 a.sideBarColor = sideBarColor
545 return a
546 }
547 func (a *App) SideBarColorGet() string {
548 return a.sideBarColor
549 }
550
551 func (a *App) SideBarGet() []l.Widget {
552 return a.sideBar
553 }
554
555 func (a *App) StatusBar(bar, barR []l.Widget) *App {
556 a.statusBar = bar
557 a.statusBarRight = barR
558 return a
559 }
560 func (a *App) StatusBarBackground(statusBarBackground string) *App {
561 a.statusBarBackground = statusBarBackground
562 return a
563 }
564 func (a *App) StatusBarBackgroundGet() string {
565 return a.statusBarBackground
566 }
567
568 func (a *App) StatusBarColor(statusBarColor string) *App {
569 a.statusBarColor = statusBarColor
570 return a
571 }
572 func (a *App) StatusBarColorGet() string {
573 return a.statusBarColor
574 }
575
576 func (a *App) StatusBarGet() (bar []l.Widget) {
577 return a.statusBar
578 }
579 func (a *App) Title(title string) *App {
580 a.title = title
581 return a
582 }
583 func (a *App) TitleBarBackground(TitleBarBackground string) *App {
584 a.bodyBackground = TitleBarBackground
585 return a
586 }
587 func (a *App) TitleBarBackgroundGet() string {
588 return a.titleBarBackground
589 }
590
591 func (a *App) TitleBarColor(titleBarColor string) *App {
592 a.titleBarColor = titleBarColor
593 return a
594 }
595 func (a *App) TitleBarColorGet() string {
596 return a.titleBarColor
597 }
598
599 func (a *App) TitleFont(font string) *App {
600 a.titleFont = font
601 return a
602 }
603 func (a *App) TitleFontGet() string {
604 return a.titleFont
605 }
606 func (a *App) TitleGet() string {
607 return a.title
608 }
609
610 func (a *App) Placeholder(title string) func(gtx l.Context) l.Dimensions {
611 return func(gtx l.Context) l.Dimensions {
612 return a.VFlex().
613 AlignMiddle().
614 SpaceSides().
615 Rigid(
616 a.Flex().
617 Flexed(0.5, EmptyMaxWidth()).
618 Rigid(
619 a.H1(title).Fn,
620 ).
621 Flexed(0.5, EmptyMaxWidth()).
622 Fn,
623 ).
624 Fn(gtx)
625 }
626 }
627