From ac3478889c250658a1dd5de8ea7471ff3e2be540 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Fri, 10 Apr 2026 15:48:57 -0700 Subject: [PATCH] Make header action cluster own menus --- internal/appui/header.go | 104 ++++++++++++++++++----------- internal/appui/header_sync_menu.go | 7 ++ 2 files changed, 72 insertions(+), 39 deletions(-) diff --git a/internal/appui/header.go b/internal/appui/header.go index d73df2c..841cd7d 100644 --- a/internal/appui/header.go +++ b/internal/appui/header.go @@ -39,68 +39,87 @@ 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, SyncInnerSpacing: gtx.Dp(unit.Dp(3))} - if !u.usesCompactViewport() { - metrics.SyncInnerSpacing = gtx.Dp(unit.Dp(4)) - } - 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, metrics.SyncPrimaryDims, metrics.SyncToggleDims = u.syncButtonGroupWithMetrics(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) - + 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 { - rowCall.Add(gtx.Ops) - return metrics.RowDims + cluster.RowCall.Add(gtx.Ops) + return cluster.Metrics.RowDims }) if u.usesCompactViewport() { - u.maybeLogHeaderBounds(newHeaderButtonBounds(image.Pt(u.frameInsetPx, u.frameInsetPx), metrics.Bounds())) + u.maybeLogHeaderBounds(newHeaderButtonBounds(image.Pt(u.frameInsetPx, u.frameInsetPx), cluster.Metrics.Bounds())) } if u.usesCompactViewport() { if u.syncMenuOpen { u.phoneSyncMenuVisible = true - u.phoneSyncMenuAnchor = metrics.SyncAnchor().Point() + u.phoneSyncMenuAnchor = cluster.Metrics.SyncAnchor().Point() } if u.mainMenuOpen { u.phoneMainMenuVisible = true - u.phoneMainMenuAnchor = metrics.MainAnchor().Point() + u.phoneMainMenuAnchor = cluster.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 cluster.ShowSyncMenu() { + surface.Draw(gtx, cluster.Metrics.SyncAnchor(), cluster.SyncMenu) } - if u.mainMenuOpen { - surface.Draw(gtx, metrics.MainAnchor(), u.mainMenu) + if cluster.ShowMainMenu() { + surface.Draw(gtx, cluster.Metrics.MainAnchor(), cluster.MainMenu) } return rowDims } +type headerActionCluster struct { + Metrics headerlayout.ActionMetrics + RowCall op.CallOp + SyncMenu layout.Widget + MainMenu layout.Widget +} + +func (c headerActionCluster) ShowSyncMenu() bool { return c.SyncMenu != nil } + +func (c headerActionCluster) ShowMainMenu() bool { return c.MainMenu != nil } + +func (u *ui) newHeaderActionCluster(gtx layout.Context) headerActionCluster { + cluster := headerActionCluster{ + SyncMenu: u.syncMenuWidget(), + MainMenu: u.mainMenuWidget(), + } + spacing := gtx.Dp(unit.Dp(8)) + cluster.Metrics = headerlayout.ActionMetrics{Spacing: spacing, SyncInnerSpacing: gtx.Dp(unit.Dp(3))} + 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) layoutRow(gtx layout.Context, u *ui) layout.Dimensions { + return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + c.Metrics.SyncDims, c.Metrics.SyncPrimaryDims, c.Metrics.SyncToggleDims = u.syncButtonGroupWithMetrics(gtx) + return c.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") + c.Metrics.LockDims = btn.Layout(gtx) + return c.Metrics.LockDims + }), + layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + c.Metrics.MainDims = u.mainMenuButtonGroup(gtx) + return c.Metrics.MainDims + }), + ) +} + type headerButtonBounds struct { SyncPrimary image.Rectangle SyncToggle image.Rectangle @@ -212,6 +231,13 @@ func (u *ui) brandImage(gtx layout.Context, src paint.ImageOp, widthDP, heightDP return img.Layout(gtx) } +func (u *ui) mainMenuWidget() layout.Widget { + if !u.mainMenuOpen { + return nil + } + return u.mainMenu +} + func (u *ui) mainMenu(gtx layout.Context) layout.Dimensions { rows := []layout.Widget{ func(gtx layout.Context) layout.Dimensions { diff --git a/internal/appui/header_sync_menu.go b/internal/appui/header_sync_menu.go index b3053f9..e289188 100644 --- a/internal/appui/header_sync_menu.go +++ b/internal/appui/header_sync_menu.go @@ -58,6 +58,13 @@ func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions { return btn.Layout(gtx) } +func (u *ui) syncMenuWidget() layout.Widget { + if !u.syncMenuOpen { + return nil + } + return u.syncMenu +} + func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions { model := u.buildSyncMenuModel() profiles := u.availableRemoteProfiles()