Unify action menus and collapse empty detail pane
This commit is contained in:
+12
-33
@@ -1850,8 +1850,17 @@ func (u *ui) navigationHeaderLabel() string {
|
||||
|
||||
func (u *ui) entryRow(gtx layout.Context, click *widget.Clickable, idx int, item entry) layout.Dimensions {
|
||||
for click.Clicked(gtx) {
|
||||
_ = u.state.ToggleVisibleIndex(idx)
|
||||
if !u.shouldShowDetailPane() {
|
||||
if idx >= 0 && idx < len(u.visible) {
|
||||
u.state.SelectedEntryID = u.visible[idx].ID
|
||||
}
|
||||
} else {
|
||||
_ = u.state.ToggleVisibleIndex(idx)
|
||||
}
|
||||
u.loadSelectedEntryIntoEditor()
|
||||
if u.invalidate != nil {
|
||||
u.invalidate()
|
||||
}
|
||||
}
|
||||
return click.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
selected := item.ID == u.state.SelectedEntryID
|
||||
@@ -2014,21 +2023,7 @@ func (u *ui) detailPanel(gtx layout.Context) layout.Dimensions {
|
||||
return panel(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
if u.shouldShowDesktopWorkingHeader() {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Alignment: layout.Middle, Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Dimensions{}
|
||||
}),
|
||||
layout.Rigid(u.syncButtonGroup),
|
||||
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")
|
||||
return btn.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(u.mainMenuButtonGroup),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(u.headerActions),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
return u.detailPanelContent(gtx)
|
||||
@@ -2049,7 +2044,7 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
||||
panel := u.staticDetailPanel()
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, panel...)
|
||||
case detaillayout.ModeEmpty:
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, u.emptyDetailChildren()...)
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
item, ok := u.selectedEntry()
|
||||
if mode == detaillayout.ModeEditor {
|
||||
@@ -2089,22 +2084,6 @@ func (u *ui) staticDetailPanel() []layout.FlexChild {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *ui) emptyDetailChildren() []layout.FlexChild {
|
||||
return []layout.FlexChild{
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(18), "Entry details")
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(16), u.detailPlaceholderMessage())
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *ui) detailEditorContent(gtx layout.Context, hasSelected bool) layout.Dimensions {
|
||||
rows := []layout.Widget{
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"git.julianfamily.org/keepassgo/internal/apiaudit"
|
||||
"git.julianfamily.org/keepassgo/internal/apitokens"
|
||||
"git.julianfamily.org/keepassgo/internal/appstate"
|
||||
detaillayout "git.julianfamily.org/keepassgo/internal/appui/detail/layout"
|
||||
"git.julianfamily.org/keepassgo/internal/clipboard"
|
||||
"git.julianfamily.org/keepassgo/internal/session"
|
||||
)
|
||||
@@ -1310,6 +1311,8 @@ func (u *ui) primaryContent(gtx layout.Context) layout.Dimensions {
|
||||
return u.lifecycleScreen(gtx)
|
||||
case u.shouldUseLockedSinglePane():
|
||||
return u.detailPanel(gtx)
|
||||
case !u.shouldShowDetailPane():
|
||||
return u.listPanel(gtx)
|
||||
case u.usesCompactViewport():
|
||||
return u.compactPrimaryContent(gtx)
|
||||
default:
|
||||
@@ -1317,6 +1320,17 @@ func (u *ui) primaryContent(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *ui) shouldShowDetailPane() bool {
|
||||
_, hasSelectedEntry := u.selectedEntry()
|
||||
mode := detaillayout.Resolve(
|
||||
u.isVaultLocked(),
|
||||
u.state.Section == appstate.SectionAPITokens || u.state.Section == appstate.SectionAPIAudit || u.state.Section == appstate.SectionAbout,
|
||||
hasSelectedEntry,
|
||||
u.editingEntry,
|
||||
)
|
||||
return mode != detaillayout.ModeEmpty
|
||||
}
|
||||
|
||||
func (u *ui) compactPrimaryContent(gtx layout.Context) layout.Dimensions {
|
||||
u.phoneSpan = gtx.Constraints.Max.Y
|
||||
listHeight := int(float32(gtx.Constraints.Max.Y) * u.phoneSplit.Value)
|
||||
|
||||
+74
-49
@@ -36,57 +36,21 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
|
||||
func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
||||
if u.shouldShowLifecycleSetup() || u.isVaultLocked() || u.shouldShowDesktopWorkingHeader() {
|
||||
if u.shouldShowLifecycleSetup() || u.isVaultLocked() {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
cluster := u.newHeaderActionCluster(gtx)
|
||||
surface := headerlayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0}
|
||||
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.maybeLogHeaderMenuToggle("sync-visible", true)
|
||||
placement, menuCall := compactSurface.Place(gtx, cluster.Metrics.SyncAnchor(), u.syncMenu)
|
||||
u.phoneSyncMenuOrigin = placement.Origin
|
||||
u.phoneSyncMenuSize = placement.Size
|
||||
u.phoneSyncMenuCall = menuCall
|
||||
u.maybeLogHeaderMenuPlacement("sync-phone", compactSurface, placement)
|
||||
}
|
||||
if u.mainMenuOpen {
|
||||
u.phoneMainMenuVisible = true
|
||||
u.maybeLogHeaderMenuToggle("main-visible", true)
|
||||
placement, menuCall := compactSurface.Place(gtx, cluster.Metrics.MainAnchor(), u.mainMenu)
|
||||
u.phoneMainMenuOrigin = placement.Origin
|
||||
u.phoneMainMenuSize = placement.Size
|
||||
u.phoneMainMenuCall = menuCall
|
||||
u.maybeLogHeaderMenuPlacement("main-phone", compactSurface, placement)
|
||||
}
|
||||
cluster.prepareCompactMenus(gtx, u)
|
||||
return layout.Dimensions{Size: image.Pt(gtx.Constraints.Max.X, rowDims.Size.Y)}
|
||||
}
|
||||
|
||||
if cluster.ShowSyncMenu() {
|
||||
placement, menuCall := surface.Place(gtx, cluster.Metrics.SyncAnchor(), cluster.SyncMenu)
|
||||
u.maybeLogHeaderMenuPlacement("sync", surface, placement)
|
||||
stack := op.Offset(placement.Origin).Push(gtx.Ops)
|
||||
menuCall.Add(gtx.Ops)
|
||||
stack.Pop()
|
||||
}
|
||||
if cluster.ShowMainMenu() {
|
||||
placement, menuCall := surface.Place(gtx, cluster.Metrics.MainAnchor(), cluster.MainMenu)
|
||||
u.maybeLogHeaderMenuPlacement("main", surface, placement)
|
||||
stack := op.Offset(placement.Origin).Push(gtx.Ops)
|
||||
menuCall.Add(gtx.Ops)
|
||||
stack.Pop()
|
||||
}
|
||||
cluster.drawOverlayMenus(gtx, u, headerlayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0})
|
||||
|
||||
return rowDims
|
||||
}
|
||||
@@ -125,6 +89,17 @@ func (c *headerActionCluster) layout(gtx layout.Context, u *ui) layout.Dimension
|
||||
})
|
||||
}
|
||||
|
||||
func (c headerActionCluster) activeMenu() layout.Widget {
|
||||
switch {
|
||||
case c.ShowSyncMenu():
|
||||
return c.SyncMenu
|
||||
case c.ShowMainMenu():
|
||||
return c.MainMenu
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -145,6 +120,61 @@ func (c *headerActionCluster) layoutRow(gtx layout.Context, u *ui) layout.Dimens
|
||||
)
|
||||
}
|
||||
|
||||
func (c *headerActionCluster) prepareCompactMenus(gtx layout.Context, u *ui) {
|
||||
compactSurface := headerlayout.DropdownSurface{
|
||||
ContainerWidth: gtx.Constraints.Max.X,
|
||||
LeftInset: u.frameInsetPx,
|
||||
TopInset: u.frameInsetPx,
|
||||
}
|
||||
if c.ShowSyncMenu() {
|
||||
u.phoneSyncMenuVisible = true
|
||||
u.maybeLogHeaderMenuToggle("sync-visible", true)
|
||||
placement, menuCall := compactSurface.Place(gtx, c.Metrics.SyncAnchor(), c.SyncMenu)
|
||||
u.phoneSyncMenuOrigin = placement.Origin
|
||||
u.phoneSyncMenuSize = placement.Size
|
||||
u.phoneSyncMenuCall = menuCall
|
||||
u.maybeLogHeaderMenuPlacement("sync-phone", compactSurface, placement)
|
||||
}
|
||||
if c.ShowMainMenu() {
|
||||
u.phoneMainMenuVisible = true
|
||||
u.maybeLogHeaderMenuToggle("main-visible", true)
|
||||
placement, menuCall := compactSurface.Place(gtx, c.Metrics.MainAnchor(), c.MainMenu)
|
||||
u.phoneMainMenuOrigin = placement.Origin
|
||||
u.phoneMainMenuSize = placement.Size
|
||||
u.phoneMainMenuCall = menuCall
|
||||
u.maybeLogHeaderMenuPlacement("main-phone", compactSurface, placement)
|
||||
}
|
||||
}
|
||||
|
||||
func (c headerActionCluster) drawOverlayMenus(gtx layout.Context, u *ui, surface headerlayout.DropdownSurface) {
|
||||
if c.ShowSyncMenu() {
|
||||
placement, menuCall := surface.Place(gtx, c.Metrics.SyncAnchor(), c.SyncMenu)
|
||||
u.maybeLogHeaderMenuPlacement("sync", surface, placement)
|
||||
stack := op.Offset(placement.Origin).Push(gtx.Ops)
|
||||
menuCall.Add(gtx.Ops)
|
||||
stack.Pop()
|
||||
}
|
||||
if c.ShowMainMenu() {
|
||||
placement, menuCall := surface.Place(gtx, c.Metrics.MainAnchor(), c.MainMenu)
|
||||
u.maybeLogHeaderMenuPlacement("main", surface, placement)
|
||||
stack := op.Offset(placement.Origin).Push(gtx.Ops)
|
||||
menuCall.Add(gtx.Ops)
|
||||
stack.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func (c headerActionCluster) layoutCompactMenuRow(gtx layout.Context) layout.Dimensions {
|
||||
menu := c.activeMenu()
|
||||
if menu == nil {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
fullWidthGTX := gtx
|
||||
fullWidthGTX.Constraints.Min = image.Point{}
|
||||
fullWidthGTX.Constraints.Min.X = fullWidthGTX.Constraints.Max.X
|
||||
dims := layout.E.Layout(fullWidthGTX, menu)
|
||||
return layout.Dimensions{Size: image.Pt(fullWidthGTX.Constraints.Max.X, dims.Size.Y)}
|
||||
}
|
||||
|
||||
type headerButtonBounds struct {
|
||||
SyncPrimary image.Rectangle
|
||||
SyncToggle image.Rectangle
|
||||
@@ -188,26 +218,21 @@ func (u *ui) phoneHeaderMenus(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
|
||||
cluster := u.newHeaderActionCluster(gtx)
|
||||
if u.syncMenuVisibleOnPhone() {
|
||||
return layout.UniformInset(unit.Dp(16)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
stack := op.Offset(image.Pt(0, max(0, u.phoneSyncMenuOrigin.Y-u.frameInsetPx))).Push(gtx.Ops)
|
||||
defer stack.Pop()
|
||||
fullWidthGTX := gtx
|
||||
fullWidthGTX.Constraints.Min = image.Point{}
|
||||
fullWidthGTX.Constraints.Min.X = fullWidthGTX.Constraints.Max.X
|
||||
dims := layout.E.Layout(fullWidthGTX, u.syncMenu)
|
||||
return layout.Dimensions{Size: image.Pt(fullWidthGTX.Constraints.Max.X, max(dims.Size.Y, u.phoneSyncMenuOrigin.Y))}
|
||||
dims := cluster.layoutCompactMenuRow(gtx)
|
||||
return layout.Dimensions{Size: image.Pt(gtx.Constraints.Max.X, max(dims.Size.Y, u.phoneSyncMenuOrigin.Y))}
|
||||
})
|
||||
}
|
||||
if u.mainMenuVisibleOnPhone() {
|
||||
return layout.UniformInset(unit.Dp(16)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
stack := op.Offset(image.Pt(0, max(0, u.phoneMainMenuOrigin.Y-u.frameInsetPx))).Push(gtx.Ops)
|
||||
defer stack.Pop()
|
||||
fullWidthGTX := gtx
|
||||
fullWidthGTX.Constraints.Min = image.Point{}
|
||||
fullWidthGTX.Constraints.Min.X = fullWidthGTX.Constraints.Max.X
|
||||
dims := layout.E.Layout(fullWidthGTX, u.mainMenu)
|
||||
return layout.Dimensions{Size: image.Pt(fullWidthGTX.Constraints.Max.X, max(dims.Size.Y, u.phoneMainMenuOrigin.Y))}
|
||||
dims := cluster.layoutCompactMenuRow(gtx)
|
||||
return layout.Dimensions{Size: image.Pt(gtx.Constraints.Max.X, max(dims.Size.Y, u.phoneMainMenuOrigin.Y))}
|
||||
})
|
||||
}
|
||||
return layout.Dimensions{}
|
||||
|
||||
@@ -16,11 +16,6 @@ import (
|
||||
"git.julianfamily.org/keepassgo/internal/vault"
|
||||
)
|
||||
|
||||
func (u *ui) syncButtonGroup(gtx layout.Context) layout.Dimensions {
|
||||
group, _, _ := u.syncButtonGroupWithMetrics(gtx)
|
||||
return group
|
||||
}
|
||||
|
||||
func (u *ui) syncButtonGroupWithMetrics(gtx layout.Context) (layout.Dimensions, layout.Dimensions, layout.Dimensions) {
|
||||
spacing := unit.Dp(4)
|
||||
if u.usesCompactViewport() {
|
||||
|
||||
Reference in New Issue
Block a user