mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Slides: Ctrl+A in the slide pane selects every slide (#188)
The UI remembers whether the last press landed in the thumbnail pane (the sorter always counts) and passes scope slides to edit.selectAll in that case.
This commit is contained in:
@@ -209,6 +209,8 @@ pub struct SlideApp {
|
||||
pub(crate) notes_buf: (u64, usize, String),
|
||||
/// Thumbnail pane drag (slide index, current drop index).
|
||||
pub(crate) thumb_drag: Option<(usize, usize)>,
|
||||
/// The slide thumbnail pane was the last thing clicked, so ⌘A selects slides, not shapes.
|
||||
pub(crate) slides_focused: bool,
|
||||
}
|
||||
|
||||
impl SlideApp {
|
||||
@@ -244,6 +246,7 @@ impl SlideApp {
|
||||
quit_confirmed: false,
|
||||
notes_buf: (0, usize::MAX, String::new()),
|
||||
thumb_drag: None,
|
||||
slides_focused: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,6 +368,12 @@ impl SlideApp {
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
/// Whether ⌘A should act on the slide list: always in the sorter, in Normal view once the
|
||||
/// thumbnail pane has been clicked.
|
||||
pub(crate) fn slides_list_focused(&self) -> bool {
|
||||
self.ui.view == ViewMode::Sorter || (self.ui.view == ViewMode::Normal && self.slides_focused)
|
||||
}
|
||||
|
||||
pub fn start_show(&mut self, from: usize, reading: bool) {
|
||||
self.media.stop_owned(media::Owner::Editor);
|
||||
if let Some(d) = self.session.active() {
|
||||
@@ -938,4 +947,46 @@ mod tests {
|
||||
app.run("app.home", json!({})).unwrap();
|
||||
assert!(!app.home_open() && app.home.is_none(), "with nothing open the start screen shows anyway");
|
||||
}
|
||||
|
||||
/// #188: Ctrl/Cmd+A with the slide thumbnail pane focused selects every slide; with the
|
||||
/// canvas focused it still selects shapes.
|
||||
#[test]
|
||||
fn select_all_follows_the_focused_pane() {
|
||||
let mut harness = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|
||||
|ui, app: &mut SlideApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
SlideApp::new(Session::with_new(), Services::default()),
|
||||
);
|
||||
harness.run_steps(2);
|
||||
for _ in 0..2 {
|
||||
harness.state_mut().run("slide.new", json!({})).unwrap();
|
||||
}
|
||||
harness.state_mut().run("slide.go", json!({"index": 0})).unwrap();
|
||||
harness.run_steps(3);
|
||||
let click = |h: &mut egui_kittest::Harness<'_, SlideApp>, pos: egui::Pos2| {
|
||||
h.event(egui::Event::PointerMoved(pos));
|
||||
for pressed in [true, false] {
|
||||
h.event(egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE });
|
||||
}
|
||||
h.run_steps(2);
|
||||
};
|
||||
// Press in the thumbnail pane, then Cmd+A.
|
||||
let w = harness.state().ui.thumbs_width;
|
||||
click(&mut harness, egui::pos2(w / 2.0, 300.0));
|
||||
assert!(harness.state().slides_focused);
|
||||
harness.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::A);
|
||||
harness.run_steps(2);
|
||||
let st = harness.state().session.active().unwrap();
|
||||
assert_eq!(st.selection.slides.len(), 3, "every slide is selected");
|
||||
|
||||
// A press on the canvas moves focus away: Cmd+A selects shapes again.
|
||||
click(&mut harness, egui::pos2(700.0, 400.0));
|
||||
assert!(!harness.state().slides_focused);
|
||||
harness.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::A);
|
||||
harness.run_steps(2);
|
||||
let st = harness.state().session.active().unwrap();
|
||||
assert!(!st.selection.shapes.is_empty(), "canvas select all picks the shapes");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ use crate::theme::{self, Tokens};
|
||||
/// Shortcuts the UI handles itself (views, show, zoom, palette, file dialogs). True when consumed.
|
||||
pub fn ui_shortcut(app: &mut SlideApp, key: egui::Key, m: Mods) -> bool {
|
||||
use egui::Key::*;
|
||||
if key == A && m.cmd && !m.shift && !m.alt && app.slides_list_focused() {
|
||||
// The engine can't see which pane has focus, so the UI says so through the scope param.
|
||||
let _ = app.run("edit.selectAll", json!({"scope": "slides"}));
|
||||
return true;
|
||||
}
|
||||
let id = match (key, m.cmd, m.shift, m.alt) {
|
||||
(F5, _, false, _) => {
|
||||
app.start_show(0, false);
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::theme::{self, Tokens};
|
||||
pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let in_master = app.session.active().is_some_and(|d| d.selection.target != Target::Slides);
|
||||
egui::Panel::left("thumbs")
|
||||
let panel = egui::Panel::left("thumbs")
|
||||
.resizable(true)
|
||||
.default_size(app.ui.thumbs_width)
|
||||
.size_range(150.0..=420.0)
|
||||
@@ -28,6 +28,10 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
ui.add_space(20.0);
|
||||
});
|
||||
});
|
||||
// Focus follows the last press: inside the pane keeps it, anywhere else (canvas, ribbon) drops it.
|
||||
if let Some(pos) = ui.input(|i| if i.pointer.primary_pressed() { i.pointer.interact_pos() } else { None }) {
|
||||
app.slides_focused = panel.response.rect.contains(pos);
|
||||
}
|
||||
}
|
||||
|
||||
fn list(app: &mut SlideApp, ui: &mut Ui) {
|
||||
|
||||
Reference in New Issue
Block a user