R3.3: the LOK view's pointer follows the kernel; an arrow over cells
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> [skip ci]
This commit is contained in:
1 parent
ee1b8ad375
commit
26c0a64665
11 files changed
+72
-6
No files matched your search
@@ -285,3 +285,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
|
||||
- 2026-10-11:`ci.sh` 用 `ELY_SHARE` 时,CI 克隆里的 `engines`、`reference` 须是指向共享克隆的链接(Windows 为目录联接,不查);是本克隆自建的目录或指向别处即报错,不混用。`.gitignore` 的 `/engines`、`/reference` 不带斜杠,链接也被忽略(`source.sh` 要求工作区干净)。
|
||||
- 2026-10-11(用户定:ElyMark 不用 CEF,不用 Electron):ElyMark 改为原生。界面用组件库的 Markdown 组件(`documents::MarkdownEditor` 一族,缺的在组件库补并推到 Gitea),解析用 markdown-rs(`markdown` 1.0.0),公式用 RaTeX,Mermaid 用 mermaid-rs-renderer,PDF 用 MuPDF Story;CEF 随之删去,ElyMail 的正文也不用 CEF 与 Electron。进度在 `tasks/TASKS.md` 的 C14。组件库 fcad0ff 与 508cc55:`MarkdownRenderer`、`MarkdownEditor`、Markdown 着色、块范围与链接查找改读 markdown-rs 的语法树(GitHub 风格加公式),引用式链接与图片按定义解析,图片把所在段落分开,单行 `$$…$$` 为块公式,软换行(LF、CRLF、CR)读作空格,嵌套在引用与列表里的定义同样解析,强调与链接里的图片仍是图片(链接里的图片可按);`BlockEditor` 的导入导出不属 ElyMark,仍用 pulldown-cmark。markdown-rs 读嵌套图片时只留外层的说明。组件库 d5ecc5a:块公式经 `typography::Latex` 由 RaTeX 排出(字形为轮廓,按窗口缩放画),```` ```mermaid ```` 块经 `documents::Diagram` 由 mermaid-rs-renderer 画出(Mermaid 自己的深浅主题,底色与字色取 Ely 色板),都作为 gpui 资源在后台排版,RaTeX 或 Mermaid 对已解析的输入 panic 时显示为错误;`MarkdownRenderer::base` 给出文档所在文件夹,相对与 file 地址的图片从中读取并按自身大小、不超过栏宽显示,`data:` 地址解码,网络图片不载入(写明)。组件库 ab9a14c:每个公式、图与图片元素经 `primitives::held` 持有其资源,按资源计数持有者(8b9820b;画出的图存在共享的记录里,9036c92 起由最后离开的持有者释放,哪怕它自己还没收到图),最后一个持有者换了内容或离开窗口才从 gpui 资源缓存移除、在本帧之后从图集释放,共用同一内容的元素(含 gpui 缓存的视图)不受影响;公式按字形实际范围取画框,字形不被裁;公式失败显示原因、RaTeX 给的位置与原文,日志不含公式;`file:` 地址去掉查询与片段,`localhost` 为本机,Windows 上别的主机为共享路径,他处拒绝;Mermaid 的节点与饼图文字取 Ely 的前景色。组件库 2b6f551:`documents::MarkdownDocument` 是文件级的 Markdown 文档:全文与撤销在一个 `CodeEditor` 里(`CodeEditor::highlighter` 以 `markdown_highlights` 着色,无行号);可视以 gpui `list` 逐块画出文件的顶层块(链接定义、HTML、front matter 照原文显示,每个字节都在块里或块间),按下的块开成源码(`TextInput`),其键入即刻算作文档的文字,写回为一步撤销,上下键越过块边进入相邻块,Esc 写回;Cmd(他处 Ctrl)按下交给链接;预览只读;源码为编辑器本身;128 KiB 以内当场解析,更长的在后台解析,解析中按下块不打开(日志);新解析保留两端相同的块,滚动不跳。`CodeEditor` 的撤销历史最多留 64 MiB 文字,超出先丢最旧的步;`CodeEditor::reset` 换文字且撤销重新开始;`MarkdownMode` 加 `Preview`。组件库 fc181b4:一个插入点通用于各模式(编辑器的选区,写回的块移动它);`set_caret` 与 `set_top_line` 分别设插入点与视图,换模式保持顶行;按下的块在前一块写回后仍是所按的块(按偏移换算,结构变了即不开并记日志);块是 Tab 停靠点,Enter 或空格打开;预览不收按下、撤销与重做;撤销与重做每次记录、输入法组字、撤销、重做后都限在 64 MiB 内(先丢最旧的撤销,再丢最远的重做)。组件库 433c38f:只有对当下文字的解析才能打开块(解析的文字与编辑器相同),否则拒绝;要求的顶行在其文字解析后才落定:先到所在块,下一帧按块高的比例到块内位置,再有新的要求即接着放下一个;源码与列表之间切换保持顶行,可视与预览共用一个列表,切换不动滚动。组件库 abac8b4:顶行的要求带编号,迟到的帧回调只完成自己的那个;文字一换即按新文字重新放,放时按当下文字的行数夹住;`reveal` 是同样的要求(插入点设在该行,该行放到顶上),新文字解析后才落定。组件库 f59c9f7:文字超过读取上限(2 MiB)即丢掉旧的读取,大纲与块为空。ElyMark(C14.4):视图即一个 `MarkdownDocument`,文本即文件原文(BOM 与 CRLF 照 `text.rs` 存回),新建、打开、保存、另存、重命名经外壳照旧;视图在建成后的下一轮更新向外壳报一次载入(文档在 `new` 里当场解析,外壳此时还未订阅);文字超过 2 MiB 即只用源码并转去源码(随当前文字,不只看打开时);网页实现的查找替换、表格、附件、书写视图的应用、导出与打印Line truncated
|
||||
- 2026-10-12(用户报:Windows 上 ElyMark 的最近列表显示 `\\?\C:\…`):凡取真实路径一律 `dunce::canonicalize`(ely-files 的会话登记、ely-lok 的内核目录与配置目录、ElyMark 的文件夹、最近列表、会话与标签比对;ElyTree 原已如此):Windows 上能写成普通路径即普通路径,他处同 `std::fs::canonicalize`。只供读写的路径照旧保留 `\\?\`(`ely_files::save` 的落盘与同文件判定:在 250 字的文件夹里,普通写法的暂存文件名越过 260 而写不进),保存后交给会话与 `Saved` 的路径以 `dunce::simplified` 整体判定后为普通写法;ely-office 另存的目标先以完整路径求出,再以 `dunce::simplified` 整体判定能否写成普通路径(先简化文件夹再接文件名时,结尾带空格的名字会被 Windows 削成另一个已打开的文件)。`ely_files::recent` 读入时以 `dunce::simplified` 去掉旧版写下的 `\\?\` 并去重,下次写入即为普通路径。
|
||||
- 2026-10-12(用户报:ElyTable 的网格上一直是文字指针):`LokView` 原把指针写死为文字指针。现不按键的鼠标移动也交内核(悬停,不算编辑、不加修订),内核以 `LOK_CALLBACK_MOUSE_POINTER` 回报 CSS 指针名(`vcl` 的 `gaLOKPointerMap`),随事件批次到视图,换成 gpui 的指针:单元格上为箭头,编辑单元格或文字时为文字指针,链接为手;未知的名字记警告并用箭头。
|
||||
@@ -14,6 +14,7 @@ fn unpack_pushed(body: &[u8]) -> io::Result<(u64, Events)> {
|
||||
let rects = rects_of(&mut unpack)?;
|
||||
let cursor = rects_of(&mut unpack)?.first().copied();
|
||||
let cursor_visible = unpack.u64()? != 0;
|
||||
let pointer = unpack.str()?;
|
||||
let selection = rects_of(&mut unpack)?;
|
||||
let graphic = rects_of(&mut unpack)?.first().copied();
|
||||
let cropping = unpack.u64()? != 0;
|
||||
@@ -55,6 +56,7 @@ fn unpack_pushed(body: &[u8]) -> io::Result<(u64, Events)> {
|
||||
rects,
|
||||
cursor,
|
||||
cursor_visible,
|
||||
pointer,
|
||||
selection,
|
||||
graphic,
|
||||
cropping,
|
||||
|
||||
@@ -195,3 +195,27 @@ fn a_theme_switch_is_no_edit() {
|
||||
assert_eq!(undo["actions"], "", "{scheme} left undo steps");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_pointer_is_text_only_while_a_cell_edits() {
|
||||
if install().is_none() {
|
||||
eprintln!("SKIP: no pinned LOK build");
|
||||
return;
|
||||
}
|
||||
let (_dir, mut worker, doc) = open_book(FODS);
|
||||
let hover = |worker: &mut Worker, x| {
|
||||
let at = Mouse { kind: 2, x, y: 120, count: 1, buttons: 0, modifiers: 0 };
|
||||
worker.post_mouse(&doc, at).expect("hovers");
|
||||
};
|
||||
hover(&mut worker, 400);
|
||||
let reading = poll_until(&mut worker, &doc, &Events::default(), SETTLE, |seen| {
|
||||
!seen.pointer.is_empty()
|
||||
});
|
||||
assert_eq!(reading.pointer, "default", "a cell shows the arrow");
|
||||
worker.post_key(&doc, 0, 769).expect("F2 edits A1");
|
||||
hover(&mut worker, 500);
|
||||
let editing = poll_until(&mut worker, &doc, &reading, SETTLE, |seen| {
|
||||
seen.pointer == "text"
|
||||
});
|
||||
assert_eq!(editing.pointer, "text", "the edited cell shows the text pointer");
|
||||
}
|
||||
@@ -46,6 +46,7 @@ const FONTS: i32 = 57;
|
||||
const SIZE_CHANGED: i32 = 13;
|
||||
const SET_PART: i32 = 14;
|
||||
const CELL_CURSOR: i32 = 17;
|
||||
const MOUSE_POINTER: i32 = 18;
|
||||
const CELL_FORMULA: i32 = 19;
|
||||
const HEADER: i32 = 33;
|
||||
const GEOMETRY: i32 = 50;
|
||||
@@ -73,6 +74,8 @@ pub struct Events {
|
||||
pub rects: Vec<Rect>,
|
||||
pub cursor: Option<Rect>,
|
||||
pub cursor_visible: bool,
|
||||
/// The CSS pointer name; empty until the kernel says.
|
||||
pub pointer: String,
|
||||
pub selection: Vec<Rect>,
|
||||
/// The selected object's frame, if any.
|
||||
pub graphic: Option<Rect>,
|
||||
@@ -141,7 +144,8 @@ fn signal() -> &'static Condvar {
|
||||
SIGNAL.get_or_init(Condvar::new)
|
||||
}
|
||||
|
||||
const KINDS: [i32; 33] = [
|
||||
const KINDS: [i32; 34] = [
|
||||
MOUSE_POINTER,
|
||||
REFERENCE_MARKS,
|
||||
FUNCTION_LIST,
|
||||
TOOLTIP,
|
||||
@@ -233,6 +237,7 @@ fn record(slot: &mut Slot, kind: i32, text: &str) -> Option<u32> {
|
||||
.collect();
|
||||
}
|
||||
CURSOR_VISIBLE => batch.cursor_visible = text == "true",
|
||||
MOUSE_POINTER => batch.pointer = text.to_string(),
|
||||
GRAPHIC => {
|
||||
batch.graphic = rect_in(batch, "graphic selection", text);
|
||||
batch.cropping = batch.graphic.is_some() && text.contains("\"isCropMode\": true");
|
||||
|
||||
@@ -117,6 +117,7 @@ pub struct LokView {
|
||||
watching: Option<Task<()>>,
|
||||
cursor: Option<Rect>,
|
||||
cursor_visible: bool,
|
||||
pointer: gpui::CursorStyle,
|
||||
selection: Vec<Rect>,
|
||||
cell: crate::Cell,
|
||||
/// Whether the kernel was last told to paint dark.
|
||||
@@ -222,6 +223,7 @@ impl LokView {
|
||||
copied: None,
|
||||
cursor: None,
|
||||
cursor_visible: true,
|
||||
pointer: gpui::CursorStyle::Arrow,
|
||||
selection: Vec::new(),
|
||||
cell: crate::Cell::default(),
|
||||
dark: None,
|
||||
@@ -387,6 +389,9 @@ impl LokView {
|
||||
self.follow_graphic(batch.graphic, cx);
|
||||
self.cursor = batch.cursor;
|
||||
self.cursor_visible = batch.cursor_visible;
|
||||
if !batch.pointer.is_empty() {
|
||||
self.pointer = render::pointer(&batch.pointer);
|
||||
}
|
||||
self.selection = batch.selection;
|
||||
if batch.cell.functions != self.cell.functions {
|
||||
self.picked = 0;
|
||||
|
||||
@@ -384,6 +384,9 @@ impl LokView {
|
||||
}
|
||||
if self.dragging && event.pressed_button == Some(MouseButton::Left) {
|
||||
self.mouse(2, event.position, 1, 1, &event.modifiers);
|
||||
} else if event.pressed_button.is_none() {
|
||||
// A hover: the kernel answers with its pointer.
|
||||
self.mouse(2, event.position, 0, 1, &event.modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ impl Render for LokView {
|
||||
.aria_value(self.a11y.text.clone())
|
||||
.key_context(CONTEXT)
|
||||
.track_focus(&self.focus)
|
||||
.cursor(CursorStyle::IBeam)
|
||||
.cursor(self.pointer)
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.bg(canvas_color)
|
||||
@@ -360,3 +360,29 @@ impl LokView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The cursor for a kernel CSS pointer name.
|
||||
pub(super) fn pointer(css: &str) -> CursorStyle {
|
||||
match css {
|
||||
"default" | "wait" | "help" => CursorStyle::Arrow,
|
||||
"text" => CursorStyle::IBeam,
|
||||
"vertical-text" => CursorStyle::IBeamCursorForVerticalLayout,
|
||||
"crosshair" | "fill" => CursorStyle::Crosshair,
|
||||
"move" => CursorStyle::ClosedHand,
|
||||
"grab" => CursorStyle::OpenHand,
|
||||
"pointer" => CursorStyle::PointingHand,
|
||||
"n-resize" | "s-resize" => CursorStyle::ResizeUpDown,
|
||||
"e-resize" | "w-resize" => CursorStyle::ResizeLeftRight,
|
||||
"ne-resize" | "sw-resize" => CursorStyle::ResizeUpRightDownLeft,
|
||||
"nw-resize" | "se-resize" => CursorStyle::ResizeUpLeftDownRight,
|
||||
"col-resize" => CursorStyle::ResizeColumn,
|
||||
"row-resize" => CursorStyle::ResizeRow,
|
||||
"copy" => CursorStyle::DragCopy,
|
||||
"alias" => CursorStyle::DragLink,
|
||||
"not-allowed" => CursorStyle::OperationNotAllowed,
|
||||
_ => {
|
||||
log::warn!("lok: unknown pointer {css}");
|
||||
CursorStyle::Arrow
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ impl LokView {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Ctrl (Cmd on macOS) with the wheel zooms; else it scrolls.
|
||||
/// Ctrl (macOS: Cmd) and the wheel zoom; else scroll.
|
||||
pub(super) fn on_wheel(&mut self, event: &ScrollWheelEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.wheel_zoom(event, cx) {
|
||||
self.on_scroll(event, window, cx);
|
||||
|
||||
@@ -37,7 +37,7 @@ fn pack_events(batch: Events) -> Vec<u8> {
|
||||
.u64(batch.all as u64);
|
||||
let pack = rects(pack, &batch.rects);
|
||||
let pack = rects(pack, batch.cursor.as_slice());
|
||||
let pack = pack.u64(batch.cursor_visible as u64);
|
||||
let pack = pack.u64(batch.cursor_visible as u64).str(&batch.pointer);
|
||||
let pack = rects(pack, &batch.selection);
|
||||
let pack = rects(pack, batch.graphic.as_slice()).u64(batch.cropping as u64);
|
||||
let pack = pack
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn steps(event: &ScrollWheelEvent, carry: &mut f32) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Over its parent, takes a zooming wheel before the parent scrolls.
|
||||
/// Takes a zooming wheel before the parent scrolls.
|
||||
pub fn zoom_layer(
|
||||
on: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static,
|
||||
) -> impl IntoElement {
|
||||
|
||||
+1
-1
@@ -226,5 +226,5 @@
|
||||
|
||||
- [x] R3.1 路径不带 `\?\`:凡取真实路径一律 `dunce::canonicalize`(Windows 上能写成普通路径即普通路径,他处同 `std::fs::canonicalize`);最近列表读入时去掉旧的 `\?\` 并去重;ElyMark 最近列表、ElyTree 等界面不再显示 `\?\` — R1 先简化文件夹再接文件名(结尾空格的名字改写已打开的文件)、暂存文件名越过 260;R2 另存后会话路径与测试不一;R3 PASS
|
||||
- [x] R3.2 缩放:Doc、Table、Show、PDF、Mark 以 Ctrl 加滚轮(macOS 为 Cmd)缩放,原认 Windows 键与 Super(Hyprland 占用,Linux 上无效);滚轮一格为一档(原 Windows 一格三行,一下放大 1.8 倍),触控板的像素滚动照旧连续(LOK)或累积成档(PDF、Mark);PDF 与 Mark 原无滚轮缩放
|
||||
- [ ] R3.3 LOK 视图的指针随内核:`LOK_CALLBACK_MOUSE_POINTER`(单元格上为箭头,编辑单元格或文字时为文字指针,边线为拖动指针,链接为手),原一律为文字指针
|
||||
- [x] R3.3 LOK 视图的指针随内核:`LOK_CALLBACK_MOUSE_POINTER`(单元格上为箭头,编辑单元格或文字时为文字指针,边线为拖动指针,链接为手),原一律为文字指针
|
||||
- [ ] R3.4 版本 0.1.4:`Cargo.toml` 与随版本的文档,提交并推送 main,打标签 v0.1.4,本机建三端并发布(用户定,R3.2 审查略去)
|
||||
Reference in new issue
Block a user