7.3 KiB
7.3 KiB
Phase 04 — GTK4 App Skeleton & Main Window
Goal
Build the complete application shell: window, header bar, config selector bar, sidebar navigation, and empty page placeholders. All navigation must work. No option editing yet — pages show "Coming soon" content.
Files to implement
src/app.rs(replace stub)src/window.rs(replace stub)src/ui/mod.rssrc/ui/pages/mod.rssrc/ui/widgets/mod.rsdata/style.css
src/app.rs
use gtk4::prelude::*;
use libadwaita::prelude::*;
use crate::window::MainWindow;
use crate::system::detect;
pub struct MangoTuneApp {
app: libadwaita::Application,
}
impl MangoTuneApp {
pub fn new() -> Self {
let app = libadwaita::Application::builder()
.application_id("com.mangotune.MangoTune")
.flags(gio::ApplicationFlags::FLAGS_NONE)
.build();
let app_clone = app.clone();
app.connect_activate(move |_| {
// Load CSS
let provider = gtk4::CssProvider::new();
provider.load_from_data(include_str!("../data/style.css"));
gtk4::style_context_add_provider_for_display(
&gdk::Display::default().expect("No display"),
&provider,
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
// Run system detection async, then build window
let ctx = glib::MainContext::default();
ctx.spawn_local(async move {
let system_info = detect::detect_system().await
.unwrap_or_else(|_| detect::SystemInfo::unknown());
let window = MainWindow::new(&app_clone, system_info);
window.present();
});
});
MangoTuneApp { app }
}
pub fn run(&self) -> i32 {
self.app.run().into()
}
}
src/window.rs
use gtk4::prelude::*;
use libadwaita::prelude::*;
use crate::system::detect::SystemInfo;
use crate::ui::pages;
pub struct MainWindow {
pub window: libadwaita::ApplicationWindow,
}
impl MainWindow {
pub fn new(app: &libadwaita::Application, system_info: SystemInfo) -> Self {
let window = libadwaita::ApplicationWindow::builder()
.application(app)
.title("MangoTune")
.default_width(1200)
.default_height(780)
.build();
// Restore window size from GSettings
// Build layout:
// AdwToolbarView
// top: AdwHeaderBar
// top: ConfigBarWidget (custom)
// content: AdwOverlaySplitView
// sidebar: navigation list
// content: AdwNavigationView (page stack)
let toolbar_view = libadwaita::ToolbarView::new();
let header = build_header_bar();
let config_bar = build_config_bar(&system_info);
let split_view = build_split_view(&system_info);
toolbar_view.add_top_bar(&header);
toolbar_view.add_top_bar(&config_bar);
toolbar_view.set_content(Some(&split_view));
window.set_content(Some(&toolbar_view));
MainWindow { window }
}
pub fn present(&self) { self.window.present(); }
}
Header Bar implementation
AdwHeaderBar
title-widget: AdwWindowTitle { title: "MangoTune", subtitle: "No config loaded" }
start: AdwSplitButton "Save" (insensitive by default)
dropdown items: "Save As…", "Revert to Saved", "Create Backup"
end: GtkMenuButton (gear icon)
popover menu: "Preferences", "Keyboard Shortcuts", "About MangoTune"
Config Bar implementation
Custom GtkBox with @card_bg_color background:
GtkBox (horizontal, spacing=8, margin=6)
GtkImage (config type icon — globe/per-app/warning)
GtkLabel "Editing:"
GtkDropDown ← lists all discovered config layers
model: StringList populated from resolver
GtkLabel "⚠ 2 conflicts" (hidden if no conflicts)
GtkButton "View All Layers" → navigate to conflicts page
Split View implementation
AdwOverlaySplitView
sidebar-width-fraction: 0.22
min-sidebar-width: 180
max-sidebar-width: 260
show-sidebar: true
collapsed at width < 980
sidebar: NavigationSidebar (GtkListBox with .navigation-sidebar CSS class)
content: AdwNavigationView ← all pages pushed here
Navigation Sidebar
The sidebar is a GtkListBox with rows grouped by sections.
See docs/design_system.md → Sidebar Navigation for the full list of sections and items.
Each row stores the page ID as data. On row activation:
- Call
navigation_view.push_by_tag(page_id) - Highlight the active row
Section headers: GtkLabel with .heading CSS class, not selectable.
Page Stubs (all pages in this phase return placeholder content)
Create all page files listed in src/ui/pages/ with a stub that returns:
pub fn build_page() -> libadwaita::PreferencesPage {
let page = libadwaita::PreferencesPage::new();
page.set_title("Page Name");
let group = libadwaita::PreferencesGroup::new();
group.set_title("Coming Soon");
group.set_description(Some("This page will be implemented in a future phase."));
page.add(&group);
page
}
About Dialog
AdwAboutDialog with:
application-name: "MangoTune"
application-icon: "com.mangotune.MangoTune"
version: env!("CARGO_PKG_VERSION")
comments: "A modern, accurate MangoHud configurator for Linux"
license-type: Gtk::License::Gpl30
website: "https://github.com/your-org/mangotune"
issue-url: "https://github.com/your-org/mangotune/issues"
developers: ["MangoTune Contributors"]
data/style.css
Only custom styles that libadwaita/GTK4 don't provide natively.
/* Config layer priority badges */
.layer-badge-env {
background-color: @destructive_color;
color: @destructive_fg_color;
border-radius: 4px;
padding: 2px 6px;
font-size: 0.75em;
font-weight: bold;
}
.layer-badge-perapp {
background-color: @warning_color;
color: @warning_fg_color;
border-radius: 4px;
padding: 2px 6px;
font-size: 0.75em;
font-weight: bold;
}
.layer-badge-global {
background-color: @success_color;
color: @success_fg_color;
border-radius: 4px;
padding: 2px 6px;
font-size: 0.75em;
font-weight: bold;
}
/* Shadowed option text in cascade view */
.option-shadowed {
text-decoration: line-through;
opacity: 0.5;
}
/* Color swatch button */
.color-swatch-button {
min-width: 28px;
min-height: 28px;
border-radius: 4px;
border: 1px solid @borders;
padding: 0;
}
/* Config bar */
.config-bar {
background-color: @card_bg_color;
border-bottom: 1px solid @borders;
padding: 6px 12px;
}
/* Conflict count badge */
.conflict-badge {
background-color: @destructive_color;
color: @destructive_fg_color;
border-radius: 8px;
padding: 1px 6px;
font-size: 0.75em;
font-weight: bold;
}
Acceptance Criteria
- App launches without crashing
- Window appears at 1200×780
- Header bar shows "MangoTune" title and (insensitive) Save button
- Config bar renders below header
- Sidebar shows all navigation sections with correct icons
- Clicking each sidebar item navigates to its placeholder page
- About dialog opens from gear menu
- System detection runs on startup (check tracing log output)
- App responds to window resize (sidebar collapses at narrow width)
- No GTK warnings or critical messages in stderr on launch