R7.2: the settings window draws no app menus or update banner
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b12858c7f0
commit
03e8e9bcb3
9 files changed
+88
-6
No files matched your search
@@ -309,3 +309,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
|
||||
- 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 的触控板)同按手势:一次手势换一页(满一格或 50 像素),惯性不算;有起止的手势(macOS)只到结束才算完,没有起止的停 200 毫秒即为下一次。X11 的滚轮与触控板同为行、gpui 不分,以整格开头的触控板滑动在出现小数之前会按格换页(已知限制)。未选中对象、未编辑文字时上下键换页。换页一律经有序输入队列交 worker(`TURN`):worker 按之前各输入回报后的状态判定,有选区、选中对象或插入点即把方向键照常交内核,否则当场切页(到首末页即停,同缩略图条的 `setPart`),视图在落地时换上 worker 所示的页,其后排队的键与点按都落在新页上(Tab 后紧接的 Down 移动所选对象;Down、Tab、Down 是换页后在新页上选中并移动);ElyShow 后台读回的当前页只在视图已无待落地输入时采用(R6.13)。
|
||||
- 2026-10-16(用户定):合集安装包(R7.1)。macOS 一个公证的 `ElyOffice-<版本>-<架构>.dmg`,装首批六个已签名的 .app 与“应用程序”替身,用户拖入所要的(`release.sh` 的 `dmg` 与单个应用共用);无 Developer ID Installer 证书,不出 .pkg。Windows 一个 `ElyOffice-<版本>-windows-x86_64-setup.exe`(`packaging/windows/bundle.nsi`),组件页勾选应用,所选的各自的安装程序以 `/S` 运行,所以装法、位置、升级、卸载项与单装完全相同;某个退出码非零即停下并写明其名与退出码(2 为文件被占用或位置被占),之前装上的照旧。Linux 不出合集。单个应用的包、更新清单照旧。
|
||||
- 2026-10-16(Gitea #6):设置窗口多出一行应用菜单(R7.2)。设置窗口沿用主窗口的外壳 `Shell`,Windows 与 Linux 上随之画出菜单栏(macOS 菜单在系统菜单栏,不受影响)与更新横幅。菜单栏与更新横幅只属主窗口:`Shell::secondary` 去掉两者,设置窗口用它;`settings/tests.rs` 量内容起点距标题栏底:只装菜单时主窗口大于零(macOS 为零),再有待下载的新版时更大,设置窗口为零。
|
||||
@@ -53,3 +53,44 @@ fn each_app_shows_its_own_sections(cx: &mut TestAppContext) {
|
||||
assert_eq!(shown, keys, "{app:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A main window's frame.
|
||||
struct Main(gpui::FocusHandle);
|
||||
|
||||
impl gpui::Render for Main {
|
||||
fn render(&mut self, _: &mut gpui::Window, _: &mut gpui::Context<Self>) -> impl gpui::IntoElement {
|
||||
crate::shell::Shell::new("main", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// How far below the title bar the content starts.
|
||||
fn below_title(cx: &mut TestAppContext, window: gpui::AnyWindowHandle) -> gpui::Pixels {
|
||||
let mut shown = gpui::VisualTestContext::from_window(window, cx);
|
||||
for _ in 0..3 {
|
||||
shown.update(|window, _| window.refresh());
|
||||
shown.run_until_parked();
|
||||
}
|
||||
let title = shown.debug_bounds("shell-title").expect("a title bar");
|
||||
shown.debug_bounds("shell-body").expect("a body").origin.y - title.bottom()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn only_the_main_window_carries_menus_and_updates(cx: &mut TestAppContext) {
|
||||
installed(cx);
|
||||
cx.update(|cx| {
|
||||
let menus = crate::commands::menus(AppId::Mark, &crate::commands::OPENING, false, Vec::new(), cx);
|
||||
cx.set_menus(menus);
|
||||
});
|
||||
let main = cx.add_window(|_, cx| Main(cx.focus_handle()));
|
||||
let menus = below_title(cx, main.into());
|
||||
// macOS keeps menus in the system bar.
|
||||
match cfg!(target_os = "macos") {
|
||||
true => assert_eq!(menus, gpui::px(0.0), "no menu row on macOS"),
|
||||
false => assert!(menus > gpui::px(0.0), "the main window shows the menus"),
|
||||
}
|
||||
cx.update(crate::updates::offer_for_tests);
|
||||
let main = cx.add_window(|_, cx| Main(cx.focus_handle()));
|
||||
assert!(below_title(cx, main.into()) > menus, "the main window shows the update");
|
||||
let settings = cx.add_window(|window, cx| SettingsView::new(AppId::Mark, window, cx));
|
||||
assert_eq!(below_title(cx, settings.into()), gpui::px(0.0), "settings repeats neither");
|
||||
}
|
||||
@@ -353,6 +353,7 @@ impl Render for SettingsView {
|
||||
})
|
||||
.collect();
|
||||
Shell::new(text(cx, "settings.title", &[]), &self.focus)
|
||||
.secondary()
|
||||
.child(div().size_full().p_6().flex().flex_col().gap_4().children(unreadable).children(app_error).children(unsaved).child(layout))
|
||||
.children(dialog)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ use ely_gpui_component::{
|
||||
theme::{ActiveTheme, TextSize},
|
||||
};
|
||||
use gpui::{
|
||||
AnyElement, AnyView, App, ElementId, FocusHandle, IntoElement, ParentElement, RenderOnce,
|
||||
SharedString, Styled, Window, div,
|
||||
AnyElement, AnyView, App, ElementId, FocusHandle, InteractiveElement, IntoElement,
|
||||
ParentElement, RenderOnce, SharedString, Styled, Window, div,
|
||||
};
|
||||
|
||||
mod page;
|
||||
@@ -32,6 +32,7 @@ pub struct Shell {
|
||||
right: Option<AnyView>,
|
||||
status: Option<StatusBar>,
|
||||
page: Option<ElementId>,
|
||||
main: bool,
|
||||
body: Vec<AnyElement>,
|
||||
}
|
||||
|
||||
@@ -45,10 +46,17 @@ impl Shell {
|
||||
right: None,
|
||||
status: None,
|
||||
page: None,
|
||||
main: true,
|
||||
body: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Beside the main window: no menus, no update banner.
|
||||
pub fn secondary(mut self) -> Self {
|
||||
self.main = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tabs(mut self, tabs: EditorTabs) -> Self {
|
||||
self.tabs = Some(tabs);
|
||||
self
|
||||
@@ -98,6 +106,7 @@ impl RenderOnce for Shell {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let length = motion::duration(motion::BASE, cx);
|
||||
let content = div()
|
||||
.debug_selector(|| "shell-body".into())
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.h_full()
|
||||
@@ -123,9 +132,13 @@ impl RenderOnce for Shell {
|
||||
let header = div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(TitleBar::new("titlebar").title(self.title))
|
||||
.children(crate::updates::updater(cx))
|
||||
.children(menu_bar(cx))
|
||||
.child(
|
||||
div()
|
||||
.debug_selector(|| "shell-title".into())
|
||||
.child(TitleBar::new("titlebar").title(self.title)),
|
||||
)
|
||||
.children(self.main.then(|| crate::updates::updater(cx)).flatten())
|
||||
.children(self.main.then(|| menu_bar(cx)).flatten())
|
||||
.children(self.tabs);
|
||||
// Panels give way so the content keeps its room.
|
||||
let (left, right) = (panel::fitted(Side::Left, window, cx), panel::fitted(Side::Right, window, cx));
|
||||
|
||||
@@ -359,6 +359,25 @@ impl Render for Updates {
|
||||
}
|
||||
}
|
||||
|
||||
/// An offered release, for window tests.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn offer_for_tests(cx: &mut App) {
|
||||
let release = Release {
|
||||
version: semver::Version::new(9, 0, 0),
|
||||
url: "https://example.test/9.dmg".into(),
|
||||
sha256: String::new(),
|
||||
};
|
||||
let updates = cx.new(|_| Updates {
|
||||
app: AppId::Mark,
|
||||
bundle: PathBuf::new(),
|
||||
offer: Some((release, Phase::Offered)),
|
||||
dismissed: false,
|
||||
pending: None,
|
||||
note: None,
|
||||
});
|
||||
cx.set_global(Updater(updates));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
+1
-1
@@ -277,7 +277,7 @@
|
||||
## R7 0.1.6(用户 2026-10-16 定:Gitea #3 至 #6 与 ElyMark 正文块;合集安装包;修完发 0.1.6,逐条答复,功能建议另开 [FEATURE REQUEST])
|
||||
|
||||
- [x] R7.1 合集安装包(§4,用户定):macOS 一个公证的 dmg 装六个 .app 与“应用程序”替身,用户拖入所要的;Windows 一个安装程序,组件页勾选六个应用,每个与单独安装程序装得一样;Linux 不做合集;单个应用的包照旧(`release.sh` 的 `dmg` 单个与合集共用,Mac mini 上以两个假应用核对卷内内容;`bundle.nsi` 逐个以 `/S` 运行所选的安装程序,某个退出码非零即停下并写明;`windows-bundle.sh` 三个假应用:都装上,第二个被拒时退出码 2、第三个不运行;真包随 R7.7 建出 — 第 1 轮:被拒的是最后一个,去掉 `Abort` 测试仍过;加第三个应用后去掉即失败;第 2 轮 PASS)
|
||||
- [ ] R7.2 设置窗口多出一行应用菜单(#6,§5):设置窗口沿用主窗口的外壳 `Shell`,Windows 与 Linux 上随之画出菜单栏与更新横幅;设置窗口不画这两者
|
||||
- [x] R7.2 设置窗口多出一行应用菜单(#6,§5):设置窗口沿用主窗口的外壳 `Shell`,Windows 与 Linux 上随之画出菜单栏与更新横幅;设置窗口不画这两者(`Shell::secondary`;测试量内容起点距标题栏底,五种变体各自失败 — 第 1 轮:只查标记容器;第 2 轮:主窗口两者合计;第 3 轮 PASS)
|
||||
- [ ] R7.3 装了宋体仍报缺少(#4,§7、§8):内核按 `宋体` 找到 SimSun,缺字检查却比较原名与所用字体的英文名,报为缺失;查明 Linux 上的版面是否真用 SimSun,一页变两页的根因
|
||||
- [ ] R7.4 打开 PDF 总说已损坏(#5,§11):画图块时 MuPDF 的任何警告都算损坏,未嵌入的中日韩字体(“non-embedded font using identity encoding”)即触发;损坏只认结构修复、读不出的对象与流、画不出的图片与内容
|
||||
- [ ] R7.5 ElyMark 打开的正文块变成一块深色框(§14,用户定):按下的段落保持原样,不加底色与内边距,不挪位置;代码块仍有自己的底板
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
crates/ely-ui/src/settings/tests.rs:72 — 回归测试只断言 `shell-menus` 标记容器是否存在,没有检查实际菜单,也没有安装带可见更新状态的 updater,因而不能验证 R7.2 要求隐藏的两种控件。具体复现:在隔离副本中保留该容器及其 `self.main` 条件,把 `.children(crate::updates::updater(cx))` 和 `.children(menu_bar(cx))` 移回无条件绘制的 header,设置窗口便恢复重复菜单,并会在有更新时显示横幅,但新增测试及全部 41 个 ely-ui 单元测试仍通过(原代码的 `cargo test -p ely-ui --lib` 也为 41 项通过;证据在 `.work/R7.2-r1-review/mutation.diff.log` 与 `mutation-test.log`)。修复方向:断言实际菜单内容,并注入可见更新状态,分别核对主窗口保留、设置窗口隐藏;恢复任一控件时对应测试必须失败。
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
crates/ely-ui/src/settings/tests.rs:85 — 主窗口断言只要求标题下方的总间距大于零,菜单和更新横幅任留一个即可通过,仍未落实第 1 轮要求的“主窗口分别保留两者”。设置窗口恢复任一控件现在确实会使测试失败,但在 Windows 的隔离副本中,分别删除 `shell/mod.rs:140` 的 updater 子节点或 `:141` 的菜单子节点,主窗口分别失去更新下载入口或整行应用菜单,每种变体的全部 41 项测试仍通过。原树的 `cargo build -p ely-ui` 与 `cargo test -p ely-ui --lib` 均通过;变体证据在 `.work/R7.2-r2-review/main-update-removed-test.log`、`main-menu-removed-test.log` 及对应的 `.diff.log`,副本与构建目录已删除。修复方向:分别验证主窗口实际渲染菜单和更新横幅,例如分开设置菜单与更新状态后各自断言;删除任一控件必须使对应测试失败,不能以两者合计的间距代替。
|
||||
@@ -0,0 +1 @@
|
||||
PASS
|
||||
Reference in new issue
Block a user