Initial import

This commit is contained in:
2026-03-30 22:51:56 -04:00
commit 08e2910b9d
103 changed files with 35475 additions and 0 deletions
+341
View File
@@ -0,0 +1,341 @@
# 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: <bool>,
}
```
On toggle: validate, update model, check dependencies.
### Int with range → `AdwSpinRow`
```
AdwSpinRow {
title: "Font Size",
subtitle: "font_size — valid range: 872",
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`.