diff --git a/main.go b/main.go index 8cfc4d4..3815c5b 100644 --- a/main.go +++ b/main.go @@ -518,6 +518,10 @@ type ui struct { deleteGroupPath []string apiPolicyGroupScope bool apiTokenSecret string + phoneSyncMenuAnchor image.Point + phoneMainMenuAnchor image.Point + phoneSyncMenuVisible bool + phoneMainMenuVisible bool selectedAuditIndex int statusExpiresAt time.Time now func() time.Time @@ -4132,6 +4136,8 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions { // Clear the full frame explicitly so mobile surfaces don't start from an // unpainted black buffer before nested background widgets run. paint.FillShape(gtx.Ops, bgColor, clip.Rect{Max: gtx.Constraints.Max}.Op()) + u.phoneSyncMenuVisible = false + u.phoneMainMenuVisible = false u.syncHostedAPI() u.filter() u.processShortcuts(gtx) @@ -4164,9 +4170,15 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions { } for u.toggleSyncMenu.Clicked(gtx) { u.syncMenuOpen = !u.syncMenuOpen + if u.syncMenuOpen { + u.mainMenuOpen = false + } } for u.toggleMainMenu.Clicked(gtx) { u.mainMenuOpen = !u.mainMenuOpen + if u.mainMenuOpen { + u.syncMenuOpen = false + } } for u.openAdvancedSync.Clicked(gtx) { u.openAdvancedSyncDialog() @@ -4773,6 +4785,9 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions { } return u.approvalDialog(gtx) }), + layout.Stacked(func(gtx layout.Context) layout.Dimensions { + return u.phoneHeaderMenus(gtx) + }), layout.Stacked(u.statusToast), ) } @@ -5502,6 +5517,29 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions { gtx.Constraints.Min.X = gtx.Constraints.Max.X return u.headerActions(gtx) }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + if !u.syncMenuOpen && !u.mainMenuOpen { + return layout.Dimensions{} + } + return layout.Inset{Top: unit.Dp(6)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.E.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + var menu layout.Widget + if u.syncMenuOpen { + menu = u.syncMenu + } else { + menu = u.mainMenu + } + measureGTX := gtx + measureGTX.Constraints.Min = image.Point{} + macro := op.Record(gtx.Ops) + dims := menu(measureGTX) + _ = macro.Stop() + gtx.Constraints.Min.X = dims.Size.X + gtx.Constraints.Max.X = dims.Size.X + return menu(gtx) + }) + }) + }), ) } if u.shouldShowDesktopWorkingHeader() { @@ -5575,6 +5613,20 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions { rowCall.Add(gtx.Ops) rowStack.Pop() + if u.mode == "phone" { + if u.syncMenuOpen { + u.phoneSyncMenuVisible = true + u.phoneSyncMenuAnchor = image.Pt(rowOriginX+syncDims.Size.X, rowDims.Size.Y) + } + if u.mainMenuOpen { + triggerRightX := syncDims.Size.X + spacing + lockDims.Size.X + spacing + mainDims.Size.X + u.phoneMainMenuVisible = true + u.phoneMainMenuAnchor = image.Pt(rowOriginX+triggerRightX, rowDims.Size.Y) + } + width := gtx.Constraints.Max.X + return layout.Dimensions{Size: image.Pt(width, rowDims.Size.Y)} + } + if u.syncMenuOpen { drawMenu(u.syncMenu, syncDims.Size.X, rowDims.Size.Y) } @@ -5584,9 +5636,6 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions { } width := rowDims.Size.X - if u.mode == "phone" { - width = gtx.Constraints.Max.X - } return layout.Dimensions{Size: image.Pt(width, rowDims.Size.Y)} } @@ -5600,7 +5649,7 @@ func (u *ui) mainMenu(gtx layout.Context) layout.Dimensions { func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Settings") }, } rowWidth := menuActionWidth(gtx, rows) - return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { + return intrinsicCompactCard(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { return rightAlignedMenuAction(gtx, rowWidth, rows[0]) @@ -5715,8 +5764,13 @@ func (u *ui) syncButtonGroup(gtx layout.Context) layout.Dimensions { func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions { btn := material.IconButton(u.theme, &u.toggleSyncMenu, u.chevronDownIcon, "More synchronize actions") - btn.Background = color.NRGBA{R: 231, G: 236, B: 232, A: 255} - btn.Color = accentColor + if u.syncMenuOpen { + btn.Background = accentColor + btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255} + } else { + btn.Background = color.NRGBA{R: 231, G: 236, B: 232, A: 255} + btn.Color = accentColor + } btn.Size = unit.Dp(18) btn.Inset = layout.UniformInset(unit.Dp(8)) if u.mode == "phone" { @@ -5764,7 +5818,7 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions { }) } actionWidth := menuActionWidth(gtx, actionRows) - return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { + return intrinsicCompactCard(gtx, func(gtx layout.Context) layout.Dimensions { rows := []layout.FlexChild{ layout.Rigid(func(gtx layout.Context) layout.Dimensions { lbl := material.Label(u.theme, unit.Sp(11), "Need another source or direction?") @@ -5951,6 +6005,20 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions { }) } +func intrinsicCompactCard(gtx layout.Context, w layout.Widget) layout.Dimensions { + measureGTX := gtx + measureGTX.Constraints.Min = image.Point{} + macro := op.Record(gtx.Ops) + contentDims := w(measureGTX) + _ = macro.Stop() + width := contentDims.Size.X + gtx.Dp(unit.Dp(20)) + if width > 0 { + gtx.Constraints.Min.X = width + gtx.Constraints.Max.X = width + } + return compactCard(gtx, w) +} + func (u *ui) sectionSpacing() unit.Dp { if u.mode == "phone" { if u.denseLayout { @@ -7252,8 +7320,13 @@ func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions { icon = u.settingsIcon } btn := material.IconButton(u.theme, &u.toggleMainMenu, icon, "Menu") - btn.Background = selectedColor - btn.Color = accentColor + if u.mainMenuOpen { + btn.Background = accentColor + btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255} + } else { + btn.Background = selectedColor + btn.Color = accentColor + } btn.Size = unit.Dp(18) btn.Inset = layout.UniformInset(unit.Dp(8)) return btn.Layout(gtx) @@ -7261,6 +7334,42 @@ func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions { return button(gtx) } +func (u *ui) phoneHeaderMenus(gtx layout.Context) layout.Dimensions { + if u.mode != "phone" { + return layout.Dimensions{} + } + if !u.syncMenuVisibleOnPhone() && !u.mainMenuVisibleOnPhone() { + return layout.Dimensions{} + } + gtx.Constraints.Min = gtx.Constraints.Max + + drawMenu := func(anchor image.Point, menu layout.Widget) layout.Dimensions { + _ = anchor + return layout.NE.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Inset{ + Top: unit.Dp(56), + Right: unit.Dp(16), + }.Layout(gtx, menu) + }) + } + + if u.syncMenuVisibleOnPhone() { + drawMenu(u.phoneSyncMenuAnchor, u.syncMenu) + } + if u.mainMenuVisibleOnPhone() { + drawMenu(u.phoneMainMenuAnchor, u.mainMenu) + } + return layout.Dimensions{Size: gtx.Constraints.Max} +} + +func (u *ui) syncMenuVisibleOnPhone() bool { + return u.mode == "phone" && u.phoneSyncMenuVisible && u.syncMenuOpen +} + +func (u *ui) mainMenuVisibleOnPhone() bool { + return u.mode == "phone" && u.phoneMainMenuVisible && u.mainMenuOpen +} + func (u *ui) syncMenuDropsBelowTrigger() bool { return true } @@ -7510,6 +7619,9 @@ func menuActionWidth(gtx layout.Context, rows []layout.Widget) int { } func rightAlignedMenuAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions { + if width < gtx.Constraints.Max.X { + width = gtx.Constraints.Max.X + } if width <= 0 { return child(gtx) }