From ce140f526a46d35094a9739592f8cc5a5f665d61 Mon Sep 17 00:00:00 2001 From: kuddev Date: Thu, 13 Aug 2026 13:29:51 +0800 Subject: [PATCH] feat(gpui): GPUI UI layer moves into nebula_app - one product, one codebase Physically relocates the validated GPUI surface out of the lab crate into nebula_app/src/gpui_shell/ (git mv keeps history): the terminal stack (view/element/session/keymap/mouse_protocol/colors), the config bridge (nebula.toml + nebula_settings.txt + theme tables), the workspace view and the theme/prelude glue. Per product ruling, nebula_gpui is a scratch lab - it keeps only the component gallery (terminal preview removed) plus annotated copies of theme/prelude, and its Cargo manifest drops the terminal/settings dependencies. The gpui-shell feature is now self-contained: nebula depends directly on gpui/gpui-component/-assets (redirected to our fork by the root patch table), nebula-settings and futures; the nebula-gpui crate dependency is gone. Entry points: 'nebula --gpui' runs the GPUI main-window form on the main thread with winit never starting (P3 acceptance form; gates get re-measured here), and the NEBULA_GPUI_SHELL=1 dedicated-thread spike stays until the P3 takeover completes. Verified: cargo check on both crates, and nebula --gpui opens the Nebula main window with working tabs, ConPTY terminal, CJK text and powerline prompt (screenshot .tmp_parity_gpui-in-nebula.png). --- Cargo.lock | 12 ++-- nebula_app/Cargo.toml | 37 ++++++++-- nebula_app/src/cli.rs | 5 ++ .../src/gpui_shell/config.rs | 12 ++-- nebula_app/src/gpui_shell/mod.rs | 70 +++++++++++++++++++ nebula_app/src/gpui_shell/prelude.rs | 25 +++++++ .../src/gpui_shell}/terminal/colors.rs | 0 .../src/gpui_shell}/terminal/element.rs | 0 .../src/gpui_shell}/terminal/keymap.rs | 0 .../src/gpui_shell}/terminal/mod.rs | 0 .../gpui_shell}/terminal/mouse_protocol.rs | 0 .../src/gpui_shell}/terminal/session.rs | 0 .../src/gpui_shell}/terminal/view.rs | 2 +- nebula_app/src/gpui_shell/theme.rs | 46 ++++++++++++ .../src/gpui_shell}/workspace.rs | 16 +---- nebula_app/src/main.rs | 14 +++- nebula_gpui/Cargo.toml | 16 +---- nebula_gpui/src/app.rs | 22 +----- nebula_gpui/src/lib.rs | 17 ++--- nebula_gpui/src/ui/bootstrap.rs | 9 --- nebula_gpui/src/ui/prelude.rs | 5 +- nebula_gpui/src/ui/theme.rs | 3 + nebula_gpui/src/views/component_gallery.rs | 21 +----- nebula_gpui/src/views/mod.rs | 1 - 24 files changed, 220 insertions(+), 113 deletions(-) rename nebula_gpui/src/config/mod.rs => nebula_app/src/gpui_shell/config.rs (97%) create mode 100644 nebula_app/src/gpui_shell/mod.rs create mode 100644 nebula_app/src/gpui_shell/prelude.rs rename {nebula_gpui/src => nebula_app/src/gpui_shell}/terminal/colors.rs (100%) rename {nebula_gpui/src => nebula_app/src/gpui_shell}/terminal/element.rs (100%) rename {nebula_gpui/src => nebula_app/src/gpui_shell}/terminal/keymap.rs (100%) rename {nebula_gpui/src => nebula_app/src/gpui_shell}/terminal/mod.rs (100%) rename {nebula_gpui/src => nebula_app/src/gpui_shell}/terminal/mouse_protocol.rs (100%) rename {nebula_gpui/src => nebula_app/src/gpui_shell}/terminal/session.rs (100%) rename {nebula_gpui/src => nebula_app/src/gpui_shell}/terminal/view.rs (99%) create mode 100644 nebula_app/src/gpui_shell/theme.rs rename {nebula_gpui/src/views => nebula_app/src/gpui_shell}/workspace.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index 935cf52..7db8f0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4570,10 +4570,14 @@ dependencies = [ "dirs 6.0.0", "dwrote", "embed-resource", + "futures", "getrandom 0.3.3", "gl_generator", "global-hotkey", "glutin", + "gpui", + "gpui-component", + "gpui-component-assets", "home", "html-escape", "image", @@ -4581,7 +4585,7 @@ dependencies = [ "log", "mlua", "nebula-completions", - "nebula-gpui", + "nebula-settings", "nebula_config", "nebula_config_derive", "nebula_terminal", @@ -4635,15 +4639,9 @@ dependencies = [ name = "nebula-gpui" version = "0.1.0" dependencies = [ - "dirs 6.0.0", - "futures", "gpui", "gpui-component", "gpui-component-assets", - "nebula-settings", - "nebula_terminal", - "serde", - "toml 0.9.11+spec-1.1.0", ] [[package]] diff --git a/nebula_app/Cargo.toml b/nebula_app/Cargo.toml index de8b372..844a190 100644 --- a/nebula_app/Cargo.toml +++ b/nebula_app/Cargo.toml @@ -25,9 +25,28 @@ version = "1.0.0" path = "../nebula_config" version = "1.0.0" -# 可选:进程内 GPUI UI 层(feature = "gpui-shell")。 -[dependencies.nebula-gpui] -path = "../nebula_gpui" +# 可选:GPUI UI 层(feature = "gpui-shell",模块 src/gpui_shell/)。 +# gpui 与 gpui-component 成对锁定:0.5.1 编译在 gpui 0.2.2 上;两者由根 +# Cargo.toml 的 [patch.crates-io] 重定向到我们的 fork。 +[dependencies.gpui] +version = "=0.2.2" +optional = true + +[dependencies.gpui-component] +version = "=0.5.1" +optional = true + +[dependencies.gpui-component-assets] +version = "=0.5.1" +optional = true + +# 运行时用户设置(nebula_settings.txt + 主题终端色表)的共享权威实现。 +[dependencies.nebula-settings] +path = "../nebula_settings" +optional = true + +[dependencies.futures] +version = "0.3" optional = true [dependencies.nebula-completions] @@ -198,6 +217,12 @@ wayland = [ ] nightly = [] wgpu-foundation = ["dep:pollster", "dep:wgpu"] -# C 路线:在 nebula.exe 进程内的专用线程上拉起 GPUI UI 层。 -# 默认关闭,不影响常规构建;开启后由 NEBULA_GPUI_SHELL=1 触发。 -gpui-shell = ["dep:nebula-gpui"] +# GPUI UI 层(产品代码在 src/gpui_shell/)。默认关闭,不影响常规构建。 +# 拉起:`nebula --gpui`(主窗形态)或 NEBULA_GPUI_SHELL=1(spike 并存形态)。 +gpui-shell = [ + "dep:gpui", + "dep:gpui-component", + "dep:gpui-component-assets", + "dep:nebula-settings", + "dep:futures", +] diff --git a/nebula_app/src/cli.rs b/nebula_app/src/cli.rs index 20b8b35..7348f9c 100644 --- a/nebula_app/src/cli.rs +++ b/nebula_app/src/cli.rs @@ -33,6 +33,11 @@ pub struct Options { #[clap(long)] pub embed: Option, + /// Launch the GPUI UI shell as the main window (P3 acceptance form). + #[cfg(feature = "gpui-shell")] + #[clap(long)] + pub gpui: bool, + /// Specify alternative configuration file [default: /// $XDG_CONFIG_HOME/nebula/nebula.toml]. #[cfg(not(any(target_os = "macos", windows)))] diff --git a/nebula_gpui/src/config/mod.rs b/nebula_app/src/gpui_shell/config.rs similarity index 97% rename from nebula_gpui/src/config/mod.rs rename to nebula_app/src/gpui_shell/config.rs index 0f62f0b..365b3ca 100644 --- a/nebula_gpui/src/config/mod.rs +++ b/nebula_app/src/gpui_shell/config.rs @@ -1,4 +1,4 @@ -//! 读取用户配置:`nebula.toml` + `nebula_settings.txt`,与 `nebula_app` +//! 读取用户配置:`nebula.toml` + `nebula_settings.txt`,与 `nebula_app` //! 共享同一套文件与语义。 //! //! 两个来源、一个优先级:`nebula_settings.txt`(设置界面持久化的运行时 @@ -23,7 +23,7 @@ use nebula_settings::{CursorShapeName, RuntimeSettings}; use nebula_terminal::vte::ansi::CursorShape; use serde::Deserialize; -use crate::terminal::colors::Palette; +use crate::gpui_shell::terminal::colors::Palette; /// 应用启动时装载一次的全局设置。 pub struct Settings { @@ -206,7 +206,7 @@ fn load_merged_toml(path: &Path) -> toml::Value { if import_path.exists() { merged = merge_values(merged, read_toml(&import_path)); } else { - eprintln!("[nebula-gpui] config import not found: {}", import_path.display()); + eprintln!("[nebula:gpui] config import not found: {}", import_path.display()); } } merge_values(merged, main) @@ -216,7 +216,7 @@ fn read_toml(path: &Path) -> toml::Value { let text = match std::fs::read_to_string(path) { Ok(text) => text, Err(err) => { - eprintln!("[nebula-gpui] failed to read config {}: {err}", path.display()); + eprintln!("[nebula:gpui] failed to read config {}: {err}", path.display()); return toml::Value::Table(Default::default()); }, }; @@ -224,7 +224,7 @@ fn read_toml(path: &Path) -> toml::Value { match text.parse::() { Ok(table) => toml::Value::Table(table), Err(err) => { - eprintln!("[nebula-gpui] failed to parse config {}: {err}", path.display()); + eprintln!("[nebula:gpui] failed to parse config {}: {err}", path.display()); toml::Value::Table(Default::default()) }, } @@ -234,7 +234,7 @@ fn read_toml(path: &Path) -> toml::Value { fn resolve_import_path(import: &str, base_config: &Path) -> PathBuf { let mut path = PathBuf::from(import); if let Ok(stripped) = path.strip_prefix("~/") { - if let Some(home) = dirs::home_dir() { + if let Some(home) = home::home_dir() { path = home.join(stripped); } } diff --git a/nebula_app/src/gpui_shell/mod.rs b/nebula_app/src/gpui_shell/mod.rs new file mode 100644 index 0000000..e00b186 --- /dev/null +++ b/nebula_app/src/gpui_shell/mod.rs @@ -0,0 +1,70 @@ +//! GPUI UI 层(产品代码,feature = "gpui-shell")。 +//! +//! 这里是 nebula.exe 体内的新 UI:终端视图/渲染元素/工作区与配置桥全部 +//! 住在本模块,`nebula_gpui` crate 只是组件验收的实验场,不承载产品代码 +//! (ROADMAP 终局形态第 1 条)。 +//! +//! 两种拉起形态: +//! - `nebula --gpui`:GPUI 主窗形态——主线程直接进 GPUI 消息循环,winit +//! 旧壳完全不启动。P3 的产品验收形态;三闸门复测都在这里做。 +//! - `NEBULA_GPUI_SHELL=1`(需 gpui-shell 构建):spike 形态——GPUI 跑在 +//! 专用线程,与 winit 旧壳同进程并存,仅用于双运行时验证,P3 完成后移除。 + +pub mod config; +pub mod prelude; +pub mod terminal; +pub mod theme; +pub mod workspace; + +use gpui::{App, AppContext as _, Bounds, WindowBounds, WindowOptions, px, size}; +use gpui_component::{Root, TitleBar}; +use gpui_component_assets::Assets; + +use workspace::NebulaWorkspace; + +/// 在当前线程启动 GPUI 运行时并打开主窗口,阻塞直至 UI 退出。 +/// +/// GPUI 拥有自己的消息循环:主窗形态从主线程调用(winit 不启动); +/// spike 形态从专用线程调用。一个进程内只允许调用一次。 +pub fn run_shell() { + gpui::Application::new().with_assets(Assets).run(|cx| { + init(cx); + cx.activate(true); + open_main_window(cx); + }); +} + +/// 组件库/主题/快捷键/用户配置的一次性初始化。 +fn init(cx: &mut App) { + // 组件库必须只初始化一次,否则全局 action、菜单和主题状态会重复注册。 + gpui_component::init(cx); + theme::apply_nebula_theme(cx); + workspace::init(cx); + + // 用户 nebula.toml + nebula_settings.txt,启动读一次;失败静默回退默认。 + // 诊断走 stderr:GPUI 主窗形态在旧壳 logger 建立之前拉起。 + let settings = config::Settings::load(); + match &settings.source_path { + Some(path) => eprintln!("[nebula:gpui] config loaded: {}", path.display()), + None => eprintln!("[nebula:gpui] no user config found, using defaults"), + } + cx.set_global(settings); +} + +fn open_main_window(cx: &mut App) { + let bounds = Bounds::centered(None, size(px(1080.0), px(720.0)), cx); + let options = WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + window_min_size: Some(size(px(760.0), px(540.0))), + // 自绘 TitleBar 要求系统标题栏透明化,窗口控制按钮由组件库接管。 + titlebar: Some(TitleBar::title_bar_options()), + app_id: Some("nebula".to_owned()), + ..Default::default() + }; + + cx.open_window(options, |window, cx| { + let workspace = cx.new(|cx| NebulaWorkspace::new(window, cx)); + cx.new(|cx| Root::new(workspace, window, cx)) + }) + .expect("failed to open Nebula GPUI window"); +} diff --git a/nebula_app/src/gpui_shell/prelude.rs b/nebula_app/src/gpui_shell/prelude.rs new file mode 100644 index 0000000..b9c6c34 --- /dev/null +++ b/nebula_app/src/gpui_shell/prelude.rs @@ -0,0 +1,25 @@ +//! 业务视图只从这里导入获准使用的第三方组件。 +//! +//! 简单组件直接复用;只有需要承载 Nebula 业务合同的组件才在本 crate 中封装。 + +// 这些导出是受控的业务入口,未被当前验收页使用的组件仍应保留,避免业务代码 +// 重新从上游散落导入而绕过升级边界。 +#[allow(unused_imports)] +pub use gpui_component::{ + ActiveTheme as _, Icon, IconName, Sizable as _, TitleBar, + button::{Button, ButtonVariants as _}, + checkbox::Checkbox, + dialog::{Dialog, DialogButtonProps}, + dock::{DockArea, DockAreaState, DockItem, Panel, PanelControl, PanelEvent, PanelState, TabPanel}, + h_flex, + input::{Input, InputState}, + menu::{ContextMenuExt as _, PopupMenu}, + scroll::ScrollableElement as _, + select::{Select, SelectState}, + sidebar::{Sidebar, SidebarGroup, SidebarMenu, SidebarMenuItem}, + tab::{Tab, TabBar}, + v_flex, +}; + +pub use gpui_component::IndexPath; +pub use gpui_component::WindowExt as _; diff --git a/nebula_gpui/src/terminal/colors.rs b/nebula_app/src/gpui_shell/terminal/colors.rs similarity index 100% rename from nebula_gpui/src/terminal/colors.rs rename to nebula_app/src/gpui_shell/terminal/colors.rs diff --git a/nebula_gpui/src/terminal/element.rs b/nebula_app/src/gpui_shell/terminal/element.rs similarity index 100% rename from nebula_gpui/src/terminal/element.rs rename to nebula_app/src/gpui_shell/terminal/element.rs diff --git a/nebula_gpui/src/terminal/keymap.rs b/nebula_app/src/gpui_shell/terminal/keymap.rs similarity index 100% rename from nebula_gpui/src/terminal/keymap.rs rename to nebula_app/src/gpui_shell/terminal/keymap.rs diff --git a/nebula_gpui/src/terminal/mod.rs b/nebula_app/src/gpui_shell/terminal/mod.rs similarity index 100% rename from nebula_gpui/src/terminal/mod.rs rename to nebula_app/src/gpui_shell/terminal/mod.rs diff --git a/nebula_gpui/src/terminal/mouse_protocol.rs b/nebula_app/src/gpui_shell/terminal/mouse_protocol.rs similarity index 100% rename from nebula_gpui/src/terminal/mouse_protocol.rs rename to nebula_app/src/gpui_shell/terminal/mouse_protocol.rs diff --git a/nebula_gpui/src/terminal/session.rs b/nebula_app/src/gpui_shell/terminal/session.rs similarity index 100% rename from nebula_gpui/src/terminal/session.rs rename to nebula_app/src/gpui_shell/terminal/session.rs diff --git a/nebula_gpui/src/terminal/view.rs b/nebula_app/src/gpui_shell/terminal/view.rs similarity index 99% rename from nebula_gpui/src/terminal/view.rs rename to nebula_app/src/gpui_shell/terminal/view.rs index 4245c06..37ba463 100644 --- a/nebula_gpui/src/terminal/view.rs +++ b/nebula_app/src/gpui_shell/terminal/view.rs @@ -23,7 +23,7 @@ use super::element::TerminalElement; use super::keymap; use super::mouse_protocol; use super::session::{self, GridSize, TerminalSession}; -use crate::config::Settings; +use crate::gpui_shell::config::Settings; use futures::StreamExt as _; diff --git a/nebula_app/src/gpui_shell/theme.rs b/nebula_app/src/gpui_shell/theme.rs new file mode 100644 index 0000000..e593d08 --- /dev/null +++ b/nebula_app/src/gpui_shell/theme.rs @@ -0,0 +1,46 @@ +use gpui::{App, hsla, px}; +use gpui_component::{Theme, ThemeMode}; + +pub fn apply_nebula_theme(cx: &mut App) { + Theme::change(ThemeMode::Dark, None, cx); + + // 先改全局 token,而不是逐组件覆写样式,确保后续直接引入的组件自然进入 + // Nebula 视觉系统,也让上游升级时需要维护的差异集中在一个位置。 + let theme = Theme::global_mut(cx); + let background = hsla(220.0 / 360.0, 0.16, 0.075, 1.0); + let surface = hsla(220.0 / 360.0, 0.13, 0.105, 1.0); + let border = hsla(216.0 / 360.0, 0.12, 0.21, 1.0); + let text = hsla(210.0 / 360.0, 0.12, 0.91, 1.0); + let muted_text = hsla(214.0 / 360.0, 0.09, 0.62, 1.0); + let accent = hsla(155.0 / 360.0, 0.62, 0.47, 1.0); + let accent_hover = hsla(155.0 / 360.0, 0.58, 0.52, 1.0); + let accent_active = hsla(155.0 / 360.0, 0.66, 0.40, 1.0); + + theme.background = background; + theme.foreground = text; + theme.group_box = surface; + theme.group_box_foreground = text; + theme.popover = surface; + theme.popover_foreground = text; + theme.border = border; + theme.input = border; + theme.primary = accent; + theme.primary_hover = accent_hover; + theme.primary_active = accent_active; + theme.primary_foreground = background; + theme.secondary = surface; + theme.secondary_hover = border; + theme.secondary_active = background; + theme.secondary_foreground = text; + theme.muted = surface; + theme.muted_foreground = muted_text; + theme.accent = accent; + theme.accent_foreground = background; + theme.caret = accent; + theme.ring = accent; + theme.selection = hsla(155.0 / 360.0, 0.52, 0.34, 0.72); + theme.font_size = px(14.0); + theme.mono_font_size = px(13.0); + theme.radius = px(5.0); + theme.radius_lg = px(7.0); +} diff --git a/nebula_gpui/src/views/workspace.rs b/nebula_app/src/gpui_shell/workspace.rs similarity index 95% rename from nebula_gpui/src/views/workspace.rs rename to nebula_app/src/gpui_shell/workspace.rs index c82e787..073ac1d 100644 --- a/nebula_gpui/src/views/workspace.rs +++ b/nebula_app/src/gpui_shell/workspace.rs @@ -11,8 +11,8 @@ use gpui::{ Styled as _, Subscription, Window, div, px, }; -use crate::terminal::view::{TerminalView, TerminalViewEvent}; -use crate::ui::prelude::*; +use crate::gpui_shell::prelude::*; +use crate::gpui_shell::terminal::view::{TerminalView, TerminalViewEvent}; gpui::actions!(nebula_workspace, [NewTerminal, CloseActiveTerminal, ToggleSidebar]); @@ -199,18 +199,6 @@ impl NebulaWorkspace { ), ) .child(v_flex().flex_1().gap_1().children(items)) - .child( - h_flex().px_1().child( - Button::new("open-gallery") - .icon(IconName::Settings) - .ghost() - .small() - .tooltip("组件验收页") - .on_click(|_, _, cx| { - crate::app::open_gallery_window(cx); - }), - ), - ) } } diff --git a/nebula_app/src/main.rs b/nebula_app/src/main.rs index b58fd32..074859e 100644 --- a/nebula_app/src/main.rs +++ b/nebula_app/src/main.rs @@ -46,6 +46,8 @@ mod event; mod file_uri; #[cfg(windows)] mod font_install; +#[cfg(feature = "gpui-shell")] +mod gpui_shell; mod input; mod logging; #[cfg(target_os = "macos")] @@ -129,14 +131,22 @@ fn main() -> Result<(), Box> { // Load command line options. let options = Options::new(); + // P3 主窗形态:GPUI 作为 nebula.exe 的 UI 层,从主线程直接进 GPUI + // 消息循环,winit 旧壳完全不启动。三闸门在此形态复测。 + #[cfg(feature = "gpui-shell")] + if options.gpui { + gpui_shell::run_shell(); + return Ok(()); + } + // C 路线 spike:GPUI UI 层跑在专用线程,拥有自己的消息循环与窗口; // 与主线程的 winit 循环互不接管。仅在显式设置环境变量时启动, - // 用于验证双 UI 运行时共存(焦点/IME/DPI)。 + // 用于验证双 UI 运行时共存(焦点/IME/DPI);P3 主窗接管完成后移除。 #[cfg(feature = "gpui-shell")] if std::env::var_os("NEBULA_GPUI_SHELL").is_some() { std::thread::Builder::new() .name("gpui-shell".into()) - .spawn(nebula_gpui::run_shell) + .spawn(gpui_shell::run_shell) .expect("spawn gpui-shell thread"); } diff --git a/nebula_gpui/Cargo.toml b/nebula_gpui/Cargo.toml index 1e20dac..b7612bf 100644 --- a/nebula_gpui/Cargo.toml +++ b/nebula_gpui/Cargo.toml @@ -2,13 +2,13 @@ name = "nebula-gpui" version = "0.1.0" license = "GPL-3.0-or-later" -description = "Nebula's GPUI application foundation and component acceptance surface" +description = "Nebula's GPUI component acceptance lab (scratch; product UI lives in nebula_app)" publish = false edition = "2024" rust-version = "1.85.0" -# lib + bin 双形态:bin 是开发/验证入口;lib 供 nebula.exe(C 路线) -# 在专用线程上以 run_shell() 拉起进程内 GPUI UI 层。 +# lib + bin 双形态:bin 是组件验收入口;产品 GPUI UI 层已迁入 +# nebula_app/src/gpui_shell/(feature = "gpui-shell"),本 crate 不再承载。 [lib] name = "nebula_gpui" path = "src/lib.rs" @@ -25,16 +25,6 @@ path = "src/main.rs" gpui = "=0.2.2" gpui-component = "=0.5.1" gpui-component-assets = "=0.5.1" -# 终端核心是纯 Rust(vte/grid/ConPTY),没有 GUI 原生依赖,因此可以跨 workspace -# 边界直接 path 引用而不会重新引入 fontconfig links 冲突。 -nebula_terminal = { path = "../nebula_terminal" } -# 运行时用户设置(nebula_settings.txt + 主题终端色表)的共享权威实现。 -nebula-settings = { path = "../nebula_settings" } -futures = "0.3" -# 读取用户 nebula.toml 的字体/配色子集;版本与主仓保持一致。 -serde = { version = "1", features = ["derive"] } -toml = "0.9" -dirs = "6.0.0" [lints.clippy] dbg_macro = "deny" diff --git a/nebula_gpui/src/app.rs b/nebula_gpui/src/app.rs index 7606608..8f86533 100644 --- a/nebula_gpui/src/app.rs +++ b/nebula_gpui/src/app.rs @@ -1,28 +1,8 @@ use gpui::{App, AppContext as _, Bounds, WindowBounds, WindowOptions, px, size}; -use gpui_component::{Root, TitleBar}; +use gpui_component::Root; use crate::views::component_gallery::ComponentGallery; -use crate::views::workspace::NebulaWorkspace; -pub fn open_main_window(cx: &mut App) { - let bounds = Bounds::centered(None, size(px(1080.0), px(720.0)), cx); - let options = WindowOptions { - window_bounds: Some(WindowBounds::Windowed(bounds)), - window_min_size: Some(size(px(760.0), px(540.0))), - // 自绘 TitleBar 要求系统标题栏透明化,窗口控制按钮由组件库接管。 - titlebar: Some(TitleBar::title_bar_options()), - app_id: Some("nebula-gpui".to_owned()), - ..Default::default() - }; - - cx.open_window(options, |window, cx| { - let workspace = cx.new(|cx| NebulaWorkspace::new(window, cx)); - cx.new(|cx| Root::new(workspace, window, cx)) - }) - .expect("failed to open Nebula GPUI window"); -} - -/// 组件验收页降级为辅助窗口,从工作区标题栏进入,仍用于回归组件接入面。 pub fn open_gallery_window(cx: &mut App) { let bounds = Bounds::centered(None, size(px(1080.0), px(720.0)), cx); let options = WindowOptions { diff --git a/nebula_gpui/src/lib.rs b/nebula_gpui/src/lib.rs index c915716..fa584e5 100644 --- a/nebula_gpui/src/lib.rs +++ b/nebula_gpui/src/lib.rs @@ -1,23 +1,20 @@ -//! GPUI UI 层(C 路线):既可作为独立 bin 运行(开发/验证形态), -//! 也可被 nebula.exe 作为库在专用线程上拉起(产品形态)。 -//! 铁律:领域逻辑不进本 crate;这里只有视图与组件接线。 +//! GPUI 组件验收实验场(scratch)。 +//! +//! 产品代码不在这里:GPUI UI 层住在 `nebula_app/src/gpui_shell/` +//! (feature = "gpui-shell"),本 crate 只保留组件验收页,用于快速验证 +//! gpui-component 组件、fork 补丁与上游升级回归,验证通过的方案进产品。 mod app; -mod config; -mod terminal; mod ui; mod views; use gpui_component_assets::Assets; -/// 在当前线程启动 GPUI 运行时并打开主窗口,阻塞直至 UI 退出。 -/// -/// GPUI 拥有自己的消息循环,宿主必须从一个专用线程调用(不能是 -/// winit 主循环所在线程)。一个进程内只允许调用一次。 +/// 在当前线程启动 GPUI 运行时并打开组件验收窗口,阻塞直至 UI 退出。 pub fn run_shell() { gpui::Application::new().with_assets(Assets).run(|cx| { ui::bootstrap::init(cx); cx.activate(true); - app::open_main_window(cx); + app::open_gallery_window(cx); }); } diff --git a/nebula_gpui/src/ui/bootstrap.rs b/nebula_gpui/src/ui/bootstrap.rs index 83bcf89..680a2dd 100644 --- a/nebula_gpui/src/ui/bootstrap.rs +++ b/nebula_gpui/src/ui/bootstrap.rs @@ -4,13 +4,4 @@ pub fn init(cx: &mut App) { // 组件库必须只初始化一次,否则全局 action、菜单和主题状态会重复注册。 gpui_component::init(cx); super::theme::apply_nebula_theme(cx); - crate::views::workspace::init(cx); - - // 用户 nebula.toml(字体/终端配色),启动读一次;失败静默回退默认值。 - let settings = crate::config::Settings::load(); - match &settings.source_path { - Some(path) => eprintln!("[nebula-gpui] config loaded: {}", path.display()), - None => eprintln!("[nebula-gpui] no user config found, using defaults"), - } - cx.set_global(settings); } diff --git a/nebula_gpui/src/ui/prelude.rs b/nebula_gpui/src/ui/prelude.rs index b9c6c34..2da8b3f 100644 --- a/nebula_gpui/src/ui/prelude.rs +++ b/nebula_gpui/src/ui/prelude.rs @@ -1,6 +1,5 @@ -//! 业务视图只从这里导入获准使用的第三方组件。 -//! -//! 简单组件直接复用;只有需要承载 Nebula 业务合同的组件才在本 crate 中封装。 +//! 实验场副本:产品权威在 `nebula_app/src/gpui_shell/prelude.rs`。 +//! 业务视图只从这里导入获准使用的第三方组件;改导出面时两处同步。 // 这些导出是受控的业务入口,未被当前验收页使用的组件仍应保留,避免业务代码 // 重新从上游散落导入而绕过升级边界。 diff --git a/nebula_gpui/src/ui/theme.rs b/nebula_gpui/src/ui/theme.rs index e593d08..6cb701b 100644 --- a/nebula_gpui/src/ui/theme.rs +++ b/nebula_gpui/src/ui/theme.rs @@ -1,3 +1,6 @@ +//! 实验场副本:产品权威在 `nebula_app/src/gpui_shell/theme.rs`。 +//! gallery 用产品同款视觉 token 验收组件;改 token 时两处同步。 + use gpui::{App, hsla, px}; use gpui_component::{Theme, ThemeMode}; diff --git a/nebula_gpui/src/views/component_gallery.rs b/nebula_gpui/src/views/component_gallery.rs index 8481009..08370ba 100644 --- a/nebula_gpui/src/views/component_gallery.rs +++ b/nebula_gpui/src/views/component_gallery.rs @@ -4,7 +4,6 @@ use gpui::{ Window, div, px, }; -use crate::terminal::view::TerminalView; use crate::ui::prelude::*; gpui::actions!(component_gallery, [Increment]); @@ -14,7 +13,6 @@ pub struct ComponentGallery { editor: Entity, select: Entity>>, dock_area: Entity, - terminal: Entity, enabled: bool, clicks: usize, active_tab: usize, @@ -95,9 +93,7 @@ impl ComponentGallery { dock.set_center(DockItem::tab(panel, &weak_dock_area, window, cx), window, cx); }); - let terminal = cx.new(TerminalView::new); - - Self { input, editor, select, dock_area, terminal, enabled: true, clicks: 0, active_tab: 0 } + Self { input, editor, select, dock_area, enabled: true, clicks: 0, active_tab: 0 } } } @@ -238,21 +234,6 @@ impl Render for ComponentGallery { .child(self.dock_area.clone()), ), ) - .child( - div().text_sm().child( - "终端垂直切片(ConPTY + nebula_terminal + 自定义 Element)", - ), - ) - .child( - div() - .w_full() - .h(px(360.0)) - .border_1() - .border_color(cx.theme().border) - .rounded_md() - .overflow_hidden() - .child(self.terminal.clone()), - ) .child(div().text_sm().child("Code Editor / Tree-sitter 基础")) .child(Input::new(&self.editor).h(px(220.0))) .child( diff --git a/nebula_gpui/src/views/mod.rs b/nebula_gpui/src/views/mod.rs index 55cdb16..7066585 100644 --- a/nebula_gpui/src/views/mod.rs +++ b/nebula_gpui/src/views/mod.rs @@ -1,2 +1 @@ pub mod component_gallery; -pub mod workspace;