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: 2 additions & 2 deletions crates/app/src/ui/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use plotx_core::state::{
ObjectFrame, ObjectId, PanDrag, PanelLabelDrag, PanelNoteEditState, PhaseDrag, PhaseDragKind,
PhaseOrient, PlotxApp, Region, RegionDrag, RegionDragKind, ResizeHandle, SHEET_COL_W_PT,
SHEET_HEADER_H_PT, SHEET_MAX_ROWS, SHEET_ROW_H_PT, Selection, SelectionDrag, TableDataset,
TextEditState, TileDropPreview, Tool, ZoomAxis, ZoomDrag, board_frames, frame_board_pos,
frame_board_rect, set_frame_board_pos, toggle_frame_selection_synced,
TextEditState, TileDropCacheKey, TileDropPreview, Tool, ZoomAxis, ZoomDrag, board_frames,
frame_board_pos, frame_board_rect, set_frame_board_pos, toggle_frame_selection_synced,
};
use plotx_core::{Integral2D, IntegralResult};
use plotx_render::Rect as PlotRect;
Expand Down
186 changes: 142 additions & 44 deletions crates/app/src/ui/canvas/tiling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,19 @@ pub(crate) fn update_tile_drop(
let page_pt = app.doc.canvases[target].size_pt();
let layout = app.doc.canvases[target].layout;
let existing_ids = app.doc.canvases[target].plot_object_ids();
if app.session.ui.tile_drop.as_ref().is_some_and(|preview| {
preview.target == target
&& preview
.existing
.iter()
.map(|(id, _)| *id)
.eq(existing_ids.iter().copied())
&& preview_cell_matches(preview, page_pt, existing_ids.len(), pointer_page)
}) {
let region = plotx_core::layout::tiling_drop_region(
page_pt,
existing_ids.len(),
[pointer_page.x, pointer_page.y],
);
let cache_key = tile_cache_key(drag, target, page_pt, layout, &existing_ids, region);
if app
.session
.ui
.tile_drop
.as_ref()
.is_some_and(|preview| preview.cache_key == cache_key)
{
return true;
}
let existing_items: Vec<_> = existing_ids
Expand All @@ -69,13 +73,33 @@ pub(crate) fn update_tile_drop(
[pointer_page.x, pointer_page.y],
);
app.session.ui.tile_drop = Some(TileDropPreview {
cache_key,
target,
newcomer: plan.newcomer,
existing: plan.existing,
});
true
}

fn tile_cache_key(
drag: &ObjectDrag,
target_canvas: usize,
target_page_pt: [f32; 2],
target_layout: plotx_core::layout::PageLayout,
target_existing_ids: &[ObjectId],
region: plotx_core::layout::TilingDropRegion,
) -> TileDropCacheKey {
TileDropCacheKey {
source_canvas: drag.canvas,
source_object: drag.object,
target_canvas,
target_page_pt,
target_layout,
target_existing_ids: target_existing_ids.to_vec(),
region,
}
}

fn layout_item(canvas: &CanvasDocument, id: ObjectId) -> Option<plotx_core::layout::LayoutItem> {
let object = canvas.object(id)?;
let plot = object.plot()?;
Expand All @@ -86,41 +110,6 @@ fn layout_item(canvas: &CanvasDocument, id: ObjectId) -> Option<plotx_core::layo
))
}

fn preview_cell_matches(
preview: &TileDropPreview,
page: [f32; 2],
existing_count: usize,
pointer: Pos2,
) -> bool {
if existing_count != 1 {
return true;
}
let Some((_, existing)) = preview.existing.first() else {
return false;
};
let nx = if page[0] > 0.0 {
pointer.x / page[0]
} else {
0.5
};
let ny = if page[1] > 0.0 {
pointer.y / page[1]
} else {
0.5
};
let horizontal = (nx - 0.5).abs() >= (ny - 0.5).abs();
let newcomer_last = if horizontal { nx >= 0.5 } else { ny >= 0.5 };
let dx = preview.newcomer.x - existing.x;
let dy = preview.newcomer.y - existing.y;
let preview_horizontal = dx.abs() >= dy.abs();
let preview_last = if preview_horizontal {
dx >= 0.0
} else {
dy >= 0.0
};
horizontal == preview_horizontal && newcomer_last == preview_last
}

/// Falls back to a plain move if the atomic action cannot be built.
pub(crate) fn commit_tile_drop(
app: &mut PlotxApp,
Expand Down Expand Up @@ -185,3 +174,112 @@ pub(crate) fn paint_tile_preview(
painter.add(segment);
}
}

#[cfg(test)]
mod tests {
use super::*;

fn drag(canvas: usize, object: ObjectId) -> ObjectDrag {
ObjectDrag {
canvas,
object,
kind: ObjectDragKind::Move,
before: ObjectFrame::new(0.0, 0.0, 10.0, 10.0),
start_pointer: [0.0; 2],
start_pointer_screen: [0.0; 2],
others: Vec::new(),
active: true,
}
}

#[test]
fn tile_cache_identity_tracks_source_region_target_and_existing_order() {
let layout = plotx_core::layout::PageLayout::default();
let page = [400.0, 300.0];
let base = tile_cache_key(
&drag(0, 10),
2,
page,
layout,
&[20, 21],
plotx_core::layout::TilingDropRegion::Left,
);
assert_eq!(
base,
tile_cache_key(
&drag(0, 10),
2,
page,
layout,
&[20, 21],
plotx_core::layout::TilingDropRegion::Left,
)
);
assert_ne!(
base,
tile_cache_key(
&drag(1, 11),
2,
page,
layout,
&[20, 21],
plotx_core::layout::TilingDropRegion::Left,
)
);
assert_ne!(
base,
tile_cache_key(
&drag(0, 10),
3,
page,
layout,
&[20, 21],
plotx_core::layout::TilingDropRegion::Left,
)
);
assert_ne!(
base,
tile_cache_key(
&drag(0, 10),
2,
[401.0, 300.0],
layout,
&[20, 21],
plotx_core::layout::TilingDropRegion::Left,
)
);
assert_ne!(
base,
tile_cache_key(
&drag(0, 10),
2,
page,
plotx_core::layout::PageLayout { cols: 2, ..layout },
&[20, 21],
plotx_core::layout::TilingDropRegion::Left,
)
);
assert_ne!(
base,
tile_cache_key(
&drag(0, 10),
2,
page,
layout,
&[20, 21],
plotx_core::layout::TilingDropRegion::Right,
)
);
assert_ne!(
base,
tile_cache_key(
&drag(0, 10),
2,
page,
layout,
&[21, 20],
plotx_core::layout::TilingDropRegion::Left,
)
);
}
}
49 changes: 48 additions & 1 deletion crates/app/src/ui/export_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ pub(super) fn export_options_window(app: &mut PlotxApp, ctx: &egui::Context) {
});
}

ui.add_space(8.0);
ui.checkbox(
&mut pending.trim_to_visible_content,
"Trim page to visible content",
)
.on_hover_text(
"Removes page whitespace around visible content without enlarging the content.\n\
With journal/column presets, the final physical page width may be smaller than the preset.\n\
Empty pages keep their original size.",
);

let preset = pending.preset;
let scope = pending.scope;
let dpi = pending.dpi;
Expand Down Expand Up @@ -112,13 +123,29 @@ pub(super) fn export_options_window(app: &mut PlotxApp, ctx: &egui::Context) {
if export {
app.session.ui.export_options = None;
if let Some(settings) = settings {
crate::ui::file_dialogs::export_with_options(app, settings);
let trim = settings.trim_to_visible_content;
if let Some(path) = crate::ui::file_dialogs::choose_export_path(&settings) {
plotx_core::settings::update(move |settings| {
apply_confirmed_export_default(&mut settings.export, trim, true);
});
app.export_to(settings, &path);
}
}
} else if cancel || modal.should_close() {
app.session.ui.export_options = None;
}
}

fn apply_confirmed_export_default(
defaults: &mut plotx_core::settings::ExportDefaults,
trim_to_visible_content: bool,
path_confirmed: bool,
) {
if path_confirmed {
defaults.trim_to_visible_content = trim_to_visible_content;
}
}

fn build_report(
app: &PlotxApp,
preset: ExportPreset,
Expand Down Expand Up @@ -170,3 +197,23 @@ fn status_dot(ui: &mut Ui, status: ComplianceStatus) {
let (rect, _) = ui.allocate_exact_size(Vec2::splat(10.0), Sense::hover());
ui.painter().circle_filled(rect.center(), 4.0, color);
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn only_confirmed_path_updates_trim_and_never_dpi() {
let mut defaults = plotx_core::settings::ExportDefaults {
dpi: 600,
..Default::default()
};
apply_confirmed_export_default(&mut defaults, true, false);
assert!(!defaults.trim_to_visible_content);
assert_eq!(defaults.dpi, 600);

apply_confirmed_export_default(&mut defaults, true, true);
assert!(defaults.trim_to_visible_content);
assert_eq!(defaults.dpi, 600);
}
}
8 changes: 2 additions & 6 deletions crates/app/src/ui/file_dialogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,16 +390,12 @@ fn open_folder_path(app: &mut PlotxApp, path: &std::path::Path) {
}
}

pub(crate) fn export_with_options(app: &mut PlotxApp, settings: ExportSettings) {
let Some(path) = rfd::FileDialog::new()
pub(crate) fn choose_export_path(settings: &ExportSettings) -> Option<std::path::PathBuf> {
rfd::FileDialog::new()
.add_filter(settings.format.label(), &[settings.format.extension()])
.set_file_name(settings.format.default_file_name())
.set_title(settings.format.dialog_title())
.save_file()
else {
return;
};
app.export_to(settings, &path);
}

pub(crate) fn load_processing_scheme(app: &mut PlotxApp, di: usize) {
Expand Down
12 changes: 4 additions & 8 deletions crates/core/src/actions/arrange.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,14 +324,10 @@ fn simplified_axis_changes(
.filter_map(|(&id, (keep_x, keep_y))| {
let before = canvas.object(id)?.plot()?.axis_overrides.clone();
let mut after = before.clone();
if !keep_x {
after.x_show_tick_labels = Some(false);
after.x_show_label = Some(false);
}
if !keep_y {
after.y_show_tick_labels = Some(false);
after.y_show_label = Some(false);
}
after.x_show_tick_labels = (!keep_x).then_some(false);
after.x_show_label = (!keep_x).then_some(false);
after.y_show_tick_labels = (!keep_y).then_some(false);
after.y_show_label = (!keep_y).then_some(false);
(after != before).then_some(AxisOverrideChange { id, before, after })
})
.collect()
Expand Down
Loading
Loading