ElyTable: a new chart goes in empty cells beside its data, off the view if need be, and scrolls into view (R2.10)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-09 06:47:31 -04:00
1 parent 409805e863
commit f4dad1d269
6 files changed
+156 -19

No files matched your search

+52 -11
View File
@@ -334,20 +334,61 @@ fn undoing_a_new_kind_brings_the_whole_chart_back() {
);
}
/// A sheet of ones filling each `(columns, rows)` block.
fn blocks(filled: &[(std::ops::RangeInclusive<usize>, std::ops::RangeInclusive<usize>)]) -> String {
let rows: String = (1..=60)
.map(|r| {
let cells: String = (0..20)
.map(|c| match filled.iter().any(|(cols, rows)| cols.contains(&c) && rows.contains(&r)) {
true => r#"<table:table-cell office:value-type="float" office:value="1"><text:p>1</text:p></table:table-cell>"#,
false => "<table:table-cell/>",
})
.collect();
format!("<table:table-row>{cells}</table:table-row>")
})
.collect();
format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
<office:body><office:spreadsheet><table:table table:name="S">{rows}</table:table></office:spreadsheet></office:body>
</office:document>"#
)
}
/// Charts `source` and checks it clears each data block.
fn charted(sheet: &str, visible: (i32, i32, i32, i32), source: &str, data: &[(&str, &str)]) {
let (_dir, mut worker, doc) = open_book(sheet);
doc.visible_area(&mut worker, visible, (256, 3840)).expect("the area");
let mut seen = poll_until(&mut worker, &doc, &Events::default(), SETTLE, |_| false);
let mut cell = |worker: &mut Worker, name: &str| {
worker.run(&doc, "cell.goto", &[name]).expect("the cell");
let before = seen.cell.cursor;
seen = poll_until(worker, &doc, &seen, SETTLE, |now| now.cell.cursor.is_some() && now.cell.cursor != before);
seen.cell.cursor.expect("the cell's rect")
};
let spans: Vec<_> = data.iter().map(|(from, to)| (cell(&mut worker, from), cell(&mut worker, to))).collect();
let all = written(&mut worker, &doc, source);
worker.run(&doc, "cell.goto", &["A1"]).expect("A1");
worker.run(&doc, "chart.insert", &[&all, "false", &Look { kind: "column".into(), ..Look::default() }.to_json()]).expect("inserts");
let framed = poll_until(&mut worker, &doc, &seen, SETTLE, |now| now.graphic.is_some());
let (x, y, w, h) = framed.graphic.expect("the chart is selected");
for ((a, b), (from, to)) in spans.iter().zip(data) {
let clear = x >= b.0 + b.2 || x + w <= a.0 || y >= b.1 + b.3 || y + h <= a.1;
assert!(clear, "the chart {:?} covers {from}:{to}", (x, y, w, h));
}
}
#[test]
fn a_new_chart_lands_beside_its_data() {
if skip() {
return;
}
let (_dir, mut worker, doc) = open_book(&data());
let all = written(&mut worker, &doc, "A1:C5");
let base = poll_until(&mut worker, &doc, &Events::default(), SETTLE, |_| false);
worker.run(&doc, "cell.goto", &["C5"]).expect("C5");
let corner = poll_until(&mut worker, &doc, &base, SETTLE, |seen| seen.cell.cursor.is_some() && seen.cell.cursor != base.cell.cursor);
let (x, y, w, h) = corner.cell.cursor.expect("the last data cell");
worker.run(&doc, "cell.goto", &["A1"]).expect("A1");
worker.run(&doc, "chart.insert", &[&all, "false", &Look { kind: "column".into(), ..Look::default() }.to_json()]).expect("inserts");
let framed = poll_until(&mut worker, &doc, &corner, SETTLE, |seen| seen.graphic.is_some());
let (left, top, _, _) = framed.graphic.expect("the chart is selected");
assert!(left >= x + w || top >= y + h, "the chart at {left},{top} covers data ending {},{}", x + w, y + h);
// Its own data, a neighbour past two empty columns.
charted(&blocks(&[(0..=2, 1..=5), (5..=15, 1..=20)]), (0, 0, 15000, 10000), "A1:C5", &[("A1", "C5"), ("F1", "P20")]);
// Data wider and taller than the view.
charted(&blocks(&[(0..=15, 1..=50)]), (0, 0, 15000, 10000), "A1:P50", &[("A1", "P50")]);
// A view narrower than the chart.
charted(&blocks(&[(0..=2, 1..=5)]), (0, 0, 6000, 3500), "A1:C5", &[("A1", "C5")]);
}
+1
View File
@@ -389,6 +389,7 @@ impl LokView {
{
self.show(cell, cx);
}
self.follow_graphic(batch.graphic, cx);
self.cursor = batch.cursor;
self.cursor_visible = batch.cursor_visible;
self.selection = batch.selection;
+7
View File
@@ -41,6 +41,13 @@ impl LokView {
self.scroll_by(dx, dy, cx);
}
/// A newly selected object, such as a chart, shows.
pub(super) fn follow_graphic(&mut self, frame: Option<Rect>, cx: &mut Context<Self>) {
if let (None, Some(frame), true) = (self.graphic, frame, self.viewport.1 > 0.0) {
self.show(frame, cx);
}
}
/// Scrolls print-twips `rect` into view; frozen parts always show.
pub fn show(&mut self, rect: Rect, cx: &mut Context<Self>) {
let (x, y, w, h) = self.tile_rect(rect);
+24
View File
@@ -215,3 +215,27 @@ fn a_sheet_brings_far_cut_and_frozen_cells_into_view() {
});
});
}
#[test]
fn a_newly_selected_object_comes_into_view_a_moved_one_stays() {
let Some(install) = crate::client::tests::install() else {
eprintln!("SKIP: no pinned LOK build");
return;
};
let cx = gpui::TestAppContext::single();
cx.update(|app| {
let (view, _) = live_view_of(app, &install, "tall.fods", TALL);
view.update(app, |view, cx| {
let bounds = Bounds { origin: point(px(0.0), px(0.0)), size: size(px(800.0), px(600.0)) };
view.prepare(bounds);
let far = (60_000, 40_000, 9_000, 5_000);
let chart = |graphic| crate::Events { graphic, ..crate::Events::default() };
view.apply_batch(chart(Some(far)), None, cx);
view.prepare(bounds);
let (x, y) = (view.scroll.0, view.scroll.1);
assert!(x > 0 && y > 0, "the new chart is shown: scroll {x},{y}");
view.apply_batch(chart(Some((61_000, 40_000, 9_000, 5_000))), None, cx);
assert_eq!((view.scroll.0, view.scroll.1), (x, y), "dragging it does not scroll");
});
});
}