feat: 实现矢量编辑模式下的剪切命令逻辑并添加测试

在矢量编辑模式中,剪切命令会处理选中的顶点并自动修复路径,而非直接删除整个图层。新增了专门的cut函数处理该逻辑,同时添加了vector_keys_tests测试文件,覆盖了不同场景下的剪切行为,包括Windows系统的Shift+Delete映射、选中顶点时的剪切、无选中项时的剪切以及非编辑模式下的剪切。
This commit is contained in:
Nicek
2026-10-22 12:03:19 +08:00
parent fa53463ddd
commit 6bb1f3a456
3 changed files with 88 additions and 1 deletions
+2
View File
@@ -219,6 +219,8 @@ mod variables_rename_tests;
#[cfg(test)]
mod variants_discovery_tests;
#[cfg(test)]
mod vector_keys_tests;
#[cfg(test)]
mod vector_panel_tests;
#[cfg(test)]
mod vector_tail_ui_tests;
+16 -1
View File
@@ -125,7 +125,7 @@ pub fn handle(app: &mut FigApp, ctx: &Context, typing: bool, swallowed: Swallowe
for ev in &events {
match ev {
Event::Copy => crate::menus::invoke(app, "edit.copy"),
Event::Cut => crate::menus::invoke(app, "edit.cut"),
Event::Cut => cut(app),
Event::Paste(text) if !swallowed.drop_text => crate::menus::paste(app, Some(text), None),
Event::Key { key, physical_key, pressed: true, modifiers, repeat }
if key_event(app, *key, *physical_key, *modifiers, *repeat, now, ctx) =>
@@ -137,6 +137,21 @@ pub fn handle(app: &mut FigApp, ctx: &Context, typing: bool, swallowed: Swallowe
}
}
/// Cut: usually the layers. Windows sends Shift+Delete (remove the points and heal the path)
/// as a cut command and never as a key, so in vector edit mode the selection is points: they are
/// removed, a heal joining the path through them.
fn cut(app: &mut FigApp) {
let Some(ve) = app.session.ds().ok().and_then(|d| d.vector_edit.clone()) else {
crate::menus::invoke(app, "edit.cut");
return;
};
if !ve.vertices.is_empty() {
app.run("vector.deleteHeal", json!({}));
} else if !ve.segments.is_empty() {
app.run("vector.edit.delete", json!({}));
}
}
/// The command Cmd/Ctrl+V runs with `m` held too: `edit.paste`, `edit.pasteOver` (Shift),
/// `edit.pasteProperties` (Alt), or `None` for a chord nothing is bound to. The keymap decides,
/// whether the key press arrived (browser) or only a paste event and the release (desktop).
+70
View File
@@ -0,0 +1,70 @@
//! Keys in vector edit mode that the window's key handling twists: Windows sends Shift+Delete
//! (delete the points and heal the path) as a cut command and no key at all, so the canvas must
//! heal the selected points rather than cut the layer.
use egui::{Event, Modifiers};
use figcraft_doc::NodeId;
use figcraft_engine::Session;
use serde_json::json;
use crate::layers_drag_tests::Ui;
fn id(v: serde_json::Value) -> NodeId {
NodeId(v["id"].as_u64().unwrap())
}
fn net(u: &Ui, path: NodeId) -> figcraft_doc::VectorNetwork {
let doc = u.app.session.doc().unwrap();
figcraft_engine::vectoredit::network(doc, path).unwrap().clone()
}
/// A three-point open path A-B-C in edit mode, B selected.
fn abc(u: &mut Ui) -> NodeId {
let path = id(u.app.session.execute("vector.create", &json!({"points": [[100, 100], [200, 100], [300, 100]], "closed": false})).unwrap());
u.app.session.execute("vector.edit.begin", &json!({"id": path})).unwrap();
u.app.session.execute("vector.edit.select", &json!({"vertices": [1]})).unwrap();
path
}
#[test]
fn shift_delete_in_vector_edit_mode_heals_the_point_instead_of_cutting_the_layer() {
let mut u = Ui::new(Session::new());
let path = abc(&mut u);
// Windows: egui-winit turns Shift+Delete into a cut command, with no key event.
u.frame_with(vec![Event::Cut], Modifiers::SHIFT);
assert!(u.app.session.doc().unwrap().node(path).is_some(), "the layer stays");
let n = net(&u, path);
assert_eq!(n.vertices.len(), 2, "the point is gone: {n:?}");
assert_eq!(n.segments.len(), 1, "A and C are joined");
let (a, b) = (n.vertices.first().map(|v| v.x).unwrap_or_default(), n.vertices.get(1).map(|v| v.x).unwrap_or_default());
assert!(b - a == 200.0 && n.vertices.iter().all(|v| v.y == 0.0), "it runs straight from A to C: {n:?}");
}
#[test]
fn a_cut_command_in_edit_mode_takes_the_selected_points_not_the_layer() {
let mut u = Ui::new(Session::new());
let path = abc(&mut u);
u.frame_with(vec![Event::Cut], Modifiers::COMMAND);
assert!(u.app.session.doc().unwrap().node(path).is_some(), "Ctrl+X on points keeps the layer");
assert_eq!(net(&u, path).vertices.len(), 2, "it takes the selected point");
}
#[test]
fn a_cut_command_in_edit_mode_with_nothing_selected_keeps_the_layer() {
let mut u = Ui::new(Session::new());
let path = abc(&mut u);
u.app.session.execute("vector.edit.select", &json!({"vertices": []})).unwrap();
u.frame_with(vec![Event::Cut], Modifiers::SHIFT);
assert!(u.app.session.doc().unwrap().node(path).is_some(), "nothing selected: nothing goes");
assert_eq!(net(&u, path).vertices.len(), 3);
}
#[test]
fn a_cut_command_outside_edit_mode_cuts_the_layer() {
let mut s = Session::new();
let path = id(s.execute("vector.create", &json!({"points": [[100, 100], [200, 100], [300, 100]], "closed": false})).unwrap());
s.execute("select.set", &json!({"ids": [path]})).unwrap();
let mut u = Ui::new(s);
u.frame_with(vec![Event::Cut], Modifiers::SHIFT);
assert!(u.app.session.doc().unwrap().node(path).is_none(), "outside edit mode the selected layer is cut");
}