ElyTable: clicks and drags land on the cell under them far down a sheet (R2.16)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-09 02:04:13 -04:00
1 parent 148885248b
commit 0d529e1b0d
15 files changed
+297 -33

No files matched your search

+7 -6
View File
@@ -60,8 +60,8 @@ pub(super) enum Input {
from: (i32, i32),
to: (i32, i32),
},
/// The area the view shows, in twips.
Visible(Rect),
/// The area shown in twips, and the tile scale.
Visible(Rect, (u32, i32)),
/// Shows sheet or slide `part`, from 0.
Part(usize),
}
@@ -74,7 +74,7 @@ impl Input {
Input::Key { key_code, .. } => !matches!(key_code & 0x0FFF, 1024..=1031 | 1281),
Input::Mouse(_)
| Input::Copy { cut: false }
| Input::Visible(_)
| Input::Visible(..)
| Input::Part(_)
| Input::Theme(_) => false,
Input::Run { commands, .. } => commands.iter().any(|(id, _)| *id != "edit.select_all"),
@@ -171,7 +171,7 @@ fn lok_button(button: MouseButton) -> Option<i32> {
/// Runs one input; gives a copy's mark and the revision.
pub(super) fn run(worker: &Mutex<Worker>, doc: &Doc, input: Input) -> (io::Result<Option<String>>, Option<u64>) {
let mut worker = worker.lock().expect("the worker lock");
let counts = !matches!(input, Input::Visible(_));
let counts = !matches!(input, Input::Visible(..));
let copied = apply(&mut worker, doc, input);
// A failed input may have moved the revision too.
let now = match counts {
@@ -189,7 +189,7 @@ fn apply(worker: &mut Worker, doc: &Doc, input: Input) -> io::Result<Option<Stri
} => worker.post_key(doc, char_code, key_code),
Input::Mouse(event) => worker.post_mouse(doc, event),
Input::Drag { from, to } => worker.drag_graphic(doc, from, to),
Input::Visible(area) => doc.visible_area(worker, area),
Input::Visible(area, scale) => doc.visible_area(worker, area, scale),
Input::Part(part) => doc.show_sheet(worker, part),
Input::Complete(name) => doc.complete_function(worker, &name),
Input::Theme(scheme) => worker.run(doc, "view.theme", &[scheme]),
@@ -280,7 +280,8 @@ impl LokView {
count: usize,
modifiers: &Modifiers,
) {
let (x, y) = self.doc_point(position);
// The kernel reads clicks in tile twips.
let (x, y) = self.tile_point(position);
self.send(Input::Mouse(Mouse {
kind,
x,
+5 -4
View File
@@ -272,7 +272,7 @@ impl LokView {
},
);
let pages = if self.pages.is_empty() {
vec![self.screen_rect((0, 0, extent.0, extent.1))]
vec![self.tile_screen_rect((0, 0, extent.0, extent.1))]
} else {
self.pages
.iter()
@@ -283,10 +283,11 @@ impl LokView {
geom.twips_of(bounds.size.width.into()),
geom.twips_of(bounds.size.height.into()),
);
if self.shown != Some((sx, sy, vw, vh)) {
self.shown = Some((sx, sy, vw, vh));
let scale = (geom.tile_pixels(), TILE_TWIPS);
if self.shown != Some(((sx, sy, vw, vh), scale)) {
self.shown = Some(((sx, sy, vw, vh), scale));
self.failed.clear();
self.send(input::Input::Visible((sx, sy, vw, vh)));
self.send(input::Input::Visible((sx, sy, vw, vh), scale));
}
let (ax, ay) = self.axes();
let side = px(geom.px_of(TILE_TWIPS));
+31 -14
View File
@@ -41,9 +41,10 @@ impl LokView {
self.scroll_by(dx, dy, cx);
}
/// Scrolls `rect` into view; frozen parts always show.
pub fn show(&mut self, (x, y, w, h): Rect, cx: &mut Context<Self>) {
let (fx, fy) = self.frozen;
/// 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);
let (fx, fy) = self.frozen_tiles();
let (vw, vh) = (
self.geom.twips_of(self.viewport.0) - fx,
self.geom.twips_of(self.viewport.1) - fy,
@@ -63,7 +64,7 @@ impl LokView {
cx.notify();
}
/// Freezes what lies before `frozen`, in twips.
/// Freezes what lies before `frozen`, in print twips.
pub fn set_frozen(&mut self, frozen: (i32, i32), cx: &mut Context<Self>) {
if self.frozen != frozen {
log::info!("lok: frozen at {frozen:?}");
@@ -75,7 +76,8 @@ impl LokView {
/// Scrolls `rect` into view and puts the cursor there.
pub fn reveal(&mut self, rect: Rect, cx: &mut Context<Self>) {
self.show(rect, cx);
let (x, y, _, h) = rect;
// Clicks land in tile twips.
let (x, y, _, h) = self.tile_rect(rect);
log::info!("lok: reveal at {x},{y}");
for kind in [0, 1] {
let (count, buttons, modifiers) = (1, 1, 0);
@@ -108,8 +110,13 @@ impl LokView {
)
}
/// The document point under a view position (LOK-04).
/// The print-twips document point under a view position (LOK-04).
pub fn doc_point(&self, position: Point<Pixels>) -> (i32, i32) {
self.print_of(self.tile_point(position))
}
/// The tile-twips point under a view position.
pub(super) fn tile_point(&self, position: Point<Pixels>) -> (i32, i32) {
let local = position - self.origin;
let (ax, ay) = self.axes();
(
@@ -121,14 +128,17 @@ impl LokView {
/// The panes of each axis.
pub(super) fn axes(&self) -> (super::panes::Axis, super::panes::Axis) {
let axis = |frozen, scroll| super::panes::Axis { frozen, scroll };
(
axis(self.frozen.0, self.scroll.0),
axis(self.frozen.1, self.scroll.1),
)
let (fx, fy) = self.frozen_tiles();
(axis(fx, self.scroll.0), axis(fy, self.scroll.1))
}
/// The window point of a document point in twips (LOK-04).
pub fn window_point(&self, (x, y): (i32, i32)) -> gpui::Point<Pixels> {
/// The window point of a print-twips document point (LOK-04).
pub fn window_point(&self, at: (i32, i32)) -> gpui::Point<Pixels> {
self.tile_window_point(self.tile_of(at))
}
/// The window point of a tile-twips point.
pub(super) fn tile_window_point(&self, (x, y): (i32, i32)) -> gpui::Point<Pixels> {
let (ax, ay) = self.axes();
let (x, y) = (self.geom.px_of(ax.show(x)), self.geom.px_of(ay.show(y)));
self.origin + point(px(self.spare.0 + x), px(self.spare.1 + y))
@@ -139,12 +149,19 @@ impl LokView {
Bounds::from_corners(self.window_point((x, y)), self.window_point((x + w, y + h)))
}
/// A tile-twips rect in window pixels.
pub(super) fn tile_screen_rect(&self, (x, y, w, h): crate::Rect) -> Bounds<Pixels> {
Bounds::from_corners(self.tile_window_point((x, y)), self.tile_window_point((x + w, y + h)))
}
/// The cursor rect in window pixels.
pub fn cursor_bounds(&self) -> Option<Bounds<Pixels>> {
let (x, y, w, h) = self.cursor?;
let origin = self.window_point((x, y));
let end = self.window_point((x + w, y + h));
Some(Bounds {
origin: self.window_point((x, y)),
size: size(px(self.geom.px_of(w.max(1))), px(self.geom.px_of(h))),
origin,
size: size((end.x - origin.x).max(px(self.geom.px_of(1))), end.y - origin.y),
})
}
}
+56
View File
@@ -0,0 +1,56 @@
//! Calc reports print twips; tiles and clicks take tile twips.
use gpui::Context;
use super::{LokView, TILE_TWIPS};
use crate::{Rect, sheet::Geometry};
impl LokView {
/// Lays the sheet out the kernel's way.
pub fn set_geometry(&mut self, geometry: Geometry, cx: &mut Context<Self>) {
if self.sheet.as_ref() != Some(&geometry) {
self.sheet = Some(geometry);
self.clamp_scroll();
cx.notify();
}
}
/// Device pixels per twip, as the kernel paints our tiles.
fn ppt(&self) -> f64 {
f64::from(self.geom.tile_pixels()) / f64::from(TILE_TWIPS)
}
/// A print-twips point in tile twips.
pub(super) fn tile_of(&self, (x, y): (i32, i32)) -> (i32, i32) {
match &self.sheet {
Some(sheet) => (sheet.columns.tile(x, self.ppt()), sheet.rows.tile(y, self.ppt())),
None => (x, y),
}
}
/// A tile-twips point in print twips.
pub(super) fn print_of(&self, (x, y): (i32, i32)) -> (i32, i32) {
match &self.sheet {
Some(sheet) => (sheet.columns.print(x, self.ppt()), sheet.rows.print(y, self.ppt())),
None => (x, y),
}
}
/// A print-twips rect in tile twips.
pub(super) fn tile_rect(&self, (x, y, w, h): Rect) -> Rect {
let (left, top) = self.tile_of((x, y));
let (right, bottom) = self.tile_of((x + w, y + h));
(left, top, right - left, bottom - top)
}
/// The frozen extent in tile twips.
pub(super) fn frozen_tiles(&self) -> (i32, i32) {
self.tile_of(self.frozen)
}
/// Where the scrolling pane starts, in print twips.
pub fn scrolled_start(&self) -> (i32, i32) {
let (fx, fy) = self.frozen_tiles();
self.print_of((fx + self.scroll.0, fy + self.scroll.1))
}
}
+55
View File
@@ -60,3 +60,58 @@ fn a_tall_sheet_paints_and_keeps_only_tiles_near_the_screen() {
});
});
}
/// 6000 rows of 0.5 cm; tiles round each to pixels.
const SHORT_ROWS: &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="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-row table:style-name="ro1" table:number-rows-repeated="6000"><table:table-cell table:number-columns-repeated="4"/></table:table-row>
</table:table></office:spreadsheet></office:body>
</office:document>"#;
#[test]
fn a_click_far_down_a_sheet_selects_the_cell_under_it() {
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, "short.fods", SHORT_ROWS));
let json = cx.update(|app| {
let read = view.read(app);
worker.lock().expect("the worker lock").values(&read.doc, crate::sheet::GEOMETRY).expect("geometry")
});
let geometry = crate::sheet::geometry(&json).expect("parsed");
let (row, column) = (4997, 1);
let cell = (
geometry.columns.start(column),
geometry.rows.start(row),
geometry.columns.size(column),
geometry.rows.size(row),
);
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(cell, cx);
let at = view.window_point((cell.0 + cell.2 / 2, cell.1 + cell.3 / 2));
for kind in [0, 1] {
view.mouse(kind, at, 1, 1, &gpui::Modifiers::default());
}
})
});
let mut address = String::new();
for _ in 0..200 {
cx.executor().tick();
address = cx.update(|app| view.read(app).cell().address.clone());
if address == "B4998" {
break;
}
}
assert_eq!(address, "B4998");
}