# 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.rs` - `src/ui/pages/mod.rs` - `src/ui/widgets/mod.rs` - `data/style.css` --- ## src/app.rs ```rust 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 ```rust 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: ```rust 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. ```css /* 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