Merge feature/troubleshoot-wizard-view: capture the troubleshoot wizard modal
All checks were successful
dev release / build (push) Successful in 3m10s

This commit is contained in:
Breadway 2026-07-29 21:59:22 +08:00
commit e0012f83b1
3 changed files with 53 additions and 9 deletions

View file

@ -7,6 +7,19 @@
//! see `ui::tabs`. The window is a plain top-level (not layer-shell), so a //! see `ui::tabs`. The window is a plain top-level (not layer-shell), so a
//! full known-size canvas capture is enough, same reasoning as breadpad's //! full known-size canvas capture is enough, same reasoning as breadpad's
//! popup view. //! popup view.
//!
//! One more view isn't a tab: "troubleshoot-wizard" opens the modal wizard
//! window (`ui::troubleshoot_wizard`, normally reachable via Home's
//! "Something's wrong?" button) directly.
//!
//! Deliberately NOT covered here: the onboarding tour (`ui::tour`). It's a
//! live, multi-step walkthrough that overlays callouts on the *real*
//! desktop as the user does real things (opens real apps, switches real
//! workspaces) — there's no meaningful "screenshot" of a single step in an
//! isolated environment with no real desktop for it to point at.
//! `ui::guide_view` and `ui::keybind_viewer` also aren't separate views:
//! both render embedded *inside* the Learn/Home tabs respectively, so
//! they're already covered by those tabs' own captures.
use gtk4::prelude::*; use gtk4::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
@ -17,7 +30,12 @@ use std::time::Duration;
/// anything has been drawn into it. /// anything has been drawn into it.
const SETTLE_DELAY: Duration = Duration::from_millis(300); const SETTLE_DELAY: Duration = Duration::from_millis(300);
const KNOWN_VIEWS: &[&str] = &["home", "learn", "ask"]; /// Delay before opening the wizard modal — same reasoning as every other
/// app's PRE_POPUP_DELAY: the parent window's own layout needs a beat to
/// settle first.
const PRE_POPUP_DELAY: Duration = Duration::from_millis(300);
const KNOWN_VIEWS: &[&str] = &["home", "learn", "ask", "troubleshoot-wizard"];
#[derive(Clone)] #[derive(Clone)]
pub struct ScreenshotRequest { pub struct ScreenshotRequest {
@ -39,20 +57,41 @@ pub fn dispatch(window: &gtk4::ApplicationWindow, stack: &gtk4::Stack, req: Scre
); );
std::process::exit(1); std::process::exit(1);
} }
stack.set_visible_child_name(&req.view);
let output = req.output; let output = req.output;
let (width, height) = (req.width as i32, req.height as i32); let (width, height) = (req.width as i32, req.height as i32);
if req.view == "troubleshoot-wizard" {
window.connect_map(move |win| {
let output = output.clone();
let win = win.clone();
gtk4::glib::timeout_add_local_once(PRE_POPUP_DELAY, move || {
crate::ui::troubleshoot_wizard::open(&win, move |dialog| {
let output = output.clone();
dialog.connect_map(move |_| {
let output = output.clone();
gtk4::glib::timeout_add_local_once(SETTLE_DELAY, move || {
finish(bread_screenshots::capture_region(0, 0, width, height, &output));
});
});
});
});
});
// The caller (`ui::window::present`) returns immediately after this
// for the screenshot path, skipping its own normal `window.present()`
// call — trigger it here instead, so `connect_map` above actually has
// something to fire for.
window.present();
return;
}
stack.set_visible_child_name(&req.view);
window.connect_map(move |_| { window.connect_map(move |_| {
let output = output.clone(); let output = output.clone();
gtk4::glib::timeout_add_local_once(SETTLE_DELAY, move || { gtk4::glib::timeout_add_local_once(SETTLE_DELAY, move || {
finish(bread_screenshots::capture_region(0, 0, width, height, &output)); finish(bread_screenshots::capture_region(0, 0, width, height, &output));
}); });
}); });
// The caller (`ui::window::present`) returns immediately after this for
// the screenshot path, skipping its own normal `window.present()` call
// — trigger it here instead, so `connect_map` above actually has
// something to fire for.
window.present(); window.present();
} }

View file

@ -83,7 +83,7 @@ pub fn build(
troubleshoot_btn.set_halign(Align::Start); troubleshoot_btn.set_halign(Align::Start);
{ {
let parent = parent_window.clone(); let parent = parent_window.clone();
troubleshoot_btn.connect_clicked(move |_| troubleshoot_wizard::open(&parent)); troubleshoot_btn.connect_clicked(move |_| troubleshoot_wizard::open(&parent, |_| {}));
} }
vbox.append(&troubleshoot_btn); vbox.append(&troubleshoot_btn);

View file

@ -25,7 +25,11 @@ struct Wizard {
trees: HashMap<String, Vec<SymptomNode>>, trees: HashMap<String, Vec<SymptomNode>>,
} }
pub fn open(parent: &impl IsA<gtk4::Window>) { /// `on_build` runs on the freshly built window *before* it's presented —
/// screenshot mode's only hook point, since `connect_map` registered any
/// later would miss a map that already happened. The real call site
/// (`ui::home`) passes a no-op.
pub fn open(parent: &impl IsA<gtk4::Window>, on_build: impl FnOnce(&Window)) {
let window = Window::builder() let window = Window::builder()
.transient_for(parent) .transient_for(parent)
.modal(true) .modal(true)
@ -41,6 +45,7 @@ pub fn open(parent: &impl IsA<gtk4::Window>) {
wizard.render_picker(); wizard.render_picker();
window.set_child(Some(&content)); window.set_child(Some(&content));
on_build(&window);
window.present(); window.present();
} }