mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 21:27:00 -06:00
* Shortcuts: the keymap, overrides and dispatch come from craft-keymap
The editor's catalogue (commands, UI commands, panels, tools; one tool
group per tool; show/hide commands marked as toggles) builds a
craft-keymap Keymap. UiState::shortcut_overrides is a craft_keymap
Overrides stored through keymap::stored, which writes the same map of
entry key -> one string as before, with panel: keys and VectorCraft's
spellings, so earlier versions read the file; a value that is not text
drops that entry instead of every preference.
Key presses run through craft_keymap's Dispatcher with accept filters:
keys that type text go to the one-key text path, and while the Type
tool edits only command-key chords run. Held keys repeat. Conflicts
and assignment use the crate's conflicts and set_bindings. Extra Shift
no longer falls back to the chord without it.
* Shortcuts: .kys import matches and applies through craft-keymap
The review proposes targets with craft_keymap's namematch, and Apply
hands the ready rows to craft_keymap::plan as one set, each row's
target passed as the remembered answer for its entry. The later row
takes a chord, and commands outside the set lose the chords the set
gives. Rows with one name in one block keep their own targets.
The engine's own namematch module is removed.
* Shortcuts: Cmd+, beside other hits, web Mac platform, cached dialog view
- Pin craft-keymap at 362eb59f.
- Cmd+, opens Settings on a Mac after the frame's other shortcuts run, with the
Mac read from egui's OS; drop settings_chord, which dispatch no longer used.
- The web build follows the browser's OS, so Ctrl stays Ctrl on a Mac.
- The Keyboard Shortcuts dialog keeps its keymap, row texts and conflict count
between frames while the working set is unchanged.
- Every default shortcut text becomes its entry's binding (test).
- craft-keymap counts as a UI crate for the layer check.
- Translate "too many changed shortcuts"; the parse error reuses the existing
"can't parse shortcut `{c}`" message.
* Shortcuts: the chords the operating system takes can't be assigned
The keymap reserves craft_keymap::reserved::system for its platform:
window switching and closing, and the virtual consoles off the Mac.
The Keyboard Shortcuts dialog and shortcuts.set refuse them with
"<chord> is reserved by the system", and a saved override on one keeps
the entry's default. No default shortcut uses a reserved chord.
* Shortcuts: craft-keymap at craft-libs 2eca9b3a
craft-keymap comes from storytold/craft-libs (craft-libs #164 and #186), at the rev craft-pdf
already uses. The web build keeps the browser's full platform, so Windows browsers get Windows'
reserved chords, and each platform caches its own default keymap.
109 lines
5.5 KiB
Rust
109 lines
5.5 KiB
Rust
//! The paste chords (egui reports every Cmd+V chord as a paste, not as its key) and the default
|
|
//! shortcuts (no chord does two things).
|
|
|
|
use egui::{Event, Key, Modifiers};
|
|
use serde_json::json;
|
|
use vectorcraft_engine::Session;
|
|
|
|
use crate::{VectorcraftApp, shortcut_editor, shortcuts};
|
|
|
|
/// One headless frame of the shortcut handler (and the app's logic, which publishes copies) with
|
|
/// `modifiers` held and `events`.
|
|
fn frame(app: &mut VectorcraftApp, modifiers: Modifiers, events: Vec<Event>) -> egui::FullOutput {
|
|
let events = std::iter::once(Event::ModifiersChanged(modifiers)).chain(events).collect();
|
|
let mut out = egui::Context::default().run_ui(egui::RawInput { events, ..Default::default() }, |ui| {
|
|
shortcuts::handle(app, ui.ctx());
|
|
app.logic(ui.ctx());
|
|
});
|
|
out.textures_delta.clear();
|
|
out
|
|
}
|
|
|
|
fn last_object_bounds(app: &VectorcraftApp) -> (f64, f64) {
|
|
let st = app.session.active().unwrap();
|
|
let id = st.doc.layers[0].children().unwrap().last().unwrap().id;
|
|
let b = st.doc.node(id).unwrap().geometric_bounds().unwrap();
|
|
(b.x0, b.y0)
|
|
}
|
|
|
|
#[test]
|
|
fn cmd_shift_v_pastes_in_place() {
|
|
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
|
app.run("file.new", json!({"width": 400, "height": 400})).unwrap();
|
|
let id = app.run("shape.rectangle", json!({"x": 10, "y": 20, "width": 30, "height": 30})).unwrap()["id"].clone();
|
|
app.run("select.set", json!({"ids": [id]})).unwrap();
|
|
let out = frame(&mut app, Modifiers::COMMAND, vec![Event::Copy]);
|
|
let svg = out
|
|
.platform_output
|
|
.commands
|
|
.iter()
|
|
.find_map(|c| match c {
|
|
egui::OutputCommand::CopyText(t) => Some(t.clone()),
|
|
_ => None,
|
|
})
|
|
.unwrap();
|
|
// The text egui read from the clipboard comes with the chord still held.
|
|
frame(&mut app, Modifiers::COMMAND | Modifiers::SHIFT, vec![Event::Paste(svg.clone())]);
|
|
assert_eq!(app.session.active().unwrap().doc.layers[0].children().unwrap().len(), 2);
|
|
assert_eq!(last_object_bounds(&app), (10.0, 20.0), "Cmd+Shift+V pastes in place");
|
|
frame(&mut app, Modifiers::COMMAND, vec![Event::Paste(svg)]);
|
|
assert_eq!(app.session.active().unwrap().doc.layers[0].children().unwrap().len(), 3);
|
|
assert_ne!(last_object_bounds(&app), (10.0, 20.0), "Cmd+V pastes offset or centred");
|
|
}
|
|
|
|
#[test]
|
|
fn a_paste_takes_the_command_of_the_v_chord_held() {
|
|
let cmd = Modifiers::COMMAND;
|
|
assert_eq!(shortcuts::paste_command(cmd), "edit.paste");
|
|
assert_eq!(shortcuts::paste_command(cmd | Modifiers::SHIFT), "edit.pasteInPlace");
|
|
assert_eq!(shortcuts::paste_command(cmd | Modifiers::ALT), "edit.pasteWithoutFormatting");
|
|
assert_eq!(shortcuts::paste_command(cmd | Modifiers::ALT | Modifiers::SHIFT), "edit.pasteOnAllArtboards");
|
|
// The Paste key, and Shift+Insert, paste.
|
|
assert_eq!(shortcuts::paste_command(Modifiers::NONE), "edit.paste");
|
|
assert_eq!(shortcuts::paste_command(Modifiers::SHIFT), "edit.paste");
|
|
// What isn't text: only the key's release is reported, with the chord's modifiers.
|
|
let mut chord = shortcuts::PasteChord::default();
|
|
let release = Event::Key { key: Key::V, physical_key: None, pressed: false, repeat: false, modifiers: cmd | Modifiers::SHIFT };
|
|
assert_eq!(chord.textless_paste(&[release]), Some(cmd | Modifiers::SHIFT));
|
|
}
|
|
|
|
#[test]
|
|
fn no_two_commands_tools_or_panels_share_a_default_shortcut() {
|
|
assert_eq!(shortcut_editor::all_conflicts(&craft_keymap::Overrides::new()), vec![]);
|
|
}
|
|
|
|
/// egui-winit reports every Cmd+C chord as a Copy, Shift and Alt held too, never as its key: the
|
|
/// Cmd+Shift+C alignment fired on a Mac (the menu's key equivalent) but copied elsewhere (#1940).
|
|
#[test]
|
|
fn a_copy_with_more_modifiers_is_the_c_chord_held() {
|
|
use vectorcraft_doc::{Justify, NodeId, NodeKind};
|
|
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
|
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
|
|
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "One\nTwo"})).unwrap()["id"].as_u64().unwrap();
|
|
let justify = |app: &VectorcraftApp| match &app.session.active().unwrap().doc.node(NodeId(id)).unwrap().kind {
|
|
NodeKind::Text(t) => (0..t.paragraph_count()).map(|i| t.para_at(i).justify).collect::<Vec<_>>(),
|
|
_ => panic!("text"),
|
|
};
|
|
let copied = |out: &egui::FullOutput| out.platform_output.commands.iter().any(|c| matches!(c, egui::OutputCommand::CopyText(_)));
|
|
app.run("select.set", json!({"ids": [id]})).unwrap();
|
|
let out = frame(&mut app, Modifiers::COMMAND | Modifiers::SHIFT, vec![Event::Copy]);
|
|
assert_eq!(justify(&app), [Justify::Center, Justify::Center]);
|
|
assert!(!copied(&out), "Cmd+Shift+C doesn't copy");
|
|
// Cmd+C still copies.
|
|
let out = frame(&mut app, Modifiers::COMMAND, vec![Event::Copy]);
|
|
assert!(copied(&out));
|
|
// While the Type tool edits text, it aligns the paragraph the caret is in.
|
|
app.select_tool("type");
|
|
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
|
|
app.session.set_tool_option("select", &json!({"start": 5, "end": 5}));
|
|
assert!(app.session.tool_wants_text());
|
|
frame(
|
|
&mut app,
|
|
Modifiers::COMMAND | Modifiers::SHIFT,
|
|
vec![Event::Key { key: Key::R, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::COMMAND | Modifiers::SHIFT }],
|
|
);
|
|
assert_eq!(justify(&app), [Justify::Center, Justify::Right]);
|
|
frame(&mut app, Modifiers::COMMAND | Modifiers::SHIFT, vec![Event::Copy]);
|
|
assert_eq!(justify(&app), [Justify::Center, Justify::Center]);
|
|
}
|