merge: 同步主分支并保留全部专项与原生测试
This commit is contained in:
commit
518add7fa7
11 files changed
+177
-11
No files matched your search
@@ -28,7 +28,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-24.04, windows-2022, macos-15, macos-15-intel]
|
||||
os: [ubuntu-24.04, windows-2022, macos-26, macos-26-intel]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
@@ -52,6 +52,11 @@ jobs:
|
||||
libwayland-dev libxkbcommon-dev libxkbcommon-x11-dev \
|
||||
libx11-xcb-dev libxcb1-dev libxcb-render0-dev \
|
||||
libxcb-shape0-dev libxcb-xfixes0-dev libxcb-xinput-dev libxcb-xkb-dev libxi-dev
|
||||
- name: Require the AppKit SDK used by this PR
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
|
||||
test "${sdk_version%%.*}" -ge 26
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: "1.97.1"
|
||||
@@ -63,6 +68,10 @@ jobs:
|
||||
if: ${{ !cancelled() }}
|
||||
run: >-
|
||||
cargo test --locked -p nebula-settings reset::tests::
|
||||
- name: Test native titlebar alignment
|
||||
if: runner.os == 'macOS'
|
||||
run: >-
|
||||
cargo test --locked -p nebula --bin pebrel --features gpui-test-support platform::window_chrome::macos::tests::
|
||||
- name: Test all Python helpers
|
||||
if: ${{ !cancelled() }}
|
||||
run: python -m unittest discover -s scripts/tests -v
|
||||
@@ -74,7 +83,7 @@ jobs:
|
||||
run: cargo test --locked --workspace
|
||||
- name: Test all GPUI product and interaction cases
|
||||
if: ${{ !cancelled() }}
|
||||
run: cargo test --locked -p nebula --bin pebrel --features gpui-test-support
|
||||
run: cargo test --locked -p nebula --features gpui-test-support
|
||||
- name: Check source architecture
|
||||
if: ${{ !cancelled() && runner.os == 'Linux' }}
|
||||
run: python scripts/check_architecture.py
|
||||
|
||||
@@ -302,10 +302,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15
|
||||
- runner: macos-26
|
||||
architecture: aarch64
|
||||
native_architecture: arm64
|
||||
- runner: macos-15-intel
|
||||
- runner: macos-26-intel
|
||||
architecture: x86_64
|
||||
native_architecture: x86_64
|
||||
runs-on: ${{ matrix.runner }}
|
||||
@@ -323,6 +323,11 @@ jobs:
|
||||
- name: Verify native runner architecture
|
||||
run: test "$(uname -m)" = "${{ matrix.native_architecture }}"
|
||||
|
||||
- name: Require the modern AppKit SDK for native window controls
|
||||
run: |
|
||||
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
|
||||
test "${sdk_version%%.*}" -ge 26
|
||||
|
||||
- name: Test native DMG capacity and mounted contents
|
||||
run: python3 -m unittest scripts.tests.test_macos_dmg -v
|
||||
|
||||
|
||||
@@ -253,11 +253,11 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-15
|
||||
- runner: macos-26
|
||||
architecture: aarch64
|
||||
native_architecture: arm64
|
||||
asset_architecture: arm64
|
||||
- runner: macos-15-intel
|
||||
- runner: macos-26-intel
|
||||
architecture: x86_64
|
||||
native_architecture: x86_64
|
||||
asset_architecture: x64
|
||||
@@ -272,6 +272,10 @@ jobs:
|
||||
python-version: "3.13"
|
||||
- name: Verify native runner architecture
|
||||
run: test "$(uname -m)" = "${{ matrix.native_architecture }}"
|
||||
- name: Require the modern AppKit SDK for native window controls
|
||||
run: |
|
||||
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
|
||||
test "${sdk_version%%.*}" -ge 26
|
||||
- name: Test native DMG capacity and mounted contents
|
||||
run: python3 -m unittest scripts.tests.test_macos_dmg -v
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
+4
-1
@@ -128,7 +128,10 @@ Keep the extracted directory structure intact:
|
||||
Install [rustup](https://rustup.rs) and the build dependencies for your platform.
|
||||
The repository pins Rust 1.97.1 in `rust-toolchain.toml`. Linux dependency packages
|
||||
are listed in `.github/workflows/release.yml`; macOS requires Xcode command-line
|
||||
tools. Windows requires Windows 10 1809+ / 11 and a supported Rust linker toolchain.
|
||||
tools with macOS SDK 26 or newer (`xcrun --sdk macosx --show-sdk-version`) for
|
||||
the native Liquid Glass window controls. This build requirement does not raise
|
||||
the macOS 14 minimum runtime version. Windows requires Windows 10 1809+ / 11
|
||||
and a supported Rust linker toolchain.
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/Kuddev/pebrel
|
||||
|
||||
@@ -181,7 +181,10 @@ objc2-foundation = { version = "0.3.1", default-features = false, features = [
|
||||
objc2-app-kit = { version = "0.3.1", default-features = false, features = [
|
||||
"std",
|
||||
"NSColorSpace",
|
||||
"NSButton",
|
||||
"NSControl",
|
||||
"NSResponder",
|
||||
"NSToolbar",
|
||||
"NSView",
|
||||
"NSWindow",
|
||||
] }
|
||||
|
||||
@@ -87,6 +87,7 @@ impl NebulaWorkspace {
|
||||
cx: &mut Context<Self>,
|
||||
) -> gpui::Div {
|
||||
let top_tabs = self.tabs_position == nebula_settings::TabsPositionName::Top;
|
||||
let native_layout = crate::platform::window_chrome::layout(window);
|
||||
let bar = TitleBar::new()
|
||||
// Leave 8px above and below the existing 32px controls.
|
||||
.h(px(48.0))
|
||||
@@ -94,8 +95,11 @@ impl NebulaWorkspace {
|
||||
.when(settings_active, |bar| {
|
||||
bar.border_b_1().border_color(crate::gpui_shell::theme::settings_hairline(cx))
|
||||
})
|
||||
.when(top_tabs && native_layout.is_none(), |bar| {
|
||||
bar.pl(px(top_tabs::TOP_TAB_LEFT_INSET))
|
||||
})
|
||||
.when(top_tabs, |bar| {
|
||||
bar.pl(px(top_tabs::TOP_TAB_LEFT_INSET)).child(self.render_top_title_bar(
|
||||
bar.child(self.render_top_title_bar(
|
||||
files_active,
|
||||
git_active,
|
||||
settings_active,
|
||||
@@ -112,6 +116,14 @@ impl NebulaWorkspace {
|
||||
))
|
||||
});
|
||||
|
||||
// AppKit owns both the control group and its geometry. Read the live
|
||||
// frames so system layout, resize and full-screen transitions agree.
|
||||
let bar = if let Some((height, inset)) = native_layout {
|
||||
bar.h(px(height)).pl(px(inset))
|
||||
} else {
|
||||
bar
|
||||
};
|
||||
|
||||
div()
|
||||
.relative()
|
||||
.flex_shrink_0()
|
||||
|
||||
@@ -310,7 +310,7 @@ fn workspace_window_options(cx: &mut App, focus: bool, role: WindowRole) -> Wind
|
||||
)
|
||||
},
|
||||
);
|
||||
WindowOptions {
|
||||
crate::platform::window_chrome::configure_options(WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
window_min_size: Some(size(px(760.0), px(540.0)).min(&bounds.size)),
|
||||
titlebar: Some(TitleBar::title_bar_options()),
|
||||
@@ -318,7 +318,7 @@ fn workspace_window_options(cx: &mut App, focus: bool, role: WindowRole) -> Wind
|
||||
window_background: crate::gpui_shell::wallpaper::initial_background_appearance(),
|
||||
focus,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
},
|
||||
#[cfg(windows)]
|
||||
WindowRole::QuickTerminal => {
|
||||
@@ -382,6 +382,7 @@ fn open_workspace_window(
|
||||
let hwnd_out = hwnd_slot.clone();
|
||||
let handle = cx.open_window(options, move |window, cx| {
|
||||
window.set_window_title(crate::brand::NAME);
|
||||
crate::platform::window_chrome::configure(window);
|
||||
*hwnd_out.borrow_mut() = native_hwnd(window).unwrap_or_default();
|
||||
#[cfg(windows)]
|
||||
crate::gpui_shell::set_native_window_icon(window);
|
||||
|
||||
@@ -27,6 +27,8 @@ pub mod notifications;
|
||||
pub mod shell;
|
||||
pub mod shell_integration;
|
||||
pub mod startup;
|
||||
#[cfg(feature = "gpui-shell")]
|
||||
pub(crate) mod window_chrome;
|
||||
|
||||
pub use capabilities::CAPABILITIES;
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//! Native window decoration belongs to the platform boundary. Workspace code
|
||||
//! supplies ordinary GPUI options and uses optional geometry without depending
|
||||
//! on AppKit types or duplicating platform detection.
|
||||
|
||||
use gpui::{Window, WindowOptions};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
pub(crate) fn configure_options(options: WindowOptions) -> WindowOptions {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let mut options = options;
|
||||
if let Some(titlebar) = options.titlebar.as_mut() {
|
||||
// A manual position makes GPUI rewrite AppKit's titlebar container,
|
||||
// breaking the native toolbar layout.
|
||||
titlebar.traffic_light_position = None;
|
||||
}
|
||||
options.app_owns_titlebar_drag = true;
|
||||
options
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
options
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn configure(_window: &Window) {
|
||||
#[cfg(target_os = "macos")]
|
||||
macos::configure(_window);
|
||||
}
|
||||
|
||||
/// Height and left inset when native controls determine the titlebar geometry.
|
||||
pub(crate) fn layout(_window: &Window) -> Option<(f32, f32)> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Some(macos::layout(_window))
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//! Keep the standard window controls in AppKit's native toolbar hierarchy.
|
||||
|
||||
use gpui::Window;
|
||||
use objc2::{MainThreadOnly, rc::Retained};
|
||||
use objc2_app_kit::{NSToolbar, NSView, NSWindow, NSWindowButton, NSWindowToolbarStyle};
|
||||
use objc2_foundation::{NSRect, ns_string};
|
||||
use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
|
||||
|
||||
fn native_window(window: &Window) -> Option<Retained<NSWindow>> {
|
||||
let handle = HasWindowHandle::window_handle(window).ok()?;
|
||||
let RawWindowHandle::AppKit(handle) = handle.as_raw() else { return None };
|
||||
// SAFETY: GPUI owns this live NSView and calls the adapter on the UI thread.
|
||||
let view = unsafe { handle.ns_view.cast::<NSView>().as_ref() };
|
||||
view.window()
|
||||
}
|
||||
|
||||
pub(super) fn configure(window: &Window) {
|
||||
let Some(native) = native_window(window) else { return };
|
||||
let toolbar = NSToolbar::initWithIdentifier(
|
||||
NSToolbar::alloc(native.mtm()),
|
||||
ns_string!("pebrel-window-controls"),
|
||||
);
|
||||
// An empty native toolbar supplies the system's current window-control
|
||||
// treatment. AppKit retains it; GPUI continues to render the app controls.
|
||||
native.setToolbarStyle(NSWindowToolbarStyle::Unified);
|
||||
native.setToolbar(Some(&toolbar));
|
||||
}
|
||||
|
||||
pub(super) fn layout(window: &Window) -> (f32, f32) {
|
||||
if window.is_fullscreen() {
|
||||
return (52.0, 8.0);
|
||||
}
|
||||
let Some(native) = native_window(window) else { return (52.0, 96.0) };
|
||||
let Some(close) = native.standardWindowButton(NSWindowButton::CloseButton) else {
|
||||
return (52.0, 96.0);
|
||||
};
|
||||
let Some(zoom) = native.standardWindowButton(NSWindowButton::ZoomButton) else {
|
||||
return (52.0, 96.0);
|
||||
};
|
||||
let Some(content) = native.contentView() else { return (52.0, 96.0) };
|
||||
let close = close.convertRect_toView(close.bounds(), Some(&content));
|
||||
let zoom = zoom.convertRect_toView(zoom.bounds(), Some(&content));
|
||||
control_layout(close, zoom, content.bounds(), content.isFlipped())
|
||||
}
|
||||
|
||||
fn control_layout(close: NSRect, zoom: NSRect, content: NSRect, flipped: bool) -> (f32, f32) {
|
||||
let center = close.origin.y + close.size.height / 2.0;
|
||||
let center_from_top = if flipped {
|
||||
center - content.origin.y
|
||||
} else {
|
||||
content.origin.y + content.size.height - center
|
||||
};
|
||||
(
|
||||
2.0 * center_from_top as f32,
|
||||
(zoom.origin.x + zoom.size.width - content.origin.x + 16.0) as f32,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use objc2_foundation::{NSPoint, NSSize};
|
||||
|
||||
#[test]
|
||||
fn app_controls_follow_native_centers_and_leave_room_for_the_group() {
|
||||
for height in [600.0, 900.0] {
|
||||
for (inset, center) in [(19.0, 26.0), (12.0, 20.0)] {
|
||||
for flipped in [false, true] {
|
||||
let content = NSRect::new(NSPoint::new(5.0, 3.0), NSSize::new(900.0, height));
|
||||
let y = 3.0 + if flipped { center - 7.0 } else { height - center - 7.0 };
|
||||
let close = NSRect::new(NSPoint::new(5.0 + inset, y), NSSize::new(14.0, 14.0));
|
||||
let zoom = NSRect::new(NSPoint::new(5.0 + inset + 46.0, y), close.size);
|
||||
let (bar_height, left_padding) = control_layout(close, zoom, content, flipped);
|
||||
assert_eq!(bar_height / 2.0, center as f32);
|
||||
assert_eq!(left_padding, (inset + 46.0 + 14.0 + 16.0) as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,11 @@ role = sys.argv[2]
|
||||
if role != 'grandchild':
|
||||
next_role = 'child' if role == 'parent' else 'grandchild'
|
||||
subprocess.Popen([sys.executable, '-I', '-S', __file__, str(root), next_role])
|
||||
(root / (role + '.pid')).write_text(str(os.getpid()))
|
||||
# Publish the PID only after the contents are complete. The parent polls for
|
||||
# file existence, so creating the final path first exposes an empty file.
|
||||
pid_path = root / (role + '.pid.tmp')
|
||||
pid_path.write_text(str(os.getpid()))
|
||||
pid_path.replace(root / (role + '.pid'))
|
||||
while True:
|
||||
if role == 'parent' and (root / 'exit').exists():
|
||||
code = root / 'exit-code'
|
||||
|
||||
Reference in new issue
Block a user