Replace single NavigationView with ViewSwitcher (Installed, Catalog, Updates)

Restructure the app from a single NavigationView into an AdwViewStack
with 3 top-level pages: Installed, Catalog, and Updates. Each page
keeps its own header bar while an AdwViewSwitcherBar at the bottom
provides tab-style navigation between them. The Installed page retains
its own NavigationView for detail drill-down. The catalog action now
switches the ViewStack tab instead of pushing a page.
This commit is contained in:
lashman
2026-02-28 01:33:43 +02:00
parent 3eb15af2c6
commit b23f9e14f8

View File

@@ -27,6 +27,7 @@ use crate::ui::library_view::{LibraryState, LibraryView};
use crate::ui::preferences;
use crate::ui::security_report;
use crate::ui::update_dialog;
use crate::ui::updates_view;
use crate::ui::widgets;
mod imp {
@@ -35,7 +36,8 @@ mod imp {
pub struct DriftwoodWindow {
pub settings: OnceCell<gio::Settings>,
pub toast_overlay: OnceCell<adw::ToastOverlay>,
pub navigation_view: OnceCell<adw::NavigationView>,
pub view_stack: OnceCell<adw::ViewStack>,
pub installed_nav: OnceCell<adw::NavigationView>,
pub library_view: OnceCell<LibraryView>,
pub database: OnceCell<Rc<Database>>,
pub drop_overlay: OnceCell<gtk::Box>,
@@ -48,7 +50,8 @@ mod imp {
Self {
settings: OnceCell::new(),
toast_overlay: OnceCell::new(),
navigation_view: OnceCell::new(),
view_stack: OnceCell::new(),
installed_nav: OnceCell::new(),
library_view: OnceCell::new(),
database: OnceCell::new(),
drop_overlay: OnceCell::new(),
@@ -162,9 +165,41 @@ impl DriftwoodWindow {
// Library view (contains header bar, search, grid/list, empty state)
let library_view = LibraryView::new(&menu);
// Navigation view
let navigation_view = adw::NavigationView::new();
navigation_view.push(&library_view.page);
// Installed view: NavigationView for drill-down (detail, dashboard, etc.)
let installed_nav = adw::NavigationView::new();
installed_nav.push(&library_view.page);
// Catalog view
let catalog_page = catalog_view::build_catalog_page(self.database());
// Updates view
let updates_toolbar = updates_view::build_updates_view(self.database());
// ViewStack with 3 top-level pages
let view_stack = adw::ViewStack::new();
view_stack.set_vexpand(true);
let installed_vs_page = view_stack.add_titled(&installed_nav, Some("installed"), &i18n("Installed"));
installed_vs_page.set_icon_name(Some("view-grid-symbolic"));
let catalog_vs_page = view_stack.add_titled(&catalog_page, Some("catalog"), &i18n("Catalog"));
catalog_vs_page.set_icon_name(Some("system-software-install-symbolic"));
let updates_vs_page = view_stack.add_titled(&updates_toolbar, Some("updates"), &i18n("Updates"));
updates_vs_page.set_icon_name(Some("software-update-available-symbolic"));
// ViewSwitcherBar at the bottom for tab navigation
let view_switcher_bar = adw::ViewSwitcherBar::builder()
.stack(&view_stack)
.reveal(true)
.build();
// Main content box: ViewStack + bottom switcher bar
let main_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.build();
main_box.append(&view_stack);
main_box.append(&view_switcher_bar);
// Drop overlay - centered opaque card over a dimmed scrim
let drop_overlay_icon = gtk::Image::builder()
@@ -253,9 +288,9 @@ impl DriftwoodWindow {
drop_overlay_content.add_controller(click);
}
// Overlay wraps navigation view so the drop indicator sits on top
// Overlay wraps main content so the drop indicator sits on top
let overlay = gtk::Overlay::new();
overlay.set_child(Some(&navigation_view));
overlay.set_child(Some(&main_box));
overlay.add_overlay(&drop_overlay_content);
// Toast overlay wraps the overlay
@@ -353,7 +388,7 @@ impl DriftwoodWindow {
// Wire up card/row activation to push detail view (or toggle selection)
{
let nav = navigation_view.clone();
let nav = installed_nav.clone();
let db = self.database().clone();
let window_weak = self.downgrade();
library_view.connect_grid_activated(move |record_id| {
@@ -371,7 +406,7 @@ impl DriftwoodWindow {
});
}
{
let nav = navigation_view.clone();
let nav = installed_nav.clone();
let db = self.database().clone();
let window_weak = self.downgrade();
library_view.connect_list_activated(move |record_id| {
@@ -394,7 +429,7 @@ impl DriftwoodWindow {
{
let db = self.database().clone();
let window_weak = self.downgrade();
navigation_view.connect_popped(move |_nav, page| {
installed_nav.connect_popped(move |_nav, page| {
if page.tag().as_deref() == Some("detail") {
if let Some(window) = window_weak.upgrade() {
// Update window title for accessibility (WCAG 2.4.8)
@@ -413,7 +448,7 @@ impl DriftwoodWindow {
// Update window title when navigating to sub-pages (WCAG 2.4.8 Location)
{
let window_weak = self.downgrade();
navigation_view.connect_pushed(move |nav| {
installed_nav.connect_pushed(move |nav| {
if let Some(window) = window_weak.upgrade() {
if let Some(page) = nav.visible_page() {
let page_title = page.title();
@@ -439,9 +474,13 @@ impl DriftwoodWindow {
.set(toast_overlay)
.expect("ToastOverlay already set");
self.imp()
.navigation_view
.set(navigation_view)
.expect("NavigationView already set");
.view_stack
.set(view_stack)
.expect("ViewStack already set");
self.imp()
.installed_nav
.set(installed_nav)
.expect("InstalledNav already set");
if self.imp().library_view.set(library_view).is_err() {
panic!("LibraryView already set");
}
@@ -454,7 +493,7 @@ impl DriftwoodWindow {
let dashboard_action = gio::ActionEntry::builder("dashboard")
.activate(|window: &Self, _, _| {
let db = window.database().clone();
let nav = window.imp().navigation_view.get().unwrap();
let nav = window.imp().installed_nav.get().unwrap();
let page = dashboard::build_dashboard_page(&db);
nav.push(&page);
})
@@ -554,7 +593,7 @@ impl DriftwoodWindow {
let security_report_action = gio::ActionEntry::builder("security-report")
.activate(|window: &Self, _, _| {
let db = window.database().clone();
let nav = window.imp().navigation_view.get().unwrap();
let nav = window.imp().installed_nav.get().unwrap();
let page = security_report::build_security_report_page(&db);
nav.push(&page);
})
@@ -568,13 +607,11 @@ impl DriftwoodWindow {
})
.build();
// Catalog browser action
// Catalog browser action - switches to catalog tab
let catalog_action = gio::ActionEntry::builder("catalog")
.activate(|window: &Self, _, _| {
let db = window.database().clone();
let catalog_page = catalog_view::build_catalog_page(&db);
let nav = window.imp().navigation_view.get().unwrap();
nav.push(&catalog_page);
let view_stack = window.imp().view_stack.get().unwrap();
view_stack.set_visible_child_name("catalog");
})
.build();