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
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ These instructions apply to the entire repository.

## Rust workspace

- Stable resources and components use typed IDs. Collection indices are
one-shot lookup positions only; they must not cross action, job, frame, or
persistence boundaries.
- Respect crate boundaries: parsing in `plotx-io`, scientific algorithms in
`plotx-analysis`, spectral transforms in `plotx-processing`, presentation
models in `plotx-figure`, rendering in `plotx-render`, application state in
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/app/src/shot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ fn setup(app: &mut PlotxApp) {
&& let Some(object) = app.doc.canvases[ci].active_plot_object_id()
{
app.session.ui.analysis_selection = Some(AnalysisSelection {
dataset: 0,
canvas: ci,
dataset: app.doc.datasets[0].resource_id(),
canvas: app.doc.canvases[ci].resource_id,
object,
x_range: AxisRange::new(FIT_LO, FIT_HI),
y_range: None,
Expand Down
6 changes: 3 additions & 3 deletions crates/app/src/ui/batch_workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ impl AutomationUi {
.and_then(|index| app.doc.canvases.get(index))
{
let target = plotx_core::automation::ResourceRef {
id: canvas.resource_id.clone(),
id: canvas.resource_id.to_string(),
kind: plotx_core::automation::ResourceKindId::new(
plotx_core::automation::KIND_CANVAS,
),
Expand All @@ -424,15 +424,15 @@ fn highlight(app: &mut PlotxApp, id: &str) {
.doc
.datasets
.iter()
.position(|dataset| dataset.resource_id() == id)
.position(|dataset| dataset.resource_id().to_string() == id)
{
app.set_active_dataset(Some(index));
}
if let Some(index) = app
.doc
.canvases
.iter()
.position(|canvas| canvas.resource_id == id)
.position(|canvas| canvas.resource_id.to_string() == id)
{
app.session.active_canvas = Some(index);
app.sync_selection_to_active_canvas();
Expand Down
6 changes: 4 additions & 2 deletions crates/app/src/ui/canvas/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,8 +485,10 @@ fn activate_frame(app: &mut PlotxApp, frame: FrameRef) {
FrameRef::Page(ci) => {
activate_page(app, ci);
if let Some(canvas) = app.doc.canvases.get(ci) {
let datasets = canvas.dataset_indices();
let lead = canvas.active_dataset();
let lead = canvas
.active_dataset()
.and_then(|id| app.doc.dataset_index(id));
let datasets = app.doc.page_dataset_indices(ci);
app.focus_datasets(&datasets, lead);
}
}
Expand Down
7 changes: 4 additions & 3 deletions crates/app/src/ui/canvas/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,8 @@ mod tests {
use super::*;
use plotx_core::state::TextBox;

fn text_object(id: ObjectId, frame: ObjectFrame) -> CanvasObject {
fn text_object(id: u64, frame: ObjectFrame) -> CanvasObject {
let id = ObjectId::new(id);
CanvasObject {
id,
name: format!("o{id}"),
Expand Down Expand Up @@ -461,7 +462,7 @@ mod tests {
zoom: 2.0,
};
let page = bt.page_screen_rect(&canvas);
let r = bt.object_screen_rect(&canvas, 5).unwrap();
let r = bt.object_screen_rect(&canvas, ObjectId::new(5)).unwrap();
assert!((r.left - (page.left() + 12.0 * 2.0)).abs() < 1e-3);
assert!((r.top - (page.top() + 8.0 * 2.0)).abs() < 1e-3);
assert!((r.width - 40.0 * 2.0).abs() < 1e-3);
Expand Down Expand Up @@ -615,6 +616,6 @@ mod tests {
.objects
.push(text_object(2, ObjectFrame::new(20.0, 20.0, 50.0, 50.0)));
let hit = hit_object(&canvas, Pos2::new(35.0, 35.0), 1.0).unwrap();
assert_eq!(hit.object, 2);
assert_eq!(hit.object, ObjectId::new(2));
}
}
12 changes: 8 additions & 4 deletions crates/app/src/ui/canvas/interactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ pub(crate) fn finish_selection_drag(
});

app.session.ui.analysis_selection = Some(AnalysisSelection {
dataset,
canvas: ci,
dataset: app.doc.datasets[dataset].resource_id(),
canvas: app.doc.canvases[ci].resource_id,
object: object_id,
x_range: x,
y_range: y,
Expand Down Expand Up @@ -481,8 +481,12 @@ fn select_object_datasets(app: &mut PlotxApp, ci: usize, id: ObjectId) {
let Some(object) = app.doc.canvases[ci].object(id) else {
return;
};
let active = object.dataset();
let datasets = object.dataset_indices();
let active = object.dataset().and_then(|id| app.doc.dataset_index(id));
let datasets = object
.dataset_ids()
.into_iter()
.filter_map(|id| app.doc.dataset_index(id))
.collect::<Vec<_>>();
if !datasets.is_empty() {
app.focus_datasets(&datasets, active);
} else {
Expand Down
21 changes: 11 additions & 10 deletions crates/app/src/ui/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ pub fn render_central(app: &mut PlotxApp, ui: &mut Ui) {
let Some(di) = app.doc.canvases[ci]
.object(object_id)
.and_then(|object| object.dataset())
.and_then(|id| app.doc.dataset_index(id))
else {
return;
};
Expand Down Expand Up @@ -557,15 +558,15 @@ fn resize_cursor(handle: ResizeHandle) -> egui::CursorIcon {
mod tests {
use super::*;
use plotx_core::state::{
CanvasObject, CanvasObjectKind, CanvasViewport, PanelMeta, PlotObject, TextBox,
CanvasObject, CanvasObjectKind, CanvasViewport, DatasetId, PanelMeta, PlotObject, TextBox,
};
use plotx_figure::{Axis, Figure};

#[test]
fn hit_object_selects_text_box() {
let mut canvas = CanvasDocument::new("page".to_owned(), [200.0, 200.0]);
canvas.objects.push(CanvasObject {
id: 7,
id: ObjectId::new(7),
name: "Text".to_owned(),
frame: ObjectFrame::new(20.0, 20.0, 100.0, 30.0),
locked: false,
Expand All @@ -576,21 +577,21 @@ mod tests {

let hit = hit_object(&canvas, Pos2::new(50.0, 30.0), 1.0);

assert_eq!(hit.map(|hit| hit.object), Some(7));
assert_eq!(hit.map(|hit| hit.object), Some(ObjectId::new(7)));
}

#[test]
fn hit_object_finds_object_outside_page_bounds() {
let mut canvas = CanvasDocument::new("page".to_owned(), [100.0, 100.0]);
canvas.objects.push(CanvasObject {
id: 1,
id: ObjectId::new(1),
name: "plot".to_owned(),
frame: ObjectFrame::new(-30.0, 20.0, 50.0, 40.0),
locked: false,
visible: true,
group: None,
kind: CanvasObjectKind::Plot(Box::new(PlotObject {
binding: plotx_core::state::DataBinding::single(0),
binding: plotx_core::state::DataBinding::single(DatasetId::new()),
chart: plotx_core::state::ChartSpec::default(),
stack: plotx_core::state::StackSpec::default(),
projections: plotx_core::state::AxisProjections::default(),
Expand All @@ -607,22 +608,22 @@ mod tests {

let hit = hit_object(&canvas, Pos2::new(-10.0, 30.0), 1.0);

assert_eq!(hit.map(|hit| hit.object), Some(1));
assert_eq!(hit.map(|hit| hit.object), Some(ObjectId::new(1)));
}

#[test]
fn data_edit_target_requires_data_tool_and_selected_plot() {
let mut app = PlotxApp::new();
let mut canvas = CanvasDocument::new("page".to_owned(), [200.0, 200.0]);
canvas.objects.push(CanvasObject {
id: 3,
id: ObjectId::new(3),
name: "plot".to_owned(),
frame: ObjectFrame::new(10.0, 10.0, 80.0, 60.0),
locked: false,
visible: true,
group: None,
kind: CanvasObjectKind::Plot(Box::new(PlotObject {
binding: plotx_core::state::DataBinding::single(0),
binding: plotx_core::state::DataBinding::single(DatasetId::new()),
chart: plotx_core::state::ChartSpec::default(),
stack: plotx_core::state::StackSpec::default(),
projections: plotx_core::state::AxisProjections::default(),
Expand All @@ -638,13 +639,13 @@ mod tests {
});
app.doc.canvases.push(canvas);
app.session.active_canvas = Some(0);
app.doc.canvases[0].selected_object = Some(3);
app.doc.canvases[0].selected_object = Some(ObjectId::new(3));

app.session.tool = Tool::Select;
assert_eq!(data_edit_target(&app, 0), None);

app.session.tool = Tool::BrowseZoom;
assert_eq!(data_edit_target(&app, 0), Some(3));
assert_eq!(data_edit_target(&app, 0), Some(ObjectId::new(3)));
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/app/src/ui/canvas/painting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub(crate) fn paint_analysis_selection(
let Some(selection) = &app.session.ui.analysis_selection else {
return;
};
if selection.canvas != ci || selection.object != object_id {
if selection.canvas != app.doc.canvases[ci].resource_id || selection.object != object_id {
return;
}
let Some(object) = app.doc.canvases[ci].object(object_id) else {
Expand Down
1 change: 1 addition & 0 deletions crates/app/src/ui/canvas/phase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub(crate) fn handle_phase_before_paint(
let Some(di) = app.doc.canvases[ci]
.object(object_id)
.and_then(|object| object.dataset())
.and_then(|id| app.doc.dataset_index(id))
else {
return;
};
Expand Down
26 changes: 14 additions & 12 deletions crates/app/src/ui/canvas/tiling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,10 @@ pub(crate) fn paint_tile_preview(
mod tests {
use super::*;

fn drag(canvas: usize, object: ObjectId) -> ObjectDrag {
fn drag(canvas: usize, object: u64) -> ObjectDrag {
ObjectDrag {
canvas,
object,
object: ObjectId::new(object),
kind: ObjectDragKind::Move,
before: ObjectFrame::new(0.0, 0.0, 10.0, 10.0),
start_pointer: [0.0; 2],
Expand All @@ -285,12 +285,14 @@ mod tests {
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 ids = [ObjectId::new(20), ObjectId::new(21)];
let reversed_ids = [ObjectId::new(21), ObjectId::new(20)];
let base = tile_cache_key(
&drag(0, 10),
2,
page,
layout,
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Left,
None,
);
Expand All @@ -301,7 +303,7 @@ mod tests {
2,
page,
layout,
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Left,
None,
)
Expand All @@ -313,7 +315,7 @@ mod tests {
2,
page,
layout,
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Left,
None,
)
Expand All @@ -325,7 +327,7 @@ mod tests {
3,
page,
layout,
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Left,
None,
)
Expand All @@ -337,7 +339,7 @@ mod tests {
2,
[401.0, 300.0],
layout,
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Left,
None,
)
Expand All @@ -349,7 +351,7 @@ mod tests {
2,
page,
plotx_core::layout::PageLayout { cols: 2, ..layout },
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Left,
None,
)
Expand All @@ -361,7 +363,7 @@ mod tests {
2,
page,
layout,
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Right,
None,
)
Expand All @@ -373,7 +375,7 @@ mod tests {
2,
page,
layout,
&[21, 20],
&reversed_ids,
plotx_core::layout::TilingDropRegion::Left,
None,
)
Expand All @@ -383,7 +385,7 @@ mod tests {
2,
page,
layout,
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Retile,
Some(0),
);
Expand All @@ -392,7 +394,7 @@ mod tests {
2,
page,
layout,
&[20, 21],
&ids,
plotx_core::layout::TilingDropRegion::Retile,
Some(3),
);
Expand Down
2 changes: 1 addition & 1 deletion crates/app/src/ui/canvas_size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ pub(crate) fn page_size_chrome(
label = format!("{label} · auto");
}
let overflows = content_overflows(canvas);
let resource_id = canvas.resource_id.clone();
let resource_id = canvas.resource_id.to_string();
let suggestion =
wider_preset_suggestion(canvas).filter(|s| !suggestion_dismissed(&ctx, &resource_id, s));

Expand Down
6 changes: 3 additions & 3 deletions crates/app/src/ui/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ pub(super) fn chart_plot_target(app: &PlotxApp, dataset: usize) -> Option<(usize
continue;
};
let hit = canvas.objects.iter().find(|object| {
object
.plot()
.is_some_and(|plot| plot.binding.primary_dataset() == dataset)
object.plot().is_some_and(|plot| {
plot.binding.primary_dataset() == Some(app.doc.datasets[dataset].resource_id())
})
});
if let Some(object) = hit {
return Some((ci, object.id));
Expand Down
4 changes: 2 additions & 2 deletions crates/app/src/ui/commands_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,8 @@ fn transient_state_never_changes_ribbon_group_visibility() {
app.session.tool = Tool::Integrate;
app.session.ui.peak_column = Some(plotx_core::data::ColumnId::new());
app.session.ui.analysis_selection = Some(AnalysisSelection {
dataset: 0,
canvas,
dataset: app.doc.datasets[0].resource_id(),
canvas: app.doc.canvases[canvas].resource_id,
object,
x_range: range,
y_range: None,
Expand Down
4 changes: 2 additions & 2 deletions crates/app/src/ui/data_sheet/transform.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use plotx_core::data::{RelPlanV1, SnapshotRead, TableSchema};
use plotx_core::state::TableEditDelta;
use plotx_core::state::{DatasetId, TableEditDelta};

pub(super) struct TableTransformRequest {
pub input_datasets: Vec<usize>,
Expand All @@ -18,7 +18,7 @@ pub(super) struct TableSheetContext<'a> {
pub dataset: usize,
pub commit: &'a mut Option<TableEditDelta>,
pub transform: &'a mut Option<TableTransformRequest>,
pub refresh: &'a mut Option<(usize, Vec<usize>)>,
pub refresh: &'a mut Option<(usize, Vec<DatasetId>)>,
pub catalog: &'a [TableCatalogEntry],
pub transform_running: bool,
}
Loading
Loading