package appui import ( "image" "gioui.org/layout" "gioui.org/op" "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" 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.Flexed(1, func(gtx layout.Context) layout.Dimensions { return u.brandMark(gtx, 196, 56) }), layout.Rigid(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} row := 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 = row(gtx) rowCall := rowOps.Stop() if u.usesCompactViewport() { metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X) } surface := headerlayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0} rowStack := op.Offset(image.Pt(metrics.RowOriginX, 0)).Push(gtx.Ops) rowCall.Add(gtx.Ops) rowStack.Pop() 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, metrics.RowDims.Size.Y)} } if u.syncMenuOpen { surface.Draw(gtx, metrics.SyncAnchor(), u.syncMenu) } if u.mainMenuOpen { surface.Draw(gtx, metrics.MainAnchor(), u.mainMenu) } return layout.Dimensions{Size: image.Pt(metrics.RowDims.Size.X, metrics.RowDims.Size.Y)} } 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) }