Fix compact header overlay ordering

This commit is contained in:
Joe Julian
2026-04-10 18:55:02 -07:00
parent 550d9f362c
commit 54f13d352c
3 changed files with 55 additions and 26 deletions
+5
View File
@@ -15,6 +15,7 @@ import (
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
@@ -490,6 +491,10 @@ type ui struct {
apiTokenSecret string
phoneSyncMenuAnchor image.Point
phoneMainMenuAnchor image.Point
phoneSyncMenuOrigin image.Point
phoneMainMenuOrigin image.Point
phoneSyncMenuCall op.CallOp
phoneMainMenuCall op.CallOp
phoneSyncMenuVisible bool
phoneMainMenuVisible bool
selectedAuditIndex int
+6 -1
View File
@@ -3,6 +3,7 @@ package appui
import (
"errors"
"fmt"
"image"
"path/filepath"
"slices"
"strings"
@@ -590,6 +591,10 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
paint.FillShape(gtx.Ops, bgColor, clip.Rect{Max: gtx.Constraints.Max}.Op())
u.phoneSyncMenuVisible = false
u.phoneMainMenuVisible = false
u.phoneSyncMenuOrigin = image.Point{}
u.phoneMainMenuOrigin = image.Point{}
u.phoneSyncMenuCall = op.CallOp{}
u.phoneMainMenuCall = op.CallOp{}
u.syncHostedAPI()
u.filter()
u.processShortcuts(gtx)
@@ -615,7 +620,7 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
layout.Stacked(u.securityDialogOverlay),
layout.Stacked(u.remotePrefsDialogOverlay),
layout.Stacked(u.approvalDialogOverlay),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
return u.phoneHeaderMenus(gtx)
}),
layout.Stacked(u.statusToast),
+44 -25
View File
@@ -12,6 +12,7 @@ import (
"gioui.org/widget/material"
headerview "git.julianfamily.org/keepassgo/internal/appui/header"
headerlayout "git.julianfamily.org/keepassgo/internal/appui/header/layout"
"git.julianfamily.org/keepassgo/internal/appui/platform"
)
func (u *ui) header(gtx layout.Context) layout.Dimensions {
@@ -41,25 +42,34 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
}
cluster := u.newHeaderActionCluster(gtx)
surface := headerlayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0}
rowDims := layout.E.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
cluster.RowCall.Add(gtx.Ops)
return cluster.Metrics.RowDims
})
rowDims := cluster.layout(gtx, u)
if u.usesCompactViewport() {
u.maybeLogHeaderBounds(newHeaderButtonBounds(image.Pt(u.frameInsetPx, u.frameInsetPx), cluster.Metrics.Bounds()))
}
if u.usesCompactViewport() {
compactSurface := headerlayout.DropdownSurface{
ContainerWidth: gtx.Constraints.Max.X,
LeftInset: u.frameInsetPx,
TopInset: u.frameInsetPx,
}
if u.syncMenuOpen {
u.phoneSyncMenuVisible = true
u.phoneSyncMenuAnchor = cluster.Metrics.SyncAnchor().Point()
u.maybeLogHeaderMenuToggle("sync-visible", true)
placement, menuCall := compactSurface.Place(gtx, cluster.Metrics.SyncAnchor(), u.syncMenu)
u.phoneSyncMenuOrigin = placement.Origin
u.phoneSyncMenuCall = menuCall
u.maybeLogHeaderMenuPlacement("sync-phone", compactSurface, placement)
}
if u.mainMenuOpen {
u.phoneMainMenuVisible = true
u.phoneMainMenuAnchor = cluster.Metrics.MainAnchor().Point()
u.maybeLogHeaderMenuToggle("main-visible", true)
placement, menuCall := compactSurface.Place(gtx, cluster.Metrics.MainAnchor(), u.mainMenu)
u.phoneMainMenuOrigin = placement.Origin
u.phoneMainMenuCall = menuCall
u.maybeLogHeaderMenuPlacement("main-phone", compactSurface, placement)
}
return layout.Dimensions{Size: image.Pt(gtx.Constraints.Max.X, rowDims.Size.Y)}
}
@@ -84,7 +94,6 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
type headerActionCluster struct {
Metrics headerlayout.ActionMetrics
RowCall op.CallOp
SyncMenu layout.Widget
MainMenu layout.Widget
}
@@ -103,13 +112,20 @@ func (u *ui) newHeaderActionCluster(gtx layout.Context) headerActionCluster {
if !u.usesCompactViewport() {
cluster.Metrics.SyncInnerSpacing = gtx.Dp(unit.Dp(4))
}
rowOps := op.Record(gtx.Ops)
cluster.Metrics.RowDims = cluster.layoutRow(gtx, u)
cluster.RowCall = rowOps.Stop()
cluster.Metrics.RowOriginX = max(0, gtx.Constraints.Max.X-cluster.Metrics.RowDims.Size.X)
return cluster
}
func (c *headerActionCluster) layout(gtx layout.Context, u *ui) layout.Dimensions {
rowOps := op.Record(gtx.Ops)
c.Metrics.RowDims = c.layoutRow(gtx, u)
rowCall := rowOps.Stop()
c.Metrics.RowOriginX = max(0, gtx.Constraints.Max.X-c.Metrics.RowDims.Size.X)
return layout.E.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
rowCall.Add(gtx.Ops)
return c.Metrics.RowDims
})
}
func (c *headerActionCluster) layoutRow(gtx layout.Context, u *ui) layout.Dimensions {
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
@@ -169,29 +185,32 @@ func (u *ui) topRightActionOrder() []string {
}
func (u *ui) phoneHeaderMenus(gtx layout.Context) layout.Dimensions {
if u.debugLogHeaderBounds {
platform.LogInfo("KeePassGO", fmt.Sprintf(
"keepassgo phone-header-menus compact=%t syncVisible=%t syncOpen=%t mainVisible=%t mainOpen=%t syncCall=%t mainCall=%t max=%dx%d",
u.usesCompactViewport(),
u.phoneSyncMenuVisible,
u.syncMenuOpen,
u.phoneMainMenuVisible,
u.mainMenuOpen,
u.phoneSyncMenuCall != (op.CallOp{}),
u.phoneMainMenuCall != (op.CallOp{}),
gtx.Constraints.Max.X,
gtx.Constraints.Max.Y,
))
}
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() {
placement, menuCall := surface.Place(gtx, headerlayout.DropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu)
u.maybeLogHeaderMenuPlacement("sync-phone", surface, placement)
stack := op.Offset(placement.Origin).Push(gtx.Ops)
menuCall.Add(gtx.Ops)
stack := op.Offset(u.phoneSyncMenuOrigin).Push(gtx.Ops)
u.phoneSyncMenuCall.Add(gtx.Ops)
stack.Pop()
}
if u.mainMenuVisibleOnPhone() {
placement, menuCall := surface.Place(gtx, headerlayout.DropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, u.mainMenu)
u.maybeLogHeaderMenuPlacement("main-phone", surface, placement)
stack := op.Offset(placement.Origin).Push(gtx.Ops)
menuCall.Add(gtx.Ops)
stack := op.Offset(u.phoneMainMenuOrigin).Push(gtx.Ops)
u.phoneMainMenuCall.Add(gtx.Ops)
stack.Pop()
}
return layout.Dimensions{Size: gtx.Constraints.Max}