Place: drops that arrive before one UI pass stay separate

All pending drops went into one batch, so two single images counted as several: both queued and neither filled the frame it was dropped on. Files of one raw input now share a batch, and each batch is opened on its own.
This commit is contained in:
texjer committed 2026-11-04 19:56:22 -05:00
1 parent b458bec4c5
commit 267917b8de
3 files changed
+41 -6

No files matched your search

+7 -6
View File
@@ -62,10 +62,11 @@ impl DesignApp {
// A drop without pointer events (dragged in from another application) asks the
// system now, while the pointer is still where the files were let go.
let system = if pointer.is_none() && !raw.dropped_files.is_empty() { self.system_pointer(ctx, raw) } else { None };
let batch = self.pending_drops.last().map_or(0, |d| d.batch.wrapping_add(1));
for file in std::mem::take(&mut raw.dropped_files) {
let path = file.path().to_string_lossy().to_string();
if !path.is_empty() {
self.pending_drops.push(PendingDrop { path, pointer, system, generation });
self.pending_drops.push(PendingDrop { path, pointer, system, batch, generation });
}
}
}
@@ -154,12 +155,12 @@ impl DesignApp {
// suppress later files that belong to the same captured batch.
let current = self.input_generation();
let drops: Vec<_> = std::mem::take(&mut self.pending_drops).into_iter().filter(|d| d.generation == current).collect();
if drops.is_empty() {
return;
}
// Dropped together, they go together: several images load the place cursor at once. Each
// file keeps the pointer it was dropped at, or the system's from when it arrived.
self.open_dropped_batch(drops.into_iter().map(|d| (d.path, d.pointer.or(d.system))).collect());
// file keeps the pointer it was dropped at, or the system's from when it arrived. Separate
// drops stay separate, so a single image still fills the frame it was dropped on.
for batch in drops.chunk_by(|a, b| a.batch == b.batch) {
self.open_dropped_batch(batch.iter().map(|d| (d.path.clone(), d.pointer.or(d.system))).collect());
}
}
/// Where the pointer is in the window, asked of the system ([`crate::Services::pointer`]),
+3
View File
@@ -115,6 +115,9 @@ struct PendingDrop {
/// for a drop that brought no pointer events ([`crate::Services::pointer`]). Each drop keeps
/// its own, converted with the window's place and the UI scale of that input.
system: Option<egui::Pos2>,
/// Which drop this file came with: files of one raw input share it, so drops that arrive
/// before the same UI pass stay separate.
batch: u64,
generation: InputGeneration,
}
+31
View File
@@ -1011,6 +1011,37 @@ fn a_pointerless_drop_keeps_the_system_pointer_of_its_own_input() {
assert!(d.assets[&g.asset].name.contains("drop-own-input"));
}
/// Two drops that arrive before one UI pass stay separate: the first still fills the frame it was
/// dropped on, and the second, off any frame, loads the place cursor. Merged, they'd both queue.
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn separate_drops_before_one_pass_are_not_one_batch() {
let (mut app, frame, inside, outside) = app_with_drop_frame();
let at = std::rc::Rc::new(std::cell::Cell::new(inside));
let reported = at.clone();
app.services.pointer = Some(Box::new(move || Some(reported.get())));
let ctx = egui::Context::default();
let window = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 900.0));
let (first, second) = (dropped_png("separate-drop-a"), dropped_png("separate-drop-b"));
let mut raw = pointerless_drop(&first, window);
app.raw_input_hook(&ctx, &mut raw);
at.set(outside);
let mut raw = pointerless_drop(&second, window);
app.raw_input_hook(&ctx, &mut raw);
assert_eq!(app.pending_drops.len(), 2);
ctx.run_ui(raw, |_| app.take_pending_drops()).textures_delta.clear();
let _ = std::fs::remove_file(&first);
let _ = std::fs::remove_file(&second);
let d = &app.session.doc().unwrap().doc;
let designcraft_doc::Content::Graphic(g) = &d.item(designcraft_doc::ItemId(frame)).unwrap().content else {
panic!("the first drop fills the frame it was dropped on")
};
assert!(d.assets[&g.asset].name.contains("separate-drop-a"));
let loaded = app.session.loaded.as_ref().expect("the second drop loads the place cursor");
assert!(d.assets[&loaded.asset].name.contains("separate-drop-b"));
assert!(app.session.loaded_queue.is_empty());
}
/// A filled compound path (Object > Compound Paths > Make) is a frame a dropped image fills, as
/// in InDesign, through its ring and not its hole.
#[test]