Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/gpui/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ pub struct PlatformNativeToolbarButtonItem {
pub icon: Option<SharedString>,
pub image_url: Option<SharedString>,
pub image_circular: bool,
pub hosted_surface_view: Option<*mut std::ffi::c_void>,
pub on_click: Option<Box<dyn Fn()>>,
}

Expand Down Expand Up @@ -711,6 +712,7 @@ pub struct PlatformNativeToolbarMenuButtonItem {
pub icon: Option<SharedString>,
pub image_url: Option<SharedString>,
pub image_circular: bool,
pub hosted_surface_view: Option<*mut std::ffi::c_void>,
pub shows_indicator: bool,
pub items: Vec<PlatformNativeToolbarMenuItemData>,
pub on_select: Option<Box<dyn Fn(usize)>>,
Expand Down Expand Up @@ -1067,6 +1069,10 @@ pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
std::ptr::null_mut()
}

fn raw_native_window_ptr(&self) -> *mut std::ffi::c_void {
std::ptr::null_mut()
}

fn native_controls(&self) -> Option<&dyn native_controls::PlatformNativeControls> {
None
}
Expand Down
135 changes: 125 additions & 10 deletions crates/gpui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ pub struct NativeToolbarButton {
icon: Option<SharedString>,
image_url: Option<SharedString>,
image_circular: bool,
hosted_view: Option<AnyView>,
on_click: Option<Box<dyn Fn(&NativeToolbarClickEvent, &mut Window, &mut App) + 'static>>,
}

Expand All @@ -724,6 +725,7 @@ impl NativeToolbarButton {
icon: None,
image_url: None,
image_circular: false,
hosted_view: None,
on_click: None,
}
}
Expand Down Expand Up @@ -753,6 +755,15 @@ impl NativeToolbarButton {
self
}

/// Replaces the native icon/image with GPUI-rendered visual content while
/// preserving the native toolbar button's interaction behavior.
pub fn content_view<V: Render>(mut self, view: Entity<V>) -> Self {
self.hosted_view = Some(AnyView::from(view));
self.icon = None;
self.image_url = None;
self
}

/// Registers a callback invoked when this button is clicked.
pub fn on_click(
mut self,
Expand Down Expand Up @@ -1179,6 +1190,7 @@ pub struct NativeToolbarMenuButton {
icon: Option<SharedString>,
image_url: Option<SharedString>,
image_circular: bool,
hosted_view: Option<AnyView>,
shows_indicator: bool,
items: Vec<NativeToolbarMenuItem>,
on_select:
Expand All @@ -1199,6 +1211,7 @@ impl NativeToolbarMenuButton {
icon: None,
image_url: None,
image_circular: false,
hosted_view: None,
shows_indicator: true,
items,
on_select: None,
Expand Down Expand Up @@ -1230,6 +1243,15 @@ impl NativeToolbarMenuButton {
self
}

/// Replaces the native icon/image with GPUI-rendered visual content while
/// preserving the native toolbar menu button's interaction behavior.
pub fn content_view<V: Render>(mut self, view: Entity<V>) -> Self {
self.hosted_view = Some(AnyView::from(view));
self.icon = None;
self.image_url = None;
self
}

/// Controls whether the dropdown chevron indicator is visible.
pub fn shows_indicator(mut self, shows: bool) -> Self {
self.shows_indicator = shows;
Expand Down Expand Up @@ -1354,6 +1376,11 @@ pub struct NativeToolbar {
items: Vec<NativeToolbarItem>,
}

struct NativeToolbarHostedSurface {
item_id: SharedString,
view: AnyView,
}

impl NativeToolbar {
/// Creates a toolbar configuration with a stable identifier.
pub fn new(identifier: impl Into<SharedString>) -> Self {
Expand Down Expand Up @@ -1407,7 +1434,8 @@ impl NativeToolbar {
self,
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
invalidator: WindowInvalidator,
) -> PlatformNativeToolbar {
) -> (PlatformNativeToolbar, Vec<NativeToolbarHostedSurface>) {
let mut hosted_surfaces = Vec::new();
let items = self
.items
.into_iter()
Expand All @@ -1425,13 +1453,21 @@ impl NativeToolbar {
)
});

if let Some(hosted_view) = button.hosted_view {
hosted_surfaces.push(NativeToolbarHostedSurface {
item_id: button.id.clone(),
view: hosted_view,
});
}

PlatformNativeToolbarItem::Button(PlatformNativeToolbarButtonItem {
id: button.id,
label: button.label,
tool_tip: button.tool_tip,
icon: button.icon,
image_url: button.image_url,
image_circular: button.image_circular,
hosted_surface_view: None,
on_click,
})
}
Expand Down Expand Up @@ -1651,6 +1687,13 @@ impl NativeToolbar {
})
}
NativeToolbarItem::MenuButton(menu_button) => {
if let Some(hosted_view) = menu_button.hosted_view {
hosted_surfaces.push(NativeToolbarHostedSurface {
item_id: menu_button.id.clone(),
view: hosted_view,
});
}

let btn_id = menu_button.id.clone();
let on_select = menu_button.on_select.map(|handler| {
schedule_native_toolbar_callback(
Expand All @@ -1671,6 +1714,7 @@ impl NativeToolbar {
icon: menu_button.icon,
image_url: menu_button.image_url,
image_circular: menu_button.image_circular,
hosted_surface_view: None,
shows_indicator: menu_button.shows_indicator,
items: convert_toolbar_menu_items(&menu_button.items),
on_select,
Expand All @@ -1686,14 +1730,17 @@ impl NativeToolbar {
})
.collect();

PlatformNativeToolbar {
identifier: self.identifier,
title: self.title,
display_mode: self.display_mode.into(),
size_mode: self.size_mode.into(),
shows_baseline_separator: self.shows_baseline_separator,
items,
}
(
PlatformNativeToolbar {
identifier: self.identifier,
title: self.title,
display_mode: self.display_mode.into(),
size_mode: self.size_mode.into(),
shows_baseline_separator: self.shows_baseline_separator,
items,
},
hosted_surfaces,
)
}
}

Expand Down Expand Up @@ -3269,6 +3316,8 @@ pub struct Window {
active_popover_surface: Option<SurfaceId>,
#[cfg(target_os = "macos")]
active_panel_surface: Option<SurfaceId>,
#[cfg(target_os = "macos")]
active_toolbar_surfaces: Vec<SurfaceId>,
}

#[derive(Clone, Debug, Default)]
Expand Down Expand Up @@ -3798,6 +3847,8 @@ impl Window {
active_popover_surface: None,
#[cfg(target_os = "macos")]
active_panel_surface: None,
#[cfg(target_os = "macos")]
active_toolbar_surfaces: Vec::new(),
})
}

Expand Down Expand Up @@ -4425,6 +4476,14 @@ impl Window {
}
}

#[cfg(target_os = "macos")]
fn clear_hosted_toolbar_surfaces(&mut self) {
let surface_ids: Vec<_> = self.active_toolbar_surfaces.drain(..).collect();
for surface_id in surface_ids {
self.unregister_surface(surface_id);
}
}

#[cfg(target_os = "macos")]
fn prepare_hosted_surface(
&mut self,
Expand Down Expand Up @@ -4485,8 +4544,47 @@ impl Window {
/// On macOS this installs an `NSToolbar` with native items.
/// On other platforms this is currently a no-op.
pub fn set_native_toolbar(&mut self, toolbar: Option<NativeToolbar>) {
#[cfg(target_os = "macos")]
self.clear_hosted_toolbar_surfaces();

let toolbar = toolbar.map(|toolbar| {
toolbar.into_platform(self.next_frame_callbacks.clone(), self.invalidator.clone())
let (mut platform_toolbar, hosted_surfaces) =
toolbar.into_platform(self.next_frame_callbacks.clone(), self.invalidator.clone());

#[cfg(target_os = "macos")]
for hosted_surface in hosted_surfaces {
let handle = self.register_surface(hosted_surface.view);
self.active_toolbar_surfaces.push(handle.id);

if let Some(PlatformNativeToolbarItem::Button(button_item)) = platform_toolbar
.items
.iter_mut()
.find(|item| {
matches!(
item,
PlatformNativeToolbarItem::Button(button_item)
if button_item.id == hosted_surface.item_id
)
})
{
button_item.hosted_surface_view = Some(handle.native_view_ptr);
} else if let Some(PlatformNativeToolbarItem::MenuButton(menu_button_item)) =
platform_toolbar.items.iter_mut().find(|item| {
matches!(
item,
PlatformNativeToolbarItem::MenuButton(menu_button_item)
if menu_button_item.id == hosted_surface.item_id
)
})
{
menu_button_item.hosted_surface_view = Some(handle.native_view_ptr);
}
}

#[cfg(not(target_os = "macos"))]
let _ = hosted_surfaces;

platform_toolbar
});
self.platform_window.set_native_toolbar(toolbar);
}
Expand Down Expand Up @@ -4644,6 +4742,23 @@ impl Window {
}
}

/// Returns the bounds of a native toolbar item in window coordinates.
///
/// On macOS this resolves the installed `NSToolbarItem` by identifier and
/// returns its current frame in the content-view-local coordinate space. On
/// unsupported platforms this returns `None`.
pub fn native_toolbar_item_bounds(&self, item_id: &str) -> Option<Bounds<Pixels>> {
self.platform_window
.native_controls()
.and_then(|native_controls| {
let native_window = self.platform_window.raw_native_window_ptr();
if native_window.is_null() {
return None;
}
native_controls.get_toolbar_item_frame(native_window, item_id)
})
}

/// Returns the platform's native controls implementation for creating
/// and updating native UI elements (buttons, text fields, etc.).
pub fn native_controls(&self) -> &dyn crate::platform::native_controls::PlatformNativeControls {
Expand Down
19 changes: 16 additions & 3 deletions crates/gpui_macos/src/native_controls/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ pub(crate) unsafe fn is_native_panel_visible(panel: id) -> bool {
}
}

/// Gets the screen frame of a toolbar item by its identifier.
/// Gets the content-view-local frame of a toolbar item by its identifier.
/// Returns None if the toolbar or item is not found.
pub(crate) unsafe fn get_toolbar_item_screen_frame(
window: id,
Expand Down Expand Up @@ -361,8 +361,21 @@ pub(crate) unsafe fn get_toolbar_item_screen_frame(
if view_window == nil {
return None;
}
let screen_rect: NSRect = msg_send![view_window, convertRectToScreen: window_rect];
return Some(screen_rect);
let content_view: id = msg_send![view_window, contentView];
if content_view == nil {
return None;
}
let content_rect: NSRect =
msg_send![content_view, convertRect: window_rect fromView: nil];
let content_bounds: NSRect = msg_send![content_view, bounds];
let flipped_rect = NSRect::new(
NSPoint::new(
content_rect.origin.x,
content_bounds.size.height - content_rect.origin.y - content_rect.size.height,
),
content_rect.size,
);
return Some(flipped_rect);
}
}

Expand Down
Loading
Loading