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/key"
"gioui.org/io/pointer" "gioui.org/io/pointer"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip" "gioui.org/op/clip"
"gioui.org/op/paint" "gioui.org/op/paint"
"gioui.org/unit" "gioui.org/unit"
@@ -490,6 +491,10 @@ type ui struct {
apiTokenSecret string apiTokenSecret string
phoneSyncMenuAnchor image.Point phoneSyncMenuAnchor image.Point
phoneMainMenuAnchor image.Point phoneMainMenuAnchor image.Point
phoneSyncMenuOrigin image.Point
phoneMainMenuOrigin image.Point
phoneSyncMenuCall op.CallOp
phoneMainMenuCall op.CallOp
phoneSyncMenuVisible bool phoneSyncMenuVisible bool
phoneMainMenuVisible bool phoneMainMenuVisible bool
selectedAuditIndex int selectedAuditIndex int
+6 -1
View File
@@ -3,6 +3,7 @@ package appui
import ( import (
"errors" "errors"
"fmt" "fmt"
"image"
"path/filepath" "path/filepath"
"slices" "slices"
"strings" "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()) paint.FillShape(gtx.Ops, bgColor, clip.Rect{Max: gtx.Constraints.Max}.Op())
u.phoneSyncMenuVisible = false u.phoneSyncMenuVisible = false
u.phoneMainMenuVisible = false u.phoneMainMenuVisible = false
u.phoneSyncMenuOrigin = image.Point{}
u.phoneMainMenuOrigin = image.Point{}
u.phoneSyncMenuCall = op.CallOp{}
u.phoneMainMenuCall = op.CallOp{}
u.syncHostedAPI() u.syncHostedAPI()
u.filter() u.filter()
u.processShortcuts(gtx) u.processShortcuts(gtx)
@@ -615,7 +620,7 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
layout.Stacked(u.securityDialogOverlay), layout.Stacked(u.securityDialogOverlay),
layout.Stacked(u.remotePrefsDialogOverlay), layout.Stacked(u.remotePrefsDialogOverlay),
layout.Stacked(u.approvalDialogOverlay), layout.Stacked(u.approvalDialogOverlay),
layout.Stacked(func(gtx layout.Context) layout.Dimensions { layout.Expanded(func(gtx layout.Context) layout.Dimensions {
return u.phoneHeaderMenus(gtx) return u.phoneHeaderMenus(gtx)
}), }),
layout.Stacked(u.statusToast), layout.Stacked(u.statusToast),
+44 -25
View File
@@ -12,6 +12,7 @@ import (
"gioui.org/widget/material" "gioui.org/widget/material"
headerview "git.julianfamily.org/keepassgo/internal/appui/header" headerview "git.julianfamily.org/keepassgo/internal/appui/header"
headerlayout "git.julianfamily.org/keepassgo/internal/appui/header/layout" 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 { 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) cluster := u.newHeaderActionCluster(gtx)
surface := headerlayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0} surface := headerlayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0}
rowDims := cluster.layout(gtx, u)
rowDims := layout.E.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
cluster.RowCall.Add(gtx.Ops)
return cluster.Metrics.RowDims
})
if u.usesCompactViewport() { if u.usesCompactViewport() {
u.maybeLogHeaderBounds(newHeaderButtonBounds(image.Pt(u.frameInsetPx, u.frameInsetPx), cluster.Metrics.Bounds())) u.maybeLogHeaderBounds(newHeaderButtonBounds(image.Pt(u.frameInsetPx, u.frameInsetPx), cluster.Metrics.Bounds()))
} }
if u.usesCompactViewport() { if u.usesCompactViewport() {
compactSurface := headerlayout.DropdownSurface{
ContainerWidth: gtx.Constraints.Max.X,
LeftInset: u.frameInsetPx,
TopInset: u.frameInsetPx,
}
if u.syncMenuOpen { if u.syncMenuOpen {
u.phoneSyncMenuVisible = true u.phoneSyncMenuVisible = true
u.phoneSyncMenuAnchor = cluster.Metrics.SyncAnchor().Point() u.phoneSyncMenuAnchor = cluster.Metrics.SyncAnchor().Point()
u.maybeLogHeaderMenuToggle("sync-visible", true) 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 { if u.mainMenuOpen {
u.phoneMainMenuVisible = true u.phoneMainMenuVisible = true
u.phoneMainMenuAnchor = cluster.Metrics.MainAnchor().Point() u.phoneMainMenuAnchor = cluster.Metrics.MainAnchor().Point()
u.maybeLogHeaderMenuToggle("main-visible", true) 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)} 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 { type headerActionCluster struct {
Metrics headerlayout.ActionMetrics Metrics headerlayout.ActionMetrics
RowCall op.CallOp
SyncMenu layout.Widget SyncMenu layout.Widget
MainMenu layout.Widget MainMenu layout.Widget
} }
@@ -103,13 +112,20 @@ func (u *ui) newHeaderActionCluster(gtx layout.Context) headerActionCluster {
if !u.usesCompactViewport() { if !u.usesCompactViewport() {
cluster.Metrics.SyncInnerSpacing = gtx.Dp(unit.Dp(4)) 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 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 { func (c *headerActionCluster) layoutRow(gtx layout.Context, u *ui) layout.Dimensions {
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions { 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 { 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()) { if !u.usesCompactViewport() || (!u.syncMenuVisibleOnPhone() && !u.mainMenuVisibleOnPhone()) {
return layout.Dimensions{} 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() { if u.syncMenuVisibleOnPhone() {
placement, menuCall := surface.Place(gtx, headerlayout.DropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu) stack := op.Offset(u.phoneSyncMenuOrigin).Push(gtx.Ops)
u.maybeLogHeaderMenuPlacement("sync-phone", surface, placement) u.phoneSyncMenuCall.Add(gtx.Ops)
stack := op.Offset(placement.Origin).Push(gtx.Ops)
menuCall.Add(gtx.Ops)
stack.Pop() stack.Pop()
} }
if u.mainMenuVisibleOnPhone() { if u.mainMenuVisibleOnPhone() {
placement, menuCall := surface.Place(gtx, headerlayout.DropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, u.mainMenu) stack := op.Offset(u.phoneMainMenuOrigin).Push(gtx.Ops)
u.maybeLogHeaderMenuPlacement("main-phone", surface, placement) u.phoneMainMenuCall.Add(gtx.Ops)
stack := op.Offset(placement.Origin).Push(gtx.Ops)
menuCall.Add(gtx.Ops)
stack.Pop() stack.Pop()
} }
return layout.Dimensions{Size: gtx.Constraints.Max} return layout.Dimensions{Size: gtx.Constraints.Max}