Files
pixstrip/pixstrip-gtk/src/welcome.rs
lashman f353bbe5e6 Wire welcome dialog with navigation and first-run detection
- Welcome dialog buttons navigate between pages
- Done button closes dialog and marks first_run_complete
- Show welcome dialog on first launch only
- Add first_run_complete field to AppConfig with serde(default)
2026-03-06 11:43:25 +02:00

229 lines
6.9 KiB
Rust

use adw::prelude::*;
pub fn show_welcome_if_first_launch(window: &adw::ApplicationWindow) {
let config = pixstrip_core::storage::ConfigStore::new();
if let Ok(cfg) = config.load()
&& cfg.first_run_complete
{
return;
}
let dialog = build_welcome_dialog();
let win = window.clone();
dialog.connect_closed(move |_| {
let config = pixstrip_core::storage::ConfigStore::new();
if let Ok(mut cfg) = config.load() {
cfg.first_run_complete = true;
let _ = config.save(&cfg);
}
});
dialog.present(Some(&win));
}
fn build_welcome_dialog() -> adw::Dialog {
let dialog = adw::Dialog::builder()
.title("Welcome to Pixstrip")
.content_width(500)
.content_height(450)
.build();
let nav_view = adw::NavigationView::new();
let welcome_page = build_welcome_page(&nav_view);
nav_view.add(&welcome_page);
let skill_page = build_skill_page(&nav_view);
nav_view.add(&skill_page);
let output_page = build_output_page(&dialog);
nav_view.add(&output_page);
dialog.set_child(Some(&nav_view));
dialog
}
fn build_welcome_page(nav_view: &adw::NavigationView) -> adw::NavigationPage {
let content = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(12)
.margin_top(24)
.margin_bottom(24)
.margin_start(24)
.margin_end(24)
.build();
let status = adw::StatusPage::builder()
.title("Welcome to Pixstrip")
.description("A quick and powerful batch image processor.\nResize, convert, compress, strip metadata, watermark, and rename - all in a simple wizard.")
.icon_name("image-x-generic-symbolic")
.vexpand(true)
.build();
let next_button = gtk::Button::builder()
.label("Get Started")
.halign(gtk::Align::Center)
.build();
next_button.add_css_class("suggested-action");
next_button.add_css_class("pill");
let nav = nav_view.clone();
next_button.connect_clicked(move |_| {
nav.push_by_tag("skill-level");
});
content.append(&status);
content.append(&next_button);
adw::NavigationPage::builder()
.title("Welcome")
.tag("welcome")
.child(&content)
.build()
}
fn build_skill_page(nav_view: &adw::NavigationView) -> adw::NavigationPage {
let content = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(12)
.margin_top(24)
.margin_bottom(24)
.margin_start(24)
.margin_end(24)
.build();
let title = gtk::Label::builder()
.label("How much detail do you want?")
.css_classes(["title-2"])
.halign(gtk::Align::Start)
.build();
let subtitle = gtk::Label::builder()
.label("You can change this anytime in Settings.")
.css_classes(["dim-label"])
.halign(gtk::Align::Start)
.build();
let group = adw::PreferencesGroup::new();
let simple_row = adw::ActionRow::builder()
.title("Simple")
.subtitle("Fewer options visible, great for quick tasks. Advanced options are still available behind expanders.")
.activatable(true)
.build();
simple_row.add_prefix(&gtk::Image::from_icon_name("emblem-ok-symbolic"));
let simple_check = gtk::CheckButton::new();
simple_check.set_active(true);
simple_row.add_suffix(&simple_check);
simple_row.set_activatable_widget(Some(&simple_check));
let detailed_row = adw::ActionRow::builder()
.title("Detailed")
.subtitle("All options visible by default. Best for power users who want full control.")
.activatable(true)
.build();
detailed_row.add_prefix(&gtk::Image::from_icon_name("preferences-system-symbolic"));
let detailed_check = gtk::CheckButton::new();
detailed_check.set_group(Some(&simple_check));
detailed_row.add_suffix(&detailed_check);
detailed_row.set_activatable_widget(Some(&detailed_check));
group.add(&simple_row);
group.add(&detailed_row);
let next_button = gtk::Button::builder()
.label("Continue")
.halign(gtk::Align::Center)
.build();
next_button.add_css_class("suggested-action");
next_button.add_css_class("pill");
let nav = nav_view.clone();
next_button.connect_clicked(move |_| {
nav.push_by_tag("output-location");
});
content.append(&title);
content.append(&subtitle);
content.append(&group);
content.append(&next_button);
adw::NavigationPage::builder()
.title("Skill Level")
.tag("skill-level")
.child(&content)
.build()
}
fn build_output_page(dialog: &adw::Dialog) -> adw::NavigationPage {
let content = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(12)
.margin_top(24)
.margin_bottom(24)
.margin_start(24)
.margin_end(24)
.build();
let title = gtk::Label::builder()
.label("Where should processed images go?")
.css_classes(["title-2"])
.halign(gtk::Align::Start)
.build();
let subtitle = gtk::Label::builder()
.label("You can change this per-batch or in Settings.")
.css_classes(["dim-label"])
.halign(gtk::Align::Start)
.build();
let group = adw::PreferencesGroup::new();
let subfolder_row = adw::ActionRow::builder()
.title("Subfolder next to originals")
.subtitle("Creates a 'processed' folder next to your images")
.activatable(true)
.build();
subfolder_row.add_prefix(&gtk::Image::from_icon_name("folder-symbolic"));
let subfolder_check = gtk::CheckButton::new();
subfolder_check.set_active(true);
subfolder_row.add_suffix(&subfolder_check);
subfolder_row.set_activatable_widget(Some(&subfolder_check));
let fixed_row = adw::ActionRow::builder()
.title("Fixed output folder")
.subtitle("Always save to the same folder")
.activatable(true)
.build();
fixed_row.add_prefix(&gtk::Image::from_icon_name("folder-open-symbolic"));
let fixed_check = gtk::CheckButton::new();
fixed_check.set_group(Some(&subfolder_check));
fixed_row.add_suffix(&fixed_check);
fixed_row.set_activatable_widget(Some(&fixed_check));
group.add(&subfolder_row);
group.add(&fixed_row);
let done_button = gtk::Button::builder()
.label("Start Using Pixstrip")
.halign(gtk::Align::Center)
.build();
done_button.add_css_class("suggested-action");
done_button.add_css_class("pill");
let dlg = dialog.clone();
done_button.connect_clicked(move |_| {
dlg.close();
});
content.append(&title);
content.append(&subtitle);
content.append(&group);
content.append(&done_button);
adw::NavigationPage::builder()
.title("Output Location")
.tag("output-location")
.child(&content)
.build()
}