R6.13 round 2 fix: the worker turns in input order; scrolls by gesture
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d427d05b96
commit
407ce00ab9
17 files changed
+91
-109
No files matched your search
@@ -305,4 +305,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
|
||||
- 2026-10-15(用户报:ElyMark 源码模式“测”后的插入点画在“测”的中间):组件库 `CodeEditor` 原按“列 × m 的宽”放插入点、选区、按下处与输入法框,中日韩字与表情约宽两格。组件库 9fb607b 起按 gpui 排好的这一行定位(`editor::measure`,含着色带来的字重),块状与下划线插入点取所在字的宽;标尺与缩进参考线仍按列(R6.8)。上下移动仍按字数记目标列。cd61653:按下处取最近的字首或行末(gpui 的 `closest_index_for_x` 越过末字字首即给行末);上次绘制得出的括号与配对位置只属当时的文字(`Buffer` 每次改动换一个戳),输入法改字后、下次绘制前的测量不用它们(原先把新字切进不同字体段,DirectWrite 崩溃)。
|
||||
- 2026-10-15(用户报:ElyMark 三、四级标题不显眼):组件库 5954132 起标题字号随正文按 Typora 的倍数(一至六级 2.25、1.75、1.5、1.25、1、1),一律加粗,六级为次要色,行高为字号的 1.3 倍(原为固定的 24、20、16、14 像素,正文 13,三、四级只是中等字重)(R6.9)。90bd37b:粗体与标题同一字重(原粗体为半粗,标题里的粗体反而更细,正文粗体随之由半粗改为粗,同 Typora);标题行高按自身字号取 1.3 倍。Windows 真窗口核对(`.work/r69`):可视模式各级标题分明、“你好↵测试”两行,源码模式“测”后的插入点落在两字之间。
|
||||
- 2026-10-15(用户报:macOS 打开一个文档,程序坞里有两个同样的图标):LOK worker 在应用包的 `Contents/MacOS` 里启动 AppKit,系统按包的 Info.plist 把它也登记为常规应用(`lsappinfo` 里应用与 worker 都是 Foreground),程序坞多一个图标;内核自己从不设激活策略。worker 在内核启动 AppKit 之前(`Lok::hook` 打开内核之后、调钩子之前)把本进程主包信息里的 `LSUIElement` 设为真,AppKit 登记时即为辅助应用(无程序坞图标、无菜单栏,仍可建屏外窗口);钩子之后核对激活策略是辅助,不是即报错、不启动(`ffi/accessory.rs`);包外的 worker(`cargo run`、单测)无包可登记,策略为禁止,此时改设辅助(R6.12 第 2 轮)。原先在登记之后才改策略,审查实测 15 次启动有 11 次先登记为 Foreground 约 30 毫秒(R6.12 第 1 轮)。`tests/scripts/dock-macos.sh` 连续启动三个应用与包外的开发构建,从 worker 启动起采样其类型到首块图块,见 Foreground 即失败(R6.12)。
|
||||
- 2026-10-15(用户报:ElyShow 编辑区滚轮与上下键不换页,三端):内核在未选中对象时把上下键当作滚动窗口(`fupoor.cxx`),Ely 的画布整页适合、无处可滚,滚轮同样。`LokView::turn_parts`(ElyShow 首次适合时调用)之后:整页可见时滚轮换页(`view/turn.rs`):滚轮按本平台每格的行数(`ely_lok::notch`:macOS 1,Windows 取系统设置,X11 与 Wayland 为 3)计格,每格一页;不足一格的行(X11 与 Windows 的触控板)与像素(macOS、Wayland 的触控板)一次手势只换一页,惯性不算;有起止的手势(macOS)只到结束才算完,没有的静止 250 毫秒再计。上下键经有序输入队列交 worker(`TURN_KEY`),worker 按之前各输入回报后的状态判定:无选区、无选中对象、无插入点即换页,否则照常交内核(Tab 之后紧接的 Down 移动所选对象,不换页);视图发 `Turn`,ElyShow 从视图所示的页起算(连续换页累加),经缩略图条同一路径 `show_part`,到首末页即停;后台读回的当前页只在视图已无待落地输入时采用,排队的换页不被先前的读取拉回(R6.13)。
|
||||
- 2026-10-15(用户报:ElyShow 编辑区滚轮与上下键不换页,三端):内核在未选中对象时把上下键当作滚动窗口(`fupoor.cxx`),Ely 的画布整页适合、无处可滚,滚轮同样。`LokView::turn_parts`(ElyShow 首次适合时调用)之后:整页可见时滚轮换页(`view/turn.rs`):滚轮的行按本平台每格的行数(`ely_lok::notch`:macOS 1,Windows 取系统设置,X11 与 Wayland 为 3)折成格;X11 的滚轮与触控板同为行、分不出,所以行与像素一律按手势计:一次手势换一页(满一格或 50 像素),惯性不算;有起止的手势(macOS 触控板)只到结束才算完,没有起止的停 200 毫秒即为下一次(逐格有停顿地滚,每格一页)。未选中对象、未编辑文字时上下键换页。换页一律经有序输入队列交 worker(`TURN`):worker 按之前各输入回报后的状态判定,有选区、选中对象或插入点即把方向键照常交内核,否则当场切页(到首末页即停,同缩略图条的 `setPart`),视图在落地时换上 worker 所示的页,其后排队的键与点按都落在新页上(Tab 后紧接的 Down 移动所选对象;Down、Tab、Down 是换页后在新页上选中并移动);ElyShow 后台读回的当前页只在视图已无待落地输入时采用(R6.13)。
|
||||
@@ -99,7 +99,6 @@ impl Host for Deck {
|
||||
view.fit_page(cx);
|
||||
view.turn_parts();
|
||||
});
|
||||
cx.subscribe(view, move |deck, view, turn: &ely_lok::Turn, cx| deck.turn(id, &view, turn.0, cx)).detach();
|
||||
}
|
||||
// Pictures follow the kernel's light or dark scheme.
|
||||
let dark = view.read(cx).dark();
|
||||
@@ -267,7 +266,7 @@ impl Deck {
|
||||
Ok(((slides, at), (design, objects, notes, remarks, (motion, media)), size, history)) => {
|
||||
tab.extra.read_at = Some(at);
|
||||
log::info!("show: tab {id} has {} parts", slides.list.len());
|
||||
// A queued turn outranks a read made before it.
|
||||
// A queued turn or part outranks an earlier read.
|
||||
view.update(cx, |view, _| {
|
||||
if view.settled() {
|
||||
view.set_part(slides.current)
|
||||
|
||||
@@ -9,11 +9,10 @@ use ely_gpui_component::{
|
||||
};
|
||||
use ely_office::prelude::*;
|
||||
use gpui::{
|
||||
Action, App, Context, Div, Entity, InteractiveElement as _, IntoElement, Menu, MenuItem,
|
||||
Action, App, Context, Div, InteractiveElement as _, IntoElement, Menu, MenuItem,
|
||||
ParentElement as _, Render, RenderImage, Role, StatefulInteractiveElement as _, Styled as _,
|
||||
WeakEntity, Window, actions, div, img, prelude::FluentBuilder as _, px,
|
||||
};
|
||||
use ely_lok::LokView;
|
||||
use image::{Frame, ImageBuffer, Rgba};
|
||||
|
||||
use crate::deck::Deck;
|
||||
@@ -99,19 +98,6 @@ impl Deck {
|
||||
self.run_active(id, &[], cx);
|
||||
}
|
||||
|
||||
/// Shows the slide `step` away, as the strip would.
|
||||
pub(crate) fn turn(&mut self, id: u64, view: &Entity<LokView>, step: i32, cx: &mut Context<Self>) {
|
||||
let Some(count) = self.tab(id).map(|tab| tab.extra.slides.list.len()).filter(|count| *count > 0) else {
|
||||
return;
|
||||
};
|
||||
// From the part asked last, so quick turns add up.
|
||||
let shown = view.read(cx).part();
|
||||
let part = (shown as i64 + i64::from(step)).clamp(0, count as i64 - 1) as usize;
|
||||
if part != shown {
|
||||
view.update(cx, |view, _| view.show_part(part));
|
||||
}
|
||||
}
|
||||
|
||||
/// Paints the next missing or stale slide picture.
|
||||
pub fn paint_next(&mut self, id: u64, cx: &mut Context<Self>) {
|
||||
let Some(tab) = self.tab(id) else {
|
||||
|
||||
@@ -64,4 +64,8 @@ fn the_wheel_and_up_down_turn_slides() {
|
||||
shown.simulate_keystrokes("tab down");
|
||||
assert_eq!(current(&mut shown, &deck), 0, "Down after Tab moves, not turns");
|
||||
assert!(shown.update(|_, app| selection(app, &deck)).is_some(), "Tab picked an object");
|
||||
// A turn lands before the keys queued after it.
|
||||
shown.simulate_keystrokes("escape down tab down");
|
||||
assert_eq!(current(&mut shown, &deck), 1, "Down turns, then moves");
|
||||
assert!(shown.update(|_, app| selection(app, &deck)).is_some(), "Tab picked on the new slide");
|
||||
}
|
||||
@@ -237,12 +237,13 @@ impl Worker {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Up or Down: whether it turned, not reaching the kernel.
|
||||
pub fn turn_key(&mut self, doc: &Doc, char_code: u32, key_code: i32) -> io::Result<bool> {
|
||||
/// Turns `step` parts, or posts `key` if something is picked.
|
||||
pub fn turn(&mut self, doc: &Doc, step: i32, key: Option<(u32, i32)>) -> io::Result<Option<usize>> {
|
||||
let (char_code, key_code) = key.unwrap_or_default();
|
||||
let doc_id = self.doc_id(doc)?;
|
||||
let payload = Pack::new().u64(doc_id).i64(char_code as i64).i64(key_code as i64).bytes();
|
||||
let reply = self.request(tag::TURN_KEY, &payload)?;
|
||||
Ok(Unpack::new(&reply).u64()? == 1)
|
||||
let payload = Pack::new().u64(doc_id).i64(step.into()).i64(char_code.into()).i64(key_code.into()).bytes();
|
||||
let shown = Unpack::new(&self.request(tag::TURN, &payload)?).i64()?;
|
||||
Ok(usize::try_from(shown).ok())
|
||||
}
|
||||
|
||||
/// Drags a selected object's handle, start then end.
|
||||
|
||||
@@ -88,8 +88,8 @@ pub mod tag {
|
||||
pub const REVISION: u8 = 34;
|
||||
/// One layer's pixels from the last `SLIDE_LAYERS`.
|
||||
pub const SLIDE_LAYER: u8 = 35;
|
||||
/// Up or Down: turns the part when nothing is picked.
|
||||
pub const TURN_KEY: u8 = 36;
|
||||
/// Turns parts; an arrow with something picked goes in.
|
||||
pub const TURN: u8 = 36;
|
||||
pub const OK: u8 = 100;
|
||||
pub const ERR: u8 = 101;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ pub use client::{
|
||||
};
|
||||
#[cfg(feature = "view")]
|
||||
pub use view::{
|
||||
CONTEXT, Copy, Cut, Geom, Layout, LokError, LokView, Paste, Reported, Turn, ZOOM_STEPS, notch,
|
||||
CONTEXT, Copy, Cut, Geom, Layout, LokError, LokView, Paste, Reported, ZOOM_STEPS, notch,
|
||||
Zoomed, init, layout, paper, read_off, scheme,
|
||||
};
|
||||
#[cfg(feature = "view")]
|
||||
|
||||
@@ -34,7 +34,7 @@ pub use input::{CONTEXT, Copy, Cut, Paste, init};
|
||||
pub use off::read_off;
|
||||
pub use render::{paper, scheme};
|
||||
mod turn;
|
||||
pub use turn::{Turn, notch};
|
||||
pub use turn::notch;
|
||||
|
||||
pub(crate) use crate::{TILE_TWIPS, TWIPS_PER_PIXEL};
|
||||
|
||||
|
||||
@@ -64,18 +64,14 @@ pub(super) enum Input {
|
||||
Visible(Rect, (u32, i32)),
|
||||
/// Shows sheet or slide `part`, from 0.
|
||||
Part(usize),
|
||||
/// Up or Down; turns `step` parts when nothing is picked.
|
||||
Turn {
|
||||
char_code: u32,
|
||||
key_code: i32,
|
||||
step: i32,
|
||||
},
|
||||
/// Turns `step` parts; a picked object takes the arrow `key`.
|
||||
Turn { step: i32, key: Option<(u32, i32)> },
|
||||
}
|
||||
|
||||
/// What an input gives back.
|
||||
pub(super) enum Back {
|
||||
Copied(String),
|
||||
Turned(i32),
|
||||
Shown(usize),
|
||||
}
|
||||
|
||||
impl Input {
|
||||
@@ -200,10 +196,7 @@ fn apply(worker: &mut Worker, doc: &Doc, input: Input) -> io::Result<Option<Back
|
||||
char_code,
|
||||
key_code,
|
||||
} => worker.post_key(doc, char_code, key_code),
|
||||
Input::Turn { char_code, key_code, step } => {
|
||||
let turned = worker.turn_key(doc, char_code, key_code)?;
|
||||
return Ok(turned.then_some(Back::Turned(step)));
|
||||
}
|
||||
Input::Turn { step, key } => return Ok(worker.turn(doc, step, key)?.map(Back::Shown)),
|
||||
Input::Mouse(event) => worker.post_mouse(doc, event),
|
||||
Input::Drag { from, to } => worker.drag_graphic(doc, from, to),
|
||||
Input::Visible(area, scale) => doc.visible_area(worker, area, scale),
|
||||
|
||||
@@ -10,7 +10,7 @@ use futures::{StreamExt, channel::mpsc, select_biased};
|
||||
use gpui::Context;
|
||||
|
||||
use super::input::{Back, Input};
|
||||
use super::{Geom, LokError, LokView, Reported, TILE_TWIPS, Turn};
|
||||
use super::{Geom, LokError, LokView, Reported, TILE_TWIPS};
|
||||
use crate::{Doc, Tile, Worker};
|
||||
|
||||
/// An input slower than this is logged as a warning.
|
||||
@@ -113,7 +113,12 @@ impl LokView {
|
||||
self.seen_revision(now.unwrap_or(0));
|
||||
match outcome {
|
||||
Ok(Some(Back::Copied(copied))) => self.copied = Some(copied),
|
||||
Ok(Some(Back::Turned(step))) => cx.emit(Turn(step)),
|
||||
Ok(Some(Back::Shown(part))) if part != self.part => {
|
||||
// The worker turned in order.
|
||||
self.part = part;
|
||||
self.invalidate(cx);
|
||||
}
|
||||
Ok(Some(Back::Shown(_))) => {}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
log::error!("lok: input failed ({:?})", error.kind());
|
||||
|
||||
@@ -2,21 +2,15 @@
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use gpui::{Context, EventEmitter, Keystroke, Modifiers, ScrollDelta, ScrollWheelEvent, TouchPhase};
|
||||
use gpui::{Keystroke, Modifiers, ScrollDelta, ScrollWheelEvent, TouchPhase};
|
||||
|
||||
use super::LokView;
|
||||
use super::input::{Input, vcl_key};
|
||||
|
||||
/// Asks for the part `step` away from the shown one.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Turn(pub i32);
|
||||
|
||||
impl EventEmitter<Turn> for LokView {}
|
||||
|
||||
/// Trackpad pixels one turn takes.
|
||||
const SWIPE: f32 = 50.0;
|
||||
/// A pause this long ends a gesture without phases.
|
||||
const PAUSE: Duration = Duration::from_millis(250);
|
||||
const PAUSE: Duration = Duration::from_millis(200);
|
||||
|
||||
/// Lines one wheel notch scrolls on this platform.
|
||||
pub fn notch() -> f32 {
|
||||
@@ -48,8 +42,6 @@ pub(super) struct Turning {
|
||||
spent: bool,
|
||||
/// The platform began a gesture it has not ended.
|
||||
held: bool,
|
||||
/// Fractional lines: a smooth touchpad, not a wheel.
|
||||
smooth: bool,
|
||||
last: Option<Instant>,
|
||||
}
|
||||
|
||||
@@ -58,42 +50,27 @@ impl Turning {
|
||||
pub(super) fn wheel(&mut self, delta: ScrollDelta, phase: TouchPhase, now: Instant, notch: f32) -> i32 {
|
||||
let paused = self.last.is_none_or(|last| now.duration_since(last) >= PAUSE);
|
||||
self.last = Some(now);
|
||||
let travel = match delta {
|
||||
// X11 lines mix wheels and touchpads; they carry no phase.
|
||||
let (travel, phase) = match delta {
|
||||
ScrollDelta::Lines(_) if notch <= 0.0 => return 0,
|
||||
ScrollDelta::Lines(lines) => {
|
||||
let notches = lines.y / notch;
|
||||
let whole = notches.round();
|
||||
// Whole notches outside a smooth stream: each turns once.
|
||||
if whole != 0.0 && (notches - whole).abs() < 1e-3 && (paused || !self.smooth) {
|
||||
(self.travel, self.spent, self.smooth) = (0.0, false, false);
|
||||
return -(whole as i32);
|
||||
}
|
||||
if paused || !self.smooth {
|
||||
(self.travel, self.spent) = (0.0, false);
|
||||
}
|
||||
self.smooth = true;
|
||||
notches
|
||||
}
|
||||
ScrollDelta::Pixels(pixels) => {
|
||||
self.smooth = false;
|
||||
// Without phases, only a pause ends a gesture.
|
||||
if phase == TouchPhase::Started || paused && !self.held {
|
||||
(self.travel, self.spent) = (0.0, false);
|
||||
}
|
||||
self.held = match phase {
|
||||
TouchPhase::Started => true,
|
||||
TouchPhase::Ended | TouchPhase::Cancelled => false,
|
||||
TouchPhase::Moved => self.held,
|
||||
};
|
||||
f32::from(pixels.y) / SWIPE
|
||||
}
|
||||
ScrollDelta::Lines(lines) => (lines.y / notch, None),
|
||||
ScrollDelta::Pixels(pixels) => (f32::from(pixels.y) / SWIPE, Some(phase)),
|
||||
};
|
||||
// Phases bound a gesture; without them, a pause does.
|
||||
if phase == Some(TouchPhase::Started) || paused && !self.held {
|
||||
(self.travel, self.spent) = (0.0, false);
|
||||
}
|
||||
self.held = match phase {
|
||||
Some(TouchPhase::Started) => true,
|
||||
Some(TouchPhase::Moved) => self.held,
|
||||
Some(TouchPhase::Ended | TouchPhase::Cancelled) | None => false,
|
||||
};
|
||||
// One gesture turns once, momentum included.
|
||||
if self.spent {
|
||||
return 0;
|
||||
}
|
||||
self.travel += travel;
|
||||
if self.travel.abs() < 1.0 {
|
||||
if self.travel.abs() < 1.0 - 1e-3 {
|
||||
return 0;
|
||||
}
|
||||
self.spent = true;
|
||||
@@ -123,15 +100,15 @@ impl LokView {
|
||||
"up" => -1,
|
||||
_ => return false,
|
||||
};
|
||||
let Some((char_code, key_code)) = vcl_key(keystroke) else {
|
||||
let Some(key) = vcl_key(keystroke) else {
|
||||
return false;
|
||||
};
|
||||
self.send(Input::Turn { char_code, key_code, step });
|
||||
self.send(Input::Turn { step, key: Some(key) });
|
||||
true
|
||||
}
|
||||
|
||||
/// The wheel turns while the whole page shows.
|
||||
pub(super) fn turn_wheel(&mut self, event: &ScrollWheelEvent, cx: &mut Context<Self>) -> bool {
|
||||
pub(super) fn turn_wheel(&mut self, event: &ScrollWheelEvent) -> bool {
|
||||
let (width, height) = self.extent();
|
||||
// A pixel of slack for rounding.
|
||||
let whole = self.geom.px_of(width) <= self.viewport.0 + 1.0
|
||||
@@ -141,7 +118,8 @@ impl LokView {
|
||||
};
|
||||
let step = turning.wheel(event.delta, event.touch_phase, Instant::now(), notch());
|
||||
if step != 0 {
|
||||
cx.emit(Turn(step));
|
||||
// In order with the input around it.
|
||||
self.send(Input::Turn { step, key: None });
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -164,26 +142,29 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_notch_turns_once() {
|
||||
fn each_notch_with_a_pause_turns_once() {
|
||||
let (mut turning, now) = (Turning::default(), std::time::Instant::now());
|
||||
let mut wheel = |y| turning.wheel(lines(y), TouchPhase::Moved, now, NOTCH);
|
||||
assert_eq!(wheel(-3.0), 1, "Windows and Linux scroll three lines a notch");
|
||||
assert_eq!(wheel(-3.0), 1);
|
||||
assert_eq!(wheel(3.0), -1);
|
||||
assert_eq!(wheel(-6.0), 2, "two notches in one event");
|
||||
let mut wheel = |y, at| turning.wheel(lines(y), TouchPhase::Moved, at, NOTCH);
|
||||
assert_eq!(wheel(-3.0, now), 1, "Windows and Linux scroll three lines a notch");
|
||||
assert_eq!(wheel(-3.0, now + PAUSE), 1);
|
||||
assert_eq!(wheel(3.0, now + PAUSE * 2), -1);
|
||||
assert_eq!(wheel(-6.0, now + PAUSE * 3), 1, "two notches at once turn once");
|
||||
assert_eq!(wheel(-1.0, now + PAUSE * 4), 0, "a third of a notch");
|
||||
let mut mac = Turning::default();
|
||||
assert_eq!(mac.wheel(lines(-1.0), TouchPhase::Moved, now, 1.0), 1);
|
||||
assert_eq!(mac.wheel(lines(-1.0), TouchPhase::Moved, now, 1.0), 1, "macOS scrolls a line a notch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smooth_lines_turn_once_a_gesture() {
|
||||
fn a_stream_of_lines_turns_once() {
|
||||
let (mut turning, now) = (Turning::default(), std::time::Instant::now());
|
||||
// An X11 or Windows touchpad: fractions, no phases.
|
||||
let turned: i32 = (0..30).map(|_| turning.wheel(lines(-0.4), TouchPhase::Moved, now, NOTCH)).sum();
|
||||
// X11 smooth scrolling: whole steps, then fractions, no pause.
|
||||
let stream = [-3.0, -3.0, -0.4, -0.4, -0.4, -0.4, -0.4, -0.4, -0.4, -0.4];
|
||||
let turned: i32 = stream.iter().map(|&y| turning.wheel(lines(y), TouchPhase::Moved, now, NOTCH)).sum();
|
||||
assert_eq!(turned, 1, "one swipe turns once");
|
||||
assert_eq!(turning.wheel(lines(-3.0), TouchPhase::Moved, now, NOTCH), 0, "a whole step mid-swipe");
|
||||
let later = now + PAUSE;
|
||||
assert_eq!(turning.wheel(lines(-3.0), TouchPhase::Moved, later, NOTCH), 1, "a pause ends the swipe");
|
||||
let mut turning = Turning::default();
|
||||
let turned: i32 = (0..30).map(|_| turning.wheel(lines(-0.4), TouchPhase::Moved, now, NOTCH)).sum();
|
||||
assert_eq!(turned, 1, "fractions add up once");
|
||||
assert_eq!(turning.wheel(lines(-3.0), TouchPhase::Moved, now + PAUSE, NOTCH), 1, "a pause ends it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -67,7 +67,7 @@ impl LokView {
|
||||
|
||||
/// Ctrl (macOS: Cmd) wheel zooms; else it turns or scrolls.
|
||||
pub(super) fn on_wheel(&mut self, event: &ScrollWheelEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.wheel_zoom(event, cx) && !self.turn_wheel(event, cx) {
|
||||
if !self.wheel_zoom(event, cx) && !self.turn_wheel(event) {
|
||||
self.on_scroll(event, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ fn answer(lok: &Lok, tag: u8, id: u64, payload: &[u8]) {
|
||||
}
|
||||
}),
|
||||
tag::POST_KEY => input::key(unpack),
|
||||
tag::TURN_KEY => input::turn_key(unpack),
|
||||
tag::TURN => input::turn(unpack),
|
||||
tag::POST_MOUSE => input::mouse(unpack),
|
||||
tag::GRAPHIC => input::graphic(unpack),
|
||||
tag::POST_TEXT => input::text(unpack),
|
||||
|
||||
@@ -99,18 +99,26 @@ pub(super) fn key(unpack: &mut Unpack) -> io::Result<Vec<u8>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Up or Down after earlier input: 1 if it turns.
|
||||
pub(super) fn turn_key(unpack: &mut Unpack) -> io::Result<Vec<u8>> {
|
||||
let (id, char_code, key_code) = (unpack.u64()?, unpack.i64()?, unpack.i64()?);
|
||||
/// Turns `step` parts in order, or posts the arrow (-1).
|
||||
pub(super) fn turn(unpack: &mut Unpack) -> io::Result<Vec<u8>> {
|
||||
let (id, step, char_code, key_code) = (unpack.u64()?, unpack.i64()?, unpack.i64()?, unpack.i64()?);
|
||||
// Earlier input's callbacks already landed here.
|
||||
if !crate::events::picked(id) {
|
||||
return Ok(Pack::new().u64(1).bytes());
|
||||
if key_code != 0 && crate::events::picked(id) {
|
||||
with_doc(id, |doc| {
|
||||
doc.post_key(0, char_code as i32, key_code as i32);
|
||||
doc.post_key(1, char_code as i32, key_code as i32);
|
||||
})?;
|
||||
return Ok(Pack::new().i64(-1).bytes());
|
||||
}
|
||||
with_doc(id, |doc| {
|
||||
doc.post_key(0, char_code as i32, key_code as i32);
|
||||
doc.post_key(1, char_code as i32, key_code as i32);
|
||||
let shown = with_doc(id, |doc| {
|
||||
let (part, last) = (i64::from(doc.part()), i64::from(doc.parts()) - 1);
|
||||
let next = (part + step).clamp(0, last.max(0));
|
||||
if next != part {
|
||||
doc.set_part(next as i32);
|
||||
}
|
||||
next
|
||||
})?;
|
||||
Ok(Pack::new().u64(0).bytes())
|
||||
Ok(Pack::new().i64(shown).bytes())
|
||||
}
|
||||
|
||||
/// One mouse event: kind, x, y, count, buttons, modifiers.
|
||||
|
||||
@@ -51,7 +51,7 @@ pub fn expect(unpack: &mut Unpack) -> io::Result<Vec<u8>> {
|
||||
pub fn bump(request: u8, payload: &[u8]) {
|
||||
let changes = matches!(
|
||||
request,
|
||||
tag::POST_KEY | tag::TURN_KEY | tag::POST_MOUSE | tag::GRAPHIC | tag::POST_TEXT | tag::POST_UNO | tag::PASTE | tag::CUT | tag::COMPLETE_FUNCTION
|
||||
tag::POST_KEY | tag::TURN | tag::POST_MOUSE | tag::GRAPHIC | tag::POST_TEXT | tag::POST_UNO | tag::PASTE | tag::CUT | tag::COMPLETE_FUNCTION
|
||||
);
|
||||
let mut unpack = Unpack::new(payload);
|
||||
let Ok(id) = unpack.u64() else {
|
||||
|
||||
+1
-1
@@ -271,5 +271,5 @@
|
||||
- [x] R6.11 Windows 升级后旧文件仍在(R6.10 本机重装查出):安装程序只覆盖不删除,旧版 ElyMark 的 `git` 与 CEF 文件留在安装目录;安装程序记下所装的文件,升级与卸载只移去这些;文件被占用即什么也不改,失败时放回旧版。第 3 轮后(最后一轮,修完不再审查):备份按清单次序编号,近 260 字的路径照样升级与卸载;卸载先查清单本身,删不掉即放回全部
|
||||
- [x] R6.10 重发 0.1.5(用户定:仍为 0.1.5):删去 v0.1.5 的全部文件,标签移到修复的提交,三端全部包重建并上传,本机重装(标签在 53630ce;推标签即起发布流程,其 Windows 与 Linux 任务建出并上传,与本机同时起的 Windows 构建撞上,本机那份未上传;macOS 任务未上传,dmg 由 Mac mini 本机建、公证、钉票;44 个文件,同名的源码包只留 Linux 的一份,见 P3-16;下载回的 ElyMark 安装程序与清单的散列与构建相同;本机六个应用 0.1.5 经新安装程序升级,ElyMark 目录无 `git`,冒烟启动均正常退出、日志无警告)
|
||||
- [x] R6.12 macOS 打开一个文档,程序坞里 ElyDoc、ElyTable、ElyShow 各出现两个图标(用户 2026-10-15 报):LOK worker 在应用包内启动 AppKit,系统按包的 Info.plist 把它也登记为常规应用(`lsappinfo` 两个 Foreground);worker 在内核启动 AppKit 前把本进程主包信息的 `LSUIElement` 设为真,登记即为辅助应用,钩子后核对策略(第 1 轮:原先登记后才改,15 次有 11 次先为 Foreground);`tests/scripts/dock-macos.sh` 在 Mac mini 上 30 次启动全为 UIElement,旧 worker 即失败;第 2 轮:包外的 worker(`cargo run`、单测)策略为禁止,原核对即退出,改设辅助,测试另启包外的开发构建(40 次全过);第 3 轮通过
|
||||
- [ ] R6.13 ElyShow 编辑区的滚轮与上下键不换页(用户报,三端;放映时正常):内核在未选中对象时把上下键当作滚动窗口,Ely 的画布整页适合、无处可滚;整页可见时滚轮换页(每格一页,触控板一次手势一页),未选中对象、未编辑文字时上下键换页,同缩略图条的路径,到首末页即停;第 1 轮:Windows 一格三行原换三页,X11 触控板的小数行一次滑动换多页,按住手指停顿后再滑又换一页,Tab 后紧接的 Down 按旧状态换页;改为按平台每格行数计格、小数行与像素按手势计、有起止的手势只到结束才算完、上下键由 worker 按输入次序判定
|
||||
- [ ] R6.13 ElyShow 编辑区的滚轮与上下键不换页(用户报,三端;放映时正常):内核在未选中对象时把上下键当作滚动窗口,Ely 的画布整页适合、无处可滚;整页可见时滚轮换页(每格一页,触控板一次手势一页),未选中对象、未编辑文字时上下键换页,同缩略图条的路径,到首末页即停;第 1 轮:Windows 一格三行原换三页,X11 触控板的小数行一次滑动换多页,按住手指停顿后再滑又换一页,Tab 后紧接的 Down 按旧状态换页;改为按平台每格行数计格、小数行与像素按手势计、有起止的手势只到结束才算完、上下键由 worker 按输入次序判定;第 2 轮:换页在 worker 回复后才排进队列,排在其后的键先作用于旧页,以整格开头的 X11 平滑滚动仍按格多次换页;改为换页(键与滚轮)都由 worker 在输入次序中当场切页,行与像素一律按手势计(无起止的停 200 毫秒为界)
|
||||
- [ ] R6.14 重发 0.1.5(用户定:仍为 0.1.5,全部重建并重传):删去 v0.1.5 的全部文件,标签移到 R6.12、R6.13 的提交,三端全部包重建并上传,本机重装
|
||||
@@ -0,0 +1,5 @@
|
||||
FAIL
|
||||
|
||||
- crates/ely-lok/src/view/lane.rs:116 — An arrow's actual page change is queued only after its worker reply returns to the UI, behind subsequent inputs already in the lane. On slide 1, queue Down–Tab–Down before the worker catches up: Tab selects slide 1's title and the second Down moves it, then the deferred turn shows slide 2. The rebuilt-worker integration probe changed the departing title's y coordinate from 628 to 728 (1/100 mm); Down–Tab also loses the intended selection on slide 2, while the settled control passes (`.work/R6.13-r2/probe-final.log`). Complete the page change in input order before processing following keys or mouse events, and cover navigation immediately followed by selection/editing.
|
||||
|
||||
- crates/ely-lok/src/view/turn.rs:67 — Round 1's smooth-scroll defect remains when a gesture begins with whole increments: these events are classified as discrete wheel notches until a fractional delta appears. The pinned X11 adapter emits `Lines` for smooth motion and permits whole increments. Replaying `-3, -3`, then eight `-0.4` line deltas without a pause produces three turns instead of one (`.work/R6.13-r2/scroll-probe.log`); the equivalent stream normalized to this Windows machine's seven-line setting moves ElyShow from slide 1 to slide 4 (`.work/R6.13-r2/probe-final.log`). Preserve the smooth-versus-discrete distinction in the input adapter instead of inferring it from fractional values, and test gestures beginning with whole increments.
|
||||
Reference in new issue
Block a user