Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/app/src/ui/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ pub(crate) enum CanvasInteractionClearScope {
}

pub fn render_central(app: &mut PlotiumApp, ui: &mut Ui) {
let viewport = ui.available_size();
if viewport.x > 0.0 && viewport.y > 0.0 {
app.session.ui.board_viewport_aspect = viewport.x / viewport.y;
}
let Some(ci) = app.session.active_canvas else {
if app.doc.canvases.is_empty() && !board_frames(app).is_empty() {
board::render_sheets_only(app, ui);
Expand Down
57 changes: 40 additions & 17 deletions crates/app/src/ui/file_dialogs/image_import/new_pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,24 @@ pub(super) fn commit(
job: &mut ImportJob,
results: Vec<Result<Candidate, Failure>>,
) {
let sizes: Vec<_> = results
.iter()
.filter_map(|result| result.as_ref().ok())
.map(|candidate| CanvasDocument::new(String::new(), candidate.auto_page_size_mm).size_pt())
.collect();
let positions = plotium_core::state::imported_board_positions(
app,
&sizes,
app.session.ui.board_viewport_aspect,
);
let first_canvas = app.doc.canvases.len();
let active_before = app.session.active_canvas;
let selection = app.session.ui.selection.clone();
let hierarchical = app.session.ui.hierarchical_selection.clone();
let panel_label = app.session.ui.panel_label_selection;
let plot_part = app.session.ui.plot_part_selection;
let data_selection = app.session.ui.data_selection.clone();
let view = app.session.view;
let mut failures = Vec::new();
let mut warnings = Vec::new();
let mut staged_assets: Vec<AssetRecord> = Vec::new();
Expand Down Expand Up @@ -45,17 +61,14 @@ pub(super) fn commit(
}

let index = first_canvas + imported;
let mut page =
CanvasDocument::new(format!("Figure {}", index + 1), candidate.auto_page_size_mm);
let name = candidate.display_name().to_owned();
let mut page = CanvasDocument::new(name, candidate.auto_page_size_mm);
page.board_pos = positions[imported];
let id = page.allocate_object_id();
let [width, height] = page.size_pt();
page.objects.push(CanvasObject {
id,
name: Path::new(&candidate.basename)
.file_stem()
.and_then(|name| name.to_str())
.unwrap_or("Image")
.to_owned(),
name: candidate.display_name().to_owned(),
frame: ObjectFrame::new(0.0, 0.0, width, height),
locked: false,
visible: true,
Expand All @@ -67,16 +80,19 @@ pub(super) fn commit(
});
page.create_panel_for_content(id)
.expect("a raster image supports a one-item panel");
let empty = CanvasDocument::new(page.name.clone(), page.size_mm);
page_actions.push(Action::insert_canvas(
index,
empty.clone(),
if imported == 0 {
active_before
} else {
Some(index - 1)
},
));
let mut empty = CanvasDocument::new(page.name.clone(), page.size_mm);
empty.board_pos = page.board_pos;
let mut insert = Action::insert_canvas(index, empty.clone(), active_before);
if let Action::InsertCanvas {
auto_place,
activate,
..
} = &mut insert
{
*auto_place = false;
*activate = false;
}
page_actions.push(insert);
page_actions.push(Action::ReplacePanelState {
canvas: index,
before: PanelState::of(&empty),
Expand Down Expand Up @@ -121,5 +137,12 @@ pub(super) fn commit(
} else {
job.state = ImportImageState::Failed;
}
app.session.active_canvas = active_before;
app.session.ui.selection = selection;
app.session.ui.hierarchical_selection = hierarchical;
app.session.ui.panel_label_selection = panel_label;
app.session.ui.plot_part_selection = plot_part;
app.session.ui.data_selection = data_selection;
app.session.view = view;
record_result(app, job.operation, imported, failures, warnings);
}
55 changes: 49 additions & 6 deletions crates/app/src/ui/file_dialogs/image_import_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ fn successful_import_into_empty_project_creates_one_undoable_page() {
assert_eq!(app.doc.canvases.len(), 1);
assert_eq!(app.doc.canvases[0].objects.len(), 1);
assert_eq!(app.doc.assets.len(), 1);
assert_eq!(app.session.active_canvas, Some(0));
assert_eq!(app.session.active_canvas, None);
assert_eq!(
app.doc.canvases[0].objects[0].frame,
ObjectFrame::new(
Expand Down Expand Up @@ -297,6 +297,8 @@ fn multiple_images_into_empty_project_create_distinct_pages_in_source_order() {
],
);
assert_eq!(app.doc.canvases.len(), 2);
assert_eq!(app.doc.canvases[0].name, "first");
assert_eq!(app.doc.canvases[1].name, "second");
assert_eq!(app.doc.canvases[0].objects[0].name, "first");
assert_eq!(app.doc.canvases[1].objects[0].name, "second");
assert_ne!(app.doc.canvases[0].size_mm, app.doc.canvases[1].size_mm);
Expand All @@ -314,7 +316,7 @@ fn multiple_images_into_empty_project_create_distinct_pages_in_source_order() {
}

#[test]
fn sequential_new_page_batches_never_retarget_the_active_imported_page() {
fn sequential_new_page_batches_preserve_the_existing_active_page() {
let mut app = PlotiumApp::default();
app.doc.canvases.push(CanvasDocument::new(
"Existing".to_owned(),
Expand All @@ -332,7 +334,7 @@ fn sequential_new_page_batches_never_retarget_the_active_imported_page() {
],
);
assert_eq!(app.doc.canvases.len(), 3);
assert_eq!(app.session.active_canvas, Some(2));
assert_eq!(app.session.active_canvas, Some(0));
assert!(app.doc.canvases[0].objects.is_empty());
assert_eq!(app.session.undo_stack.len(), 1);

Expand All @@ -343,21 +345,21 @@ fn sequential_new_page_batches_never_retarget_the_active_imported_page() {
vec![Ok(candidate("third.png", [300, 300]))],
);
assert_eq!(app.doc.canvases.len(), 4);
assert_eq!(app.session.active_canvas, Some(3));
assert_eq!(app.session.active_canvas, Some(0));
assert_eq!(app.doc.canvases[2].objects.len(), 1);
assert_eq!(app.doc.canvases[3].objects[0].name, "third");
assert_eq!(app.session.undo_stack.len(), 2);

app.undo();
assert_eq!(app.doc.canvases.len(), 3);
assert_eq!(app.session.active_canvas, Some(2));
assert_eq!(app.session.active_canvas, Some(0));
app.undo();
assert_eq!(app.doc.canvases.len(), 1);
assert_eq!(app.session.active_canvas, Some(0));
app.redo();
app.redo();
assert_eq!(app.doc.canvases.len(), 4);
assert_eq!(app.session.active_canvas, Some(3));
assert_eq!(app.session.active_canvas, Some(0));
}

#[test]
Expand Down Expand Up @@ -631,3 +633,44 @@ fn disconnected_worker_reports_and_keeps_prepared_images_once() {
app.undo();
assert!(app.doc.canvases.is_empty());
}

#[test]
fn image_batch_history_keeps_the_existing_editing_target() {
let mut app = PlotiumApp::default();
let mut existing = ready_job(&mut app, ImportImageTarget::NewPages);
commit(
&mut app,
&mut existing,
vec![Ok(candidate("existing.png", [2, 2]))],
);
app.session.active_canvas = Some(0);
let object = app.doc.canvases[0].objects[0].id;
app.select_object(0, object);
let selection = app.session.ui.hierarchical_selection.clone();
app.doc.canvases[0].board_pos = [-100.0, 140.0];
let existing = app.doc.canvases[0].board_rect_pt();
app.session.tool = plotium_core::state::Tool::ManualPhase;
let mut job = ready_job(&mut app, ImportImageTarget::NewPages);
commit(
&mut app,
&mut job,
vec![
Ok(candidate("wide.png", [400, 200])),
Ok(candidate("tall.png", [200, 400])),
],
);
assert_eq!(app.session.active_canvas, Some(0));
assert_eq!(app.session.tool, plotium_core::state::Tool::ManualPhase);
assert_eq!(app.doc.canvases[0].board_pos, [-100.0, 140.0]);
let wide = app.doc.canvases[1].board_rect_pt();
let tall = app.doc.canvases[2].board_rect_pt();
assert!(wide.top > existing.bottom() && tall.top > existing.bottom());
assert!(wide.right() < tall.left || wide.bottom() < tall.top);
app.undo();
assert_eq!(app.session.ui.selection.object(), Some(object));
assert_eq!(app.session.ui.hierarchical_selection, selection);
app.redo();
assert_eq!(app.session.active_canvas, Some(0));
assert_eq!(app.session.ui.selection.object(), Some(object));
assert_eq!(app.session.tool, plotium_core::state::Tool::ManualPhase);
}
20 changes: 15 additions & 5 deletions crates/core/src/actions/app_impl/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,10 @@ impl PlotiumApp {
index,
canvas,
auto_place,
activate,
..
} => {
self.insert_canvas_value(*index, canvas.as_ref().clone(), *auto_place);
self.insert_canvas_value(*index, canvas.as_ref().clone(), *auto_place, *activate);
}
Action::ApplyTheme { canvas, after, .. } => {
self.apply_theme_snapshot(*canvas, after);
Expand Down Expand Up @@ -429,6 +430,7 @@ impl PlotiumApp {
canvas_index,
canvas_resource_id,
prepared_canvas,
board_position,
dataset,
canvas_name,
size_mm,
Expand Down Expand Up @@ -478,18 +480,26 @@ impl PlotiumApp {
)
});
canvas.resource_id.clone_from(canvas_resource_id);
canvas.board_pos = crate::state::next_board_frame_pos(self, canvas.size_pt());
canvas.board_pos = board_position.unwrap_or_else(|| {
crate::state::next_board_frame_pos(self, canvas.size_pt())
});
for object in &mut canvas.objects {
if let Some(plot) = object.plot_mut() {
plot.set_figure_typography(self.doc.style_library.figure_typography);
}
}
self.doc.canvases.push(canvas);
self.rebuild_canvases_for(*dataset_index);
self.session.active_canvas = Some(*canvas_index);
if board_position.is_none() {
self.session.active_canvas = Some(*canvas_index);
}
}
// Background batch insertion preserves the active editing tool
// and draft; focus_single can cancel a live manual phase edit.
if board_position.is_none() {
self.focus_single(*dataset_index);
self.session.view = PrimaryView::Canvas;
}
self.focus_single(*dataset_index);
self.session.view = PrimaryView::Canvas;
}
Action::TransferObjects { .. } => self.apply_transfer(action),
Action::TileDrop { .. } => self.apply_tile_drop(action),
Expand Down
39 changes: 38 additions & 1 deletion crates/core/src/actions/app_impl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,28 @@ impl PlotiumApp {
}
}

fn insert_canvas_value(&mut self, index: usize, mut canvas: CanvasDocument, auto_place: bool) {
fn insert_canvas_value(
&mut self,
index: usize,
mut canvas: CanvasDocument,
auto_place: bool,
activate: bool,
) {
if index > self.doc.canvases.len() {
return;
}
if auto_place {
canvas.board_pos = crate::state::next_board_frame_pos(self, canvas.size_pt());
}
self.doc.canvases.insert(index, canvas);
if !activate {
if let Some(active) = &mut self.session.active_canvas
&& *active >= index
{
*active += 1;
}
return;
}
self.session.active_canvas = Some(index);
let active = self.doc.canvases[index]
.active_dataset()
Expand All @@ -262,6 +276,29 @@ impl PlotiumApp {
self.set_selection(Selection::None);
}

fn remove_background_import_canvas(&mut self, index: usize, fallback: Option<usize>) {
if index >= self.doc.canvases.len() {
return;
}
let removed = self.doc.canvases.remove(index).resource_id;
self.session
.ui
.frame_selection
.retain(|frame| *frame != crate::state::BoardFrameId::Page(removed));
self.session.ui.hierarchical_selection.clear_canvas(removed);
match self.session.active_canvas {
Some(active) if active == index => {
self.session.active_canvas = fallback.filter(|&i| i < self.doc.canvases.len());
self.sync_selection_to_active_canvas();
if self.session.tool.is_data_tool() {
self.set_tool(crate::state::Tool::BrowseZoom);
}
}
Some(active) if active > index => self.session.active_canvas = Some(active - 1),
_ => {}
}
}

fn set_canvas_size(&mut self, canvas: usize, state: &PageSizeState) {
let Some(c) = self.doc.canvases.get_mut(canvas) else {
return;
Expand Down
7 changes: 6 additions & 1 deletion crates/core/src/actions/app_impl/native_plot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ impl PlotiumApp {
self.validate_plot_insertion(insertion)?;
match insertion {
NativePlotInsertion::NewCanvas { page, .. } => {
self.insert_canvas_value(self.doc.canvases.len(), page.as_ref().clone(), false);
self.insert_canvas_value(
self.doc.canvases.len(),
page.as_ref().clone(),
false,
true,
);
}
NativePlotInsertion::ExistingCanvas {
canvas,
Expand Down
27 changes: 24 additions & 3 deletions crates/core/src/actions/app_impl/revert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,14 @@ impl PlotiumApp {
Action::InsertCanvas {
index,
active_before,
activate,
..
} => {
self.remove_canvas_at(*index, *active_before);
if *activate {
self.remove_canvas_at(*index, *active_before);
} else {
self.remove_background_import_canvas(*index, *active_before);
}
}
Action::ApplyTheme { canvas, before, .. } => {
self.apply_theme_snapshot(*canvas, before);
Expand Down Expand Up @@ -429,8 +434,11 @@ impl PlotiumApp {
inserted_object_id,
active_canvas_before,
active_dataset_before,
board_position,
..
} => {
let background = board_position.is_some();
let removed_active_dataset = self.active_dataset() == Some(*dataset_index);
if let Some(ci) = inserted_into_existing_canvas {
if let Some(canvas) = self.doc.canvases.get_mut(*ci)
&& let Some(id) = inserted_object_id
Expand Down Expand Up @@ -466,15 +474,28 @@ impl PlotiumApp {
self.session.ui.panel_note_inline_edit = None;
}
}
} else if background {
self.remove_background_import_canvas(*canvas_index, *active_canvas_before);
} else if *canvas_index < self.doc.canvases.len() {
self.doc.canvases.remove(*canvas_index);
}
if *dataset_index < self.doc.datasets.len() {
let dataset = self.doc.datasets.remove(*dataset_index);
self.forget_removed_dataset(*dataset_index, dataset.resource_id());
}
self.session.active_canvas = *active_canvas_before;
self.set_active_dataset(*active_dataset_before);
if !background {
self.session.active_canvas = *active_canvas_before;
self.set_active_dataset(*active_dataset_before);
} else if removed_active_dataset {
if self.active_dataset().is_none() {
self.set_active_dataset(
active_dataset_before.filter(|&i| i < self.doc.datasets.len()),
);
}
if self.session.tool == crate::state::Tool::ManualPhase {
self.set_tool(crate::state::Tool::BrowseZoom);
}
}
}
Action::TransferObjects { .. } => self.revert_transfer(action),
Action::TileDrop { .. } => self.revert_tile_drop(action),
Expand Down
Loading
Loading