From 54f13d352c1d6533c26179a1c9a81ff8aa8492c8 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Fri, 10 Apr 2026 18:55:02 -0700 Subject: [PATCH] Fix compact header overlay ordering --- internal/appui/app.go | 5 +++ internal/appui/frame.go | 7 +++- internal/appui/header.go | 69 +++++++++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/internal/appui/app.go b/internal/appui/app.go index ca59572..4af599d 100644 --- a/internal/appui/app.go +++ b/internal/appui/app.go @@ -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 diff --git a/internal/appui/frame.go b/internal/appui/frame.go index 2851cf4..e311f2f 100644 --- a/internal/appui/frame.go +++ b/internal/appui/frame.go @@ -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), diff --git a/internal/appui/header.go b/internal/appui/header.go index 7d8c230..ebd7062 100644 --- a/internal/appui/header.go +++ b/internal/appui/header.go @@ -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}