mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 18:03:32 +00:00
Merge pull request #5306 from storytold/fix/sec-place-queue-scope
Place: the place cursor reads its files within the roots it was loaded in
This commit is contained in:
@@ -163,6 +163,11 @@ pub struct Session {
|
||||
/// While [`Session::place_queue_read`] runs `file.place.queue`: the files read for it, which
|
||||
/// the command loads the place cursor with instead of reading them; `None` otherwise.
|
||||
pub(crate) queue_hand_off: Option<cmd::place::QueueRead>,
|
||||
/// The automation roots in force when the place cursor was last loaded (`None`: none were).
|
||||
/// The cursor places its files later, on a click that may come from the person at the
|
||||
/// keyboard: those files, and the files they link to, are read under these roots, so a queue
|
||||
/// an agent loaded never reads beyond what it was given.
|
||||
pub(crate) place_queue_roots: Option<std::sync::Arc<file_access::AutomationRoots>>,
|
||||
/// While [`Session::import_image_read`] runs `clipboard.importImage`: the image read for it,
|
||||
/// which the command loads instead of reading `dataBase64`; `None` otherwise.
|
||||
pub(crate) image_hand_off: Option<cmd::clipboard::ImageRead>,
|
||||
|
||||
@@ -79,6 +79,7 @@ impl Session {
|
||||
batch_steps: None,
|
||||
place_hand_off: None,
|
||||
queue_hand_off: None,
|
||||
place_queue_roots: None,
|
||||
image_hand_off: None,
|
||||
trace_hand_off: None,
|
||||
place_cursor_requests: false,
|
||||
|
||||
@@ -722,6 +722,39 @@ fn a_queue_of_3_with_click_drag_and_esc_places_2() {
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), 2, "one undo step per placement");
|
||||
}
|
||||
|
||||
/// A place cursor loaded within automation roots places its files within them, even on a later
|
||||
/// click outside any roots (the person at the keyboard's): a queued file outside the roots, or
|
||||
/// one it links to, is not read then. One loaded outside any roots places as before.
|
||||
#[test]
|
||||
fn the_place_cursor_places_within_the_roots_it_was_loaded_in() {
|
||||
let base = std::env::temp_dir().join(format!("vectorcraft-place-roots-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
std::fs::create_dir_all(base.join("in")).unwrap();
|
||||
let outside = base.join("outside.png");
|
||||
std::fs::write(&outside, png(40, 20, [9, 9, 9, 255], None)).unwrap();
|
||||
let entry = json!({"params": {"path": outside.to_string_lossy()}, "name": "outside.png", "width": 40, "height": 20});
|
||||
let roots = crate::file_access::AutomationRoots::new(&[&base.join("in")], &[]).unwrap().unwrap();
|
||||
let view = ViewInfo::default();
|
||||
let click = |s: &mut Session| {
|
||||
s.pointer(&PointerEvent::new(PointerKind::Down, 10.0, 10.0), view)?;
|
||||
s.pointer(&PointerEvent::new(PointerKind::Up, 10.0, 10.0), view)
|
||||
};
|
||||
let mut s = session();
|
||||
s.select_tool("place", view).unwrap();
|
||||
// Loaded within the roots (as a tool option, past the check tool.setOption makes of paths).
|
||||
crate::file_access::confine(Some(&roots), || s.set_tool_option("queue", &json!({"entries": [entry]})));
|
||||
assert!(s.place_queue_roots().is_some());
|
||||
let placed = crate::file_access::unconfined(|| click(&mut s));
|
||||
assert!(top_children(&s).is_empty(), "{placed:?}");
|
||||
// Loaded outside any roots: the click places it.
|
||||
s.select_tool("place", view).unwrap();
|
||||
crate::file_access::unconfined(|| s.set_tool_option("queue", &json!({"entries": [entry]})));
|
||||
assert!(s.place_queue_roots().is_none());
|
||||
crate::file_access::unconfined(|| click(&mut s)).unwrap();
|
||||
assert_eq!(top_children(&s).len(), 1);
|
||||
let _ = std::fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
/// With [`Session::place_cursor_requests`] set, a click of the place cursor asks the frontend to
|
||||
/// place its file ([`UiRequest::Place`]) and places nothing itself; the params it asks with place
|
||||
/// the file as the click places it without the flag.
|
||||
|
||||
@@ -658,11 +658,27 @@ impl Session {
|
||||
self.tool.options()
|
||||
}
|
||||
pub fn set_tool_option(&mut self, key: &str, v: &Value) {
|
||||
if key == "queue" && self.tool.id() == "place" {
|
||||
self.note_place_queue_roots();
|
||||
}
|
||||
self.tool.set_option(key, v);
|
||||
self.tool_set_since_reset = true;
|
||||
self.keep_tool_settings();
|
||||
}
|
||||
|
||||
/// Keep the automation roots in force now as those the place cursor's queue, being loaded,
|
||||
/// is placed under ([`Session::place_queue_roots`]).
|
||||
fn note_place_queue_roots(&mut self) {
|
||||
self.place_queue_roots = crate::file_access::current();
|
||||
}
|
||||
|
||||
/// The automation roots the place cursor's files are placed under: those in force when it
|
||||
/// was loaded. A frontend that places them itself ([`UiRequest::Place`]) reads them within
|
||||
/// these roots ([`crate::file_access::confine`]).
|
||||
pub fn place_queue_roots(&self) -> Option<std::sync::Arc<crate::file_access::AutomationRoots>> {
|
||||
self.place_queue_roots.clone()
|
||||
}
|
||||
|
||||
/// The options of tool `id`: the active tool's, or those it would have if chosen now.
|
||||
pub fn tool_options_of(&self, id: &str) -> Value {
|
||||
if id == self.tool.id() { self.tool.options() } else { self.make_tool(id).options() }
|
||||
@@ -673,6 +689,9 @@ impl Session {
|
||||
/// Liquify tools' Global Brush Dimensions, the Smooth tool's Fidelity for Pencil, Paintbrush
|
||||
/// and Blob Brush Alt smoothing). Returns the tool's options.
|
||||
pub fn set_tool_options(&mut self, id: Option<&str>, values: &Map<String, Value>) -> Value {
|
||||
if values.contains_key("queue") && id.unwrap_or(self.tool.id()) == "place" {
|
||||
self.note_place_queue_roots();
|
||||
}
|
||||
let active = id.is_none_or(|id| id == self.tool.id());
|
||||
self.tool_set_since_reset |= active;
|
||||
let mut other = (!active).then(|| self.make_tool(id.unwrap_or_default()));
|
||||
@@ -842,6 +861,13 @@ impl Session {
|
||||
}
|
||||
// The place cursor's file, placed by the frontend that asked for it.
|
||||
Action::Exec(cmd, p) if cmd == "file.place" && self.place_cursor_requests => ui.push(UiRequest::Place(p)),
|
||||
// The place cursor's file, read within the roots of whoever loaded the cursor.
|
||||
Action::Exec(cmd, p) if cmd == "file.place" => {
|
||||
let roots = self.place_queue_roots.clone();
|
||||
if let Some(w) = crate::file_access::confine(roots.as_ref(), || self.execute(&cmd, &p))?.get("warning").and_then(Value::as_str) {
|
||||
ui.push(UiRequest::Status(w.to_string()));
|
||||
}
|
||||
}
|
||||
Action::Exec(cmd, p) => {
|
||||
// A command's warning (Crop Image's when the image couldn't be decoded) goes to the
|
||||
// status bar too.
|
||||
|
||||
@@ -376,13 +376,18 @@ fn queue_files(app: &mut VectorcraftApp, p: &Value, read: Read) -> Result<Value,
|
||||
/// read off the UI thread where the files the user places are ([`crate::placing::start`]), else at
|
||||
/// once. An error shows in the status bar.
|
||||
pub(crate) fn place_from_cursor(app: &mut VectorcraftApp, p: &Value) {
|
||||
if crate::placing::available(app) {
|
||||
if let Err(e) = crate::placing::start(app, p, false) {
|
||||
app.status(e);
|
||||
// Read within the automation roots of whoever loaded the cursor, even on a click of the person
|
||||
// at the keyboard: an agent's queue reaches no file it wasn't given.
|
||||
let roots = app.session.place_queue_roots();
|
||||
vectorcraft_engine::file_access::confine(roots.as_ref(), || {
|
||||
if crate::placing::available(app) {
|
||||
if let Err(e) = crate::placing::start(app, p, false) {
|
||||
app.status(e);
|
||||
}
|
||||
} else {
|
||||
let _ = engine(app, "file.place", p);
|
||||
}
|
||||
} else {
|
||||
let _ = engine(app, "file.place", p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The cursor's thumbnails from `file.place.queue`'s reply `r`.
|
||||
|
||||
Reference in New Issue
Block a user