diff --git a/Application/ModernSetupApp/ModernSetupApp.c b/Application/ModernSetupApp/ModernSetupApp.c index 567ae94..73ff186 100644 --- a/Application/ModernSetupApp/ModernSetupApp.c +++ b/Application/ModernSetupApp/ModernSetupApp.c @@ -106,7 +106,9 @@ UefiMain ( } else if ((Focus == SetupFocusContent) && (Page == PageExit) && mModernSetupLanguageDropdownOpen) { mModernSetupLanguageDropdownSelection = (mModernSetupLanguageDropdownSelection == 0) ? 1 : 0; } else if ((Focus == SetupFocusContent) && (Page == PageDashboard)) { - if (ModernSetupGetDashboardQuickGrid (&Ui, &DashboardGrid) && (DashboardSelection >= DashboardGrid.CardsPerRow)) { + if (ModernSetupGetDashboardQuickGrid (&Ui, mModernSetupPreferences.DashboardDensity, &DashboardGrid) && + (DashboardSelection >= DashboardGrid.CardsPerRow)) + { DashboardSelection -= DashboardGrid.CardsPerRow; } else { Focus = SetupFocusNav; @@ -132,7 +134,9 @@ UefiMain ( Focus = SetupFocusContent; } } else if (Page == PageDashboard) { - if (ModernSetupGetDashboardQuickGrid (&Ui, &DashboardGrid) && ((DashboardSelection + DashboardGrid.CardsPerRow) < DASHBOARD_QUICK_CARD_COUNT)) { + if (ModernSetupGetDashboardQuickGrid (&Ui, mModernSetupPreferences.DashboardDensity, &DashboardGrid) && + ((DashboardSelection + DashboardGrid.CardsPerRow) < DASHBOARD_QUICK_CARD_COUNT)) + { DashboardSelection += DashboardGrid.CardsPerRow; } } else { @@ -158,7 +162,9 @@ UefiMain ( } else if ((Focus == SetupFocusContent) && (Page == PageExit) && mModernSetupLanguageDropdownOpen) { mModernSetupLanguageDropdownOpen = FALSE; } else if ((Focus == SetupFocusContent) && (Page == PageDashboard)) { - if (ModernSetupGetDashboardQuickGrid (&Ui, &DashboardGrid) && ((DashboardSelection % DashboardGrid.CardsPerRow) > 0)) { + if (ModernSetupGetDashboardQuickGrid (&Ui, mModernSetupPreferences.DashboardDensity, &DashboardGrid) && + ((DashboardSelection % DashboardGrid.CardsPerRow) > 0)) + { DashboardSelection--; } else if (!DashboardGrid.Visible) { DashboardSelection = (DashboardSelection == 0) ? (DASHBOARD_QUICK_CARD_COUNT - 1) : (DashboardSelection - 1); @@ -180,7 +186,7 @@ UefiMain ( mModernSetupLanguageDropdownOpen = FALSE; ModernSetupCancelPreferencePopup (); } else if (Page == PageDashboard) { - if (ModernSetupGetDashboardQuickGrid (&Ui, &DashboardGrid)) { + if (ModernSetupGetDashboardQuickGrid (&Ui, mModernSetupPreferences.DashboardDensity, &DashboardGrid)) { if ((((DashboardSelection % DashboardGrid.CardsPerRow) + 1) < DashboardGrid.CardsPerRow) && ((DashboardSelection + 1) < DASHBOARD_QUICK_CARD_COUNT)) { DashboardSelection++; } diff --git a/Application/ModernSetupApp/ModernSetupAppActions.c b/Application/ModernSetupApp/ModernSetupAppActions.c index 25864dd..2d188b7 100644 --- a/Application/ModernSetupApp/ModernSetupAppActions.c +++ b/Application/ModernSetupApp/ModernSetupAppActions.c @@ -36,15 +36,34 @@ MODERN_UI_PREFERENCES mModernSetupPreferences; Calculate the visible Dashboard Quick Access grid from the same layout contract used by drawing and keyboard navigation. - @param[in] Ui Initialized render context. Must not be NULL. - @param[out] Grid Receives the Quick Access panel and card metrics. + DashboardDensity is the app-owned ModernUi preference selecting the + Dashboard layout density. Only ModernUiDashboardDensityCompact triggers + the compact layout (tighter top summary band, smaller card gap/top, and + reduced minimum card value height). Any other value -- including the + default ModernUiDashboardDensityComfortable, the out-of-range sentinel + ModernUiDashboardDensityMax, or any unrecognized UINT8 -- falls back to + the Comfortable layout path; no validation or clamping is performed + here. Preference sanitation/defaulting is owned by + ModernUiPreferencesLib, which clamps unknown values back to + ModernUiDashboardDensityComfortable before this helper is reached. + + @param[in] Ui Initialized render context. Must not be NULL. + @param[in] DashboardDensity ModernUi dashboard density preference. Expected + values: ModernUiDashboardDensityComfortable (0, + default) or ModernUiDashboardDensityCompact. + Any other value is treated as Comfortable. + @param[out] Grid Receives the Quick Access panel and card + metrics. Must not be NULL. Zeroed on entry; + left zeroed on FALSE return. @retval TRUE Quick Access cards are visible/selectable. - @retval FALSE Quick Access cards do not fit in the current content rect. + @retval FALSE Ui or Grid is NULL, or Quick Access cards do not fit in the + current content rect. **/ BOOLEAN ModernSetupGetDashboardQuickGrid ( IN MODERN_UI_RENDER_CONTEXT *Ui, + IN UINT8 DashboardDensity, OUT MODERN_SETUP_DASHBOARD_QUICK_GRID *Grid ) { @@ -52,29 +71,36 @@ ModernSetupGetDashboardQuickGrid ( UINTN TopHeight; UINTN QuickY; UINTN QuickHeight; + UINTN QuickGap; UINTN CardAreaWidth; UINTN MaxRows; + UINTN ValueMinHeight; + BOOLEAN Compact; if ((Ui == NULL) || (Grid == NULL)) { return FALSE; } ZeroMem (Grid, sizeof (*Grid)); + Compact = (BOOLEAN)(DashboardDensity == ModernUiDashboardDensityCompact); Content = ModernSetupContentRect (Ui); - TopHeight = (Content.Height >= 460) ? 300 : 232; - QuickY = Content.Y + TopHeight + 16; - QuickHeight = (Content.Height > (TopHeight + 16)) ? (Content.Height - TopHeight - 16) : 0; + TopHeight = Compact ? ((Content.Height >= 460) ? 236 : 204) : + ((Content.Height >= 460) ? 300 : 232); + QuickGap = Compact ? 10 : 16; + QuickY = Content.Y + TopHeight + QuickGap; + QuickHeight = (Content.Height > (TopHeight + QuickGap)) ? (Content.Height - TopHeight - QuickGap) : 0; if (QuickHeight <= 110) { return FALSE; } Grid->Visible = TRUE; Grid->Panel = (MODERN_UI_RECT){ Content.X, QuickY, Content.Width, QuickHeight }; - Grid->CardGap = DASHBOARD_QUICK_CARD_GAP; - Grid->CardTop = DASHBOARD_QUICK_CARD_TOP; + Grid->CardGap = Compact ? 20 : DASHBOARD_QUICK_CARD_GAP; + Grid->CardTop = Compact ? 42 : DASHBOARD_QUICK_CARD_TOP; CardAreaWidth = (Grid->Panel.Width > 40) ? (Grid->Panel.Width - 40) : Grid->Panel.Width; - MaxRows = (Grid->Panel.Height > (DASHBOARD_QUICK_CARD_TOP + DASHBOARD_QUICK_VALUE_MIN_HEIGHT + DASHBOARD_QUICK_CARD_BOTTOM)) ? - ((Grid->Panel.Height - DASHBOARD_QUICK_CARD_TOP - DASHBOARD_QUICK_CARD_BOTTOM + Grid->CardGap) / (DASHBOARD_QUICK_VALUE_MIN_HEIGHT + Grid->CardGap)) : + ValueMinHeight = Compact ? 30 : DASHBOARD_QUICK_VALUE_MIN_HEIGHT; + MaxRows = (Grid->Panel.Height > (Grid->CardTop + ValueMinHeight + DASHBOARD_QUICK_CARD_BOTTOM)) ? + ((Grid->Panel.Height - Grid->CardTop - DASHBOARD_QUICK_CARD_BOTTOM + Grid->CardGap) / (ValueMinHeight + Grid->CardGap)) : 1; MaxRows = MAX (1, MIN (DASHBOARD_QUICK_CARD_COUNT, MaxRows)); Grid->CardsPerRow = (DASHBOARD_QUICK_CARD_COUNT + MaxRows - 1) / MaxRows; @@ -322,7 +348,8 @@ ModernSetupGetPageSelectableCount ( { MODERN_SETUP_DASHBOARD_QUICK_GRID Grid; - return ModernSetupGetDashboardQuickGrid (Ui, &Grid) ? DASHBOARD_QUICK_CARD_COUNT : 0; + return ModernSetupGetDashboardQuickGrid (Ui, mModernSetupPreferences.DashboardDensity, &Grid) ? + DASHBOARD_QUICK_CARD_COUNT : 0; } case PageBoot: { diff --git a/Application/ModernSetupApp/ModernSetupAppDashboard.c b/Application/ModernSetupApp/ModernSetupAppDashboard.c index b985aa4..7517a7a 100644 --- a/Application/ModernSetupApp/ModernSetupAppDashboard.c +++ b/Application/ModernSetupApp/ModernSetupAppDashboard.c @@ -363,7 +363,8 @@ ModernSetupDrawDashboard ( L"Read-only inventory / native owns policy" ); - TopHeight = (Content.Height >= 460) ? 300 : 232; + TopHeight = (mModernSetupPreferences.DashboardDensity == ModernUiDashboardDensityCompact) ? + ((Content.Height >= 460) ? 236 : 204) : ((Content.Height >= 460) ? 300 : 232); MonitorWidth = (Content.Width >= 760) ? ((Content.Width * 31) / 100) : 0; if ((MonitorWidth > 0) && (Content.Width > (MonitorWidth + CARD_GAP))) { SystemPanel = (MODERN_UI_RECT){ Content.X, Content.Y, Content.Width - MonitorWidth - CARD_GAP, TopHeight }; @@ -373,7 +374,7 @@ ModernSetupDrawDashboard ( MonitorPanel = (MODERN_UI_RECT){ 0, 0, 0, 0 }; } - if (!ModernSetupGetDashboardQuickGrid (Ui, &Grid)) { + if (!ModernSetupGetDashboardQuickGrid (Ui, mModernSetupPreferences.DashboardDensity, &Grid)) { QuickPanel = (MODERN_UI_RECT){ 0, 0, 0, 0 }; } else { QuickPanel = Grid.Panel; diff --git a/Application/ModernSetupApp/ModernSetupAppInternal.h b/Application/ModernSetupApp/ModernSetupAppInternal.h index ced0c90..2180b8b 100644 --- a/Application/ModernSetupApp/ModernSetupAppInternal.h +++ b/Application/ModernSetupApp/ModernSetupAppInternal.h @@ -276,6 +276,7 @@ ModernSetupGetPageSelectableCount ( BOOLEAN ModernSetupGetDashboardQuickGrid ( IN MODERN_UI_RENDER_CONTEXT *Ui, + IN UINT8 DashboardDensity, OUT MODERN_SETUP_DASHBOARD_QUICK_GRID *Grid ); diff --git a/CHANGELOG.md b/CHANGELOG.md index 7177bff..1a11974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ this file as both a release log and a lightweight development progress record. ### Added +- Phase 29 Dashboard density layout: the app-owned `DashboardDensity` + preference now feeds the Dashboard grid helper, so `Compact` reduces the top + summary area and quick-card spacing while navigation uses the same density-aware + geometry as rendering. - Phase 28 app-owned runtime theme switching: the `Theme` preference now resolves the active `ModernSetupApp` palette during redraw, adds the generic premium `Graphite Gold` dark graphite/champagne palette, and exposes the additive diff --git a/Tests/Smoke/smoke_validate.py b/Tests/Smoke/smoke_validate.py index c27f37e..df75668 100755 --- a/Tests/Smoke/smoke_validate.py +++ b/Tests/Smoke/smoke_validate.py @@ -1259,6 +1259,34 @@ def check_phase28_runtime_theme_switching(root: Path) -> list[str]: return ["PASS Phase28 runtime Theme preference applies app-owned palettes"] +def check_phase29_dashboard_density_layout(root: Path) -> list[str]: + app_dir = root / MODERN_SETUP_APP_DIR + app_main = strip_c_comments((app_dir / "ModernSetupApp.c").read_text(encoding="utf-8")) + actions = strip_c_comments((app_dir / "ModernSetupAppActions.c").read_text(encoding="utf-8")) + dashboard = strip_c_comments((app_dir / "ModernSetupAppDashboard.c").read_text(encoding="utf-8")) + internal = strip_c_comments((app_dir / "ModernSetupAppInternal.h").read_text(encoding="utf-8")) + + if "ModernSetupGetDashboardQuickGrid (" not in internal or "DashboardDensity" not in internal: + raise SmokeFailure("Phase29 Dashboard grid helper must accept DashboardDensity") + if "ModernUiDashboardDensityCompact" not in actions: + raise SmokeFailure("Phase29 Dashboard grid helper must branch on Compact density") + for token in ("Compact ? 20 : DASHBOARD_QUICK_CARD_GAP", "Compact ? 42 : DASHBOARD_QUICK_CARD_TOP", "Compact ? 10 : 16"): + if token not in actions: + raise SmokeFailure(f"Phase29 compact Dashboard grid missing layout token: {token}") + if "Compact ? ((Content.Height >= 460) ? 236 : 204)" not in actions: + raise SmokeFailure("Phase29 compact Dashboard must reduce the top summary height") + if app_main.count("mModernSetupPreferences.DashboardDensity") < 4: + raise SmokeFailure("Phase29 Dashboard navigation must use the same density-aware grid as rendering") + if "ModernSetupGetDashboardQuickGrid (Ui, mModernSetupPreferences.DashboardDensity, &Grid)" not in dashboard: + raise SmokeFailure("Phase29 Dashboard rendering must use the density-aware grid") + if "mModernSetupPreferences.DashboardDensity == ModernUiDashboardDensityCompact" not in dashboard: + raise SmokeFailure("Phase29 Dashboard top summary layout must react to Compact density") + if any(token in dashboard + actions for token in ("SetVariable", "ExtractConfig", "RouteConfig", "HiiSetBrowserData")): + raise SmokeFailure("Phase29 DashboardDensity layout must not introduce HII or variable writes") + + return ["PASS Phase29 DashboardDensity controls Dashboard layout density"] + + def check_pcie_provider_foundation(root: Path) -> list[str]: for relative in PCIE_PROVIDER_REQUIRED_FILES: if not (root / relative).exists(): @@ -1708,6 +1736,7 @@ def main() -> int: messages.extend(check_phase26_interactive_app_owned_preferences(root)) messages.extend(check_phase27_app_owned_input_preferences(root)) messages.extend(check_phase28_runtime_theme_switching(root)) + messages.extend(check_phase29_dashboard_density_layout(root)) messages.extend(check_pcie_provider_foundation(root)) messages.extend(check_hardware_health_demo_provider(root)) messages.extend(check_pcie_docs_language(root))