ElyTable: the sheet's size follows every scale change; its end stays reachable after a zoom (T-05)
This commit is contained in:
15 files changed
+294
-25
No files matched your search
@@ -98,6 +98,15 @@ impl Doc {
|
||||
Ok((unpack.i64()?, unpack.i64()?))
|
||||
}
|
||||
|
||||
/// The size and the client zoom it was measured at.
|
||||
pub fn size_at(&self, worker: &mut Worker) -> io::Result<((i64, i64), (i32, i32))> {
|
||||
let payload = worker.request(tag::DOC_SIZE, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
|
||||
let mut unpack = Unpack::new(&payload);
|
||||
let size = (unpack.i64()?, unpack.i64()?);
|
||||
let number = |value: i64| i32::try_from(value).map_err(io::Error::other);
|
||||
Ok((size, (number(unpack.i64()?)?, number(unpack.i64()?)?)))
|
||||
}
|
||||
|
||||
/// Page rectangles in twips; empty without pages.
|
||||
pub fn pages(&self, worker: &mut Worker) -> io::Result<Vec<Rect>> {
|
||||
let payload = worker.request(tag::PAGES, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
|
||||
|
||||
@@ -113,6 +113,8 @@ struct Slot {
|
||||
result: Option<(String, bool)>,
|
||||
/// The seq last pushed to the client.
|
||||
pushed: u64,
|
||||
/// The client zoom last set: tile pixels, tile twips.
|
||||
zoom: (i32, i32),
|
||||
}
|
||||
|
||||
impl Slot {
|
||||
@@ -447,6 +449,23 @@ pub fn wait_cropping(id: u64, timeout: Duration) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// The client zoom last set on `id`, in tile units.
|
||||
pub fn zoom(id: u64) -> (i32, i32) {
|
||||
slots().lock().expect("the events lock").get(&id).map_or((0, 0), |slot| slot.zoom)
|
||||
}
|
||||
|
||||
/// A new client zoom resizes the document unannounced (Calc).
|
||||
pub fn rescaled(id: u64, zoom: (i32, i32)) {
|
||||
if let Some(slot) = slots().lock().expect("the events lock").get_mut(&id)
|
||||
&& slot.zoom != zoom
|
||||
{
|
||||
slot.zoom = zoom;
|
||||
slot.batch.resized += 1;
|
||||
slot.batch.seq += 1;
|
||||
}
|
||||
signal().notify_all();
|
||||
}
|
||||
|
||||
/// Seeds the a11y text from the kernel's snapshot (§7 tail).
|
||||
pub fn seed_a11y(id: u64, paragraph: &str) {
|
||||
if let Some(slot) = slots().lock().expect("the events lock").get_mut(&id) {
|
||||
|
||||
@@ -18,11 +18,16 @@ pub use client::{
|
||||
};
|
||||
#[cfg(feature = "view")]
|
||||
pub use view::{
|
||||
CONTEXT, Copy, Cut, Geom, Layout, LokError, LokView, Paste, Reported, TILE_TWIPS, ZOOM_STEPS,
|
||||
CONTEXT, Copy, Cut, Geom, Layout, LokError, LokView, Paste, Reported, ZOOM_STEPS,
|
||||
Zoomed, init, layout, paper, read_off, scheme,
|
||||
};
|
||||
#[cfg(feature = "view")]
|
||||
mod view;
|
||||
|
||||
/// One tile edge in twips.
|
||||
pub const TILE_TWIPS: i32 = 3840;
|
||||
/// Twips per pixel at zoom 1.0 and DPI 1.0.
|
||||
pub(crate) const TWIPS_PER_PIXEL: f32 = 15.0;
|
||||
pub use ipc::STALE;
|
||||
pub use ipc::file_url_of as file_url;
|
||||
pub use ipc::path_of_file_url as file_path;
|
||||
|
||||
@@ -33,10 +33,7 @@ pub use input::{CONTEXT, Copy, Cut, Paste, init};
|
||||
pub use off::read_off;
|
||||
pub use render::scheme;
|
||||
|
||||
/// One tile edge in twips.
|
||||
pub const TILE_TWIPS: i32 = 3840;
|
||||
/// Twips per pixel at zoom 1.0 and DPI 1.0.
|
||||
pub(crate) const TWIPS_PER_PIXEL: f32 = 15.0;
|
||||
pub(crate) use crate::{TILE_TWIPS, TWIPS_PER_PIXEL};
|
||||
|
||||
/// Document/view conversion under one zoom and DPI (LOK-04).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@@ -147,7 +144,7 @@ pub struct LokView {
|
||||
/// A document's size and page frames, in twips.
|
||||
pub type Layout = ((i64, i64), Vec<Rect>);
|
||||
|
||||
type Measured = io::Result<Layout>;
|
||||
use sheet::{Measured, measure};
|
||||
|
||||
/// A page's colour; the kernel's Ely schemes share it.
|
||||
pub fn paper(cx: &gpui::App) -> gpui::Hsla {
|
||||
@@ -165,10 +162,6 @@ pub fn layout(worker: &mut Worker, doc: &Doc) -> io::Result<Layout> {
|
||||
Ok((doc.size(worker)?, doc.pages(worker)?))
|
||||
}
|
||||
|
||||
/// Reads the layout under the worker lock.
|
||||
fn measure(worker: &Mutex<Worker>, doc: &Doc) -> Measured {
|
||||
layout(&mut worker.lock().expect("the worker lock"), doc)
|
||||
}
|
||||
|
||||
impl EventEmitter<LokError> for LokView {}
|
||||
|
||||
@@ -362,8 +355,8 @@ impl LokView {
|
||||
// What the view shows is the document at this revision.
|
||||
self.seen_revision(batch.revision);
|
||||
match measured {
|
||||
Some(Ok(((width, height), pages))) => {
|
||||
self.doc_size = (width as i32, height as i32);
|
||||
Some(Ok((((width, height), pages), zoom))) => {
|
||||
self.doc_size = self.at_scale((width as i32, height as i32), zoom);
|
||||
if pages.len() != self.pages.len() {
|
||||
log::info!("lok: {} pages", pages.len());
|
||||
}
|
||||
|
||||
@@ -111,10 +111,9 @@ impl LokView {
|
||||
if self.fit {
|
||||
return self.doc_size;
|
||||
}
|
||||
self.tile_of((
|
||||
self.doc_size.0.max(self.reach.0),
|
||||
self.doc_size.1.max(self.reach.1),
|
||||
))
|
||||
// The kernel sizes a sheet in tile twips already.
|
||||
let reach = self.tile_of(self.reach);
|
||||
(self.doc_size.0.max(reach.0), self.doc_size.1.max(reach.1))
|
||||
}
|
||||
|
||||
/// The print-twips document point under a view position (LOK-04).
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
//! Calc reports print twips; tiles and clicks take tile twips.
|
||||
|
||||
use std::{io, sync::Mutex};
|
||||
|
||||
use gpui::Context;
|
||||
|
||||
use super::{LokView, TILE_TWIPS};
|
||||
use crate::{Rect, sheet::Geometry};
|
||||
use super::{Layout, LokView, TILE_TWIPS};
|
||||
use crate::{Doc, Rect, Worker, sheet::Geometry};
|
||||
|
||||
/// A layout and the client zoom it was measured at.
|
||||
pub(super) type Measured = io::Result<(Layout, (i32, i32))>;
|
||||
|
||||
/// Reads the layout under the worker lock.
|
||||
pub(super) fn measure(worker: &Mutex<Worker>, doc: &Doc) -> Measured {
|
||||
let mut worker = worker.lock().expect("the worker lock");
|
||||
let (size, zoom) = doc.size_at(&mut worker)?;
|
||||
Ok(((size, doc.pages(&mut worker)?), zoom))
|
||||
}
|
||||
|
||||
impl LokView {
|
||||
/// Lays the sheet out the kernel's way.
|
||||
@@ -51,11 +63,29 @@ impl LokView {
|
||||
/// Rescales, keeping the scrolling pane's start in place.
|
||||
pub(super) fn keep_scroll(&mut self, change: impl FnOnce(&mut Self)) {
|
||||
let start = self.scrolled_start();
|
||||
change(self);
|
||||
self.carry_size(change);
|
||||
let ((x, y), (fx, fy)) = (self.tile_of(start), self.frozen_tiles());
|
||||
self.scroll = (x - fx, y - fy);
|
||||
}
|
||||
|
||||
/// Rescales; the measured size moves to the new scale too.
|
||||
pub(super) fn carry_size(&mut self, change: impl FnOnce(&mut Self)) {
|
||||
// Until measured again, scrolls clamp against this.
|
||||
let print = self.print_of(self.doc_size);
|
||||
change(self);
|
||||
self.doc_size = self.tile_of(print);
|
||||
}
|
||||
|
||||
/// A sheet size the kernel measured at `zoom`, at ours.
|
||||
pub(super) fn at_scale(&self, size: (i32, i32), (pixels, twips): (i32, i32)) -> (i32, i32) {
|
||||
let Some(sheet) = self.sheet.as_ref().filter(|_| pixels > 0 && twips > 0) else {
|
||||
return size;
|
||||
};
|
||||
let theirs = f64::from(pixels) / f64::from(twips);
|
||||
let print = (sheet.columns.print(size.0, theirs), sheet.rows.print(size.1, theirs));
|
||||
self.tile_of(print)
|
||||
}
|
||||
|
||||
/// Where the scrolling pane starts, in print twips.
|
||||
pub fn scrolled_start(&self) -> (i32, i32) {
|
||||
let (fx, fy) = self.frozen_tiles();
|
||||
|
||||
@@ -82,14 +82,22 @@ fn far_down(cx: &mut gpui::TestAppContext, install: &std::path::Path) -> (Entity
|
||||
});
|
||||
let geometry = crate::sheet::geometry(&json).expect("parsed");
|
||||
let row = cell(&geometry, 1, 4997);
|
||||
let bounds = Bounds { origin: point(px(0.0), px(0.0)), size: size(px(800.0), px(600.0)) };
|
||||
cx.update(|app| {
|
||||
view.update(app, |view, cx| {
|
||||
view.geom.dpi = 2.0;
|
||||
view.set_geometry(geometry.clone(), cx);
|
||||
view.prepare(Bounds { origin: point(px(0.0), px(0.0)), size: size(px(800.0), px(600.0)) });
|
||||
view.show(row, cx);
|
||||
view.prepare(bounds);
|
||||
})
|
||||
});
|
||||
// The first scale's report and size land before the move.
|
||||
for _ in 0..100 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
cx.update(|app| view.update(app, |view, cx| {
|
||||
view.show(row, cx);
|
||||
view.prepare(bounds);
|
||||
}));
|
||||
(view, geometry)
|
||||
}
|
||||
|
||||
@@ -192,6 +200,10 @@ fn a_sheet_brings_far_cut_and_frozen_cells_into_view() {
|
||||
view.set_geometry(crate::sheet::geometry(&json).expect("parsed"), cx);
|
||||
view.prepare(wide);
|
||||
assert_eq!(view.spare, (0.0, 0.0), "and stays left after it");
|
||||
let size = view.doc_size;
|
||||
view.doc_size = view.tile_of((view.print_of(size).0 + 20 * 1280, size.1));
|
||||
assert_eq!(view.extent(), view.doc_size, "the kernel's size is the extent, uncut");
|
||||
view.doc_size = size;
|
||||
view.viewport = (800.0, 200.0);
|
||||
let (width, height) = (view.geom.twips_of(view.viewport.0), view.geom.twips_of(view.viewport.1));
|
||||
let shows = |view: &LokView, rect: crate::Rect| {
|
||||
@@ -254,3 +266,177 @@ fn a_newly_selected_object_comes_into_view_a_moved_one_stays() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Data out to the last cell sets the size.
|
||||
const FULL_SHEET: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
|
||||
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style: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:automatic-styles><style:style style:name="co1" style:family="table-column"><style:table-column-properties style:column-width="0.4cm"/></style:style><style:style style:name="ro1" style:family="table-row"><style:table-row-properties style:row-height="0.5cm" style:use-optimal-row-height="false"/></style:style></office:automatic-styles>
|
||||
<office:body><office:spreadsheet><table:table table:name="S">
|
||||
<table:table-column table:style-name="co1" table:number-columns-repeated="2048"/>
|
||||
<table:table-row table:style-name="ro1" table:number-rows-repeated="5999"><table:table-cell table:number-columns-repeated="2048"/></table:table-row>
|
||||
<table:table-row table:style-name="ro1"><table:table-cell table:number-columns-repeated="2047"/><table:table-cell office:value-type="float" office:value="1"><text:p>1</text:p></table:table-cell></table:table-row>
|
||||
</table:table></office:spreadsheet></office:body>
|
||||
</office:document>"#;
|
||||
|
||||
#[test]
|
||||
fn a_zoom_measures_the_sheet_again() {
|
||||
let Some(install) = crate::client::tests::install() else {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
};
|
||||
let mut cx = gpui::TestAppContext::single();
|
||||
let (view, _) = cx.update(|app| live_view_of(app, &install, "full.fods", FULL_SHEET));
|
||||
let bounds = Bounds { origin: point(px(0.0), px(0.0)), size: size(px(400.0), px(200.0)) };
|
||||
cx.update(|app| view.update(app, |view, _| view.prepare(bounds)));
|
||||
for _ in 0..100 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
let before = cx.update(|app| view.read(app).doc_size);
|
||||
cx.update(|app| view.update(app, |view, cx| {
|
||||
view.set_zoom(1.25, cx);
|
||||
view.prepare(bounds);
|
||||
}));
|
||||
// The kernel's size at the new zoom, read fresh.
|
||||
let kernel = |cx: &mut gpui::TestAppContext| {
|
||||
cx.update(|app| {
|
||||
let read = view.read(app);
|
||||
let (w, h) = read.doc.size(&mut read.worker.lock().expect("the worker lock")).expect("a size");
|
||||
(w as i32, h as i32)
|
||||
})
|
||||
};
|
||||
// The kernel takes the zoom, then the view its size.
|
||||
let (mut seen, mut now) = (before, before);
|
||||
for _ in 0..2000 {
|
||||
cx.executor().tick();
|
||||
(seen, now) = (cx.update(|app| view.read(app).doc_size), kernel(&mut cx));
|
||||
if now != before && seen == now {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_ne!(now, before, "the zoom changes the kernel's size here");
|
||||
assert_eq!(seen, now, "the view takes the size the kernel has at the new zoom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_zoom_at_the_end_of_a_sheet_keeps_the_row_in_the_middle() {
|
||||
let Some(install) = crate::client::tests::install() else {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
};
|
||||
let mut cx = gpui::TestAppContext::single();
|
||||
let (view, worker) = cx.update(|app| live_view_of(app, &install, "full.fods", FULL_SHEET));
|
||||
let json = cx.update(|app| worker.lock().expect("the worker lock").values(&view.read(app).doc, crate::sheet::GEOMETRY).expect("geometry"));
|
||||
let geometry = crate::sheet::geometry(&json).expect("parsed");
|
||||
let bounds = Bounds { origin: point(px(0.0), px(0.0)), size: size(px(400.0), px(200.0)) };
|
||||
cx.update(|app| view.update(app, |view, cx| {
|
||||
view.set_geometry(geometry.clone(), cx);
|
||||
view.prepare(bounds);
|
||||
}));
|
||||
for _ in 0..100 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
// Scrolled to the end, as by the wheel.
|
||||
cx.update(|app| view.update(app, |view, cx| {
|
||||
view.scroll_by(0.0, -1.0e7, cx);
|
||||
view.prepare(bounds);
|
||||
}));
|
||||
for _ in 0..100 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
let middle = |cx: &mut gpui::TestAppContext| {
|
||||
cx.update(|app| {
|
||||
let read = view.read(app);
|
||||
let at = read.origin() + point(px(200.0), px(100.0));
|
||||
geometry.rows.at(read.doc_point(at).1)
|
||||
})
|
||||
};
|
||||
let before = middle(&mut cx);
|
||||
cx.update(|app| view.update(app, |view, cx| {
|
||||
view.set_zoom(1.25, cx);
|
||||
view.prepare(bounds);
|
||||
}));
|
||||
let right_after = middle(&mut cx);
|
||||
for _ in 0..400 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
let measured = middle(&mut cx);
|
||||
assert!(before.abs_diff(right_after) <= 1, "row {before} became {right_after} at the zoom");
|
||||
assert!(before.abs_diff(measured) <= 1, "row {before} became {measured} once measured");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_late_size_from_the_last_zoom_keeps_the_row() {
|
||||
let Some(install) = crate::client::tests::install() else {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
};
|
||||
let mut cx = gpui::TestAppContext::single();
|
||||
let (view, worker) = cx.update(|app| live_view_of(app, &install, "full.fods", FULL_SHEET));
|
||||
let json = cx.update(|app| worker.lock().expect("the worker lock").values(&view.read(app).doc, crate::sheet::GEOMETRY).expect("geometry"));
|
||||
let geometry = crate::sheet::geometry(&json).expect("parsed");
|
||||
let bounds = Bounds { origin: point(px(0.0), px(0.0)), size: size(px(400.0), px(200.0)) };
|
||||
let settle = |cx: &mut gpui::TestAppContext| {
|
||||
for _ in 0..200 {
|
||||
cx.executor().tick();
|
||||
}
|
||||
};
|
||||
cx.update(|app| view.update(app, |view, cx| {
|
||||
view.set_geometry(geometry.clone(), cx);
|
||||
view.set_zoom(1.25, cx);
|
||||
view.prepare(bounds);
|
||||
}));
|
||||
settle(&mut cx);
|
||||
cx.update(|app| view.update(app, |view, cx| {
|
||||
view.scroll_by(0.0, -1.0e7, cx);
|
||||
view.prepare(bounds);
|
||||
}));
|
||||
settle(&mut cx);
|
||||
// Measured at 125%, landing after the zoom to 150%.
|
||||
let late = cx.update(|app| super::super::measure(&view.read(app).worker, &view.read(app).doc));
|
||||
let middle = |cx: &mut gpui::TestAppContext| {
|
||||
cx.update(|app| {
|
||||
let read = view.read(app);
|
||||
geometry.rows.at(read.doc_point(read.origin() + point(px(200.0), px(100.0))).1)
|
||||
})
|
||||
};
|
||||
cx.update(|app| view.update(app, |view, cx| view.set_zoom(1.5, cx)));
|
||||
let before = middle(&mut cx);
|
||||
cx.update(|app| view.update(app, |view, cx| view.apply_batch(crate::Events::default(), Some(late), cx)));
|
||||
let after = middle(&mut cx);
|
||||
assert!(before.abs_diff(after) <= 1, "row {before} became {after} when the old size landed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_first_size_has_a_scale_and_keeps_the_row() {
|
||||
let Some(install) = crate::client::tests::install() else {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
};
|
||||
let mut cx = gpui::TestAppContext::single();
|
||||
let (view, worker) = cx.update(|app| live_view_of(app, &install, "full.fods", FULL_SHEET));
|
||||
// Read before any visible area: the load set a scale.
|
||||
let first = cx.update(|app| super::super::measure(&view.read(app).worker, &view.read(app).doc));
|
||||
assert_eq!(first.as_ref().map(|(_, zoom)| *zoom).ok(), Some((256, 3840)), "a known scale");
|
||||
let json = cx.update(|app| worker.lock().expect("the worker lock").values(&view.read(app).doc, crate::sheet::GEOMETRY).expect("geometry"));
|
||||
let geometry = crate::sheet::geometry(&json).expect("parsed");
|
||||
let bounds = Bounds { origin: point(px(0.0), px(0.0)), size: size(px(400.0), px(200.0)) };
|
||||
cx.update(|app| view.update(app, |view, cx| {
|
||||
view.set_geometry(geometry.clone(), cx);
|
||||
view.prepare(bounds);
|
||||
view.scroll_by(0.0, -1.0e7, cx);
|
||||
}));
|
||||
let middle = |cx: &mut gpui::TestAppContext| {
|
||||
cx.update(|app| {
|
||||
let read = view.read(app);
|
||||
geometry.rows.at(read.doc_point(read.origin() + point(px(200.0), px(100.0))).1)
|
||||
})
|
||||
};
|
||||
cx.update(|app| view.update(app, |view, cx| view.set_zoom(1.25, cx)));
|
||||
let before = middle(&mut cx);
|
||||
cx.update(|app| view.update(app, |view, cx| view.apply_batch(crate::Events::default(), Some(first), cx)));
|
||||
let after = middle(&mut cx);
|
||||
assert!(before.abs_diff(after) <= 1, "row {before} became {after} when the first size landed");
|
||||
}
|
||||
@@ -54,7 +54,7 @@ impl LokView {
|
||||
let under = self.print_of((self.scroll.0 + self.geom.twips_of(sx), self.scroll.1 + self.geom.twips_of(sy)));
|
||||
self.fit = false;
|
||||
if (zoom - self.geom.zoom).abs() > 0.0001 {
|
||||
self.geom.zoom = zoom;
|
||||
self.carry_size(|view| view.geom.zoom = zoom);
|
||||
self.retire_tiles();
|
||||
self.failed.clear();
|
||||
}
|
||||
|
||||
@@ -192,8 +192,12 @@ fn answer(lok: &Lok, tag: u8, id: u64, payload: &[u8]) {
|
||||
tag::EXPORT_TEXT => text::export(unpack).map(|()| Vec::new()),
|
||||
tag::DOC_SIZE => unpack
|
||||
.u64()
|
||||
.and_then(|id| with_doc(id, |doc| doc.size()))
|
||||
.map(|(width, height)| Pack::new().i64(width).i64(height).bytes()),
|
||||
.and_then(|id| Ok((with_doc(id, |doc| doc.size())?, crate::events::zoom(id))))
|
||||
.map(|((width, height), (pixels, twips))| {
|
||||
// The scale the kernel measured at; 0 if never set.
|
||||
let pack = Pack::new().i64(width).i64(height);
|
||||
pack.i64(i64::from(pixels)).i64(i64::from(twips)).bytes()
|
||||
}),
|
||||
tag::SAVE_AS => save(unpack).map(|()| Vec::new()),
|
||||
tag::EXPORT_PDF => export_pdf(unpack).map(|()| Vec::new()),
|
||||
tag::CLOSE_DOC => unpack.u64().map(|id| {
|
||||
@@ -255,6 +259,7 @@ fn answer(lok: &Lok, tag: u8, id: u64, payload: &[u8]) {
|
||||
doc.set_client_zoom(pixels, twips);
|
||||
doc.set_visible_area(x, y, w, h)
|
||||
})
|
||||
.inspect(|_| crate::events::rescaled(id, (pixels, twips)))
|
||||
})
|
||||
.map(|()| Vec::new()),
|
||||
tag::SELECTION_TEXT => unpack
|
||||
@@ -331,6 +336,12 @@ fn load(lok: &Lok, url: &str, password: String, filter: &str) -> io::Result<u64>
|
||||
crate::events::seed_a11y(id, &doc.a11y_paragraph()?);
|
||||
// Settle the initial layout now; later paints see it (LOK-03).
|
||||
doc.post_uno(".uno:Repaginate", None)?;
|
||||
// A sheet's size has a known scale from the start.
|
||||
if doc.kind() == 1 {
|
||||
let zoom = (crate::TILE_TWIPS / crate::TWIPS_PER_PIXEL as i32, crate::TILE_TWIPS);
|
||||
doc.set_client_zoom(zoom.0, zoom.1);
|
||||
crate::events::rescaled(id, zoom);
|
||||
}
|
||||
let mut docs = docs().lock().expect("the docs lock");
|
||||
docs.insert(id, doc);
|
||||
log::info!("lok-worker: {} documents open", docs.len());
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
- [x] T-02 单元格光标露出边缘即把视图滚到中央,点击与拖选时视图跳动 — R1 拖选中仍滚、超宽格跳回起点;R2 PASS
|
||||
- [x] T-03 视口比工作表宽时网格先居中再左移一次 — 载入回复带文档种类,表格不居中;R1 测试漏几何到达前的首帧;R2 PASS
|
||||
- [x] T-04 从不调用 `setClientZoom`,内核命中与几何按默认缩放算 — R2.16 已修(worker 每次报可见区前 setClientZoom)
|
||||
- [ ] T-05 文档尺寸按内核像素取整,表尾被裁
|
||||
- [x] T-05 文档尺寸按内核像素取整,表尾被裁 — 内核尺寸按其测量时的比例换到当前比例(加载即设比例、缩放即改比例并重测、迟到的测量也换算);R1–R4 缩放后未重测、表尾锚点被截、连续缩放迟到、首次测量无比例与文件超行;R5 PASS
|
||||
- [ ] T-06 滚轮向下被内核平铺区卡住
|
||||
- [ ] T-07 表头点选、行高列宽拖动是“按编号的命令”,而每次点击与拖选都推进修订,紧接着的表头操作被当作过期拒绝
|
||||
- [ ] T-08 自动筛选的列菜单对光标所在的表而不是按钮所在的表筛选、排序
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
crates/ely-lok/src/view/scroll.rs:116(回归测试:crates/ely-lok/src/view/tests/large.rs:197):`extent()` 直接沿用旧缩放比例下测得的 `doc_size`,缩放后的表尾仍被裁切。具体复现:1× DPI 下打开 2048 列、6000 行的 FODS,列宽 0.4 cm、行高固定 0.5 cm,最后一格有值;等待初始状态落定,将缩放从 100% 调至 125%,等待输入队列与事件处理完成,再向下滚到底。视图保留高度 1,620,000 twips,内核实际高度与几何计算的末行底边均为 1,656,000 twips,可见区底边被夹在 1,620,000,末尾约 130 行无法通过滚动显示。`setClientZoom` 改变了内核尺寸,尺寸变更计数仍为 0,事件监听因此继续使用旧尺寸。新增测试手动设置 `doc_size` 后立即断言相等,漏掉了这条真实缩放路径。修复方向:在缩放或 DPI 改变后同步重测尺寸,或依据当前几何与比例重算滚动范围;补充真实内核下缩放后滚到末行的回归测试。已自行构建 worker,35 项视图测试通过;隔离探针及增加等待后的独立复测均复现上述裁切(日志:`/tmp/ely-t05-zoom-rerun.log`)。
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
crates/ely-lok/src/view/scroll.rs:116(调用:crates/ely-lok/src/view/zoom.rs:67;回归覆盖:crates/ely-lok/src/view/tests/large.rs:319):缩放瞬间的 `extent()` 仍取旧比例下的 `doc_size`,`clamp_scroll()` 因而截断已经按新比例计算的缩放锚点;异步重测更新尺寸后,丢失的滚动位置保持原状,违反 PRD LOK-04。复现:1× DPI 打开新增 `FULL_SHEET` 样本(2048 列、6000 行,列宽 0.4 cm、行高 0.5 cm,最后一格有值),载入真实几何,视口设为 400×200,等待启动事件落定后滚到数据表尾,再从 100% 缩放到 125%。视口中心从第 5995 行跳到第 5891 行;夹取时使用旧高度 1,626,885 twips,把纵向位置压到 1,624,485,随后高度重测为 1,662,804,中心仍在第 5891 行,实际内核点击确认选中 N5891。独立复测一致,日志为 `/tmp/ely-t05-r2-bottom-rerun.log`、`/tmp/ely-t05-r2-bottom-click.log`。上一轮的尺寸重测缺陷已修复,移除通知的变异 worker 能使新增测试失败;当前 36 项视图测试通过,但缩放回归从表头开始且只核对最终尺寸,覆盖不到上述边界。修复方向:让滚动夹取使用当前比例的尺寸,并保留重测期间的缩放锚点;补充真实几何下表尾缩放前后可见行保持一致的回归测试。
|
||||
@@ -0,0 +1,5 @@
|
||||
FAIL
|
||||
|
||||
crates/ely-lok/src/view/sheet.rs:64(异步覆盖:crates/ely-lok/src/view.rs:366、371;回归覆盖:crates/ely-lok/src/view/tests/large.rs:324):连续缩放时,`carry_size` 换算出的当前尺寸会被旧比例的异步测量覆盖,随后的 `clamp_scroll()` 截断缩放锚点;当前比例的尺寸随后到达,滚动位置仍保持截断后的值,违反 PRD LOK-04。真实内核复现:1× DPI 载入 `FULL_SHEET` 与真实几何,视口 400×200;反复回到 100%、等待落定并以 `scroll_by` 滚到底,再连续设为 125%、150%。隔离探针依次使用 0、1、2、3 次 executor tick 作为两次缩放之间的间隔,第 4 次出现旧测量晚到:150% 的预换算高度为 1,707,000,125% 的测量高度 1,683,216 将滚动从 1,697,778 压到 1,681,216;之后重测恢复高度 1,707,000,中心行零基索引仍从 6075 变为 6008,真实内核点击选中 N6009。初次探针与两次独立重跑均复现,日志为 `/tmp/ely-t05-r3-rapid.log`、`/tmp/ely-t05-r3-rapid-trace.log`、`/tmp/ely-t05-r3-rapid-click.log`,探针代码留在 `/tmp/ely-t05-r3-reproducer.log`。第 2 轮的单次缩放场景已修复:本轮 37 项选中测试通过,移除 `carry_size` 的变异使新增测试按预期报 `5994 -> 5890`;现有测试只做一次缩放,漏掉旧测量跨越下一次缩放的时序。修复方向:让尺寸测量携带其实际内核比例,应用时核对比例并转换或丢弃过期结果,保住当前锚点;补充上述连续缩放时旧测量晚到的回归。
|
||||
|
||||
crates/ely-lok/src/view/tests/large.rs:341:新增注释 `Scrolled as far as the sheet goes, as by the wheel.` 有 11 个单词,违反 agents.md 规则 5 的单行十词上限。运行 `python3 tests/scripts/comments.py` 即失败并指向该行。修复方向:将该注释缩至十词以内。
|
||||
@@ -0,0 +1,5 @@
|
||||
FAIL
|
||||
|
||||
crates/ely-lok/src/view/sheet.rs:69(比例来源:crates/ely-lok/src/events.rs:453、crates/ely-lok/src/worker.rs:195):首次设置可见区之前,DOC_SIZE 的比例仍为 `(0, 0)`;`at_scale` 对这份尺寸直接返回原值。该测量跨越首次缩放才到达时,仍会覆盖已换算的尺寸并截断滚动,违反 PRD LOK-04。真实内核探针:载入 FULL_SHEET 与真实几何,保留首次测量 `((442789, 1620000), (0, 0))`;视口 400×200,首次 `prepare` 排队报告可见区,滚到底后从 100% 缩放到 125%,再交付保留的测量。高度从 1,656,000 退为 1,620,000,纵向滚动从 1,653,265 被压到 1,617,600,中心行从 5995 变为 5866;后续重测仍停在 5866,真实点击选中 N5866。两次独立复跑一致,日志 `/tmp/ely-t05-r4-initial-rerun1.log`、`/tmp/ely-t05-r4-initial-rerun2.log`,复现代码 `/tmp/ely-t05-r4-reproducer.log`。隔离副本按正确初始比例换算后保持第 5995 行、点击 N5995(`/tmp/ely-t05-r4-initial-scale-probe.log`)。上轮已登记比例的连续缩放场景通过;移除 `at_scale` 的变异使新增回归按预期报出 `5995 -> 5935`。现有 240 项测试全部通过,新增测试遗漏了首次未登记比例的测量。修复方向:在首次尺寸读取前显式设置并记录内核比例,让每份尺寸均能正确换算;补充初始测量跨越首次缩放的回归。
|
||||
|
||||
crates/ely-lok/src/view.rs:503:本轮新增 Measured 注释与测量代码使该文件从 HEAD 的 500 行增至 503 行,违反 agents.md 规则 5。`wc -l crates/ely-lok/src/view.rs` 与逐行计数均为 503,末行 `mod tests;` 位于第 503 行;规则检查日志 `/tmp/ely-t05-r4-rules.log`。上轮的长注释已修复,`python3 tests/scripts/comments.py` 通过。修复方向:将布局测量辅助代码移入合适的现有模块,使每个文件保持在 500 行以内。
|
||||
@@ -0,0 +1 @@
|
||||
PASS
|
||||
Reference in new issue
Block a user