Add per-step contextual help button in header bar

A "?" button in the header bar shows an AlertDialog with help
text specific to the current wizard step, explaining what each
step does and key shortcuts available.
This commit is contained in:
2026-03-06 13:48:13 +02:00
parent 257fd53bbc
commit bcf57927e9

View File

@@ -203,6 +203,14 @@ fn build_ui(app: &adw::Application) {
let title = adw::WindowTitle::new("Pixstrip", "Batch Image Processor");
header.set_title_widget(Some(&title));
// Help button for per-step contextual help
let help_button = gtk::Button::builder()
.icon_name("help-about-symbolic")
.tooltip_text("Help for this step")
.build();
help_button.add_css_class("flat");
header.pack_end(&help_button);
// Hamburger menu
let menu = build_menu();
let menu_button = gtk::MenuButton::builder()
@@ -334,6 +342,16 @@ fn build_ui(app: &adw::Application) {
state: app_state,
};
// Wire help button to show contextual help for current step
{
let wizard = ui.state.wizard.clone();
let window_ref = window.clone();
help_button.connect_clicked(move |_| {
let step = wizard.borrow().current_step;
show_step_help(&window_ref, step);
});
}
setup_window_actions(&window, &ui);
update_nav_buttons(
&ui.state.wizard.borrow(),
@@ -2203,6 +2221,86 @@ fn format_bytes(bytes: u64) -> String {
}
}
fn show_step_help(window: &adw::ApplicationWindow, step: usize) {
let (title, body) = match step {
0 => ("Workflow", concat!(
"Choose a preset to start quickly, or configure each step manually.\n\n",
"Presets apply recommended settings for common tasks like web optimization, ",
"social media, or print preparation. You can customize any preset after applying it.\n\n",
"Use Import/Export to share presets with others."
)),
1 => ("Images", concat!(
"Add the images you want to process.\n\n",
"- Drag and drop files or folders onto this area\n",
"- Use Browse to pick files from a file dialog\n",
"- Press Ctrl+V to paste from clipboard\n\n",
"Use checkboxes to include or exclude individual images. ",
"Ctrl+A selects all, Ctrl+Shift+A deselects all."
)),
2 => ("Resize", concat!(
"Scale images to specific dimensions.\n\n",
"Choose a preset size or enter custom dimensions. Width-only or height-only ",
"resizing preserves the original aspect ratio.\n\n",
"Enable 'Allow upscale' if you need images smaller than the target to be enlarged."
)),
3 => ("Adjustments", concat!(
"Fine-tune image appearance.\n\n",
"Adjust brightness, contrast, and saturation with sliders. ",
"Apply rotation, flipping, grayscale, or sepia effects.\n\n",
"Crop to a specific aspect ratio or trim whitespace borders automatically."
)),
4 => ("Convert", concat!(
"Change image file format.\n\n",
"Convert between JPEG, PNG, WebP, AVIF, GIF, TIFF, and BMP. ",
"Each format has trade-offs between quality, file size, and compatibility.\n\n",
"WebP and AVIF offer the best compression for web use."
)),
5 => ("Compress", concat!(
"Reduce file size while preserving quality.\n\n",
"Choose a quality preset (Lossless, High, Balanced, Small, Tiny) or set custom ",
"quality values per format.\n\n",
"Expand Advanced Options for fine control over WebP encoding effort and AVIF speed."
)),
6 => ("Metadata", concat!(
"Control what metadata is kept or removed.\n\n",
"Strip All removes everything. Privacy mode keeps copyright and camera info but ",
"removes GPS and timestamps. Custom mode lets you pick exactly what to strip.\n\n",
"Removing metadata reduces file size and protects privacy."
)),
7 => ("Watermark", concat!(
"Add a text or image watermark.\n\n",
"Choose text or logo mode. Position the watermark using the visual grid. ",
"Expand Advanced Options for opacity, rotation, tiling, margin, and scale controls.\n\n",
"Logo watermarks support PNG images with transparency."
)),
8 => ("Rename", concat!(
"Rename output files using patterns.\n\n",
"Add a prefix, suffix, or use a full template with placeholders:\n",
"- {name} - original filename\n",
"- {n} - counter number\n",
"- {date} - current date\n",
"- {ext} - original extension\n\n",
"Expand Advanced Options for case conversion and find-and-replace."
)),
9 => ("Output", concat!(
"Review settings and choose where to save.\n\n",
"The summary shows all operations that will be applied. ",
"Choose an output folder or use the default 'processed' subfolder.\n\n",
"Set overwrite behavior for when output files already exist. ",
"Press Process or Ctrl+Enter to start."
)),
_ => ("Help", "No help available for this step."),
};
let dialog = adw::AlertDialog::builder()
.heading(format!("Help: {}", title))
.body(body)
.build();
dialog.add_response("ok", "Got it");
dialog.set_default_response(Some("ok"));
dialog.present(Some(window));
}
fn format_duration(ms: u64) -> String {
if ms < 1000 {
format!("{}ms", ms)