diff --git a/internal/appui/ui_header_dropdown.go b/internal/appui/layout/dropdown.go similarity index 55% rename from internal/appui/ui_header_dropdown.go rename to internal/appui/layout/dropdown.go index 5a4f952..91d5430 100644 --- a/internal/appui/ui_header_dropdown.go +++ b/internal/appui/layout/dropdown.go @@ -1,4 +1,4 @@ -package appui +package layout import ( "image" @@ -8,47 +8,62 @@ import ( "gioui.org/unit" ) -type dropdownAnchor struct { +func AnchoredMenuX(triggerWidth, menuWidth int) int { + return triggerWidth - menuWidth +} + +func AnchoredMenuOriginX(containerWidth, rowOriginX, triggerRightX, menuWidth int) int { + x := rowOriginX + triggerRightX - menuWidth + if x < 0 { + return 0 + } + if x+menuWidth > containerWidth { + return max(0, containerWidth-menuWidth) + } + return x +} + +type DropdownAnchor struct { TriggerRightX int TriggerBottomY int } -func (a dropdownAnchor) point() image.Point { +func (a DropdownAnchor) Point() image.Point { return image.Pt(a.TriggerRightX, a.TriggerBottomY) } -type dropdownSurface struct { +type DropdownSurface struct { ContainerWidth int LeftInset int TopInset int } -func (s dropdownSurface) menuConstraints(gtx layout.Context) layout.Context { +func (s DropdownSurface) MenuConstraints(gtx layout.Context) layout.Context { menuGTX := gtx menuGTX.Constraints.Min = image.Point{} menuGTX.Constraints.Max.X = max(0, s.ContainerWidth) return menuGTX } -func (s dropdownSurface) origin(anchor dropdownAnchor, menuWidth int) image.Point { - x := s.LeftInset + anchoredMenuOriginX(s.ContainerWidth, 0, anchor.TriggerRightX, menuWidth) +func (s DropdownSurface) Origin(anchor DropdownAnchor, menuWidth int) image.Point { + x := s.LeftInset + AnchoredMenuOriginX(s.ContainerWidth, 0, anchor.TriggerRightX, menuWidth) y := s.TopInset + anchor.TriggerBottomY return image.Pt(x, y) } -func (s dropdownSurface) draw(gtx layout.Context, anchor dropdownAnchor, menu layout.Widget) layout.Dimensions { - menuGTX := s.menuConstraints(gtx) +func (s DropdownSurface) Draw(gtx layout.Context, anchor DropdownAnchor, menu layout.Widget) layout.Dimensions { + menuGTX := s.MenuConstraints(gtx) menuOps := op.Record(gtx.Ops) menuDims := layout.Inset{Top: unit.Dp(6)}.Layout(menuGTX, menu) menuCall := menuOps.Stop() - menuOrigin := s.origin(anchor, menuDims.Size.X) + menuOrigin := s.Origin(anchor, menuDims.Size.X) stack := op.Offset(menuOrigin).Push(gtx.Ops) menuCall.Add(gtx.Ops) stack.Pop() return layout.Dimensions{Size: gtx.Constraints.Max} } -type headerActionMetrics struct { +type HeaderActionMetrics struct { RowOriginX int Spacing int RowDims layout.Dimensions @@ -57,16 +72,16 @@ type headerActionMetrics struct { MainDims layout.Dimensions } -func (m headerActionMetrics) syncAnchor() dropdownAnchor { - return dropdownAnchor{ +func (m HeaderActionMetrics) SyncAnchor() DropdownAnchor { + return DropdownAnchor{ TriggerRightX: m.RowOriginX + m.SyncDims.Size.X, TriggerBottomY: m.RowDims.Size.Y, } } -func (m headerActionMetrics) mainAnchor() dropdownAnchor { +func (m HeaderActionMetrics) MainAnchor() DropdownAnchor { triggerRightX := m.SyncDims.Size.X + m.Spacing + m.LockDims.Size.X + m.Spacing + m.MainDims.Size.X - return dropdownAnchor{ + return DropdownAnchor{ TriggerRightX: m.RowOriginX + triggerRightX, TriggerBottomY: m.RowDims.Size.Y, } diff --git a/internal/appui/main_test.go b/internal/appui/main_test.go index 73c7687..34a1b82 100644 --- a/internal/appui/main_test.go +++ b/internal/appui/main_test.go @@ -25,6 +25,7 @@ import ( "git.julianfamily.org/keepassgo/internal/apiaudit" "git.julianfamily.org/keepassgo/internal/apitokens" "git.julianfamily.org/keepassgo/internal/appstate" + appuilayout "git.julianfamily.org/keepassgo/internal/appui/layout" "git.julianfamily.org/keepassgo/internal/clipboard" "git.julianfamily.org/keepassgo/internal/passwords" "git.julianfamily.org/keepassgo/internal/session" @@ -372,10 +373,10 @@ func TestUIHeaderMenusUseOverlayModelAcrossModes(t *testing.T) { func TestAnchoredMenuXAllowsWiderMenusToExtendLeft(t *testing.T) { t.Parallel() - if got := anchoredMenuX(48, 160); got != -112 { + if got := appuilayout.AnchoredMenuX(48, 160); got != -112 { t.Fatalf("anchoredMenuX(48, 160) = %d, want -112", got) } - if got := anchoredMenuX(160, 48); got != 112 { + if got := appuilayout.AnchoredMenuX(160, 48); got != 112 { t.Fatalf("anchoredMenuX(160, 48) = %d, want 112", got) } } @@ -383,10 +384,10 @@ func TestAnchoredMenuXAllowsWiderMenusToExtendLeft(t *testing.T) { func TestAnchoredMenuOriginXClampsToVisibleContainer(t *testing.T) { t.Parallel() - if got := anchoredMenuOriginX(360, 312, 360, 140); got != 220 { + if got := appuilayout.AnchoredMenuOriginX(360, 312, 360, 140); got != 220 { t.Fatalf("anchoredMenuOriginX should keep a right-aligned menu visible, got %d want 220", got) } - if got := anchoredMenuOriginX(360, 0, 44, 160); got != 0 { + if got := appuilayout.AnchoredMenuOriginX(360, 0, 44, 160); got != 0 { t.Fatalf("anchoredMenuOriginX should clamp oversized left overflow to zero, got %d want 0", got) } } @@ -394,7 +395,7 @@ func TestAnchoredMenuOriginXClampsToVisibleContainer(t *testing.T) { func TestHeaderActionMetricsComputeTriggerAnchors(t *testing.T) { t.Parallel() - metrics := headerActionMetrics{ + metrics := appuilayout.HeaderActionMetrics{ RowOriginX: 24, Spacing: 8, RowDims: layout.Dimensions{Size: image.Pt(180, 40)}, @@ -403,10 +404,10 @@ func TestHeaderActionMetricsComputeTriggerAnchors(t *testing.T) { MainDims: layout.Dimensions{Size: image.Pt(36, 40)}, } - if got := metrics.syncAnchor(); got != (dropdownAnchor{TriggerRightX: 76, TriggerBottomY: 40}) { + if got := metrics.SyncAnchor(); got != (appuilayout.DropdownAnchor{TriggerRightX: 76, TriggerBottomY: 40}) { t.Fatalf("metrics.syncAnchor() = %+v, want right=76 bottom=40", got) } - if got := metrics.mainAnchor(); got != (dropdownAnchor{TriggerRightX: 172, TriggerBottomY: 40}) { + if got := metrics.MainAnchor(); got != (appuilayout.DropdownAnchor{TriggerRightX: 172, TriggerBottomY: 40}) { t.Fatalf("metrics.mainAnchor() = %+v, want right=172 bottom=40", got) } } @@ -414,15 +415,15 @@ func TestHeaderActionMetricsComputeTriggerAnchors(t *testing.T) { func TestDropdownSurfaceOriginKeepsMenusWithinVisibleArea(t *testing.T) { t.Parallel() - surface := dropdownSurface{ContainerWidth: 320, LeftInset: 16, TopInset: 16} - anchor := dropdownAnchor{TriggerRightX: 300, TriggerBottomY: 42} + surface := appuilayout.DropdownSurface{ContainerWidth: 320, LeftInset: 16, TopInset: 16} + anchor := appuilayout.DropdownAnchor{TriggerRightX: 300, TriggerBottomY: 42} - if got := surface.origin(anchor, 140); got != image.Pt(176, 58) { + if got := surface.Origin(anchor, 140); got != image.Pt(176, 58) { t.Fatalf("surface.origin(anchor, 140) = %v, want (176,58)", got) } - leftAnchor := dropdownAnchor{TriggerRightX: 36, TriggerBottomY: 42} - if got := surface.origin(leftAnchor, 120); got != image.Pt(16, 58) { + leftAnchor := appuilayout.DropdownAnchor{TriggerRightX: 36, TriggerBottomY: 42} + if got := surface.Origin(leftAnchor, 120); got != image.Pt(16, 58) { t.Fatalf("surface.origin(leftAnchor, 120) = %v, want (16,58)", got) } } diff --git a/internal/appui/ui_layout_header.go b/internal/appui/ui_layout_header.go index 83b73ac..8d078ba 100644 --- a/internal/appui/ui_layout_header.go +++ b/internal/appui/ui_layout_header.go @@ -10,6 +10,7 @@ import ( "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" + appuilayout "git.julianfamily.org/keepassgo/internal/appui/layout" ) func (u *ui) header(gtx layout.Context) layout.Dimensions { @@ -44,7 +45,7 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions { return layout.Dimensions{} } spacing := gtx.Dp(unit.Dp(8)) - metrics := headerActionMetrics{Spacing: spacing} + metrics := appuilayout.HeaderActionMetrics{Spacing: spacing} row := func(gtx layout.Context) layout.Dimensions { return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { @@ -73,7 +74,7 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions { metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X) } - surface := dropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0} + surface := appuilayout.DropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0} rowStack := op.Offset(image.Pt(metrics.RowOriginX, 0)).Push(gtx.Ops) rowCall.Add(gtx.Ops) @@ -82,21 +83,21 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions { if u.usesCompactViewport() { if u.syncMenuOpen { u.phoneSyncMenuVisible = true - u.phoneSyncMenuAnchor = metrics.syncAnchor().point() + u.phoneSyncMenuAnchor = metrics.SyncAnchor().Point() } if u.mainMenuOpen { u.phoneMainMenuVisible = true - u.phoneMainMenuAnchor = metrics.mainAnchor().point() + u.phoneMainMenuAnchor = metrics.MainAnchor().Point() } width := gtx.Constraints.Max.X return layout.Dimensions{Size: image.Pt(width, metrics.RowDims.Size.Y)} } if u.syncMenuOpen { - surface.draw(gtx, metrics.syncAnchor(), u.syncMenu) + surface.Draw(gtx, metrics.SyncAnchor(), u.syncMenu) } if u.mainMenuOpen { - surface.draw(gtx, metrics.mainAnchor(), u.mainMenu) + surface.Draw(gtx, metrics.MainAnchor(), u.mainMenu) } width := metrics.RowDims.Size.X @@ -468,17 +469,17 @@ func (u *ui) phoneHeaderMenus(gtx layout.Context) layout.Dimensions { } gtx.Constraints.Min = gtx.Constraints.Max contentInsetPx := gtx.Dp(unit.Dp(16)) - surface := dropdownSurface{ + surface := appuilayout.DropdownSurface{ ContainerWidth: max(0, gtx.Constraints.Max.X-(contentInsetPx*2)), LeftInset: contentInsetPx, TopInset: contentInsetPx, } if u.syncMenuVisibleOnPhone() { - surface.draw(gtx, dropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu) + surface.Draw(gtx, appuilayout.DropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu) } if u.mainMenuVisibleOnPhone() { - surface.draw(gtx, dropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, u.mainMenu) + surface.Draw(gtx, appuilayout.DropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, u.mainMenu) } return layout.Dimensions{Size: gtx.Constraints.Max} } @@ -511,21 +512,6 @@ func (u *ui) mainMenuRightAlignsToTrigger() bool { return true } -func anchoredMenuX(triggerWidth, menuWidth int) int { - return triggerWidth - menuWidth -} - -func anchoredMenuOriginX(containerWidth, rowOriginX, triggerRightX, menuWidth int) int { - x := rowOriginX + triggerRightX - menuWidth - if x < 0 { - return 0 - } - if x+menuWidth > containerWidth { - return max(0, containerWidth-menuWidth) - } - return x -} - func menuActionWidth(gtx layout.Context, rows []layout.Widget) int { width := 0 for _, row := range rows {