package appui import ( "image" "gioui.org/layout" "gioui.org/op" "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" headerview "git.julianfamily.org/keepassgo/internal/appui/header" headerlayout "git.julianfamily.org/keepassgo/internal/appui/header/layout" ) func (u *ui) header(gtx layout.Context) layout.Dimensions { if u.usesCompactViewport() { if u.shouldShowLifecycleSetup() || u.isVaultLocked() { return layout.Dimensions{} } gtx.Constraints.Min.X = gtx.Constraints.Max.X return u.headerActions(gtx) } if u.shouldShowDesktopWorkingHeader() { return layout.Dimensions{} } return card(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { return u.brandMark(gtx, 196, 56) }), layout.Flexed(1, u.headerActions), ) }) } func (u *ui) headerActions(gtx layout.Context) layout.Dimensions { if u.shouldShowLifecycleSetup() || u.isVaultLocked() || u.shouldShowDesktopWorkingHeader() { return layout.Dimensions{} } spacing := gtx.Dp(unit.Dp(8)) metrics := headerlayout.ActionMetrics{Spacing: spacing} actionCluster := func(gtx layout.Context) layout.Dimensions { return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { metrics.SyncDims = u.syncButtonGroup(gtx) return metrics.SyncDims }), layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { btn := material.Button(u.theme, &u.lockVault, "Lock") metrics.LockDims = btn.Layout(gtx) return metrics.LockDims }), layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { metrics.MainDims = u.mainMenuButtonGroup(gtx) return metrics.MainDims }), ) } rowOps := op.Record(gtx.Ops) metrics.RowDims = actionCluster(gtx) rowCall := rowOps.Stop() metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X) surface := headerlayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0} rowDims := layout.E.Layout(gtx, func(gtx layout.Context) layout.Dimensions { rowCall.Add(gtx.Ops) return metrics.RowDims }) if u.usesCompactViewport() { if u.syncMenuOpen { u.phoneSyncMenuVisible = true u.phoneSyncMenuAnchor = metrics.SyncAnchor().Point() } if u.mainMenuOpen { u.phoneMainMenuVisible = true u.phoneMainMenuAnchor = metrics.MainAnchor().Point() } return layout.Dimensions{Size: image.Pt(gtx.Constraints.Max.X, rowDims.Size.Y)} } if u.syncMenuOpen { surface.Draw(gtx, metrics.SyncAnchor(), u.syncMenu) } if u.mainMenuOpen { surface.Draw(gtx, metrics.MainAnchor(), u.mainMenu) } return rowDims } func (u *ui) topRightActionOrder() []string { if u.isVaultLocked() { return nil } return []string{"Sync", "Lock", "Menu"} } func (u *ui) phoneHeaderMenus(gtx layout.Context) layout.Dimensions { if !u.usesCompactViewport() || (!u.syncMenuVisibleOnPhone() && !u.mainMenuVisibleOnPhone()) { return layout.Dimensions{} } gtx.Constraints.Min = gtx.Constraints.Max contentInsetPx := gtx.Dp(unit.Dp(16)) surface := headerlayout.DropdownSurface{ ContainerWidth: max(0, gtx.Constraints.Max.X-(contentInsetPx*2)), LeftInset: contentInsetPx, TopInset: contentInsetPx, } if u.syncMenuVisibleOnPhone() { surface.Draw(gtx, headerlayout.DropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu) } if u.mainMenuVisibleOnPhone() { surface.Draw(gtx, headerlayout.DropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, u.mainMenu) } return layout.Dimensions{Size: gtx.Constraints.Max} } func (u *ui) syncMenuVisibleOnPhone() bool { return u.usesCompactViewport() && u.phoneSyncMenuVisible && u.syncMenuOpen } func (u *ui) mainMenuVisibleOnPhone() bool { return u.usesCompactViewport() && u.phoneMainMenuVisible && u.mainMenuOpen } func (u *ui) syncMenuDropsBelowTrigger() bool { return true } func (u *ui) syncMenuRightAlignsToTrigger() bool { return true } func (u *ui) headerMenusUseOverlayModel() bool { return true } func (u *ui) mainMenuDropsBelowTrigger() bool { return true } func (u *ui) mainMenuRightAlignsToTrigger() bool { return true } func (u *ui) lifecycleBranding(gtx layout.Context) layout.Dimensions { if !u.usesCompactViewport() { return layout.Dimensions{} } return layout.Dimensions{} } func (u *ui) brandMark(gtx layout.Context, widthDP, heightDP float32) layout.Dimensions { if u.usesCompactViewport() { return u.brandImage(gtx, u.splashSquare, widthDP, heightDP) } return u.brandImage(gtx, u.logoHorizontal, widthDP, heightDP) } func (u *ui) brandImage(gtx layout.Context, src paint.ImageOp, widthDP, heightDP float32) layout.Dimensions { width := gtx.Dp(unit.Dp(widthDP)) height := gtx.Dp(unit.Dp(heightDP)) if width > gtx.Constraints.Max.X { width = gtx.Constraints.Max.X } if height > gtx.Constraints.Max.Y && gtx.Constraints.Max.Y > 0 { height = gtx.Constraints.Max.Y } img := widget.Image{ Src: src, Fit: widget.Contain, Position: layout.W, Scale: 1.0 / gtx.Metric.PxPerDp, } gtx.Constraints.Min = image.Point{} gtx.Constraints.Max = image.Pt(width, height) return img.Layout(gtx) } func (u *ui) mainMenu(gtx layout.Context) layout.Dimensions { rows := []layout.Widget{ func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.showEntries, "Entries") }, func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.showRecycle, "Recycle Bin") }, func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.showAPITokens, "API Tokens") }, func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.showAPIAudit, "API Audit") }, func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.showAbout, "About") }, func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Settings") }, } return headerview.MainMenu(gtx, u.theme, rows, compactCard) } func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions { icon := u.menuIcon if icon == nil { icon = u.settingsIcon } return headerview.MainMenuButtonGroup(gtx, u.theme, &u.toggleMainMenu, icon, u.mainMenuOpen, selectedColor, accentColor) } func intrinsicCompactCard(gtx layout.Context, w layout.Widget) layout.Dimensions { return headerlayout.IntrinsicCompactCard(gtx, w, compactCard) } func menuActionWidth(gtx layout.Context, rows []layout.Widget) int { return headerlayout.MenuActionWidth(gtx, rows) } func rightAlignedMenuAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions { return headerlayout.RightAlignedAction(gtx, width, child) }