feat: add keyboard shortcuts for pan/zoom/iterations/AA
Arrow keys pan, Z/S zoom in/out, +/- adjust iteration count, R resets the view, H/I toggle the Help/Info windows, and A toggles 2x2 antialiasing. Disabled while a text field has focus. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018GFBP9qrNg6NGJWzzP36xe
This commit is contained in:
+109
-5
@@ -1285,8 +1285,8 @@ impl FractalApp {
|
|||||||
self.info_open = open;
|
self.info_open = open;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Help window: what the app does, plus a reference for mouse/touch
|
/// Help window: what the app does, plus a reference for mouse/touch and
|
||||||
/// controls (there are no in-app keyboard shortcuts today).
|
/// keyboard controls.
|
||||||
fn help_window(&mut self, ctx: &egui::Context) {
|
fn help_window(&mut self, ctx: &egui::Context) {
|
||||||
let mut open = self.help_open;
|
let mut open = self.help_open;
|
||||||
egui::Window::new("Help")
|
egui::Window::new("Help")
|
||||||
@@ -1340,10 +1340,36 @@ impl FractalApp {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.heading("Keyboard");
|
ui.heading("Keyboard");
|
||||||
|
egui::Grid::new("help_keyboard_grid")
|
||||||
|
.num_columns(2)
|
||||||
|
.spacing([12.0, 6.0])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label("Arrow keys");
|
||||||
|
ui.label("Pan the view");
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("Z / S");
|
||||||
|
ui.label("Zoom in / out toward the center");
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("+ / -");
|
||||||
|
ui.label("Increase / decrease iterations");
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("R");
|
||||||
|
ui.label("Reset to the default view");
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("H");
|
||||||
|
ui.label("Toggle this Help window");
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("I");
|
||||||
|
ui.label("Toggle the Info window");
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("A");
|
||||||
|
ui.label("Toggle antialiasing (2×2)");
|
||||||
|
ui.end_row();
|
||||||
|
});
|
||||||
ui.label(
|
ui.label(
|
||||||
"The view is driven entirely by mouse/touch — there are no \
|
"Shortcuts are ignored while a text field (e.g. the center/zoom \
|
||||||
in-app keyboard shortcuts. Esc / F11 toggle fullscreen via \
|
edit boxes) has focus. Esc / F11 toggle fullscreen via the \
|
||||||
the browser or OS, as usual.",
|
browser or OS, as usual.",
|
||||||
);
|
);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
@@ -1976,6 +2002,84 @@ impl FractalApp {
|
|||||||
ui.ctx().request_repaint();
|
ui.ctx().request_repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keyboard: arrows pan, z/s zoom in/out, +/- adjust iterations, R
|
||||||
|
// resets the view, H/I toggle the Help/Info windows. Skipped while a
|
||||||
|
// text field (e.g. the center/zoom edit boxes) has focus.
|
||||||
|
if !ui.ctx().egui_wants_keyboard_input() {
|
||||||
|
let dt = ui.input(|i| i.stable_dt as f64).clamp(0.0, 0.1);
|
||||||
|
let (left, right, up, down, zoom_in, zoom_out) = ui.input(|i| {
|
||||||
|
(
|
||||||
|
i.key_down(egui::Key::ArrowLeft),
|
||||||
|
i.key_down(egui::Key::ArrowRight),
|
||||||
|
i.key_down(egui::Key::ArrowUp),
|
||||||
|
i.key_down(egui::Key::ArrowDown),
|
||||||
|
i.key_down(egui::Key::Z),
|
||||||
|
i.key_down(egui::Key::S),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pixels/sec pan speed — matches a brisk mouse drag regardless of
|
||||||
|
// frame rate. See `pan_pixels`'s screen-space (+x right, +y down)
|
||||||
|
// convention: Right/Down pan the *camera* right/down, which is
|
||||||
|
// the opposite delta sign from a drag that would show the same
|
||||||
|
// content (a drag grabs the canvas; these keys move the camera).
|
||||||
|
const PAN_SPEED_PX: f64 = 700.0;
|
||||||
|
let mut dx = 0.0;
|
||||||
|
let mut dy = 0.0;
|
||||||
|
if left {
|
||||||
|
dx += PAN_SPEED_PX * dt;
|
||||||
|
}
|
||||||
|
if right {
|
||||||
|
dx -= PAN_SPEED_PX * dt;
|
||||||
|
}
|
||||||
|
if down {
|
||||||
|
dy += PAN_SPEED_PX * dt;
|
||||||
|
}
|
||||||
|
if up {
|
||||||
|
dy -= PAN_SPEED_PX * dt;
|
||||||
|
}
|
||||||
|
if dx != 0.0 || dy != 0.0 {
|
||||||
|
self.view.pan_pixels(dx, dy, height_px);
|
||||||
|
interacted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// e-folds/sec, same scale as the auto-zoom animation.
|
||||||
|
const ZOOM_SPEED: f64 = 1.0;
|
||||||
|
if zoom_in != zoom_out {
|
||||||
|
let rate = if zoom_in { ZOOM_SPEED } else { -ZOOM_SPEED };
|
||||||
|
let factor = (-rate * dt).exp();
|
||||||
|
self.view.zoom_at_pixel(0.0, 0.0, height_px, factor);
|
||||||
|
interacted = true;
|
||||||
|
}
|
||||||
|
if left || right || up || down || zoom_in || zoom_out {
|
||||||
|
ui.ctx().request_repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.input(|i| i.key_pressed(egui::Key::R)) {
|
||||||
|
self.view = Self::default_view_for(self.mode, self.kind);
|
||||||
|
interacted = true;
|
||||||
|
}
|
||||||
|
if ui.input(|i| i.key_pressed(egui::Key::H)) {
|
||||||
|
self.help_open = !self.help_open;
|
||||||
|
}
|
||||||
|
if ui.input(|i| i.key_pressed(egui::Key::I)) {
|
||||||
|
self.info_open = !self.info_open;
|
||||||
|
}
|
||||||
|
if ui.input(|i| i.key_pressed(egui::Key::A)) {
|
||||||
|
self.antialias = !self.antialias;
|
||||||
|
}
|
||||||
|
if ui.input(|i| i.key_pressed(egui::Key::Plus) || i.key_pressed(egui::Key::Equals)) {
|
||||||
|
self.auto_iterations = false;
|
||||||
|
self.max_iterations = ((self.max_iterations as f64 * 1.25).round() as u32)
|
||||||
|
.clamp(32, MAX_REF_POINTS as u32 - 1);
|
||||||
|
}
|
||||||
|
if ui.input(|i| i.key_pressed(egui::Key::Minus)) {
|
||||||
|
self.auto_iterations = false;
|
||||||
|
self.max_iterations = ((self.max_iterations as f64 / 1.25).round() as u32)
|
||||||
|
.clamp(32, MAX_REF_POINTS as u32 - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.buddhabrot {
|
if self.buddhabrot {
|
||||||
// No reference orbit / perturbation machinery: iterate directly in
|
// No reference orbit / perturbation machinery: iterate directly in
|
||||||
// f32 from the live view. Progressive accumulation means this
|
// f32 from the live view. Progressive accumulation means this
|
||||||
|
|||||||
Reference in New Issue
Block a user