Add main window with sidebar navigation

AdwNavigationSplitView with six sidebar items (Log, History, Charts,
Budgets, Recurring, Settings) and placeholder StatusPage content that
switches via crossfade transition on selection.
This commit is contained in:
2026-03-02 00:02:53 +02:00
parent a5f578844f
commit 6daec1ea38
2 changed files with 126 additions and 8 deletions

View File

@@ -1,3 +1,5 @@
mod window;
use adw::prelude::*; use adw::prelude::*;
use adw::Application; use adw::Application;
@@ -13,12 +15,6 @@ fn main() {
} }
fn build_ui(app: &Application) { fn build_ui(app: &Application) {
let window = adw::ApplicationWindow::builder() let main_window = window::MainWindow::new(app);
.application(app) main_window.window.present();
.title("Outlay")
.default_width(900)
.default_height(600)
.build();
window.present();
} }

122
outlay-gtk/src/window.rs Normal file
View File

@@ -0,0 +1,122 @@
use adw::prelude::*;
pub struct MainWindow {
pub window: adw::ApplicationWindow,
pub split_view: adw::NavigationSplitView,
pub content_stack: gtk::Stack,
}
struct SidebarItem {
id: &'static str,
label: &'static str,
icon: &'static str,
}
const SIDEBAR_ITEMS: &[SidebarItem] = &[
SidebarItem { id: "log", label: "Log", icon: "list-add-symbolic" },
SidebarItem { id: "history", label: "History", icon: "document-open-recent-symbolic" },
SidebarItem { id: "charts", label: "Charts", icon: "utilities-system-monitor-symbolic" },
SidebarItem { id: "budgets", label: "Budgets", icon: "wallet2-symbolic" },
SidebarItem { id: "recurring", label: "Recurring", icon: "view-refresh-symbolic" },
SidebarItem { id: "settings", label: "Settings", icon: "emblem-system-symbolic" },
];
impl MainWindow {
pub fn new(app: &adw::Application) -> Self {
let content_stack = gtk::Stack::new();
content_stack.set_transition_type(gtk::StackTransitionType::Crossfade);
for item in SIDEBAR_ITEMS {
let page = adw::StatusPage::builder()
.title(item.label)
.icon_name(item.icon)
.build();
content_stack.add_named(&page, Some(item.id));
}
let sidebar_list = gtk::ListBox::new();
sidebar_list.set_selection_mode(gtk::SelectionMode::Single);
sidebar_list.add_css_class("navigation-sidebar");
for item in SIDEBAR_ITEMS {
let row = Self::make_sidebar_row(item);
sidebar_list.append(&row);
}
let content_stack_ref = content_stack.clone();
sidebar_list.connect_row_selected(move |_, row| {
if let Some(row) = row {
let idx = row.index() as usize;
if idx < SIDEBAR_ITEMS.len() {
content_stack_ref.set_visible_child_name(SIDEBAR_ITEMS[idx].id);
}
}
});
// Select the first row by default
if let Some(first_row) = sidebar_list.row_at_index(0) {
sidebar_list.select_row(Some(&first_row));
}
let sidebar_scroll = gtk::ScrolledWindow::builder()
.hscrollbar_policy(gtk::PolicyType::Never)
.vexpand(true)
.child(&sidebar_list)
.build();
let sidebar_toolbar = adw::ToolbarView::new();
sidebar_toolbar.add_top_bar(&adw::HeaderBar::new());
sidebar_toolbar.set_content(Some(&sidebar_scroll));
let sidebar_page = adw::NavigationPage::builder()
.title("Outlay")
.child(&sidebar_toolbar)
.build();
let content_toolbar = adw::ToolbarView::new();
content_toolbar.add_top_bar(&adw::HeaderBar::new());
content_toolbar.set_content(Some(&content_stack));
let content_page = adw::NavigationPage::builder()
.title("Outlay")
.child(&content_toolbar)
.build();
let split_view = adw::NavigationSplitView::new();
split_view.set_sidebar(Some(&sidebar_page));
split_view.set_content(Some(&content_page));
let window = adw::ApplicationWindow::builder()
.application(app)
.title("Outlay")
.default_width(900)
.default_height(600)
.content(&split_view)
.build();
MainWindow {
window,
split_view,
content_stack,
}
}
fn make_sidebar_row(item: &SidebarItem) -> gtk::ListBoxRow {
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 12);
hbox.set_margin_top(8);
hbox.set_margin_bottom(8);
hbox.set_margin_start(12);
hbox.set_margin_end(12);
let icon = gtk::Image::from_icon_name(item.icon);
let label = gtk::Label::new(Some(item.label));
label.set_halign(gtk::Align::Start);
hbox.append(&icon);
hbox.append(&label);
let row = gtk::ListBoxRow::new();
row.set_child(Some(&hbox));
row
}
}