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