diff --git a/Cargo.lock b/Cargo.lock index 6f29c21..d797a43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3120,7 +3120,7 @@ dependencies = [ [[package]] name = "gpui-component" version = "0.5.2" -source = "git+https://github.com/Kuddev/gpui-component?rev=4eb3917678930561c7ea36742f432313cd30b275#4eb3917678930561c7ea36742f432313cd30b275" +source = "git+https://github.com/Kuddev/gpui-component?rev=c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7#c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7" dependencies = [ "aho-corasick", "anyhow", @@ -3203,7 +3203,7 @@ dependencies = [ [[package]] name = "gpui-component-assets" version = "0.5.1" -source = "git+https://github.com/Kuddev/gpui-component?rev=4eb3917678930561c7ea36742f432313cd30b275#4eb3917678930561c7ea36742f432313cd30b275" +source = "git+https://github.com/Kuddev/gpui-component?rev=c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7#c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7" dependencies = [ "anyhow", "gpui", @@ -3217,7 +3217,7 @@ dependencies = [ [[package]] name = "gpui-component-macros" version = "0.5.1" -source = "git+https://github.com/Kuddev/gpui-component?rev=4eb3917678930561c7ea36742f432313cd30b275#4eb3917678930561c7ea36742f432313cd30b275" +source = "git+https://github.com/Kuddev/gpui-component?rev=c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7#c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5b65a8c..ba4b2fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,9 +89,9 @@ winit = { path = "third_party/winit-0.30.13" } # 上游来源:zed-industries/zed v1.16.1 # @ eb8e1c8b5502b7007465fbbc465f4a736fa39210 # Kuddev/zed nebula-v1.16.1-owned-base @ 同一 SHA(纯上游镜像) -# Kuddev/gpui-component nebula-v1.16.1-owned-base -# @ 4eb3917678930561c7ea36742f432313cd30b275 -# (4ee9f274 基线 + Zed source 统一为 Kuddev/zed,不含 Nebula 产品补丁) +# Kuddev/gpui-component nebula-v1.16.1-math +# @ c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7 +# (4eb39176 owned base + 数学薄桥、GFM TextView 语义和紧凑阴影 helper) # # 所有 Zed 依赖统一使用 Kuddev URL:Cargo 的 source identity 包含 URL,应用 # 与 component 内部若混用官方/自有 URL,即使 SHA 相同也会解析成两套 gpui @@ -120,8 +120,9 @@ winit = { path = "third_party/winit-0.30.13" } # 轴 stretch 拿宽度,绕开百分比解析。settings_pane.rs 的设置正文即按此写。 # # 产品补丁清单(从固定基线逐条迁移并验收,不能一次性混入): -# 1. popover 单层紧凑阴影(a59f704c,见上)。 -# 2. TextView 数学公式钩子(本地 nebula-math 分支,待 push 后恢复 rev pin): +# 1. popover 单层紧凑阴影 helper(c20dbef8);不改新版组件的全局 +# popover_style,只供 Nebula 原本显式设置阴影的 surface 调用。 +# 2. TextView 数学公式钩子(nebula-v1.16.1-math 分支,9a2f663c): # markdown 解析启用 math 构造;新增 text/math.rs 的 set_math_renderer # 全局注册点;InlineNode/Node 增加数学节点并在渲染时调钩子,未注册时 # 回退现状(inline=code 文本、block=CodeBlock)。TeX 引擎/栅格化/绘制 @@ -150,7 +151,7 @@ winit = { path = "third_party/winit-0.30.13" } # update_scroll_offset,边界 clamp 现成)。补丁 4 修的是键盘移动那条路, # 这条修的是鼠标拖选,两者都要。上游 PR 候选,合并即撤。 # -# 当前只落固定基线;数学、GFM、Input 与视觉补丁将在独立提交中逐项恢复。 +# 当前精确引用已包含补丁 1-3;Input 补丁 4-5 仍保持独立,尚未带入本次升级。 gpui = { git = "https://github.com/Kuddev/zed", rev = "eb8e1c8b5502b7007465fbbc465f4a736fa39210", version = "=0.2.2" } -gpui-component = { git = "https://github.com/Kuddev/gpui-component", rev = "4eb3917678930561c7ea36742f432313cd30b275", version = "=0.5.2" } -gpui-component-assets = { git = "https://github.com/Kuddev/gpui-component", rev = "4eb3917678930561c7ea36742f432313cd30b275", version = "=0.5.1" } +gpui-component = { git = "https://github.com/Kuddev/gpui-component", rev = "c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7", version = "=0.5.2" } +gpui-component-assets = { git = "https://github.com/Kuddev/gpui-component", rev = "c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7", version = "=0.5.1" } diff --git a/nebula_app/src/gpui_shell/background_color.rs b/nebula_app/src/gpui_shell/background_color.rs index 6d4634a..b51f7d9 100644 --- a/nebula_app/src/gpui_shell/background_color.rs +++ b/nebula_app/src/gpui_shell/background_color.rs @@ -301,7 +301,7 @@ impl SettingsPane { anchor.child( deferred( anchored() - .anchor(gpui::Corner::TopRight) + .anchor(gpui::Anchor::TopRight) .position(trigger_bounds.bottom_right()) .offset(gpui::point(px(0.0), px(6.0))) .snap_to_window_with_margin(px(8.0)) @@ -408,6 +408,7 @@ impl SettingsPane { offset: gpui::point(px(0.0), px(0.0)), blur_radius: px(0.0), spread_radius: px(2.0), + inset: false, }]) }) .bg(super::rgb_hsla(color.r, color.g, color.b)) diff --git a/nebula_app/src/gpui_shell/doc_tabs.rs b/nebula_app/src/gpui_shell/doc_tabs.rs index 4bbf85a..f0a7929 100644 --- a/nebula_app/src/gpui_shell/doc_tabs.rs +++ b/nebula_app/src/gpui_shell/doc_tabs.rs @@ -19,7 +19,6 @@ use gpui::{ MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Render, RenderImage, ScrollWheelEvent, SharedString, Styled as _, Window, div, px, }; -use gpui_component::PixelsExt as _; use image::Frame; use crate::display::image_viewer::ImageView; @@ -176,8 +175,14 @@ impl Render for ImageTabView { gpui::size(px(target.2.max(1.0)), px(target.3.max(1.0))), ); window.with_content_mask(Some(ContentMask { bounds }), |window| { - let _ = - window.paint_image(target_bounds, Corners::all(px(0.0)), image, 0, false); + let _ = window.paint_image( + target_bounds, + target_bounds, + Corners::all(px(0.0)), + image, + 0, + false, + ); }); }, ) @@ -303,7 +308,7 @@ impl Render for DocTabView { }) .child( div().flex_1().min_h_0().child( - TextView::markdown(doc_id, self.content.clone(), window, cx) + TextView::markdown(doc_id, self.content.clone()) .selectable(true) .scrollable(true) // image_base:相对图片路径(README 的 logo/截图)按 diff --git a/nebula_app/src/gpui_shell/http.rs b/nebula_app/src/gpui_shell/http.rs index c4c36ee..7808234 100644 --- a/nebula_app/src/gpui_shell/http.rs +++ b/nebula_app/src/gpui_shell/http.rs @@ -132,10 +132,6 @@ fn placeholder_png() -> Vec { } impl HttpClient for UreqClient { - fn type_name(&self) -> &'static str { - std::any::type_name::() - } - fn user_agent(&self) -> Option<&gpui::http_client::http::HeaderValue> { None } diff --git a/nebula_app/src/gpui_shell/math_view.rs b/nebula_app/src/gpui_shell/math_view.rs index efaf8b9..393f336 100644 --- a/nebula_app/src/gpui_shell/math_view.rs +++ b/nebula_app/src/gpui_shell/math_view.rs @@ -455,7 +455,8 @@ impl Element for MathView { match &*slot.borrow() { FitSlot::Pending => {}, FitSlot::Text(line) => { - let _ = line.paint(bounds.origin, line_height, window, cx); + let _ = + line.paint(bounds.origin, line_height, gpui::TextAlign::Left, None, window, cx); }, FitSlot::Math { layout, pixel_size, baseline_from_bottom } => { let bounds_width = f32::from(bounds.size.width); @@ -524,7 +525,14 @@ impl MathView { ); let origin = point(bounds.left() + px(op.x), baseline + px(op.baseline_y) - line.ascent); - let _ = line.paint(origin, line.ascent + line.descent, window, cx); + let _ = line.paint( + origin, + line.ascent + line.descent, + gpui::TextAlign::Left, + None, + window, + cx, + ); } } } @@ -561,6 +569,7 @@ pub(crate) fn paint_formula_image( let image_size = size(px(geometry.width as f32 / raster_scale), px(geometry.height as f32 / raster_scale)); let _ = window.paint_image( + Bounds::new(origin, image_size), Bounds::new(origin, image_size), Corners::default(), render_image, diff --git a/nebula_app/src/gpui_shell/mod.rs b/nebula_app/src/gpui_shell/mod.rs index c504f68..5f6be9b 100644 --- a/nebula_app/src/gpui_shell/mod.rs +++ b/nebula_app/src/gpui_shell/mod.rs @@ -32,8 +32,8 @@ pub mod widgets; mod windows_backdrop; pub mod workspace; -use gpui::{App, AppContext as _}; use assets::NebulaAssets; +use gpui::{App, AppContext as _}; #[cfg(windows)] use std::borrow::Cow; @@ -108,7 +108,7 @@ pub fn run_shell(initial_cwd: Option) { // 该样式,事后再清已经无法给 system backdrop 补回挂载表面。 #[cfg(windows)] let _window_creation_hook = windows_backdrop::install_main_window_creation_hook(); - gpui::Application::new().with_assets(NebulaAssets).run(move |cx| { + gpui_platform::application().with_assets(NebulaAssets).run(move |cx| { // GPUI is the only event loop in `--gpui` mode, so it owns the same // per-process hook pipe before the first TerminalView spawns. let ai_events = crate::ai_hook::spawn_gpui_server(); diff --git a/nebula_app/src/gpui_shell/prelude.rs b/nebula_app/src/gpui_shell/prelude.rs index b7c13c9..7ccc1d3 100644 --- a/nebula_app/src/gpui_shell/prelude.rs +++ b/nebula_app/src/gpui_shell/prelude.rs @@ -12,7 +12,7 @@ pub use gpui_component::{ button::{Button, ButtonVariants as _}, checkbox::Checkbox, color_picker::{ColorPicker, ColorPickerEvent, ColorPickerState}, - dialog::{Dialog, DialogButtonProps}, + dialog::{Dialog, DialogAction, DialogButtonProps, DialogClose, DialogFooter}, dock::{ DockArea, DockAreaState, DockItem, Panel, PanelControl, PanelEvent, PanelState, TabPanel, }, @@ -40,16 +40,9 @@ pub use gpui_component::WindowExt as _; pub use gpui_component::{ActiveTheme, Colorize}; use gpui::{Window, px}; -use gpui_component::PixelsExt as _; -/// 旧壳 `draw_confirm_modal`:`(win_w - box_w)/2`、`(win_h - box_h)/2`。 -/// 组件库 Dialog 水平已经居中,垂直默认贴在 `height/10`。不改 gpui-component, -/// 只把 `margin_top` 调到窗口中线。关闭/粘贴确认约 200px 高;入场动画结束 -/// 时还会把 top 再加 30px,所以这里先减去这段位移,落点才是正中。 -pub fn center_confirm_dialog(dialog: Dialog, window: &Window) -> Dialog { - let height = window.viewport_size().height.as_f32(); - const DIALOG_H: f32 = 200.0; - const SLIDE: f32 = 30.0; - let top = ((height - DIALOG_H) / 2.0 - SLIDE).max(16.0); - dialog.margin_top(px(top)) +/// 保留旧壳确认框的 480px 内容宽度;位置和交互采用新版 Dialog。 +pub fn center_confirm_dialog(dialog: Dialog, _window: &Window) -> Dialog { + const DIALOG_W: f32 = 480.0; + dialog.width(px(DIALOG_W)) } diff --git a/nebula_app/src/gpui_shell/settings_pane.rs b/nebula_app/src/gpui_shell/settings_pane.rs index 6b438a8..2a73e9e 100644 --- a/nebula_app/src/gpui_shell/settings_pane.rs +++ b/nebula_app/src/gpui_shell/settings_pane.rs @@ -743,8 +743,8 @@ impl SettingsPane { SliderState::new().min(0.00).max(1.00).step(0.05).default_value(runtime.opacity) }); subscriptions.push(cx.subscribe(&opacity_slider, |this, _, event: &SliderEvent, cx| { - match event { - SliderEvent::Change(value) => this.set_opacity(value.start(), cx), + if let SliderEvent::Change(value) = event { + this.set_opacity(value.start(), cx); } })); let wallpaper_opacity_slider = cx.new(|_| { @@ -756,8 +756,10 @@ impl SettingsPane { }); subscriptions.push(cx.subscribe( &wallpaper_opacity_slider, - |this, _, event: &SliderEvent, cx| match event { - SliderEvent::Change(value) => this.set_wallpaper_opacity(value.start(), cx), + |this, _, event: &SliderEvent, cx| { + if let SliderEvent::Change(value) = event { + this.set_wallpaper_opacity(value.start(), cx); + } }, )); let (proxy_protocol, proxy_address) = @@ -1051,10 +1053,7 @@ impl SettingsPane { let pane = pane.clone(); center_confirm_dialog(dialog, window) .title("让背景图覆盖窗口控件区域?") - .confirm() - .button_props( - DialogButtonProps::default().ok_text("开启").cancel_text("取消"), - ) + .button_props(DialogButtonProps::default().show_cancel(true).ok_text("开启").cancel_text("取消")) .child(SharedString::from( "背景图会延伸到标题栏、窗口按钮、Tab 与 SSH 侧栏下方,低对比度图片可能影响操作可见性;界面仍会保留最低不透明度保护。", )) @@ -2156,7 +2155,7 @@ impl SettingsPane { )) .child(Self::about_action_row( "about-github", - IconName::GitHub, + IconName::Github, "GitHub", "源代码", REPOSITORY_URL.to_owned(), diff --git a/nebula_app/src/gpui_shell/settings_pane/font_picker.rs b/nebula_app/src/gpui_shell/settings_pane/font_picker.rs index b589d46..721d7fc 100644 --- a/nebula_app/src/gpui_shell/settings_pane/font_picker.rs +++ b/nebula_app/src/gpui_shell/settings_pane/font_picker.rs @@ -46,7 +46,7 @@ impl SettingsPane { self.font_picker_open = false; self.font_query_input.update(cx, |input, cx| input.set_value("", window, cx)); if restore_focus { - window.focus(&self.focus_handle); + window.focus(&self.focus_handle, cx); } cx.notify(); } @@ -534,7 +534,7 @@ impl SettingsPane { anchor.child( deferred( anchored() - .anchor(gpui::Corner::TopRight) + .anchor(gpui::Anchor::TopRight) .position(trigger_bounds.bottom_right()) .offset(gpui::point(px(0.0), px(6.0))) .snap_to_window_with_margin(px(8.0)) diff --git a/nebula_app/src/gpui_shell/settings_pane/keymap.rs b/nebula_app/src/gpui_shell/settings_pane/keymap.rs index f9479b0..a76f5c4 100644 --- a/nebula_app/src/gpui_shell/settings_pane/keymap.rs +++ b/nebula_app/src/gpui_shell/settings_pane/keymap.rs @@ -97,7 +97,11 @@ impl SettingsPane { /// interceptor 与修饰键预览共用的录制状态机;按键是否应被独占由调用 /// 方在进入这里前裁定,避免数据转换层意外吞掉普通设置输入。 - pub(super) fn handle_keymap_capture(&mut self, keystroke: &gpui::Keystroke, cx: &mut Context) { + pub(super) fn handle_keymap_capture( + &mut self, + keystroke: &gpui::Keystroke, + cx: &mut Context, + ) { let Some(row) = self.keymap_capture else { return }; match crate::display::keymap::capture_gpui(keystroke) { crate::display::keymap::CaptureOutcome::Cancel => { @@ -269,7 +273,7 @@ impl SettingsPane { this.keymap_capture_preview.clear(); // 焦点收到分区根:键盘事件沿根路径冒泡给捕获处理器, // 搜索框不再分走按键。 - window.focus(&this.focus_handle); + window.focus(&this.focus_handle, cx); } cx.notify(); }), diff --git a/nebula_app/src/gpui_shell/ssh_settings.rs b/nebula_app/src/gpui_shell/ssh_settings.rs index 9be56f8..4fed46f 100644 --- a/nebula_app/src/gpui_shell/ssh_settings.rs +++ b/nebula_app/src/gpui_shell/ssh_settings.rs @@ -271,7 +271,7 @@ impl SettingsPane { pub(super) fn close_ssh_editor(&mut self, window: &mut Window, cx: &mut Context) { self.ssh_editor = None; self.ssh_password_input.update(cx, |input, cx| input.set_value("", window, cx)); - window.focus(&self.focus_handle); + window.focus(&self.focus_handle, cx); cx.notify(); } @@ -492,7 +492,7 @@ impl SettingsPane { || Some((format!("已保存 {destination}"), false)), |error| Some((format!("已保存 {destination},但旧地址凭据清理失败: {error}"), true)), ); - window.focus(&self.focus_handle); + window.focus(&self.focus_handle, cx); cx.notify(); } @@ -1273,7 +1273,7 @@ impl SettingsPane { Some( deferred( anchored() - .anchor(gpui::Corner::TopLeft) + .anchor(gpui::Anchor::TopLeft) .position(trigger.bottom_left()) .offset(gpui::point(px(0.0), px(6.0))) .snap_to_window_with_margin(px(8.0)) diff --git a/nebula_app/src/gpui_shell/terminal/element.rs b/nebula_app/src/gpui_shell/terminal/element.rs index 731dc04..79a239e 100644 --- a/nebula_app/src/gpui_shell/terminal/element.rs +++ b/nebula_app/src/gpui_shell/terminal/element.rs @@ -19,7 +19,7 @@ use gpui::{ InspectorElementId, LayoutId, Pixels, Rgba, SharedString, Style, TextRun, UnderlineStyle, Window, fill, outline, point, px, relative, size, }; -use gpui_component::{ActiveTheme as _, PixelsExt as _}; +use gpui_component::ActiveTheme as _; use nebula_terminal::grid::Dimensions as _; use nebula_terminal::render::{RenderSnapshot, SnapshotConfig, boxdraw}; #[cfg(windows)] @@ -647,7 +647,14 @@ impl Element for TerminalElement { ); let bg = Bounds::new(anchor.origin, size(shaped.width, layout.line_height)); window.paint_quad(fill(bg, theme.background)); - let _ = shaped.paint(anchor.origin, layout.line_height, window, cx); + let _ = shaped.paint( + anchor.origin, + layout.line_height, + gpui::TextAlign::Left, + None, + window, + cx, + ); } } @@ -759,6 +766,8 @@ fn paint_link_preview( let _ = shaped.paint( point(x + pad, y + (bubble_h - layout.line_height * 0.85) * 0.5), layout.line_height * 0.85, + gpui::TextAlign::Left, + None, window, cx, ); @@ -778,7 +787,7 @@ fn paint_cell_text( line_height: Pixels, ) { let shaped = window.text_system().shape_line(text, font_size, &[run], None); - let _ = shaped.paint(origin, line_height, window, cx); + let _ = shaped.paint(origin, line_height, gpui::TextAlign::Left, None, window, cx); } /// 网格对齐的浮层文本(ghost/弹窗共用),与网格文字同一定位合同:每个可见 @@ -972,7 +981,7 @@ fn paint_completion_popup( ), ); let panel_radius = px(8.0); - window.paint_shadows( + window.paint_drop_shadows( panel_bounds, panel_radius.into(), &[ @@ -981,12 +990,14 @@ fn paint_completion_popup( offset: point(px(0.0), px(8.0)), blur_radius: px(30.0), spread_radius: px(-2.0), + inset: false, }, gpui::BoxShadow { color: colors.panel_shadow.opacity(0.72), offset: point(px(0.0), px(3.0)), blur_radius: px(10.0), spread_radius: px(-1.0), + inset: false, }, ], ); diff --git a/nebula_app/src/gpui_shell/terminal/math_overlay.rs b/nebula_app/src/gpui_shell/terminal/math_overlay.rs index 25ee0cd..1926a0f 100644 --- a/nebula_app/src/gpui_shell/terminal/math_overlay.rs +++ b/nebula_app/src/gpui_shell/terminal/math_overlay.rs @@ -312,7 +312,14 @@ pub fn paint_frame( origin.x + px(x), origin.y + px(plan.baseline_y + op.baseline_y) - line.ascent, ); - let _ = line.paint(target, line.ascent + line.descent, window, cx); + let _ = line.paint( + target, + line.ascent + line.descent, + gpui::TextAlign::Left, + None, + window, + cx, + ); } }); } diff --git a/nebula_app/src/gpui_shell/terminal/ssh_connect_overlay.rs b/nebula_app/src/gpui_shell/terminal/ssh_connect_overlay.rs index 4b9b298..91498b6 100644 --- a/nebula_app/src/gpui_shell/terminal/ssh_connect_overlay.rs +++ b/nebula_app/src/gpui_shell/terminal/ssh_connect_overlay.rs @@ -601,6 +601,7 @@ pub(super) fn overlay( offset: point(px(0.0), px(7.0)), blur_radius: px(20.0), spread_radius: px(0.0), + inset: false, }, ]) .child(identity) diff --git a/nebula_app/src/gpui_shell/terminal/view.rs b/nebula_app/src/gpui_shell/terminal/view.rs index d3d02c2..b9255b5 100644 --- a/nebula_app/src/gpui_shell/terminal/view.rs +++ b/nebula_app/src/gpui_shell/terminal/view.rs @@ -12,7 +12,7 @@ use gpui::{ Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, StatefulInteractiveElement as _, Styled as _, TextRun, UTF16Selection, Window, div, point, px, }; -use gpui_component::{PixelsExt as _, WindowExt as _}; +use gpui_component::WindowExt as _; use nebula_settings::CellWidthModeName; use nebula_terminal::event::{Event as TermEvent, Notify as _, OnResize as _, WindowSize}; use nebula_terminal::event_loop::Msg; @@ -32,11 +32,11 @@ use super::mouse_protocol; use super::session::{self, TerminalSession}; use super::suggest; use super::{KEY_CONTEXT, TerminalBackTab, TerminalTab}; -use crate::{config::UiConfig, font_install::REQUIRED_FONT_FAMILY}; use crate::gpui_shell::config::Settings; use crate::gpui_shell::prelude::{ ActiveTheme as _, Colorize as _, DialogButtonProps, center_confirm_dialog, }; +use crate::{config::UiConfig, font_install::REQUIRED_FONT_FAMILY}; use futures::StreamExt as _; /// 等宽字体描述。GPUI 的 Windows 后端收到空 feature 列表会在 @@ -1400,11 +1400,11 @@ impl TerminalView { let view = view.clone(); center_confirm_dialog(dialog, window) .title(title.clone()) - .confirm() .button_props( DialogButtonProps::default() .ok_text(ok_text.clone()) - .cancel_text(cancel_text.clone()), + .cancel_text(cancel_text.clone()) + .show_cancel(true), ) .child(body.clone()) .on_ok(move |_, _window, cx| { @@ -1528,9 +1528,9 @@ impl TerminalView { let target = dir.clone(); // 子进程往返是阻塞的,必须落在后台线程池上。 let entries = cx - .background_spawn(async move { - crate::remote_dirs::fetch_wsl(&distro, &target) - }) + .background_spawn( + async move { crate::remote_dirs::fetch_wsl(&distro, &target) }, + ) .await; crate::remote_dirs::finish_fetch(&env, &dir, entries); let _ = this.update(cx, |_, cx| cx.notify()); @@ -1736,9 +1736,8 @@ impl TerminalView { } } - /// 用终端局部 action 截住 gpui-component Root 的 Tab 焦点遍历,再回灌 - /// 同一条终端按键路径。有补齐时只接受补齐;无补齐时仍由编码器发送 Tab, - /// 因而不会产生第二套补齐/按键语义。 + /// 截住组件 Root 的 Tab 焦点遍历后回灌既有终端按键路径;有补齐时只接受补齐, + /// 否则仍由原编码器发送 Tab,避免产生第二套按键语义。 fn dispatch_terminal_tab(&mut self, shift: bool, window: &mut Window, cx: &mut Context) { let mut modifiers = gpui::Modifiers::default(); modifiers.shift = shift; @@ -1746,6 +1745,7 @@ impl TerminalView { &KeyDownEvent { keystroke: gpui::Keystroke { modifiers, key: "tab".to_owned(), key_char: None }, is_held: false, + prefer_character_input: false, }, window, cx, @@ -2049,7 +2049,7 @@ impl TerminalView { window: &mut Window, cx: &mut Context, ) { - window.focus(&self.focus_handle); + window.focus(&self.focus_handle, cx); cx.emit(TerminalViewEvent::FocusRequested); if self.session.is_none() { return; @@ -2382,7 +2382,7 @@ impl TerminalView { window: &mut Window, cx: &mut Context, ) { - window.focus(&self.focus_handle); + window.focus(&self.focus_handle, cx); cx.emit(TerminalViewEvent::FocusRequested); if suggest::popup_dismiss(&mut self.suggest) { cx.notify(); @@ -2436,7 +2436,7 @@ impl TerminalView { window: &mut Window, cx: &mut Context, ) { - window.focus(&self.focus_handle); + window.focus(&self.focus_handle, cx); cx.emit(TerminalViewEvent::FocusRequested); if suggest::popup_dismiss(&mut self.suggest) { cx.notify(); @@ -2729,7 +2729,7 @@ impl Render for TerminalView { this.write_bytes(bytes); // 粘完把焦点交回终端:用户接着就要敲命令,还要先点一下 // 才能输入的话,这个手势就白省了。 - window.focus(&this.focus_handle); + window.focus(&this.focus_handle, cx); cx.notify(); }, )) diff --git a/nebula_app/src/gpui_shell/terminal/view/broadcast.rs b/nebula_app/src/gpui_shell/terminal/view/broadcast.rs index db5aeeb..00e0a1b 100644 --- a/nebula_app/src/gpui_shell/terminal/view/broadcast.rs +++ b/nebula_app/src/gpui_shell/terminal/view/broadcast.rs @@ -27,7 +27,10 @@ pub enum TerminalInput { Key(gpui::Keystroke), /// `paste` = 走粘贴语义。接收端据此决定要不要套 bracketed-paste 哨兵: /// 给单个字符套上哨兵会让 zsh/fish 拒绝执行。 - Text { text: String, paste: bool }, + Text { + text: String, + paste: bool, + }, } impl TerminalView { @@ -72,12 +75,7 @@ impl TerminalView { } /// 文本提交与粘贴都保留语义,由接收端自行套 bracketed-paste 包装。 - pub(crate) fn apply_broadcast_text( - &mut self, - text: &str, - paste: bool, - cx: &mut Context, - ) { + pub(crate) fn apply_broadcast_text(&mut self, text: &str, paste: bool, cx: &mut Context) { if text.is_empty() || self.exited.is_some() { return; } diff --git a/nebula_app/src/gpui_shell/theme.rs b/nebula_app/src/gpui_shell/theme.rs index 490bb10..7056cfa 100644 --- a/nebula_app/src/gpui_shell/theme.rs +++ b/nebula_app/src/gpui_shell/theme.rs @@ -439,7 +439,6 @@ fn apply_skin_tokens(chrome: NebulaTheme, cx: &mut App) { // 悬停 / 选中水洗。 theme.accent = wash(sk.hover); theme.accent_foreground = ink(sk.ink_strong); - theme.accordion_hover = wash(sk.hover); theme.list_hover = wash(sk.hover); theme.list_active = wash(sk.hover_strong); theme.list_active_border = transparent; diff --git a/nebula_app/src/gpui_shell/wallpaper.rs b/nebula_app/src/gpui_shell/wallpaper.rs index dbf9696..1bbf21b 100644 --- a/nebula_app/src/gpui_shell/wallpaper.rs +++ b/nebula_app/src/gpui_shell/wallpaper.rs @@ -592,6 +592,7 @@ pub fn paint_glass_overlay(bounds: Bounds, window: &mut Window, cx: &App let mut x = bounds.origin.x; while x < right { let _ = window.paint_image( + Bounds::new(point(x, y), size(step, step)), Bounds::new(point(x, y), size(step, step)), Corners::default(), tile.clone(), @@ -620,6 +621,7 @@ pub fn paint_wallpaper_card(bounds: Bounds, window: &mut Window, cx: &Ap // 往往不等于卡片,直接画会把四角重新铺方。这里传入已裁成卡片尺寸的 // 纹理,使图片 bounds 与旧壳 shader 的圆角 clip rect 完全重合。 let _ = window.paint_image( + bounds, bounds, Corners::all(crate::gpui_shell::theme::card_radius()), image, @@ -753,7 +755,7 @@ pub fn paint_wallpaper_under_chrome(bounds: Bounds, window: &mut Window, } let Some(image) = window_wallpaper(bounds, window, wp) else { return }; window.with_content_mask(Some(ContentMask { bounds }), |window| { - let _ = window.paint_image(bounds, Corners::all(px(0.0)), image, 0, false); + let _ = window.paint_image(bounds, bounds, Corners::all(px(0.0)), image, 0, false); }); } diff --git a/nebula_app/src/gpui_shell/workspace.rs b/nebula_app/src/gpui_shell/workspace.rs index 6c52334..673bad2 100644 --- a/nebula_app/src/gpui_shell/workspace.rs +++ b/nebula_app/src/gpui_shell/workspace.rs @@ -1573,12 +1573,12 @@ impl NebulaWorkspace { let close_workspace = close_workspace.clone(); center_confirm_dialog(dialog, window) .title("关闭窗口?") - .confirm() .button_props( DialogButtonProps::default() .ok_text("关闭") .ok_variant(gpui_component::button::ButtonVariant::Danger) - .cancel_text("取消"), + .cancel_text("取消") + .show_cancel(true), ) .child(body.clone()) .on_ok(move |_, window, cx| { @@ -1618,12 +1618,12 @@ impl NebulaWorkspace { let workspace = workspace.clone(); center_confirm_dialog(dialog, window) .title("关闭此分栏?") - .confirm() .button_props( DialogButtonProps::default() .ok_text("关闭") .ok_variant(gpui_component::button::ButtonVariant::Danger) - .cancel_text("取消"), + .cancel_text("取消") + .show_cancel(true), ) .child(body.clone()) .on_ok(move |_, window, cx| { @@ -1646,12 +1646,12 @@ impl NebulaWorkspace { let workspace = workspace.clone(); center_confirm_dialog(dialog, window) .title("关闭此标签页?") - .confirm() .button_props( DialogButtonProps::default() .ok_text("关闭") .ok_variant(gpui_component::button::ButtonVariant::Danger) - .cancel_text("取消"), + .cancel_text("取消") + .show_cancel(true), ) .child(body.clone()) .on_ok(move |_, window, cx| { @@ -1918,9 +1918,7 @@ impl NebulaWorkspace { cx.spawn(async move |_this, cx| { loop { executor.timer(Duration::from_millis(1000)).await; - if cx.update(crate::gpui_shell::workspace::windowing::autosave_tick).is_err() { - return; - } + cx.update(crate::gpui_shell::workspace::windowing::autosave_tick); } }) .detach(); @@ -2221,7 +2219,7 @@ impl NebulaWorkspace { ) | None => return, }; - window.defer(cx, move |window, _| window.focus(&focus)); + window.defer(cx, move |window, cx| window.focus(&focus, cx)); } /// 聚焦 tab 的**宿主可见** cwd。必须先识别 WSL,再读 `local_cwd()`:Windows @@ -3048,7 +3046,7 @@ impl NebulaWorkspace { .child( Button::new("side-panel-git") .label(vcs_name) - .when(is_git_vcs, |button| button.icon(IconName::GitHub)) + .when(is_git_vcs, |button| button.icon(IconName::Github)) .small() .selected(git) .when(git_count > 0, |button| button.label(format!("{vcs_name} {git_count}"))) diff --git a/nebula_app/src/gpui_shell/workspace/agents.rs b/nebula_app/src/gpui_shell/workspace/agents.rs index 689529d..40d4d6e 100644 --- a/nebula_app/src/gpui_shell/workspace/agents.rs +++ b/nebula_app/src/gpui_shell/workspace/agents.rs @@ -101,14 +101,9 @@ impl NebulaWorkspace { if events.is_empty() { continue; } - if cx - .update(|cx| { - crate::gpui_shell::workspace::windowing::dispatch_ai_events(events, cx) - }) - .is_err() - { - return; - } + cx.update(|cx| { + crate::gpui_shell::workspace::windowing::dispatch_ai_events(events, cx) + }); } }) .detach(); diff --git a/nebula_app/src/gpui_shell/workspace/file_tree.rs b/nebula_app/src/gpui_shell/workspace/file_tree.rs index d66d083..4bfc4f7 100644 --- a/nebula_app/src/gpui_shell/workspace/file_tree.rs +++ b/nebula_app/src/gpui_shell/workspace/file_tree.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use gpui::prelude::FluentBuilder as _; use gpui::{ - AppContext as _, ClipboardItem, Context, Corner, DismissEvent, Entity, Focusable as _, + Anchor, AppContext as _, ClipboardItem, Context, DismissEvent, Entity, Focusable as _, InteractiveElement as _, IntoElement as _, MouseButton, MouseDownEvent, ParentElement as _, Pixels, Point, SharedString, StatefulInteractiveElement as _, Styled as _, Subscription, Window, anchored, deferred, div, px, @@ -323,7 +323,7 @@ impl NebulaWorkspace { window, ) }); - menu.focus_handle(cx).focus(window); + menu.focus_handle(cx).focus(window, cx); let subscription = cx.subscribe_in(&menu, window, |this, _, _: &DismissEvent, _, cx| { this.file_tree_menu = None; cx.notify(); @@ -340,7 +340,7 @@ impl NebulaWorkspace { anchored() .position(state.position) .snap_to_window_with_margin(px(8.0)) - .anchor(Corner::TopLeft) + .anchor(Anchor::TopLeft) .child(state.menu.clone()), ) .with_priority(1) @@ -419,12 +419,12 @@ impl NebulaWorkspace { let path = path.clone(); center_confirm_dialog(dialog, window) .title(title.clone()) - .confirm() .button_props( DialogButtonProps::default() .ok_text("删除") .ok_variant(gpui_component::button::ButtonVariant::Danger) - .cancel_text("取消"), + .cancel_text("取消") + .show_cancel(true), ) .child(body.clone()) .on_ok(move |_, _, cx| { diff --git a/nebula_app/src/gpui_shell/workspace/key_actions.rs b/nebula_app/src/gpui_shell/workspace/key_actions.rs index 5b97663..a5aae29 100644 --- a/nebula_app/src/gpui_shell/workspace/key_actions.rs +++ b/nebula_app/src/gpui_shell/workspace/key_actions.rs @@ -11,11 +11,7 @@ pub(super) fn move_target(active: usize, len: usize, right: bool) -> Option= len { return None; } - if right { - (active + 1 < len).then(|| active + 1) - } else { - active.checked_sub(1) - } + if right { (active + 1 < len).then(|| active + 1) } else { active.checked_sub(1) } } impl NebulaWorkspace { @@ -36,7 +32,12 @@ impl NebulaWorkspace { /// Ctrl+Shift+PageUp/PageDown(WT 的 `moveTab forward/backward`):把活动 /// 标签在序列里挪一格。复用拖拽提交用的 [`Self::move_tab`],两条路径共享 /// 同一份下标搬运与 active 修正,不会各自解释一套。 - pub(super) fn move_active_tab(&mut self, right: bool, window: &mut Window, cx: &mut Context) { + pub(super) fn move_active_tab( + &mut self, + right: bool, + window: &mut Window, + cx: &mut Context, + ) { let Some(to) = move_target(self.active, self.tabs.len(), right) else { return }; let from = self.active; self.move_tab(from, to, window, cx); diff --git a/nebula_app/src/gpui_shell/workspace/residency.rs b/nebula_app/src/gpui_shell/workspace/residency.rs index a18a63e..acfac39 100644 --- a/nebula_app/src/gpui_shell/workspace/residency.rs +++ b/nebula_app/src/gpui_shell/workspace/residency.rs @@ -54,14 +54,9 @@ impl NebulaWorkspace { events.push(event); } - if cx - .update(|cx| { - crate::gpui_shell::workspace::windowing::dispatch_shell_events(events, cx) - }) - .is_err() - { - return; - } + cx.update(|cx| { + crate::gpui_shell::workspace::windowing::dispatch_shell_events(events, cx) + }); } }) .detach(); diff --git a/nebula_app/src/gpui_shell/workspace/sidebar.rs b/nebula_app/src/gpui_shell/workspace/sidebar.rs index 8227c63..6231a25 100644 --- a/nebula_app/src/gpui_shell/workspace/sidebar.rs +++ b/nebula_app/src/gpui_shell/workspace/sidebar.rs @@ -881,7 +881,7 @@ impl NebulaWorkspace { ) .child( Button::new("toggle-git-tree") - .icon(IconName::GitHub) + .icon(IconName::Github) .ghost() .selected(git_active) .tooltip("Git 状态 (Ctrl+Shift+G)") diff --git a/nebula_app/src/gpui_shell/workspace/tab_drag.rs b/nebula_app/src/gpui_shell/workspace/tab_drag.rs index df3f2a7..9ac8d92 100644 --- a/nebula_app/src/gpui_shell/workspace/tab_drag.rs +++ b/nebula_app/src/gpui_shell/workspace/tab_drag.rs @@ -107,9 +107,11 @@ impl NebulaWorkspace { self.update_tab_drag_position(f32::from(event.position.x), f32::from(event.position.y), cx); // 拖到 tab 视口边缘就自动滚(仅顶栏模式且真的溢出时生效)。放在位移 // 换算之后:让位槽位仍按存储顺序算,滚动只改可视窗口。 - if self.tab_drag.as_ref().is_some_and(|drag| { - drag.active && drag.axis == TabDragAxis::Horizontal - }) { + if self + .tab_drag + .as_ref() + .is_some_and(|drag| drag.active && drag.axis == TabDragAxis::Horizontal) + { self.autoscroll_top_tabs_for_drag(f32::from(event.position.x), window, cx); } } diff --git a/nebula_app/src/gpui_shell/workspace/tab_menu.rs b/nebula_app/src/gpui_shell/workspace/tab_menu.rs index 75bbbab..30dfbce 100644 --- a/nebula_app/src/gpui_shell/workspace/tab_menu.rs +++ b/nebula_app/src/gpui_shell/workspace/tab_menu.rs @@ -21,7 +21,7 @@ //! 因此保持单层。 use gpui::{ - AnyElement, App, Context, Corner, DismissEvent, Entity, Focusable as _, + Anchor, AnyElement, App, Context, DismissEvent, Entity, Focusable as _, InteractiveElement as _, IntoElement as _, MouseButton, ParentElement as _, Pixels, Point, Styled as _, Subscription, Window, anchored, deferred, div, prelude::FluentBuilder as _, px, }; @@ -96,7 +96,7 @@ impl NebulaWorkspace { tab_count, ) }); - menu.focus_handle(cx).focus(window); + menu.focus_handle(cx).focus(window, cx); let subscription = cx.subscribe_in(&menu, window, |this, _, _: &DismissEvent, _, cx| { this.tab_menu = None; cx.notify(); @@ -115,7 +115,7 @@ impl NebulaWorkspace { anchored() .position(state.position) .snap_to_window_with_margin(px(8.0)) - .anchor(Corner::TopLeft) + .anchor(Anchor::TopLeft) .child(state.menu.clone()), ) .with_priority(1) diff --git a/nebula_app/src/gpui_shell/workspace/top_tabs.rs b/nebula_app/src/gpui_shell/workspace/top_tabs.rs index 49861d4..eacefed 100644 --- a/nebula_app/src/gpui_shell/workspace/top_tabs.rs +++ b/nebula_app/src/gpui_shell/workspace/top_tabs.rs @@ -467,7 +467,7 @@ impl NebulaWorkspace { // 相邻操作按钮下沉,标题栏两侧的独立工具仍保持垂直居中。 h_flex() .h_full() - .flex_shrink() + .flex_shrink(1.0) .min_w_0() .items_end() // 溢出翻页:只在装不下时出现。滚轮已经能滚,但滚轮是个 @@ -498,7 +498,7 @@ impl NebulaWorkspace { .id("top-tabs-viewport") .relative() .w(px(tab_viewport_w)) - .flex_shrink() + .flex_shrink(1.0) .min_w_0() .h(px(TOP_TAB_H)) .overflow_hidden() @@ -555,7 +555,7 @@ impl NebulaWorkspace { .selected(settings_active) .tooltip("更多") .dropdown_menu_with_anchor( - gpui::Corner::TopRight, + gpui::Anchor::TopRight, move |menu, _, _| { let shell_picker = menu_workspace.clone(); let new_window = menu_workspace.clone(); @@ -617,7 +617,7 @@ impl NebulaWorkspace { ) .child( Button::new("top-toggle-git-tree") - .icon(IconName::GitHub) + .icon(IconName::Github) .ghost() .selected(git_active) .tooltip("Git 状态 (Ctrl+Shift+G)") diff --git a/nebula_app/src/gpui_shell/workspace/update_dialog.rs b/nebula_app/src/gpui_shell/workspace/update_dialog.rs index d6b4417..b4d4441 100644 --- a/nebula_app/src/gpui_shell/workspace/update_dialog.rs +++ b/nebula_app/src/gpui_shell/workspace/update_dialog.rs @@ -133,36 +133,38 @@ pub(crate) fn open_update_dialog( ); center_confirm_dialog(dialog, window) .title(title.clone()) - .confirm() - .footer(move |ok, cancel, window, cx| { - let version = footer_skip_version.clone(); - let save_failed_prefix = footer_save_failed_prefix.clone(); - let error_separator = footer_error_separator.clone(); - vec![ - Button::new("skip-nebula-update") - .label(footer_skip_text.clone()) - .ghost() - .on_click(move |_, window, cx| { - if let Err(error) = crate::update_check::skip_version(&version) { - crate::gpui_shell::toast::toast( - window, - cx, - crate::display::ToastKind::Warning, - format!("{save_failed_prefix}{error_separator}{error}"), - ); - } - window.close_dialog(cx); - }) - .into_any_element(), - div().flex_1().into_any_element(), - cancel(window, cx), - ok(window, cx), - ] - }) - .button_props( - DialogButtonProps::default() - .ok_text(open_text.clone()) - .cancel_text(later_text.clone()), + .footer( + DialogFooter::new() + .child( + Button::new("skip-nebula-update") + .label(footer_skip_text.clone()) + .ghost() + .on_click({ + let version = footer_skip_version.clone(); + let save_failed_prefix = footer_save_failed_prefix.clone(); + let error_separator = footer_error_separator.clone(); + move |_, window, cx| { + if let Err(error) = crate::update_check::skip_version(&version) + { + crate::gpui_shell::toast::toast( + window, + cx, + crate::display::ToastKind::Warning, + format!("{save_failed_prefix}{error_separator}{error}"), + ); + } + window.close_dialog(cx); + } + }), + ) + .child(div().flex_1()) + .child( + DialogClose::new().child(Button::new("cancel").label(later_text.clone())), + ) + .child( + DialogAction::new() + .child(Button::new("ok").label(open_text.clone()).primary()), + ), ) .child(body) .on_ok(|_, _, cx| { diff --git a/nebula_app/src/gpui_shell/workspace/windowing.rs b/nebula_app/src/gpui_shell/workspace/windowing.rs index e5fe6f6..0700015 100644 --- a/nebula_app/src/gpui_shell/workspace/windowing.rs +++ b/nebula_app/src/gpui_shell/workspace/windowing.rs @@ -111,7 +111,7 @@ pub(crate) fn initialize(cx: &mut App, runtime_hub: crate::runtime_api::RuntimeH save_combined_session(cx, true); async {} }); - let closed_subscription = cx.on_window_closed(|cx| { + let closed_subscription = cx.on_window_closed(|cx, _window_id| { prune_entries(cx); if !cx.global::().entries.is_empty() { save_combined_session(cx, false);