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::preferences;
use crate::ui::security_report; use crate::ui::security_report;
use crate::ui::update_dialog; use crate::ui::update_dialog;
use crate::ui::updates_view;
use crate::ui::widgets; use crate::ui::widgets;
mod imp { mod imp {
@@ -35,7 +36,8 @@ mod imp {
pub struct DriftwoodWindow { pub struct DriftwoodWindow {
pub settings: OnceCell<gio::Settings>, pub settings: OnceCell<gio::Settings>,
pub toast_overlay: OnceCell<adw::ToastOverlay>, 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 library_view: OnceCell<LibraryView>,
pub database: OnceCell<Rc<Database>>, pub database: OnceCell<Rc<Database>>,
pub drop_overlay: OnceCell<gtk::Box>, pub drop_overlay: OnceCell<gtk::Box>,
@@ -48,7 +50,8 @@ mod imp {
Self { Self {
settings: OnceCell::new(), settings: OnceCell::new(),
toast_overlay: OnceCell::new(), toast_overlay: OnceCell::new(),
navigation_view: OnceCell::new(), view_stack: OnceCell::new(),
installed_nav: OnceCell::new(),
library_view: OnceCell::new(), library_view: OnceCell::new(),
database: OnceCell::new(), database: OnceCell::new(),
drop_overlay: OnceCell::new(), drop_overlay: OnceCell::new(),
@@ -162,9 +165,41 @@ impl DriftwoodWindow {
// Library view (contains header bar, search, grid/list, empty state) // Library view (contains header bar, search, grid/list, empty state)
let library_view = LibraryView::new(&menu); let library_view = LibraryView::new(&menu);
// Navigation view // Installed view: NavigationView for drill-down (detail, dashboard, etc.)
let navigation_view = adw::NavigationView::new(); let installed_nav = adw::NavigationView::new();
navigation_view.push(&library_view.page); 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 // Drop overlay - centered opaque card over a dimmed scrim
let drop_overlay_icon = gtk::Image::builder() let drop_overlay_icon = gtk::Image::builder()
@@ -253,9 +288,9 @@ impl DriftwoodWindow {
drop_overlay_content.add_controller(click); 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(); let overlay = gtk::Overlay::new();
overlay.set_child(Some(&navigation_view)); overlay.set_child(Some(&main_box));
overlay.add_overlay(&drop_overlay_content); overlay.add_overlay(&drop_overlay_content);
// Toast overlay wraps the overlay // Toast overlay wraps the overlay
@@ -353,7 +388,7 @@ impl DriftwoodWindow {
// Wire up card/row activation to push detail view (or toggle selection) // 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 db = self.database().clone();
let window_weak = self.downgrade(); let window_weak = self.downgrade();
library_view.connect_grid_activated(move |record_id| { 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 db = self.database().clone();
let window_weak = self.downgrade(); let window_weak = self.downgrade();
library_view.connect_list_activated(move |record_id| { library_view.connect_list_activated(move |record_id| {
@@ -394,7 +429,7 @@ impl DriftwoodWindow {
{ {
let db = self.database().clone(); let db = self.database().clone();
let window_weak = self.downgrade(); 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 page.tag().as_deref() == Some("detail") {
if let Some(window) = window_weak.upgrade() { if let Some(window) = window_weak.upgrade() {
// Update window title for accessibility (WCAG 2.4.8) // 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) // Update window title when navigating to sub-pages (WCAG 2.4.8 Location)
{ {
let window_weak = self.downgrade(); 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(window) = window_weak.upgrade() {
if let Some(page) = nav.visible_page() { if let Some(page) = nav.visible_page() {
let page_title = page.title(); let page_title = page.title();
@@ -439,9 +474,13 @@ impl DriftwoodWindow {
.set(toast_overlay) .set(toast_overlay)
.expect("ToastOverlay already set"); .expect("ToastOverlay already set");
self.imp() self.imp()
.navigation_view .view_stack
.set(navigation_view) .set(view_stack)
.expect("NavigationView already set"); .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() { if self.imp().library_view.set(library_view).is_err() {
panic!("LibraryView already set"); panic!("LibraryView already set");
} }
@@ -454,7 +493,7 @@ impl DriftwoodWindow {
let dashboard_action = gio::ActionEntry::builder("dashboard") let dashboard_action = gio::ActionEntry::builder("dashboard")
.activate(|window: &Self, _, _| { .activate(|window: &Self, _, _| {
let db = window.database().clone(); 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); let page = dashboard::build_dashboard_page(&db);
nav.push(&page); nav.push(&page);
}) })
@@ -554,7 +593,7 @@ impl DriftwoodWindow {
let security_report_action = gio::ActionEntry::builder("security-report") let security_report_action = gio::ActionEntry::builder("security-report")
.activate(|window: &Self, _, _| { .activate(|window: &Self, _, _| {
let db = window.database().clone(); 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); let page = security_report::build_security_report_page(&db);
nav.push(&page); nav.push(&page);
}) })
@@ -568,13 +607,11 @@ impl DriftwoodWindow {
}) })
.build(); .build();
// Catalog browser action // Catalog browser action - switches to catalog tab
let catalog_action = gio::ActionEntry::builder("catalog") let catalog_action = gio::ActionEntry::builder("catalog")
.activate(|window: &Self, _, _| { .activate(|window: &Self, _, _| {
let db = window.database().clone(); let view_stack = window.imp().view_stack.get().unwrap();
let catalog_page = catalog_view::build_catalog_page(&db); view_stack.set_visible_child_name("catalog");
let nav = window.imp().navigation_view.get().unwrap();
nav.push(&catalog_page);
}) })
.build(); .build();