# Design System — MangoTune GTK4 / libadwaita ## Guiding Principles 1. **Libadwaita first** — use `Adw::*` widgets wherever they exist before falling back to GTK4. This ensures correct dark/light mode, accent color, and GNOME HIG compliance automatically. 2. **Every field validates on change** — instant inline feedback, never wait for save. 3. **Save button state is truth** — it is only sensitive when all fields are valid AND there are unsaved changes. It is insensitive otherwise. Never disable fields; always show why. 4. **Contextual help is inline** — use `subtitle` on `AdwActionRow` for brief descriptions. Longer explanations go in an `AdwTooltip`. No separate help dialogs. 5. **Destructive actions require confirmation** — deleting a config file uses `AdwAlertDialog`. --- ## Main Window Structure ``` AdwApplicationWindow "MangoTune" AdwToolbarView ┌── [top] AdwHeaderBar │ Title: "MangoTune" │ Start: AdwSplitButton "Save" (primary action) │ End: menu button (gear icon → preferences, about) │ ├── [top] ConfigBarWidget (custom, below header) │ Shows: current file being edited │ Dropdown: select from all discovered config layers │ Conflict indicator: if any conflicts detected │ └── [content] AdwOverlaySplitView Sidebar: NavigationSidebar (AdwNavigationSidebar or custom ListBox) Content: AdwNavigationView (manages page stack) ``` ## Header Bar - Title: "MangoTune" - Subtitle: name of current config file being edited (short path) - Primary button: `AdwSplitButton` labeled "Save" - Main click: save current file - Dropdown arrow: "Save As…", "Revert to Saved", "Create Backup" - End: `Gtk::MenuButton` with gear icon - Menu items: Preferences, Keyboard Shortcuts, About MangoTune ## Config File Selector Bar Custom widget rendered between HeaderBar and the sidebar/content split. Appearance: an `AdwBanner` variant or custom `GtkBox` with background `@card_bg_color`. Contents (left to right): - Icon indicating layer type (globe for global, app icon for per-app, warning for env) - Dropdown (`GtkDropDown`) showing all discovered layers with their priority - Conflict badge: `GtkLabel` with `.error` or `.warning` CSS class if conflicts exist - Right side: "View All Layers" button → navigates to Conflicts page Layer display format in dropdown: ``` [●] ~/.config/MangoHud/MangoHud.conf (global) [◉] ~/.config/MangoHud/cs2.conf (per-app: cs2) ← currently editing [⚠] $MANGOHUD_CONFIG (env override — read only) ``` --- ## Sidebar Navigation Use `AdwNavigationSidebar` if available in libadwaita 1.4+, otherwise `GtkListBox` with `.navigation-sidebar` CSS class. Sections (use `GtkSeparator` between groups): **Config** - Overview (house icon) - Layer Conflicts (warning icon, badge with conflict count if > 0) **Display** - Performance (speedometer icon) - GPU (chip icon) - CPU (cpu icon) - Memory (memory icon) - I/O & Network (network icon) - Media Player (music note icon) - Battery (battery icon) **Appearance** - Layout & Position (layout icon) - Colors & Theme (palette icon) - Typography (text icon) **Behavior** - Keybindings (keyboard icon) - FPS Limits (gauge icon) - Logging (file icon) - Blacklist (block icon) **Advanced** - OpenGL Quirks (warning icon) - Raw Editor (code icon) **Tools** - Test Launcher (play icon) - Integrations (plugin icon) --- ## Page Layout Pattern Every config page follows this structure: ``` AdwPreferencesPage title: "GPU Metrics" icon-name: "processor-symbolic" (or custom) AdwPreferencesGroup title: "GPU Statistics" description: "Core GPU monitoring options" AdwSwitchRow ← for Flag/Bool options AdwSpinRow ← for Int options AdwEntryRow ← for String/Path options AdwComboRow ← for Enum options AdwExpanderRow ← for groups with sub-options (e.g. load color thresholds) └── nested rows inside AdwPreferencesGroup title: "Advanced" ... ``` --- ## Widget Patterns Per Option Type ### Flag / Bool → `AdwSwitchRow` ``` AdwSwitchRow { title: "GPU Temperature", subtitle: "Show GPU core temperature (gpu_temp)", active: , } ``` On toggle: validate, update model, check dependencies. ### Int with range → `AdwSpinRow` ``` AdwSpinRow { title: "Font Size", subtitle: "font_size — valid range: 8–72", value: 24.0, adjustment: Gtk::Adjustment { lower: 8, upper: 72, step-increment: 1 }, } ``` ### Float with range → `AdwSpinRow` (digits: 2 or 3) ### Enum → `AdwComboRow` ``` AdwComboRow { title: "HUD Position", subtitle: "position", model: StringList ["top-left", "top-right", "bottom-left", ...], } ``` ### String (free text) → `AdwEntryRow` ``` AdwEntryRow { title: "Custom GPU Label", text: "", // validation on ::changed signal } ``` Validation error: add `.error` CSS class to the row, set subtitle to error message. ### Path → `AdwEntryRow` + browse button ``` AdwActionRow { title: "Font File", AdwEntryRow + GtkButton "Browse…" } ``` Browse opens `GtkFileDialog` filtered to `.ttf,.otf`. Validate path exists after selection. ### Color → `AdwActionRow` with color swatch button ``` AdwActionRow { title: "GPU Color", subtitle: "gpu_color — hex RRGGBB", [suffix] GtkButton (color swatch, shows current color) → opens AdwDialog with color picker → also shows a GtkEntry for manual hex input } ``` ### Hotkey / Keybind → Custom `KeybindRow` widget ``` AdwActionRow { title: "Toggle HUD", subtitle: "toggle_hud", [suffix] GtkShortcutLabel (shows current binding) [suffix] GtkButton "Edit" → opens capture dialog } ``` Capture dialog: fullscreen-ish `AdwDialog`, listens for keypress, shows "Press a key combination…", captures and validates the combination, shows preview, OK/Cancel. ### CommaSeparatedStrings (controlled set) → `AdwExpanderRow` with checkboxes Example: `graphs`, `font_glyph_ranges`, `device_battery` ``` AdwExpanderRow { title: "Graphs", subtitle: "Select which graphs to display", [child per valid value] AdwSwitchRow or CheckButton row } ``` ### CommaSeparatedStrings (free) → `AdwEntryRow` with validation Example: `blacklist`, `network` ### FpsLimitList → Custom widget A `GtkFlowBox` of chips showing current FPS values (0, 30, 60, etc.) with + button to add and × to remove each. Each value validated as non-negative int. --- ## Inline Validation Display When a field has an error: 1. The `AdwActionRow` or `AdwEntryRow` gets `.error` CSS class applied. 2. The row's subtitle changes to the error message (red text via `.error` on a child label). 3. A validation summary appears at top of page: `AdwBanner` with "N fields have errors — fix to enable saving". 4. The Save button in the header becomes insensitive. When a dependency warning fires (e.g. user enables `gpu_mem_clock` without `vram`): 1. Show `AdwAlertDialog`: "Enabling 'GPU Memory Clock' also requires 'VRAM display' to be enabled. Enable it now?" 2. Buttons: "Enable Both" (suggested-action), "Cancel". --- ## Conflict/Layer Cascade Page This is the most distinctive page in the app. Layout: vertical stack of `AdwPreferencesGroup` cards, one per discovered layer, ordered top-to-bottom = highest-to-lowest priority. Each layer card header shows: - Priority badge (e.g. "ENV", "PER-APP", "GLOBAL") with color coding - File path or env var name - "Edit" button (disabled for env layers) - "Open in Files" button (for file layers) Inside each layer card: a `GtkListBox` showing every option set in that layer. Options that are shadowed by a higher-priority layer: - Shown with strikethrough text - A label "overridden by {LAYER}" in muted color Options that are unique to this layer (no conflict): normal display. Options that this layer wins on (it overrides lower layers): bold text. Filter bar at top of page: - "Show all options" / "Show conflicts only" / "Show shadowed only" toggle buttons --- ## Test Launcher Panel Shown as a persistent bottom bar (collapsed by default) OR as a dedicated page. Decision: dedicated page (cleaner, avoids layout complications). Layout: ``` AdwPreferencesPage "Test Launcher" AdwPreferencesGroup "Quick Test" description: "Launch a test application with MangoHud active to preview your config" AdwActionRow "vkcube (Vulkan)" subtitle: "vulkan-tools — tests Vulkan overlay" [suffix] status: "installed" / "not found" [suffix] GtkButton "Launch" AdwActionRow "glxgears (OpenGL)" subtitle: "mesa-utils — tests OpenGL overlay" [suffix] status indicator [suffix] GtkButton "Launch" AdwActionRow "Custom Application" subtitle: "Launch any app with MangoHud injected" [suffix] GtkEntry (command) [suffix] GtkButton "Launch" AdwPreferencesGroup "Launch Options" AdwSwitchRow "Auto-reload config on save" subtitle: "Sends SIGUSR1 to running MangoHud processes on save" AdwSwitchRow "Show terminal output" subtitle: "Opens a terminal window showing app stdout/stderr" AdwPreferencesGroup "Running Process" (only visible when a test process is active) AdwActionRow showing process name + PID [suffix] GtkButton "Stop" ``` When Launch is clicked: 1. Check tool is installed (which vkcube, which glxgears). 2. If not found: `AdwToast` "vkcube not found. Install vulkan-tools package." 3. If found: spawn process with `MANGOHUD=1 MANGOHUD_CONFIGFILE={current_path} {command}`. 4. Show running process row. 5. Monitor process — remove row when it exits. --- ## Theming - Follow system theme (light/dark) automatically via libadwaita. - Do NOT hardcode colors. Use only named GTK/Adwaita CSS variables: `@accent_color`, `@destructive_color`, `@warning_color`, `@success_color`, `@card_bg_color`, `@window_bg_color`, `@headerbar_bg_color`, etc. - MangoTune-specific CSS: only for the cascade view layer badges and color swatch button. Place in `data/style.css`, loaded at runtime via `GtkCssProvider`. --- ## Accessibility - All interactive widgets must have accessible labels. - Color information must never be the sole indicator of state (always pair with icon or text). - Keyboard navigation must work for all pages (GTK4 handles most of this by default). - Use `gtk_accessible_update_property` where needed for dynamic content. --- ## Window Size & Responsiveness - Default: 1200 × 780 - Minimum: 900 × 600 - The `AdwOverlaySplitView` collapses the sidebar at narrow widths (< 980px) automatically. - Persist window size via GSettings `window-width` / `window-height`.