Initial import
This commit is contained in:
@@ -0,0 +1,628 @@
|
||||
# Phases 05–11 — Implementation Phases
|
||||
|
||||
---
|
||||
|
||||
# Phase 05 — Core Config Pages: Performance, GPU, CPU, Memory
|
||||
|
||||
## Goal
|
||||
Implement the four most-used config pages with full working controls,
|
||||
inline validation, and dependency handling. The Save button becomes functional.
|
||||
|
||||
## Files to implement (replace stubs)
|
||||
- `src/ui/pages/performance.rs`
|
||||
- `src/ui/pages/gpu.rs`
|
||||
- `src/ui/pages/cpu.rs`
|
||||
- `src/ui/pages/memory.rs`
|
||||
- `src/ui/widgets/toggle_row.rs` ← shared AdwSwitchRow wrapper
|
||||
- `src/ui/widgets/validation_label.rs` ← inline error display
|
||||
|
||||
## Application State Architecture
|
||||
|
||||
Before implementing pages, establish the central app state that all pages share.
|
||||
Add to `src/window.rs`:
|
||||
|
||||
```rust
|
||||
use std::sync::{Arc, Mutex};
|
||||
use crate::config::types::AnnotatedConfig;
|
||||
use crate::config::validator;
|
||||
|
||||
/// Shared mutable application state, passed via Arc<Mutex<>> to all pages.
|
||||
pub struct AppState {
|
||||
pub config: AnnotatedConfig,
|
||||
pub validation: HashMap<String, ValidationResult>,
|
||||
pub dirty: bool,
|
||||
}
|
||||
```
|
||||
|
||||
Use `glib::MainContext::channel()` or `Arc<Mutex<AppState>>` + GObject signals
|
||||
to communicate changes from widget callbacks to the save button sensitivity.
|
||||
|
||||
## Page Implementation Pattern
|
||||
|
||||
Every page follows this exact pattern:
|
||||
|
||||
```rust
|
||||
// src/ui/pages/gpu.rs
|
||||
|
||||
pub fn build_page(state: Arc<Mutex<AppState>>) -> libadwaita::PreferencesPage {
|
||||
let page = libadwaita::PreferencesPage::new();
|
||||
page.set_title("GPU");
|
||||
page.set_icon_name(Some("computer-symbolic"));
|
||||
|
||||
// ── GROUP: GPU Statistics ──────────────────────────────────────
|
||||
let group = libadwaita::PreferencesGroup::new();
|
||||
group.set_title("GPU Statistics");
|
||||
group.set_description(Some("Core GPU monitoring display options"));
|
||||
|
||||
// gpu_stats (master toggle)
|
||||
let gpu_stats_row = build_switch_row(
|
||||
"GPU Statistics",
|
||||
"gpu_stats — master GPU section toggle",
|
||||
"gpu_stats",
|
||||
&state,
|
||||
);
|
||||
group.add(&gpu_stats_row);
|
||||
|
||||
// gpu_temp
|
||||
let gpu_temp_row = build_switch_row("Temperature", "gpu_temp", "gpu_temp", &state);
|
||||
group.add(&gpu_temp_row);
|
||||
|
||||
// ... etc for every option in Category::DisplayGpu
|
||||
|
||||
page.add(&group);
|
||||
page
|
||||
}
|
||||
```
|
||||
|
||||
## Widget Helpers to implement in this phase
|
||||
|
||||
### `build_switch_row(title, subtitle, key, state)` → AdwSwitchRow
|
||||
1. Read current value from `state.config`
|
||||
2. Set initial active state
|
||||
3. Connect `notify::active`:
|
||||
a. Update `state.config` via `parser::set_value`
|
||||
b. Run `validator::validate_value(key, value, schema_entry)`
|
||||
c. If dependency triggered: show `AdwAlertDialog` "Enable {dep} too?"
|
||||
d. If conflict triggered: show warning toast
|
||||
e. Update save button sensitivity
|
||||
|
||||
### `build_spin_row(title, subtitle, key, min, max, state)` → AdwSpinRow
|
||||
1. Read current value, set initial
|
||||
2. Connect `notify::value`:
|
||||
a. Update state
|
||||
b. Validate
|
||||
c. Show/hide error on the row (add/remove `.error` CSS class)
|
||||
d. Update save button
|
||||
|
||||
### `build_combo_row(title, subtitle, key, variants, state)` → AdwComboRow
|
||||
|
||||
### `build_entry_row(title, subtitle, key, state)` → AdwEntryRow
|
||||
|
||||
### Validation error display on rows
|
||||
```rust
|
||||
fn set_row_error(row: &impl IsA<gtk4::Widget>, error: Option<&str>) {
|
||||
if let Some(msg) = error {
|
||||
row.add_css_class("error");
|
||||
// Update row subtitle to show error message
|
||||
} else {
|
||||
row.remove_css_class("error");
|
||||
// Restore original subtitle
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Save Button Wiring
|
||||
In `window.rs`, connect save button:
|
||||
1. Run `validator::validate_all(&state.config)` — full pass
|
||||
2. If any Error: show `AdwToast` "Fix N errors before saving", abort
|
||||
3. If all Ok: call `parser::write(&state.config)`
|
||||
4. On success: show `AdwToast` "Config saved", set `state.dirty = false`,
|
||||
make save button insensitive
|
||||
|
||||
## GPU-specific notes
|
||||
- `gpu_mem_clock` and `gpu_mem_temp`: when enabled, check if `vram` is also active.
|
||||
If not, show `AdwAlertDialog`: "GPU Memory Clock requires VRAM display to be enabled.
|
||||
Enable VRAM now?" → buttons: "Enable Both" / "Cancel".
|
||||
- `gpu_voltage`: if GPU vendor != AMD, add an `AdwBanner` warning at top of group:
|
||||
"gpu_voltage is only available on AMD GPUs. This option will have no effect."
|
||||
- Color load thresholds (gpu_load_change, gpu_load_value, gpu_load_color):
|
||||
use an `AdwExpanderRow` that expands to show threshold + color sub-rows.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] All four pages render with correct controls for every option in their category
|
||||
- [ ] Toggle switches update in-memory state immediately
|
||||
- [ ] Invalid values (e.g. font_size=999) show inline error and block save
|
||||
- [ ] Save button is insensitive when no changes or when errors exist
|
||||
- [ ] Save writes correct .conf format (key=value or bare key)
|
||||
- [ ] Comments and blank lines preserved after save
|
||||
- [ ] Dependency dialog appears when enabling gpu_mem_clock without vram
|
||||
- [ ] Vendor warning shows for gpu_voltage on non-AMD systems
|
||||
|
||||
---
|
||||
|
||||
# Phase 06 — Appearance, Colors, Typography, Layout Pages
|
||||
|
||||
## Files to implement
|
||||
- `src/ui/pages/appearance.rs`
|
||||
- `src/ui/pages/colors.rs`
|
||||
- `src/ui/pages/typography.rs`
|
||||
- `src/ui/widgets/color_row.rs`
|
||||
|
||||
## Color Row Widget (color_row.rs)
|
||||
|
||||
The color row is a custom widget used for all color options.
|
||||
|
||||
```
|
||||
AdwActionRow {
|
||||
title: "GPU Color"
|
||||
subtitle: "gpu_color — hex RRGGBB (no #)"
|
||||
[suffix] GtkButton (color swatch) ← shows current color as background
|
||||
[suffix] GtkEntry (6-char hex) ← manual entry
|
||||
}
|
||||
```
|
||||
|
||||
Color swatch button click → opens `AdwDialog`:
|
||||
```
|
||||
AdwDialog "Choose Color"
|
||||
GtkColorDialogButton ← native GTK4 color chooser
|
||||
GtkEntry showing hex ← synced with the color chooser
|
||||
[footer] GtkButton "Reset to Default"
|
||||
[footer] GtkButton "Cancel"
|
||||
[footer] GtkButton "Apply" (suggested-action)
|
||||
```
|
||||
|
||||
Validation: hex entry must match `^[0-9A-Fa-f]{6}$`. Show error inline.
|
||||
On Apply: update color swatch background, update state, validate.
|
||||
|
||||
## Colors Page
|
||||
One `AdwPreferencesGroup` per logical color section:
|
||||
- "Text & Background" (text_color, background_color, text_outline*)
|
||||
- "GPU" (gpu_color, gpu_load_color)
|
||||
- "CPU" (cpu_color, cpu_load_color)
|
||||
- "Memory" (vram_color, ram_color)
|
||||
- "Other Components" (engine_color, io_color, frametime_color, etc.)
|
||||
- "Media & Battery" (media_player_color, battery_color, wine_color, network_color)
|
||||
|
||||
At top of page: `AdwBanner` with "Tip: Colors are 6-digit hex without #. Example: FF0000 for red."
|
||||
|
||||
## Typography Page
|
||||
- font_size: AdwSpinRow (8–72)
|
||||
- font_scale: AdwSpinRow (0.1–5.0, 2 decimal digits)
|
||||
- font_size_text: AdwSpinRow
|
||||
- font_scale_media_player: AdwSpinRow
|
||||
- no_small_font: AdwSwitchRow
|
||||
- font_file: AdwEntryRow + "Browse…" button → GtkFileDialog
|
||||
- font_file_text: same
|
||||
- font_glyph_ranges: AdwExpanderRow with checkboxes for each valid range
|
||||
|
||||
## Layout & Position Page
|
||||
- position: AdwComboRow with visual position preview (simple ASCII art grid in subtitle)
|
||||
- offset_x, offset_y: AdwSpinRow
|
||||
- horizontal: AdwSwitchRow (enabling it disables position since horizontal has its own placement)
|
||||
- horizontal_stretch: AdwSwitchRow (depends on horizontal)
|
||||
- hud_compact: AdwSwitchRow
|
||||
- hud_no_margin: AdwSwitchRow
|
||||
- background_alpha: AdwSpinRow (0.0–1.0) + live preview strip showing the alpha
|
||||
- alpha: AdwSpinRow
|
||||
- width, height: AdwSpinRow
|
||||
- table_columns: AdwSpinRow (1–10)
|
||||
- cellpadding_y: AdwSpinRow (-2.0–2.0)
|
||||
- round_corners: AdwSpinRow (0–50)
|
||||
- preset: AdwComboRow with descriptions for each preset value
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] All color rows show correct color swatches
|
||||
- [ ] Invalid hex values blocked with inline error
|
||||
- [ ] File browser for font_file filters to .ttf/.otf
|
||||
- [ ] font_file path validated to exist on disk
|
||||
- [ ] horizontal_stretch disables when horizontal is off
|
||||
- [ ] All layout values saved and round-trip correctly
|
||||
|
||||
---
|
||||
|
||||
# Phase 07 — Config Conflict Cascade View Page
|
||||
|
||||
## Files to implement
|
||||
- `src/ui/pages/conflicts.rs`
|
||||
- `src/ui/widgets/cascade_view.rs`
|
||||
|
||||
## This is the most visually distinctive page in the app.
|
||||
|
||||
### Page Layout
|
||||
```
|
||||
AdwPreferencesPage "Layer Conflicts"
|
||||
|
||||
[top] GtkSearchBar + filter buttons: "All" / "Conflicts Only" / "Shadowed Only"
|
||||
|
||||
[for each layer, ordered highest priority first]:
|
||||
AdwPreferencesGroup
|
||||
title: "{layer_label}" e.g. "ENV: $MANGOHUD_CONFIG" or "~/.config/MangoHud/cs2.conf"
|
||||
header-suffix: GtkBox containing:
|
||||
- GtkLabel badge (ENV / PER-APP / GLOBAL / APP-LOCAL) with CSS class
|
||||
- GtkButton "Edit" (hidden for env layers)
|
||||
- GtkButton "Open Folder" (for file layers)
|
||||
- GtkButton "Delete Config" (destructive, requires AdwAlertDialog confirm)
|
||||
|
||||
[for each option in this layer]:
|
||||
AdwActionRow
|
||||
title: option key (monospace font)
|
||||
subtitle: option value
|
||||
[if shadowed by higher layer]:
|
||||
title gets .option-shadowed CSS class (strikethrough)
|
||||
suffix: GtkLabel "overridden by {higher_layer_name}" with .dim-label
|
||||
[if this layer wins over lower layers]:
|
||||
title: bold
|
||||
suffix: GtkImage "checkmark" icon
|
||||
|
||||
[bottom if no conflicts detected]:
|
||||
AdwStatusPage
|
||||
icon-name: "emblem-ok-symbolic"
|
||||
title: "No Conflicts"
|
||||
description: "All config layers are consistent."
|
||||
```
|
||||
|
||||
### Filter Logic
|
||||
- "All": show every layer with all their options
|
||||
- "Conflicts Only": show only layers that contain conflicting options (hidden = no conflicts)
|
||||
- "Shadowed Only": show only shadowed options across all layers
|
||||
|
||||
### Empty State (no layers found)
|
||||
```
|
||||
AdwStatusPage
|
||||
icon-name: "document-open-symbolic"
|
||||
title: "No Config Files Found"
|
||||
description: "MangoHud will use compiled defaults.\nCreate a config file to get started."
|
||||
child: GtkButton "Create Global Config" (suggested-action)
|
||||
```
|
||||
|
||||
### Clicking a key in any editable layer
|
||||
Navigate to the relevant config page with that option highlighted (scroll to it).
|
||||
Implement by passing a "highlight_key" parameter to page build functions.
|
||||
The targeted option's row briefly flashes with a CSS animation.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] All layers shown in correct priority order (highest at top)
|
||||
- [ ] ENV layers show as non-editable
|
||||
- [ ] Shadowed options have strikethrough text and "overridden by X" label
|
||||
- [ ] Winning options are visually distinct (bold)
|
||||
- [ ] Filter buttons correctly show/hide options
|
||||
- [ ] Delete config prompts for confirmation
|
||||
- [ ] Clicking a key in an editable layer navigates to its editor page
|
||||
|
||||
---
|
||||
|
||||
# Phase 08 — Keybindings, I/O, Network, Media, Battery, Logging, Misc Pages
|
||||
|
||||
## Files to implement
|
||||
- `src/ui/pages/keybindings.rs`
|
||||
- `src/ui/pages/io_network.rs`
|
||||
- `src/ui/pages/media_player.rs`
|
||||
- `src/ui/pages/battery.rs`
|
||||
- `src/ui/pages/fps_limits.rs`
|
||||
- `src/ui/pages/logging.rs`
|
||||
- `src/ui/pages/blacklist.rs`
|
||||
- `src/ui/pages/opengl_quirks.rs`
|
||||
- `src/ui/pages/raw_editor.rs`
|
||||
- `src/ui/widgets/hotkey_row.rs`
|
||||
|
||||
## Hotkey Row Widget
|
||||
|
||||
Custom widget for capturing keybindings.
|
||||
|
||||
```
|
||||
AdwActionRow
|
||||
title: "Toggle HUD"
|
||||
subtitle: "toggle_hud"
|
||||
[suffix] GtkShortcutLabel ← displays current binding e.g. "⇧R + F12"
|
||||
[suffix] GtkButton "Edit" → opens capture dialog
|
||||
[suffix] GtkButton "✕" → clears binding (sets to empty = use default)
|
||||
```
|
||||
|
||||
Capture dialog:
|
||||
```
|
||||
AdwDialog "Capture Keybind"
|
||||
AdwStatusPage
|
||||
icon-name: "input-keyboard-symbolic"
|
||||
title: "Press a key combination"
|
||||
description: "Hold modifier keys (Shift, Ctrl, Alt) then press a key"
|
||||
|
||||
[on keypress captured]:
|
||||
Shows preview: "Shift_R + F12"
|
||||
GtkButton "Accept" (suggested-action)
|
||||
GtkButton "Try Again"
|
||||
GtkButton "Cancel"
|
||||
```
|
||||
|
||||
Validation: MangoHud only supports specific modifier+key combinations.
|
||||
Valid keys: F1–F12 only (MangoHud doesn't accept alphanumeric hotkeys).
|
||||
If invalid combination captured, show error label and keep "Accept" insensitive.
|
||||
|
||||
## FPS Limits Page
|
||||
|
||||
Special widget for fps_limit (comma-separated list of FPS values):
|
||||
|
||||
```
|
||||
AdwPreferencesGroup "FPS Limit Values"
|
||||
description: "Comma-separated list. 0 = unlimited. Toggle between values with Shift_L+F1."
|
||||
|
||||
[custom widget: FpsChipList]
|
||||
GtkFlowBox showing current values as removable chips:
|
||||
[0] [30] [60] [+]
|
||||
Each chip: GtkLabel + GtkButton "×"
|
||||
"+" button: opens inline entry to add new value
|
||||
Validation: each value must be non-negative integer
|
||||
Values auto-sorted ascending when saved
|
||||
|
||||
AdwPreferencesGroup "FPS Limit Method"
|
||||
AdwComboRow "Method" (fps_limit_method: early/late/"")
|
||||
|
||||
AdwPreferencesGroup "VSync"
|
||||
AdwComboRow "Vulkan VSync" (vsync: -1/0/1/2/3 with labels)
|
||||
AdwComboRow "OpenGL VSync" (gl_vsync with labels)
|
||||
```
|
||||
|
||||
## Logging Page
|
||||
|
||||
All logging options with particular attention to:
|
||||
- `output_folder`: path must be validated as an existing writable directory
|
||||
Use AdwEntryRow + "Browse…" button → GtkFileDialog in FOLDER mode
|
||||
- `permit_upload`: when toggled off, also disable `upload_logs`
|
||||
- `output_file`: free string entry
|
||||
|
||||
## Raw Editor Page
|
||||
|
||||
A `GtkTextView` showing the current config file content as raw text.
|
||||
- Monospace font
|
||||
- Syntax highlighting: comments in muted color, keys in accent color, values in text color
|
||||
(use GtkTextTag for basic highlighting — no external syntax highlighter dep)
|
||||
- Changes in raw editor update the in-memory AnnotatedConfig via re-parsing on focus-out
|
||||
- Warning banner at top: "Changes here bypass validation. Errors may prevent MangoHud from loading."
|
||||
- Show line count and option count in footer
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] Hotkey capture works and validates MangoHud-compatible combinations
|
||||
- [ ] FPS limit chips can be added and removed
|
||||
- [ ] Logging output_folder validated as writable directory
|
||||
- [ ] Raw editor shows current config content
|
||||
- [ ] Raw editor changes re-parsed on focus-out
|
||||
- [ ] All page options save correctly
|
||||
|
||||
---
|
||||
|
||||
# Phase 09 — Test Launcher
|
||||
|
||||
## Files to implement
|
||||
- `src/launcher/runner.rs` (replace stub)
|
||||
- `src/ui/pages/overview.rs` (implements the overview/dashboard)
|
||||
- (Test launcher UI is part of a dedicated page — see docs/design_system.md)
|
||||
|
||||
## runner.rs
|
||||
|
||||
```rust
|
||||
use tokio::process::{Command, Child};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct LaunchConfig {
|
||||
pub command: String,
|
||||
pub args: Vec<String>,
|
||||
pub config_path: PathBuf,
|
||||
pub show_terminal: bool,
|
||||
}
|
||||
|
||||
pub struct RunningProcess {
|
||||
pub child: Child,
|
||||
pub command: String,
|
||||
pub pid: u32,
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
/// Check if a tool is available on PATH.
|
||||
pub fn is_available(tool: &str) -> bool
|
||||
|
||||
/// Launch a tool with MANGOHUD=1 and the specified config file.
|
||||
pub async fn launch(config: LaunchConfig) -> anyhow::Result<RunningProcess>
|
||||
|
||||
/// Stop a running process (SIGTERM, then SIGKILL after 3s).
|
||||
pub async fn stop(process: RunningProcess) -> anyhow::Result<()>
|
||||
|
||||
/// Send SIGUSR1 to reload config in a running MangoHud process.
|
||||
pub async fn reload_config(pid: u32) -> anyhow::Result<()>
|
||||
}
|
||||
```
|
||||
|
||||
Launch environment:
|
||||
```
|
||||
MANGOHUD=1
|
||||
MANGOHUD_CONFIGFILE={config_path}
|
||||
```
|
||||
|
||||
If `show_terminal=true`: spawn via `xterm -e "{command}"` or detect default terminal
|
||||
(`$TERM`, then try: `gnome-terminal`, `konsole`, `xfce4-terminal`, `xterm` in that order).
|
||||
|
||||
## Overview Page
|
||||
|
||||
Dashboard shown on first launch (app startup default page).
|
||||
```
|
||||
AdwPreferencesPage "Overview"
|
||||
|
||||
[if MangoHud not installed]:
|
||||
AdwStatusPage
|
||||
icon-name: "dialog-warning-symbolic"
|
||||
title: "MangoHud Not Found"
|
||||
description: "Install MangoHud to use this app.\n\n
|
||||
Ubuntu/Debian: sudo apt install mangohud\n
|
||||
Fedora: sudo dnf install mangohud\n
|
||||
Arch: sudo pacman -S mangohud"
|
||||
|
||||
[if MangoHud installed]:
|
||||
AdwPreferencesGroup "System Status"
|
||||
AdwActionRow "MangoHud" [suffix: version label + green checkmark]
|
||||
AdwActionRow "Display" [suffix: "Wayland" or "X11"]
|
||||
AdwActionRow "GPU" [suffix: GPU name]
|
||||
AdwActionRow "Active Config" [suffix: current config path]
|
||||
|
||||
AdwPreferencesGroup "Quick Actions"
|
||||
AdwButtonRow "Launch vkcube test" → triggers launcher
|
||||
AdwButtonRow "Open Config Folder" → xdg-open ~/.config/MangoHud/
|
||||
AdwButtonRow "View Config Conflicts" → navigate to conflicts page
|
||||
AdwButtonRow "Reset to Defaults" (destructive — AdwAlertDialog confirm)
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] vkcube launches with MANGOHUD=1 and current config path
|
||||
- [ ] glxgears launches with MANGOHUD=1
|
||||
- [ ] Custom app entry accepts any shell command
|
||||
- [ ] Running process shown with PID and Stop button
|
||||
- [ ] Stop sends SIGTERM, escalates to SIGKILL after 3s
|
||||
- [ ] "Tool not found" toast shown if vkcube/glxgears missing
|
||||
- [ ] Overview shows correct system info from detect::detect_system()
|
||||
|
||||
---
|
||||
|
||||
# Phase 10 — Integrations Page
|
||||
|
||||
## Files to implement
|
||||
- `src/integrations/gamemode.rs` (replace stub)
|
||||
- `src/integrations/steam.rs` (replace stub)
|
||||
- `src/integrations/lutris.rs` (replace stub)
|
||||
- `src/integrations/heroic.rs` (replace stub)
|
||||
- `src/ui/pages/integrations.rs` (new file, not a stub page)
|
||||
|
||||
## Implementation follows docs/integrations.md exactly.
|
||||
|
||||
## Key implementation notes:
|
||||
|
||||
### Threading
|
||||
All file I/O and process detection in integrations must run on tokio, not the GTK main thread.
|
||||
Pattern:
|
||||
```rust
|
||||
let (sender, receiver) = glib::MainContext::channel(glib::Priority::DEFAULT);
|
||||
tokio::spawn(async move {
|
||||
let status = gamemode::detect().await;
|
||||
sender.send(status).ok();
|
||||
});
|
||||
receiver.attach(None, move |status| {
|
||||
update_ui_with_status(status);
|
||||
glib::ControlFlow::Break
|
||||
});
|
||||
```
|
||||
|
||||
### Heroic JSON parsing
|
||||
Parse `~/.config/heroic/GamesConfig/*.json` using serde_json.
|
||||
Handle both native and Flatpak paths (try both, use whichever exists).
|
||||
When writing back: preserve all fields not managed by MangoTune.
|
||||
Use `serde_json::Value` for the full document to avoid losing unknown fields.
|
||||
|
||||
### Lutris YAML parsing
|
||||
Lutris game configs are simple YAML. Do NOT add a full YAML parser dependency.
|
||||
Instead, use targeted line-by-line parsing:
|
||||
- Find the `system:` section
|
||||
- Find or add `mangohud: true/false` under it
|
||||
- For reading: look for `mangohud: true` line in file
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] GameMode section shows daemon running/stopped status
|
||||
- [ ] Steam launch option generator produces correct command strings
|
||||
- [ ] Flatpak Steam detected and generates different command
|
||||
- [ ] Copy to clipboard button works for generated Steam command
|
||||
- [ ] Lutris games enumerated from ~/.config/lutris/games/*.yml
|
||||
- [ ] Enabling MangoHud for Lutris game writes correct YAML
|
||||
- [ ] Heroic games enumerated from GamesConfig/*.json
|
||||
- [ ] Enabling MangoHud for Heroic game writes correct JSON (no data loss)
|
||||
- [ ] All integration sections show "Not installed" gracefully when tool absent
|
||||
|
||||
---
|
||||
|
||||
# Phase 11 — Polish, Packaging & Final QA
|
||||
|
||||
## Goals
|
||||
Final quality pass, packaging, and ensuring the app is ready for distribution.
|
||||
|
||||
## Tasks
|
||||
|
||||
### 1. Window state persistence
|
||||
- Save/restore window width+height via GSettings
|
||||
- Save/restore last-edited config path
|
||||
- Save/restore active sidebar page
|
||||
|
||||
### 2. Keyboard shortcuts
|
||||
Register app-level shortcuts:
|
||||
- `Ctrl+S` → Save
|
||||
- `Ctrl+Z` → Undo last change (basic: revert to last saved state)
|
||||
- `Ctrl+Shift+Z` → Redo
|
||||
- `Ctrl+R` → Reload config from disk
|
||||
- `Ctrl+W` → Close (prompts if unsaved changes)
|
||||
- `Ctrl+,` → Preferences
|
||||
- `F5` → Refresh system detection
|
||||
|
||||
Show shortcuts in AdwShortcutsWindow (accessible from gear menu).
|
||||
|
||||
### 3. Unsaved changes guard
|
||||
On window close attempt with unsaved changes:
|
||||
```
|
||||
AdwAlertDialog "Unsaved Changes"
|
||||
body: "You have unsaved changes to {config_name}. What would you like to do?"
|
||||
buttons:
|
||||
"Discard Changes" (destructive)
|
||||
"Cancel"
|
||||
"Save" (suggested-action)
|
||||
```
|
||||
|
||||
### 4. External config change detection
|
||||
Use the `notify` crate to watch config files for changes.
|
||||
If a watched file changes on disk while app is open:
|
||||
```
|
||||
AdwBanner (persistent until dismissed):
|
||||
"Config file changed externally. Reload to see changes."
|
||||
[button] "Reload"
|
||||
```
|
||||
|
||||
### 5. AppStream metadata
|
||||
Create `data/com.mangotune.MangoTune.metainfo.xml` following AppStream spec.
|
||||
|
||||
### 6. Install targets (for Makefile / meson)
|
||||
```makefile
|
||||
PREFIX ?= /usr
|
||||
BINDIR = $(PREFIX)/bin
|
||||
DATADIR = $(PREFIX)/share
|
||||
|
||||
install:
|
||||
install -Dm755 target/release/mangotune $(DESTDIR)$(BINDIR)/mangotune
|
||||
install -Dm644 data/com.mangotune.MangoTune.desktop \
|
||||
$(DESTDIR)$(DATADIR)/applications/com.mangotune.MangoTune.desktop
|
||||
install -Dm644 data/com.mangotune.MangoTune.gschema.xml \
|
||||
$(DESTDIR)$(DATADIR)/glib-2.0/schemas/com.mangotune.MangoTune.gschema.xml
|
||||
install -Dm644 data/icons/com.mangotune.MangoTune.svg \
|
||||
$(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps/com.mangotune.MangoTune.svg
|
||||
glib-compile-schemas $(DESTDIR)$(DATADIR)/glib-2.0/schemas/
|
||||
```
|
||||
|
||||
### 7. README.md for the actual project
|
||||
Write a user-facing README covering:
|
||||
- What MangoTune is and why it's better than GOverlay
|
||||
- Installation (distro packages + build from source)
|
||||
- How config priority works
|
||||
- How to use the test launcher
|
||||
- How to contribute
|
||||
|
||||
### 8. Final QA checklist
|
||||
- [ ] `cargo clippy -- -D warnings` passes with zero warnings
|
||||
- [ ] `cargo test` passes all tests
|
||||
- [ ] App launches cleanly on X11
|
||||
- [ ] App launches cleanly on Wayland
|
||||
- [ ] All 120+ MangoHud options save and load correctly (write a test config, load it, verify)
|
||||
- [ ] Config with comments round-trips without destroying comments
|
||||
- [ ] Validation blocks all invalid values across all types
|
||||
- [ ] Dependency auto-enable works for all dependency pairs in schema
|
||||
- [ ] Conflict detection finds overlapping options across all layer combinations
|
||||
- [ ] Heroic integration writes JSON without data loss
|
||||
- [ ] Lutris integration writes YAML without data loss
|
||||
- [ ] vkcube + glxgears launch with correct environment
|
||||
- [ ] App handles MangoHud not installed gracefully (no crash)
|
||||
- [ ] Window resize / sidebar collapse works correctly
|
||||
- [ ] All keyboard shortcuts function
|
||||
- [ ] Unsaved changes guard fires on close
|
||||
- [ ] External file change detection triggers reload banner
|
||||
- [ ] About dialog shows correct version
|
||||
- [ ] .desktop file launches app correctly
|
||||
- [ ] GSettings schema installs and compiles correctly
|
||||
Reference in New Issue
Block a user