R8.3: sheet tab strip scrolls the chosen sheet in (Gitea #8)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude committed 2026-10-18 22:41:28 -04:00
1 parent 2ca53ef585
commit 1e6147ae77
9 files changed
+136 -23

No files matched your search

Generated
+12 -12
View File
@@ -290,7 +290,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -301,7 +301,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -1805,7 +1805,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.3",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -1986,7 +1986,7 @@ dependencies = [
[[package]]
name = "ely-gpui-component"
version = "0.1.1"
source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components?rev=2e65b05a6592d8c3166a252261fc01bf6215c3c5#2e65b05a6592d8c3166a252261fc01bf6215c3c5"
source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components?rev=7d704d97f40a9d47c08891c35d7dbe26a2220e55#7d704d97f40a9d47c08891c35d7dbe26a2220e55"
dependencies = [
"alacritty_terminal",
"anyhow",
@@ -2405,7 +2405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -4611,7 +4611,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -4776,7 +4776,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -6539,7 +6539,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -7127,7 +7127,7 @@ dependencies = [
"cfg-if",
"libc",
"psm",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -7478,7 +7478,7 @@ dependencies = [
"getrandom 0.4.3",
"once_cell",
"rustix",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -7828,7 +7828,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset",
"tempfile",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -8633,7 +8633,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
+1 -1
View File
@@ -19,7 +19,7 @@ jiff = "0.2"
anyhow = "1"
directories = "6"
dunce = "1"
ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components", rev = "2e65b05a6592d8c3166a252261fc01bf6215c3c5" }
ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components", rev = "7d704d97f40a9d47c08891c35d7dbe26a2220e55" }
env_logger = "0.11"
gpui = { package = "gpui-pre", version = "=0.3.8", default-features = false, features = ["stacker"] }
gpui_platform = { package = "gpui-pre-platform", version = "=0.3.8", features = ["font-kit", "wayland", "x11"] }
+2 -1
View File
@@ -40,7 +40,7 @@ site/ 官网(Vite 8、pnpm、TypeScript,无框架;等轴 SVG 积木
## 组件映射
依赖 `ely-gpui-component`(pin 2e65b05,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。
依赖 `ely-gpui-component`(pin 7d704d9,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。
| 需要 | 用 |
| --- | --- |
@@ -319,3 +319,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
- 2026-10-16(用户定):Windows 资源管理器的右键“新建”可建 DOCX、XLSX、PPTX 与 Markdown 文件(R7.9)。空白文件在 `packaging/windows/new`(`templates.sh` 以本构建的内核把空文件转存而得,无作者与日期;Markdown 为空文件),随所属的应用装在安装目录(列在 `.ely-installed` 里),`HKCU\…\.<扩展名>\ShellNew` 的 `FileName` 指向它。资源管理器只在扩展名有默认 ProgID 时列出新建项(实测:只有 `ShellNew`、或只在 `OpenWithProgids` 下时都不列);所以只在该类型没有任何程序处理时(HKCR 下无默认值,或默认值所指的类不存在:本机 Windows 11 的 `.md` 指向不存在的 `Markdown Document`)把默认值设为本应用的 ProgID(写在 HKCU,用户自己的 `UserChoice` 不动),已有的从不改动(装着 Office、WPS 时新建菜单用它们自己的项);没有处理程序的这四种类型从此由 Ely 打开。卸载只删仍是本安装写下的 `ShellNew` 与默认值。ProgID 的名称为“<应用> Document”(新建菜单与类型列显示它)。`tests/scripts/types.sh` 核对四种都有新建项,默认值只在判空之后写。
- 2026-10-16(用户定):ElyMark 打开的块保持原样(R7.5)。组件库 bff7ebc 起,可视模式里按下的块以原文显示在它合上时的框里(同样的边距与透明边框),字号、行高与颜色同合上时:段落、列表、引用等为正文,标题按其级数的字号、1.3 倍行高、加粗(六级为次要色),照原文写出的块(链接定义等)为等宽次要色;不再加深色底板与另一套边距,按下前后不挪位置。代码块、块公式与 front matter 仍有自己的底板(代码与 front matter 为等宽小号,front matter 为次要色,同合上时)。输入框的着色(源码模式同用)给标题与粗体同一字重(原为半粗,压过所设的加粗),六级标题里的字照为次要色(行内代码照为正文色,2e65b05),front matter 照 front matter 读(原读成分隔线加标题,键名变亮变粗)(1ff993d、962964e)。表格单元格照旧。打字机模式的居中另按两端余量判定(原只按块高与视图高,打开前后同高时首块不再居中)。同批带入组件库 cdc5ce8(标题栏拖动区里的控件按下归控件,Windows)。Windows 真窗口核对(`.work/r75`):段落、三级标题、代码块打开前后位置相同。
- 2026-10-18:Word 公式(R8.2,Gitea #9)。附件 math.docx 的六类公式逐类核对(本机无 Word,以文件所写为准):键盘输入、OMML(内核转为公式对象)、MathType(OLE,显示随带的 WMF 预览)、上下标、图片原本就对;差别在两处根因。其一,运行的 `w:position`(升降)原先只给文字,图片与 OLE 公式所在的运行失去它(25 张图片与一个 ½ 浮在行上):补丁 `0049-writer-inline-objects-move-with-their-run.patch` 把它作为固定升降写到对象的锚字符上(按锚字符自己的字号换算百分比,不扩展到其后的文字),版面让“作为字符”的对象随锚字符的固定升降移动(vertAlign 的自动上下标不动对象,同原样);各导出本就写出锚字符的升降,DOCX 往返仍为 `w:position`。其二,EQ 域原先只认 `\o`、`\s\do` 与注音,其余(如分数 `\f`)整个消失:补丁 `0050-writer-eq-field-parser.patch`(读法)与 `0051-writer-eq-fields-as-formulas.patch`(导入)把 EQ 域转为公式对象:`\f` 分数、`\r` 根式、`\s` 上下标、`\b` 括号、`\l` 列表、`\a` 阵列、`\i` 积分与求和求积;`\x` 与 `\o` 只留内容(无框、无叠印),`\d` 只前移,`\ai`、`\di` 只是行距(不升降),开关不分大小写;每个命令字符保留其运行的上下标(Word 照此画),同一段上下标里字样变了仍是一个上下标;斜体字母写成标识符(StarMath 与 OMML 都画成斜体,DOCX 往返不丢),斜体的数字与符号写成 `ital` 文字,存为 DOCX 后成正体(OMML 无此写法,已知限制);没有底的上下标以空文字为底(空的底在 OMML 里是占位符,重开成 `<?>`);公式取域的字号;读不通的命令以其域代码显示为文字,不消失。只有整个域是 `\s\do N(文字)` 时照原样成为下移的文字,其余都经读法(原先小写的 `\s\do` 读到第一个括号即止,其后丢失)。`client/tests/word.rs` 以 `tests/fixtures/office/word.docx`(由 `.work/r8/gen/eq.py` 生成,不入库)核对公式文本(含开头的空格、大写开关、读不通的域、字样变化的下标、`\ai`、`\di`、小写 `\s\do` 的尾部与嵌套)、DOCX 往返后的斜体、下标与无占位符、锚字符的升降与图块里图片低于文字(去掉版面的升降即失败)。审查三轮:第 3 轮的四处已修并以上述测试核对,未再审查(每条至多三轮)。仍与 Word 不同:编辑区里公式与图片四周有内核的辅助框线(`ShowBoundaries`,视图辅助,不印出);DOCX 另存后重开行距变宽,因内核导出改写文档默认字体与段距,与公式无关。
- 2026-10-18:工作表过多时标签条(R8.3,Gitea #8)。组件库 `navigation::Tabs` 横排时可横向滚动(滚轮同样),标签可窄于自然宽度;所选标签在所选或标签条宽度变了时滚入视口,在绘制时以刚排好的宽度判定(原先只在首帧滚一次,其后右栏打开、标签条变窄,所选标签被挤出视口);手动滚动保留到二者之一再变;横排的标签至多与标签条同宽,名字过长以省略号结尾(组件库 e56a343、3a68d1b、7d704d9,同批带入上游列外壳拖动的四个提交)。ElyTable 的标签条随之收缩,添加按钮留在其旁;标签条的状态按工作簿各记一份(元素 id 带标签号),切到另一工作簿时其所选标签照样滚入。`navigation/tests/tabs.rs` 以 30 个标签核对:首帧滚到所选、滚轮回到第一个、新选的滚入、变窄后所选仍在末端(去掉按宽度判定即失败)、长名的所选标签整个在标签条内(去掉宽度上限即失败);ElyTable 的 `tests/strip.rs` 以 `sheets::strip` 画两个各有 30 张表、都选第 30 张的工作簿,滚回开头后来回切换,所选标签每次都在标签条内(两个工作簿共用一个 id 即失败)。Windows 真窗口(`.work/r8/strip3.png`):30 张表、第 30 张为当前,1100 宽时标签条末端为它。
+15 -8
View File
@@ -109,18 +109,12 @@ impl Book {
let Some(tab) = self.active_tab() else {
return div().into_any_element();
};
let Some(choices) = visible(&tab.extra.sheets) else {
let Some(tabs) = strip(tab.id, &tab.extra.sheets) else {
return div().into_any_element();
};
let this = cx.entity();
let picked = this.clone();
let tabs = SheetTabs::new(
"table-sheets",
choices,
tab.extra.sheets.current.to_string(),
)
.placement(TabPlacement::Bottom)
.on_change(move |value, _, cx| {
let tabs = tabs.on_change(move |value, _, cx| {
let index: usize = value.parse().expect("sheet tabs are indexes");
picked.update(cx, |book, cx| book.show_sheet(index, cx));
})
@@ -310,6 +304,15 @@ impl Book {
}
}
/// Workbook `id`'s sheet tabs; each workbook keeps its own scroll.
fn strip(id: u64, sheets: &ely_lok::Sheets) -> Option<SheetTabs> {
let choices = visible(sheets)?;
Some(
SheetTabs::new(("table-sheets", id), choices, sheets.current.to_string())
.placement(TabPlacement::Bottom),
)
}
/// Visible sheets as tabs, once the shown one is known.
fn visible(sheets: &ely_lok::Sheets) -> Option<Vec<Choice>> {
let shown = sheets.list.get(sheets.current)?;
@@ -327,6 +330,10 @@ fn visible(sheets: &ely_lok::Sheets) -> Option<Vec<Choice>> {
)
}
#[cfg(test)]
#[path = "tests/strip.rs"]
mod strip_tests;
#[cfg(test)]
mod tests {
use super::*;
+96
View File
@@ -0,0 +1,96 @@
//! Each workbook's strip shows its own chosen sheet.
use ely_lok::{Sheet, Sheets};
use gpui::{
Bounds, Context, IntoElement, Modifiers, ParentElement as _, Pixels, Render, RenderOnce as _,
ScrollDelta, ScrollHandle, ScrollWheelEvent, Styled as _, TestAppContext, TouchPhase,
VisualTestContext, Window, div, point, px,
};
use super::strip;
struct Books {
book: u64,
sheets: Sheets,
scroll: Option<ScrollHandle>,
}
impl Render for Books {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let tabs = strip(self.book, &self.sheets)
.expect("tabs")
.render(window, cx)
.into_any_element();
let id = gpui::ElementId::from(("table-sheets", self.book));
let handle = window.use_keyed_state((id, "scroll"), cx, |_, _| ScrollHandle::new());
self.scroll = Some(handle.read(cx).clone());
div()
.w(px(640.))
.child(div().flex().items_center().px_2().child(tabs))
}
}
fn settle(cx: &mut VisualTestContext) {
for _ in 0..5 {
cx.run_until_parked();
cx.update(|window, _| window.refresh());
}
cx.run_until_parked();
}
/// The strip's viewport and the chosen tab in it.
fn place(
view: &gpui::Entity<Books>,
cx: &mut VisualTestContext,
) -> (Bounds<Pixels>, Bounds<Pixels>) {
view.read_with(cx, |view, _| {
let scroll = view.scroll.as_ref().expect("drawn");
let mut chosen = scroll
.bounds_for_item(view.sheets.current)
.expect("chosen tab");
chosen.origin += scroll.offset();
(scroll.bounds(), chosen)
})
}
fn shown(view: &gpui::Entity<Books>, cx: &mut VisualTestContext) -> bool {
let (viewport, chosen) = place(view, cx);
chosen.left() >= viewport.left() && chosen.right() <= viewport.right()
}
#[gpui::test]
fn a_switched_workbook_shows_its_chosen_sheet(cx: &mut TestAppContext) {
cx.update(ely_gpui_component::init_for_tests);
let sheets = Sheets {
current: 29,
list: (0..30)
.map(|n| Sheet {
name: format!("Sheet {n}"),
visible: true,
})
.collect(),
};
let (view, cx) = cx.add_window_view(|_, _| Books {
book: 1,
sheets,
scroll: None,
});
settle(cx);
assert!(shown(&view, cx), "the first draw shows the chosen sheet");
for book in [2, 1, 2] {
cx.simulate_event(ScrollWheelEvent {
position: point(px(60.), px(16.)),
delta: ScrollDelta::Pixels(point(px(0.), px(50_000.))),
modifiers: Modifiers::none(),
touch_phase: TouchPhase::Moved,
});
settle(cx);
assert!(!shown(&view, cx), "the wheel moves the chosen sheet away");
view.update(cx, |view, cx| {
view.book = book;
cx.notify();
});
settle(cx);
assert!(shown(&view, cx), "workbook {book} shows its chosen sheet");
}
}
+1 -1
View File
@@ -290,6 +290,6 @@
- [x] R8.1 深色外观下内容照文件原色(§5,用户定,取代 2026-10-04 的“内容区随系统深浅色”):ElyPDF 页面不反相;ElyDoc 页面、ElyTable 单元格照文件的颜色(白底即白底),ElyShow 已是如此(`ely.xcd` 只留浅色方案,每个视图首帧发一次;MuPDF 不再反相;Windows 真窗口深色外观下四个应用都是白纸;测试以深色档案起内核、视图须把页面画白,去掉发送即失败 — 第 1 轮:`theme.sh` 仍要深色网格、测试的内核本就浅色;第 2 轮:两处注释超长;第 3 轮 PASS)
- [x] R8.2 Gitea #9 公式不兼容(§7):附件 math.docx 的键盘公式、OMML、MathType(OLE)、EQ 域、上下标、图片公式逐类与 Word 对照,查明各类差异的根因并修;修不了的写明原因与证据(补丁 0049:运行的 `w:position` 随锚字符移动图片与 OLE;0050、0051:EQ 域转为公式对象,读不通的显示域代码;`word.rs` 三项测试;第 3 轮四处修后未再审查)
- [ ] R8.3 Gitea #8 工作表过多时标签条不滚动(§9):活动工作表变化时标签条滚动,使该标签完整可见;被裁到窗口外的工作表可经标签条到达
- [x] R8.3 Gitea #8 工作表过多时标签条不滚动(§9):活动工作表变化时标签条滚动,使该标签完整可见;被裁到窗口外的工作表可经标签条到达(组件库 7d704d9:按所选与宽度滚入、长名有上限;ElyTable 标签条按工作簿各记状态;`tabs.rs` 与 `strip.rs` 测试 — 第 1 轮:共用 id、长名越宽;第 2 轮:每工作簿状态无测试;第 3 轮 PASS)
- [ ] R8.4 ElyMark 公式(§14):行内公式排版(原只显示着色的 TeX 原文);`\(…\)` 读作行内公式,`\[…\]` 读作块公式
- [ ] R8.5 答复 #8、#9,修好的关闭;`agents.md` 随之
+5
View File
@@ -0,0 +1,5 @@
FAIL
1. `apps/ely-table/src/sheets.rs:118`(组件库 `src/navigation/tabs.rs:294`):所有工作簿共用 `table-sheets`,所选值只有工作表索引,而自动滚动只比较所选值和视口宽度。打开两个各有 30 张表、当前均为第 30 张的工作簿,在第一个工作簿把标签条滚回开头,再切到第二个;状态键不变,新的活动标签仍在视口外,不符合 R8.3 的活动工作表变化时完整可见。探针测得视口为 `[8,604]`,第二个工作簿的活动标签为 `[1446,1494]`。修正方向:把工作簿身份纳入标签条的状态或跟随判定,并覆盖同索引跨工作簿切换的回归测试。证据:`.work/R8.3-r1/final-probes.log`。
2. `C:/Users/zhang/Documents/Github/Ely-GPUI-Components/src/navigation/tabs.rs:250`(标签项在 `:183` 构建):只允许标签条收缩,没有限制单个标签的宽度,长且不能断行的表名仍可宽于整个视口。640 像素窗口显示 280 像素属性栏时,正文宽 360,扣除添加按钮和边距后标签视口宽 316;使用内嵌 Inter 和 Windows DirectWrite,表名为两位序号加 29 个 `W`,切换到第 30 张表后标签宽 390,自动滚动只能将其左端对齐,右侧仍被裁掉,无法满足完整可见。修正方向:约束标签项和其文字的布局,使单项能容纳于当前视口,并用实际字体覆盖长表名与侧栏缩窄的回归测试。证据:`.work/R8.3-r1/final-probes.log` 的 `native_text_keeps_a_valid_31_character_sheet_name_fully_visible`。
+3
View File
@@ -0,0 +1,3 @@
FAIL
1. `apps/ely-table/src/sheets.rs:118`(同文件 `:331` 的测试):跨工作簿状态隔离的修复缺少回归测试。现有 `sheets::tests` 只测 `visible()`;`a_switch_stays_with_its_workbook` 在不渲染标签条的 `Blank` 窗口里只核对内核状态,组件的标签测试也只用一个固定身份。把此处退回共享的 `table-sheets`,这些测试仍无法发现:两个各有 30 张表、当前索引均为 29 的工作簿,第一个手动滚回开头后切到第二个,活动标签落在视口外。独立探针已核对新键通过、旧键复现(视口 `[8,604]`,旧键的活动标签 `[2490,2574]`),但探针不属于项目测试。修正方向:加入实际渲染 `sheet_strip` 的跨工作簿回归测试,覆盖相同当前索引、手动滚动后切换及切回,并断言活动标签完整位于视口内;恢复旧键时须失败。证据:`.work/R8.3-r2/probe-tests.log`、`probe-source.log`、`validation.log`。
+1
View File
@@ -0,0 +1 @@
PASS