Merge 222809b027
into b1b60bb7fe
This commit is contained in:
commit
a788db60f9
17 changed files with 1356 additions and 52 deletions
|
@ -363,6 +363,8 @@
|
|||
// Whether to show code action buttons in the editor toolbar.
|
||||
"code_actions": false
|
||||
},
|
||||
// Whether to allow windows to tab together based on the user’s tabbing preference (macOS only).
|
||||
"use_system_window_tabs": false,
|
||||
// Titlebar related settings
|
||||
"title_bar": {
|
||||
// Whether to show the branch icon beside branch switcher in the titlebar.
|
||||
|
|
|
@ -62,6 +62,7 @@ impl AgentNotification {
|
|||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
tabbing_identifier: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,5 +66,6 @@ fn notification_window_options(
|
|||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
tabbing_identifier: None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ fn build_window_options(display_id: DisplayId, bounds: Bounds<Pixels>) -> Window
|
|||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
tabbing_identifier: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
rc::{Rc, Weak},
|
||||
sync::{Arc, atomic::Ordering::SeqCst},
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
|
@ -17,6 +17,7 @@ use futures::{
|
|||
channel::oneshot,
|
||||
future::{LocalBoxFuture, Shared},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use parking_lot::RwLock;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
|
@ -39,8 +40,8 @@ use crate::{
|
|||
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
||||
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
|
||||
PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
|
||||
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
|
||||
WindowHandle, WindowId, WindowInvalidator,
|
||||
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window,
|
||||
WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
|
||||
colors::{Colors, GlobalColors},
|
||||
current_platform, hash, init_app_menus,
|
||||
};
|
||||
|
@ -237,6 +238,303 @@ type WindowClosedHandler = Box<dyn FnMut(&mut App)>;
|
|||
type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut App) + 'static>;
|
||||
type NewEntityListener = Box<dyn FnMut(AnyEntity, &mut Option<&mut Window>, &mut App) + 'static>;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct SystemWindowTab {
|
||||
pub id: WindowId,
|
||||
pub title: SharedString,
|
||||
pub handle: AnyWindowHandle,
|
||||
pub last_active_at: Instant,
|
||||
}
|
||||
|
||||
impl SystemWindowTab {
|
||||
/// Create a new instance of the window tab.
|
||||
pub fn new(title: SharedString, handle: AnyWindowHandle) -> Self {
|
||||
Self {
|
||||
id: handle.id,
|
||||
title,
|
||||
handle,
|
||||
last_active_at: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A controller for managing window tabs.
|
||||
#[derive(Default)]
|
||||
pub struct SystemWindowTabController {
|
||||
visible: Option<bool>,
|
||||
tab_groups: FxHashMap<usize, Vec<SystemWindowTab>>,
|
||||
}
|
||||
|
||||
impl Global for SystemWindowTabController {}
|
||||
|
||||
impl SystemWindowTabController {
|
||||
/// Create a new instance of the window tab controller.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
visible: None,
|
||||
tab_groups: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the global window tab controller.
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.set_global(SystemWindowTabController::new());
|
||||
}
|
||||
|
||||
/// Get all tab groups.
|
||||
pub fn tab_groups(&self) -> &FxHashMap<usize, Vec<SystemWindowTab>> {
|
||||
&self.tab_groups
|
||||
}
|
||||
|
||||
/// Get the next tab group window handle.
|
||||
pub fn get_next_tab_group_window(cx: &mut App, id: WindowId) -> Option<&AnyWindowHandle> {
|
||||
let controller = cx.global::<SystemWindowTabController>();
|
||||
let current_group = controller
|
||||
.tab_groups
|
||||
.iter()
|
||||
.find_map(|(group, tabs)| tabs.iter().find(|tab| tab.id == id).map(|_| group));
|
||||
|
||||
let current_group = current_group?;
|
||||
let mut group_ids: Vec<_> = controller.tab_groups.keys().collect();
|
||||
let idx = group_ids.iter().position(|g| *g == current_group)?;
|
||||
let next_idx = (idx + 1) % group_ids.len();
|
||||
|
||||
controller
|
||||
.tab_groups
|
||||
.get(group_ids[next_idx])
|
||||
.and_then(|tabs| {
|
||||
tabs.iter()
|
||||
.max_by_key(|tab| tab.last_active_at)
|
||||
.or_else(|| tabs.first())
|
||||
.map(|tab| &tab.handle)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the previous tab group window handle.
|
||||
pub fn get_prev_tab_group_window(cx: &mut App, id: WindowId) -> Option<&AnyWindowHandle> {
|
||||
let controller = cx.global::<SystemWindowTabController>();
|
||||
let current_group = controller
|
||||
.tab_groups
|
||||
.iter()
|
||||
.find_map(|(group, tabs)| tabs.iter().find(|tab| tab.id == id).map(|_| group));
|
||||
|
||||
let current_group = current_group?;
|
||||
let mut group_ids: Vec<_> = controller.tab_groups.keys().collect();
|
||||
let idx = group_ids.iter().position(|g| *g == current_group)?;
|
||||
let prev_idx = if idx == 0 {
|
||||
group_ids.len() - 1
|
||||
} else {
|
||||
idx - 1
|
||||
};
|
||||
|
||||
controller
|
||||
.tab_groups
|
||||
.get(group_ids[prev_idx])
|
||||
.and_then(|tabs| {
|
||||
tabs.iter()
|
||||
.max_by_key(|tab| tab.last_active_at)
|
||||
.or_else(|| tabs.first())
|
||||
.map(|tab| &tab.handle)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get all tabs in the same window.
|
||||
pub fn tabs(&self, id: WindowId) -> Option<&Vec<SystemWindowTab>> {
|
||||
let tab_group = self
|
||||
.tab_groups
|
||||
.iter()
|
||||
.find_map(|(group, tabs)| tabs.iter().find(|tab| tab.id == id).map(|_| *group));
|
||||
|
||||
if let Some(tab_group) = tab_group {
|
||||
self.tab_groups.get(&tab_group)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the visibility of the system window tab controller.
|
||||
pub fn init_visible(cx: &mut App, visible: bool) {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
if controller.visible.is_none() {
|
||||
controller.visible = Some(visible);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the visibility of the system window tab controller.
|
||||
pub fn is_visible(&self) -> bool {
|
||||
self.visible.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Set the visibility of the system window tab controller.
|
||||
pub fn set_visible(cx: &mut App, visible: bool) {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
controller.visible = Some(visible);
|
||||
}
|
||||
|
||||
/// Update the last active of a window.
|
||||
pub fn update_last_active(cx: &mut App, id: WindowId) {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
for windows in controller.tab_groups.values_mut() {
|
||||
for tab in windows.iter_mut() {
|
||||
if tab.id == id {
|
||||
tab.last_active_at = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the position of a tab within its group.
|
||||
pub fn update_tab_position(cx: &mut App, id: WindowId, ix: usize) {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
for (_, windows) in controller.tab_groups.iter_mut() {
|
||||
if let Some(current_pos) = windows.iter().position(|tab| tab.id == id) {
|
||||
if ix < windows.len() && current_pos != ix {
|
||||
let window_tab = windows.remove(current_pos);
|
||||
windows.insert(ix, window_tab);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the title of a tab.
|
||||
pub fn update_tab_title(cx: &mut App, id: WindowId, title: SharedString) {
|
||||
let controller = cx.global::<SystemWindowTabController>();
|
||||
let tab = controller
|
||||
.tab_groups
|
||||
.values()
|
||||
.flat_map(|windows| windows.iter())
|
||||
.find(|tab| tab.id == id);
|
||||
|
||||
if tab.map_or(true, |t| t.title == title) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
for windows in controller.tab_groups.values_mut() {
|
||||
for tab in windows.iter_mut() {
|
||||
if tab.id == id {
|
||||
tab.title = title.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a tab into a tab group.
|
||||
pub fn add_tab(cx: &mut App, id: WindowId, tabs: Vec<SystemWindowTab>) {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
let Some(tab) = tabs.clone().into_iter().find(|tab| tab.id == id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut expected_tab_ids: Vec<_> = tabs
|
||||
.iter()
|
||||
.filter(|tab| tab.id != id)
|
||||
.map(|tab| tab.id)
|
||||
.sorted()
|
||||
.collect();
|
||||
|
||||
let mut tab_group_id = None;
|
||||
for (group_id, group_tabs) in &controller.tab_groups {
|
||||
let tab_ids: Vec<_> = group_tabs.iter().map(|tab| tab.id).sorted().collect();
|
||||
if tab_ids == expected_tab_ids {
|
||||
tab_group_id = Some(*group_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tab_group_id) = tab_group_id {
|
||||
if let Some(tabs) = controller.tab_groups.get_mut(&tab_group_id) {
|
||||
tabs.push(tab);
|
||||
}
|
||||
} else {
|
||||
let new_group_id = controller.tab_groups.len();
|
||||
controller.tab_groups.insert(new_group_id, tabs);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a tab from a tab group.
|
||||
pub fn remove_tab(cx: &mut App, id: WindowId) -> Option<SystemWindowTab> {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
let mut removed_tab = None;
|
||||
|
||||
controller.tab_groups.retain(|_, tabs| {
|
||||
if let Some(pos) = tabs.iter().position(|tab| tab.id == id) {
|
||||
removed_tab = Some(tabs.remove(pos));
|
||||
}
|
||||
!tabs.is_empty()
|
||||
});
|
||||
|
||||
removed_tab
|
||||
}
|
||||
|
||||
/// Move a tab to a new tab group.
|
||||
pub fn move_tab_to_new_window(cx: &mut App, id: WindowId) {
|
||||
let mut removed_tab = Self::remove_tab(cx, id);
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
|
||||
if let Some(tab) = removed_tab {
|
||||
let new_group_id = controller.tab_groups.keys().max().map_or(0, |k| k + 1);
|
||||
controller.tab_groups.insert(new_group_id, vec![tab]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge all tab groups into a single group.
|
||||
pub fn merge_all_windows(cx: &mut App, id: WindowId) {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
let Some(initial_tabs) = controller.tabs(id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut all_tabs = initial_tabs.clone();
|
||||
for tabs in controller.tab_groups.values() {
|
||||
all_tabs.extend(
|
||||
tabs.iter()
|
||||
.filter(|tab| !initial_tabs.contains(tab))
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
|
||||
controller.tab_groups.clear();
|
||||
controller.tab_groups.insert(0, all_tabs);
|
||||
}
|
||||
|
||||
/// Selects the next tab in the tab group in the trailing direction.
|
||||
pub fn select_next_tab(cx: &mut App, id: WindowId) {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
let Some(tabs) = controller.tabs(id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let current_index = tabs.iter().position(|tab| tab.id == id).unwrap();
|
||||
let next_index = (current_index + 1) % tabs.len();
|
||||
|
||||
let _ = &tabs[next_index].handle.update(cx, |_, window, _| {
|
||||
window.activate_window();
|
||||
});
|
||||
}
|
||||
|
||||
/// Selects the previous tab in the tab group in the leading direction.
|
||||
pub fn select_previous_tab(cx: &mut App, id: WindowId) {
|
||||
let mut controller = cx.global_mut::<SystemWindowTabController>();
|
||||
let Some(tabs) = controller.tabs(id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let current_index = tabs.iter().position(|tab| tab.id == id).unwrap();
|
||||
let previous_index = if current_index == 0 {
|
||||
tabs.len() - 1
|
||||
} else {
|
||||
current_index - 1
|
||||
};
|
||||
|
||||
let _ = &tabs[previous_index].handle.update(cx, |_, window, _| {
|
||||
window.activate_window();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the state of the full application, and passed as a reference to a variety of callbacks.
|
||||
/// Other [Context] derefs to this type.
|
||||
/// You need a reference to an `App` to access the state of a [Entity].
|
||||
|
@ -369,6 +667,7 @@ impl App {
|
|||
});
|
||||
|
||||
init_app_menus(platform.as_ref(), &app.borrow());
|
||||
SystemWindowTabController::init(&mut app.borrow_mut());
|
||||
|
||||
platform.on_keyboard_layout_change(Box::new({
|
||||
let app = Rc::downgrade(&app);
|
||||
|
|
|
@ -40,8 +40,8 @@ use crate::{
|
|||
DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
|
||||
ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
|
||||
Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
|
||||
ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer, SvgSize, Task, TaskLabel, Window,
|
||||
WindowControlArea, hash, point, px, size,
|
||||
ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer, SvgSize, SystemWindowTab, Task,
|
||||
TaskLabel, Window, WindowControlArea, hash, point, px, size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use async_task::Runnable;
|
||||
|
@ -500,9 +500,26 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
|||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
|
||||
// macOS specific methods
|
||||
fn get_title(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
|
||||
None
|
||||
}
|
||||
fn tab_bar_visible(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn set_edited(&mut self, _edited: bool) {}
|
||||
fn show_character_palette(&self) {}
|
||||
fn titlebar_double_click(&self) {}
|
||||
fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
|
||||
fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
|
||||
fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
|
||||
fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
|
||||
fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
|
||||
fn merge_all_windows(&self) {}
|
||||
fn move_tab_to_new_window(&self) {}
|
||||
fn toggle_window_tab_overview(&self) {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_raw_handle(&self) -> windows::HWND;
|
||||
|
@ -1105,6 +1122,9 @@ pub struct WindowOptions {
|
|||
/// Whether to use client or server side decorations. Wayland only
|
||||
/// Note that this may be ignored.
|
||||
pub window_decorations: Option<WindowDecorations>,
|
||||
|
||||
/// Tab group name, allows opening the window as a native tab on macOS 10.12+. Windows with the same tabbing identifier will be grouped together.
|
||||
pub tabbing_identifier: Option<String>,
|
||||
}
|
||||
|
||||
/// The variables that can be configured when creating a new window
|
||||
|
@ -1144,6 +1164,8 @@ pub(crate) struct WindowParams {
|
|||
pub display_id: Option<DisplayId>,
|
||||
|
||||
pub window_min_size: Option<Size<Pixels>>,
|
||||
#[cfg(target_os = "macos")]
|
||||
pub tabbing_identifier: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents the status of how a window should be opened.
|
||||
|
@ -1194,6 +1216,7 @@ impl Default for WindowOptions {
|
|||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
tabbing_identifier: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ use crate::{
|
|||
ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
|
||||
PlatformInput, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions,
|
||||
ScaledPixels, Size, Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
|
||||
WindowControlArea, WindowKind, WindowParams, platform::PlatformInputHandler, point, px, size,
|
||||
ScaledPixels, SharedString, Size, SystemWindowTab, Timer, WindowAppearance,
|
||||
WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowKind, WindowParams,
|
||||
dispatch_get_main_queue, dispatch_sys::dispatch_async_f, platform::PlatformInputHandler, point,
|
||||
px, size,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
|
@ -24,6 +26,7 @@ use cocoa::{
|
|||
NSUserDefaults,
|
||||
},
|
||||
};
|
||||
|
||||
use core_graphics::display::{CGDirectDisplayID, CGPoint, CGRect};
|
||||
use ctor::ctor;
|
||||
use futures::channel::oneshot;
|
||||
|
@ -82,6 +85,12 @@ type NSDragOperation = NSUInteger;
|
|||
const NSDragOperationNone: NSDragOperation = 0;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSDragOperationCopy: NSDragOperation = 1;
|
||||
#[derive(PartialEq)]
|
||||
pub enum UserTabbingPreference {
|
||||
Never,
|
||||
Always,
|
||||
InFullScreen,
|
||||
}
|
||||
|
||||
#[link(name = "CoreGraphics", kind = "framework")]
|
||||
unsafe extern "C" {
|
||||
|
@ -343,6 +352,36 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C
|
|||
conclude_drag_operation as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(addTitlebarAccessoryViewController:),
|
||||
add_titlebar_accessory_view_controller as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(moveTabToNewWindow:),
|
||||
move_tab_to_new_window as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(mergeAllWindows:),
|
||||
merge_all_windows as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(selectNextTab:),
|
||||
select_next_tab as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(selectPreviousTab:),
|
||||
select_previous_tab as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.add_method(
|
||||
sel!(toggleTabBar:),
|
||||
toggle_tab_bar as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.register()
|
||||
}
|
||||
}
|
||||
|
@ -375,6 +414,11 @@ struct MacWindowState {
|
|||
// Whether the next left-mouse click is also the focusing click.
|
||||
first_mouse: bool,
|
||||
fullscreen_restore_bounds: Bounds<Pixels>,
|
||||
move_tab_to_new_window_callback: Option<Box<dyn FnMut()>>,
|
||||
merge_all_windows_callback: Option<Box<dyn FnMut()>>,
|
||||
select_next_tab_callback: Option<Box<dyn FnMut()>>,
|
||||
select_previous_tab_callback: Option<Box<dyn FnMut()>>,
|
||||
toggle_tab_bar_callback: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
impl MacWindowState {
|
||||
|
@ -534,6 +578,7 @@ impl MacWindow {
|
|||
show,
|
||||
display_id,
|
||||
window_min_size,
|
||||
tabbing_identifier,
|
||||
}: WindowParams,
|
||||
executor: ForegroundExecutor,
|
||||
renderer_context: renderer::Context,
|
||||
|
@ -541,7 +586,12 @@ impl MacWindow {
|
|||
unsafe {
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
|
||||
let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: NO];
|
||||
let allows_automatic_window_tabbing = tabbing_identifier.is_some();
|
||||
if allows_automatic_window_tabbing {
|
||||
let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: YES];
|
||||
} else {
|
||||
let () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing: NO];
|
||||
}
|
||||
|
||||
let mut style_mask;
|
||||
if let Some(titlebar) = titlebar.as_ref() {
|
||||
|
@ -660,6 +710,11 @@ impl MacWindow {
|
|||
external_files_dragged: false,
|
||||
first_mouse: false,
|
||||
fullscreen_restore_bounds: Bounds::default(),
|
||||
move_tab_to_new_window_callback: None,
|
||||
merge_all_windows_callback: None,
|
||||
select_next_tab_callback: None,
|
||||
select_previous_tab_callback: None,
|
||||
toggle_tab_bar_callback: None,
|
||||
})));
|
||||
|
||||
(*native_window).set_ivar(
|
||||
|
@ -714,6 +769,11 @@ impl MacWindow {
|
|||
WindowKind::Normal => {
|
||||
native_window.setLevel_(NSNormalWindowLevel);
|
||||
native_window.setAcceptsMouseMovedEvents_(YES);
|
||||
|
||||
if let Some(tabbing_identifier) = tabbing_identifier {
|
||||
let tabbing_id = NSString::alloc(nil).init_str(tabbing_identifier.as_str());
|
||||
let _: () = msg_send![native_window, setTabbingIdentifier: tabbing_id];
|
||||
}
|
||||
}
|
||||
WindowKind::PopUp => {
|
||||
// Use a tracking area to allow receiving MouseMoved events even when
|
||||
|
@ -742,6 +802,38 @@ impl MacWindow {
|
|||
}
|
||||
}
|
||||
|
||||
let app = NSApplication::sharedApplication(nil);
|
||||
let main_window: id = msg_send![app, mainWindow];
|
||||
if allows_automatic_window_tabbing
|
||||
&& !main_window.is_null()
|
||||
&& main_window != native_window
|
||||
{
|
||||
let main_window_is_fullscreen = main_window
|
||||
.styleMask()
|
||||
.contains(NSWindowStyleMask::NSFullScreenWindowMask);
|
||||
let user_tabbing_preference = Self::get_user_tabbing_preference()
|
||||
.unwrap_or(UserTabbingPreference::InFullScreen);
|
||||
let should_add_as_tab = user_tabbing_preference == UserTabbingPreference::Always
|
||||
|| user_tabbing_preference == UserTabbingPreference::InFullScreen
|
||||
&& main_window_is_fullscreen;
|
||||
|
||||
if should_add_as_tab {
|
||||
let main_window_can_tab: BOOL =
|
||||
msg_send![main_window, respondsToSelector: sel!(addTabbedWindow:ordered:)];
|
||||
let main_window_visible: BOOL = msg_send![main_window, isVisible];
|
||||
|
||||
if main_window_can_tab == YES && main_window_visible == YES {
|
||||
let _: () = msg_send![main_window, addTabbedWindow: native_window ordered: NSWindowOrderingMode::NSWindowAbove];
|
||||
|
||||
// Ensure the window is visible immediately after adding the tab, since the tab bar is updated with a new entry at this point.
|
||||
// Note: Calling orderFront here can break fullscreen mode (makes fullscreen windows exit fullscreen), so only do this if the main window is not fullscreen.
|
||||
if !main_window_is_fullscreen {
|
||||
let _: () = msg_send![native_window, orderFront: nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if focus && show {
|
||||
native_window.makeKeyAndOrderFront_(nil);
|
||||
} else if show {
|
||||
|
@ -796,6 +888,33 @@ impl MacWindow {
|
|||
window_handles
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_user_tabbing_preference() -> Option<UserTabbingPreference> {
|
||||
unsafe {
|
||||
let defaults: id = NSUserDefaults::standardUserDefaults();
|
||||
let domain = NSString::alloc(nil).init_str("NSGlobalDomain");
|
||||
let key = NSString::alloc(nil).init_str("AppleWindowTabbingMode");
|
||||
|
||||
let dict: id = msg_send![defaults, persistentDomainForName: domain];
|
||||
let value: id = if !dict.is_null() {
|
||||
msg_send![dict, objectForKey: key]
|
||||
} else {
|
||||
nil
|
||||
};
|
||||
|
||||
let value_str = if !value.is_null() {
|
||||
CStr::from_ptr(NSString::UTF8String(value)).to_string_lossy()
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
|
||||
match value_str.as_ref() {
|
||||
"manual" => Some(UserTabbingPreference::Never),
|
||||
"always" => Some(UserTabbingPreference::Always),
|
||||
_ => Some(UserTabbingPreference::InFullScreen),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MacWindow {
|
||||
|
@ -851,6 +970,46 @@ impl PlatformWindow for MacWindow {
|
|||
.detach();
|
||||
}
|
||||
|
||||
fn merge_all_windows(&self) {
|
||||
let native_window = self.0.lock().native_window;
|
||||
unsafe extern "C" fn merge_windows_async(context: *mut std::ffi::c_void) {
|
||||
let native_window = context as id;
|
||||
let _: () = msg_send![native_window, mergeAllWindows:nil];
|
||||
}
|
||||
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
native_window as *mut std::ffi::c_void,
|
||||
Some(merge_windows_async),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn move_tab_to_new_window(&self) {
|
||||
let native_window = self.0.lock().native_window;
|
||||
unsafe extern "C" fn move_tab_async(context: *mut std::ffi::c_void) {
|
||||
let native_window = context as id;
|
||||
let _: () = msg_send![native_window, moveTabToNewWindow:nil];
|
||||
let _: () = msg_send![native_window, makeKeyAndOrderFront: nil];
|
||||
}
|
||||
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
native_window as *mut std::ffi::c_void,
|
||||
Some(move_tab_async),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_window_tab_overview(&self) {
|
||||
let native_window = self.0.lock().native_window;
|
||||
unsafe {
|
||||
let _: () = msg_send![native_window, toggleTabOverview:nil];
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
self.0.as_ref().lock().scale_factor()
|
||||
}
|
||||
|
@ -1051,6 +1210,17 @@ impl PlatformWindow for MacWindow {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_title(&self) -> String {
|
||||
unsafe {
|
||||
let title: id = msg_send![self.0.lock().native_window, title];
|
||||
if title.is_null() {
|
||||
"".to_string()
|
||||
} else {
|
||||
title.to_str().to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_app_id(&mut self, _app_id: &str) {}
|
||||
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
||||
|
@ -1212,6 +1382,62 @@ impl PlatformWindow for MacWindow {
|
|||
self.0.lock().appearance_changed_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
|
||||
unsafe {
|
||||
let windows: id = msg_send![self.0.lock().native_window, tabbedWindows];
|
||||
if windows.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let count: NSUInteger = msg_send![windows, count];
|
||||
let mut result = Vec::new();
|
||||
for i in 0..count {
|
||||
let window: id = msg_send![windows, objectAtIndex:i];
|
||||
if msg_send![window, isKindOfClass: WINDOW_CLASS] {
|
||||
let handle = get_window_state(&*window).lock().handle;
|
||||
let title: id = msg_send![window, title];
|
||||
let title = SharedString::from(title.to_str().to_string());
|
||||
|
||||
result.push(SystemWindowTab::new(title, handle));
|
||||
}
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn tab_bar_visible(&self) -> bool {
|
||||
unsafe {
|
||||
let tab_group: id = msg_send![self.0.lock().native_window, tabGroup];
|
||||
if tab_group.is_null() {
|
||||
false
|
||||
} else {
|
||||
let tab_bar_visible: BOOL = msg_send![tab_group, isTabBarVisible];
|
||||
tab_bar_visible == YES
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_move_tab_to_new_window(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.as_ref().lock().move_tab_to_new_window_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_merge_all_windows(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.as_ref().lock().merge_all_windows_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_select_next_tab(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.as_ref().lock().select_next_tab_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_select_previous_tab(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.as_ref().lock().select_previous_tab_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_toggle_tab_bar(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.as_ref().lock().toggle_tab_bar_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &crate::Scene) {
|
||||
let mut this = self.0.lock();
|
||||
this.renderer.draw(scene);
|
||||
|
@ -1653,6 +1879,7 @@ extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
|
|||
.occlusionState()
|
||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
|
||||
{
|
||||
lock.move_traffic_light();
|
||||
lock.start_display_link();
|
||||
} else {
|
||||
lock.stop_display_link();
|
||||
|
@ -1714,7 +1941,7 @@ extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
|
|||
|
||||
extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let lock = window_state.lock();
|
||||
let mut lock = window_state.lock();
|
||||
let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
|
||||
|
||||
// When opening a pop-up while the application isn't active, Cocoa sends a spurious
|
||||
|
@ -1735,9 +1962,34 @@ extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id)
|
|||
|
||||
let executor = lock.executor.clone();
|
||||
drop(lock);
|
||||
|
||||
// If window is becoming active, trigger immediate synchronous frame request.
|
||||
if selector == sel!(windowDidBecomeKey:) && is_active {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.lock();
|
||||
|
||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||
#[cfg(not(feature = "macos-blade"))]
|
||||
lock.renderer.set_presents_with_transaction(true);
|
||||
lock.stop_display_link();
|
||||
drop(lock);
|
||||
callback(Default::default());
|
||||
|
||||
let mut lock = window_state.lock();
|
||||
lock.request_frame_callback = Some(callback);
|
||||
#[cfg(not(feature = "macos-blade"))]
|
||||
lock.renderer.set_presents_with_transaction(false);
|
||||
lock.start_display_link();
|
||||
}
|
||||
}
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
if is_active {
|
||||
lock.move_traffic_light();
|
||||
}
|
||||
|
||||
if let Some(mut callback) = lock.activate_callback.take() {
|
||||
drop(lock);
|
||||
callback(is_active);
|
||||
|
@ -2273,3 +2525,80 @@ unsafe fn remove_layer_background(layer: id) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn add_titlebar_accessory_view_controller(this: &Object, _: Sel, view_controller: id) {
|
||||
unsafe {
|
||||
let _: () = msg_send![super(this, class!(NSWindow)), addTitlebarAccessoryViewController: view_controller];
|
||||
|
||||
// Hide the native tab bar and set its height to 0, since we render our own.
|
||||
let accessory_view: id = msg_send![view_controller, view];
|
||||
let _: () = msg_send![accessory_view, setHidden: YES];
|
||||
let mut frame: NSRect = msg_send![accessory_view, frame];
|
||||
frame.size.height = 0.0;
|
||||
let _: () = msg_send![accessory_view, setFrame: frame];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn move_tab_to_new_window(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let _: () = msg_send![super(this, class!(NSWindow)), moveTabToNewWindow:nil];
|
||||
|
||||
let window_state = get_window_state(this);
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
if let Some(mut callback) = lock.move_tab_to_new_window_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().move_tab_to_new_window_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn merge_all_windows(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let _: () = msg_send![super(this, class!(NSWindow)), mergeAllWindows:nil];
|
||||
|
||||
let window_state = get_window_state(this);
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
if let Some(mut callback) = lock.merge_all_windows_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().merge_all_windows_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn select_next_tab(this: &Object, _sel: Sel, _id: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
if let Some(mut callback) = lock.select_next_tab_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().select_next_tab_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn select_previous_tab(this: &Object, _sel: Sel, _id: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
if let Some(mut callback) = lock.select_previous_tab_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().select_previous_tab_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn toggle_tab_bar(this: &Object, _sel: Sel, _id: id) {
|
||||
unsafe {
|
||||
let _: () = msg_send![super(this, class!(NSWindow)), toggleTabBar:nil];
|
||||
|
||||
let window_state = get_window_state(this);
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
lock.move_traffic_light();
|
||||
|
||||
if let Some(mut callback) = lock.toggle_tab_bar_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().toggle_tab_bar_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@ use crate::{
|
|||
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptButton, PromptLevel, Quad,
|
||||
Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge,
|
||||
SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS, ScaledPixels, Scene, Shadow, SharedString, Size,
|
||||
StrikethroughStyle, Style, SubscriberSet, Subscription, TabHandles, TaffyLayoutEngine, Task,
|
||||
TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle,
|
||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
|
||||
WindowOptions, WindowParams, WindowTextSystem, point, prelude::*, px, rems, size,
|
||||
transparent_black,
|
||||
StrikethroughStyle, Style, SubscriberSet, Subscription, SystemWindowTab,
|
||||
SystemWindowTabController, TabHandles, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
|
||||
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
|
||||
point, prelude::*, px, rems, size, transparent_black,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{FxHashMap, FxHashSet};
|
||||
|
@ -944,6 +944,8 @@ impl Window {
|
|||
app_id,
|
||||
window_min_size,
|
||||
window_decorations,
|
||||
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))]
|
||||
tabbing_identifier,
|
||||
} = options;
|
||||
|
||||
let bounds = window_bounds
|
||||
|
@ -960,8 +962,17 @@ impl Window {
|
|||
show,
|
||||
display_id,
|
||||
window_min_size,
|
||||
#[cfg(target_os = "macos")]
|
||||
tabbing_identifier,
|
||||
},
|
||||
)?;
|
||||
|
||||
let tab_bar_visible = platform_window.tab_bar_visible();
|
||||
SystemWindowTabController::init_visible(cx, tab_bar_visible);
|
||||
if let Some(tabs) = platform_window.tabbed_windows() {
|
||||
SystemWindowTabController::add_tab(cx, handle.window_id(), tabs);
|
||||
}
|
||||
|
||||
let display_id = platform_window.display().map(|display| display.id());
|
||||
let sprite_atlas = platform_window.sprite_atlas();
|
||||
let mouse_position = platform_window.mouse_position();
|
||||
|
@ -991,9 +1002,13 @@ impl Window {
|
|||
}
|
||||
|
||||
platform_window.on_close(Box::new({
|
||||
let window_id = handle.window_id();
|
||||
let mut cx = cx.to_async();
|
||||
move || {
|
||||
let _ = handle.update(&mut cx, |_, window, _| window.remove_window());
|
||||
let _ = cx.update(|cx| {
|
||||
SystemWindowTabController::remove_tab(cx, window_id);
|
||||
});
|
||||
}
|
||||
}));
|
||||
platform_window.on_request_frame(Box::new({
|
||||
|
@ -1082,7 +1097,11 @@ impl Window {
|
|||
.activation_observers
|
||||
.clone()
|
||||
.retain(&(), |callback| callback(window, cx));
|
||||
|
||||
window.bounds_changed(cx);
|
||||
window.refresh();
|
||||
|
||||
SystemWindowTabController::update_last_active(cx, window.handle.id);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
@ -1123,6 +1142,57 @@ impl Window {
|
|||
.unwrap_or(None)
|
||||
})
|
||||
});
|
||||
platform_window.on_move_tab_to_new_window({
|
||||
let mut cx = cx.to_async();
|
||||
Box::new(move || {
|
||||
handle
|
||||
.update(&mut cx, |_, _window, cx| {
|
||||
SystemWindowTabController::move_tab_to_new_window(cx, handle.window_id());
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
});
|
||||
platform_window.on_merge_all_windows({
|
||||
let mut cx = cx.to_async();
|
||||
Box::new(move || {
|
||||
handle
|
||||
.update(&mut cx, |_, _window, cx| {
|
||||
SystemWindowTabController::merge_all_windows(cx, handle.window_id());
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
});
|
||||
platform_window.on_select_next_tab({
|
||||
let mut cx = cx.to_async();
|
||||
Box::new(move || {
|
||||
handle
|
||||
.update(&mut cx, |_, _window, cx| {
|
||||
SystemWindowTabController::select_next_tab(cx, handle.window_id());
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
});
|
||||
platform_window.on_select_previous_tab({
|
||||
let mut cx = cx.to_async();
|
||||
Box::new(move || {
|
||||
handle
|
||||
.update(&mut cx, |_, _window, cx| {
|
||||
SystemWindowTabController::select_previous_tab(cx, handle.window_id())
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
});
|
||||
platform_window.on_toggle_tab_bar({
|
||||
let mut cx = cx.to_async();
|
||||
Box::new(move || {
|
||||
handle
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
let tab_bar_visible = window.platform_window.tab_bar_visible();
|
||||
SystemWindowTabController::set_visible(cx, tab_bar_visible);
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(app_id) = app_id {
|
||||
platform_window.set_app_id(&app_id);
|
||||
|
@ -4275,11 +4345,47 @@ impl Window {
|
|||
}
|
||||
|
||||
/// Perform titlebar double-click action.
|
||||
/// This is MacOS specific.
|
||||
/// This is macOS specific.
|
||||
pub fn titlebar_double_click(&self) {
|
||||
self.platform_window.titlebar_double_click();
|
||||
}
|
||||
|
||||
/// Gets the window's title at the platform level.
|
||||
/// This is macOS specific.
|
||||
pub fn window_title(&self) -> String {
|
||||
self.platform_window.get_title()
|
||||
}
|
||||
|
||||
/// Returns a list of all tabbed windows and their titles.
|
||||
/// This is macOS specific.
|
||||
pub fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
|
||||
self.platform_window.tabbed_windows()
|
||||
}
|
||||
|
||||
/// Returns the tab bar visibility.
|
||||
/// This is macOS specific.
|
||||
pub fn tab_bar_visible(&self) -> bool {
|
||||
self.platform_window.tab_bar_visible()
|
||||
}
|
||||
|
||||
/// Merges all open windows into a single tabbed window.
|
||||
/// This is macOS specific.
|
||||
pub fn merge_all_windows(&self) {
|
||||
self.platform_window.merge_all_windows()
|
||||
}
|
||||
|
||||
/// Moves the tab to a new containing window.
|
||||
/// This is macOS specific.
|
||||
pub fn move_tab_to_new_window(&self) {
|
||||
self.platform_window.move_tab_to_new_window()
|
||||
}
|
||||
|
||||
/// Shows or hides the window tab overview.
|
||||
/// This is macOS specific.
|
||||
pub fn toggle_window_tab_overview(&self) {
|
||||
self.platform_window.toggle_window_tab_overview()
|
||||
}
|
||||
|
||||
/// Toggles the inspector mode on this window.
|
||||
#[cfg(any(feature = "inspector", debug_assertions))]
|
||||
pub fn toggle_inspector(&mut self, cx: &mut App) {
|
||||
|
|
|
@ -414,7 +414,7 @@ impl RulesLibrary {
|
|||
});
|
||||
Self {
|
||||
title_bar: if !cfg!(target_os = "macos") {
|
||||
Some(cx.new(|_| PlatformTitleBar::new("rules-library-title-bar")))
|
||||
Some(cx.new(|cx| PlatformTitleBar::new("rules-library-title-bar", cx)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
|
|
@ -1,28 +1,35 @@
|
|||
use gpui::{
|
||||
AnyElement, Context, Decorations, Hsla, InteractiveElement, IntoElement, MouseButton,
|
||||
AnyElement, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement, MouseButton,
|
||||
ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::mem;
|
||||
use ui::prelude::*;
|
||||
|
||||
use crate::platforms::{platform_linux, platform_mac, platform_windows};
|
||||
use crate::{
|
||||
platforms::{platform_linux, platform_mac, platform_windows},
|
||||
system_window_tabs::SystemWindowTabs,
|
||||
};
|
||||
|
||||
pub struct PlatformTitleBar {
|
||||
id: ElementId,
|
||||
platform_style: PlatformStyle,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
should_move: bool,
|
||||
system_window_tabs: Entity<SystemWindowTabs>,
|
||||
}
|
||||
|
||||
impl PlatformTitleBar {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
pub fn new(id: impl Into<ElementId>, cx: &mut Context<Self>) -> Self {
|
||||
let platform_style = PlatformStyle::platform();
|
||||
let system_window_tabs = cx.new(|_cx| SystemWindowTabs::new());
|
||||
|
||||
Self {
|
||||
id: id.into(),
|
||||
platform_style,
|
||||
children: SmallVec::new(),
|
||||
should_move: false,
|
||||
system_window_tabs,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +73,7 @@ impl Render for PlatformTitleBar {
|
|||
let close_action = Box::new(workspace::CloseWindow);
|
||||
let children = mem::take(&mut self.children);
|
||||
|
||||
h_flex()
|
||||
let title_bar = h_flex()
|
||||
.window_control_area(WindowControlArea::Drag)
|
||||
.w_full()
|
||||
.h(height)
|
||||
|
@ -162,7 +169,12 @@ impl Render for PlatformTitleBar {
|
|||
title_bar.child(platform_windows::WindowsWindowControls::new(height))
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(title_bar)
|
||||
.child(self.system_window_tabs.clone().into_any_element())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
477
crates/title_bar/src/system_window_tabs.rs
Normal file
477
crates/title_bar/src/system_window_tabs.rs
Normal file
|
@ -0,0 +1,477 @@
|
|||
use settings::Settings;
|
||||
|
||||
use gpui::{
|
||||
AnyWindowHandle, Context, Hsla, InteractiveElement, MouseButton, ParentElement, ScrollHandle,
|
||||
Styled, SystemWindowTab, SystemWindowTabController, Window, WindowId, actions, canvas, div,
|
||||
};
|
||||
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
Color, ContextMenu, DynamicSpacing, IconButton, IconButtonShape, IconName, IconSize, Label,
|
||||
LabelSize, Tab, h_flex, prelude::*, right_click_menu,
|
||||
};
|
||||
use workspace::{
|
||||
CloseWindow, ItemSettings, Workspace,
|
||||
item::{ClosePosition, ShowCloseButton},
|
||||
};
|
||||
|
||||
actions!(
|
||||
window,
|
||||
[
|
||||
ShowNextWindowTab,
|
||||
ShowPreviousWindowTab,
|
||||
MergeAllWindows,
|
||||
MoveTabToNewWindow
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DraggedWindowTab {
|
||||
pub id: WindowId,
|
||||
pub ix: usize,
|
||||
pub handle: AnyWindowHandle,
|
||||
pub title: String,
|
||||
pub width: Pixels,
|
||||
pub is_active: bool,
|
||||
pub active_background_color: Hsla,
|
||||
pub inactive_background_color: Hsla,
|
||||
}
|
||||
|
||||
pub struct SystemWindowTabs {
|
||||
tab_bar_scroll_handle: ScrollHandle,
|
||||
measured_tab_width: Pixels,
|
||||
last_dragged_tab: Option<DraggedWindowTab>,
|
||||
}
|
||||
|
||||
impl SystemWindowTabs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tab_bar_scroll_handle: ScrollHandle::new(),
|
||||
measured_tab_width: px(0.),
|
||||
last_dragged_tab: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _| {
|
||||
workspace.register_action_renderer(|div, _, window, cx| {
|
||||
let window_id = window.window_handle().window_id();
|
||||
let controller = cx.global::<SystemWindowTabController>();
|
||||
|
||||
let tab_groups = controller.tab_groups();
|
||||
let tabs = controller.tabs(window_id);
|
||||
let Some(tabs) = tabs else {
|
||||
return div;
|
||||
};
|
||||
|
||||
div.when(tabs.len() > 1, |div| {
|
||||
div.on_action(move |_: &ShowNextWindowTab, window, cx| {
|
||||
SystemWindowTabController::select_next_tab(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
})
|
||||
.on_action(move |_: &ShowPreviousWindowTab, window, cx| {
|
||||
SystemWindowTabController::select_previous_tab(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
})
|
||||
.on_action(move |_: &MoveTabToNewWindow, window, cx| {
|
||||
SystemWindowTabController::move_tab_to_new_window(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
window.move_tab_to_new_window();
|
||||
})
|
||||
})
|
||||
.when(tab_groups.len() > 1, |div| {
|
||||
div.on_action(move |_: &MergeAllWindows, window, cx| {
|
||||
SystemWindowTabController::merge_all_windows(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
window.merge_all_windows();
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn render_tab(
|
||||
&self,
|
||||
ix: usize,
|
||||
item: SystemWindowTab,
|
||||
tabs: Vec<SystemWindowTab>,
|
||||
active_background_color: Hsla,
|
||||
inactive_background_color: Hsla,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement + use<> {
|
||||
let entity = cx.entity();
|
||||
let settings = ItemSettings::get_global(cx);
|
||||
let close_side = &settings.close_position;
|
||||
let show_close_button = &settings.show_close_button;
|
||||
|
||||
let rem_size = window.rem_size();
|
||||
let width = self.measured_tab_width.max(rem_size * 10);
|
||||
let is_active = window.window_handle().window_id() == item.id;
|
||||
let title = item.title.to_string();
|
||||
|
||||
let label = Label::new(&title)
|
||||
.size(LabelSize::Small)
|
||||
.truncate()
|
||||
.color(if is_active {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
});
|
||||
|
||||
let tab = h_flex()
|
||||
.id(ix)
|
||||
.group("tab")
|
||||
.w_full()
|
||||
.overflow_hidden()
|
||||
.h(Tab::content_height(cx))
|
||||
.relative()
|
||||
.px(DynamicSpacing::Base16.px(cx))
|
||||
.justify_center()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.cursor_pointer()
|
||||
.on_drag(
|
||||
DraggedWindowTab {
|
||||
id: item.id,
|
||||
ix,
|
||||
handle: item.handle,
|
||||
title: item.title.to_string(),
|
||||
width,
|
||||
is_active,
|
||||
active_background_color,
|
||||
inactive_background_color,
|
||||
},
|
||||
move |tab, _, _, cx| {
|
||||
entity.update(cx, |this, _cx| {
|
||||
this.last_dragged_tab = Some(tab.clone());
|
||||
});
|
||||
cx.new(|_| tab.clone())
|
||||
},
|
||||
)
|
||||
.drag_over::<DraggedWindowTab>({
|
||||
let tab_ix = ix;
|
||||
move |element, dragged_tab: &DraggedWindowTab, _, cx| {
|
||||
let mut styled_tab = element
|
||||
.bg(cx.theme().colors().drop_target_background)
|
||||
.border_color(cx.theme().colors().drop_target_border)
|
||||
.border_0();
|
||||
|
||||
if tab_ix < dragged_tab.ix {
|
||||
styled_tab = styled_tab.border_l_2();
|
||||
} else if tab_ix > dragged_tab.ix {
|
||||
styled_tab = styled_tab.border_r_2();
|
||||
}
|
||||
|
||||
styled_tab
|
||||
}
|
||||
})
|
||||
.on_drop({
|
||||
let tab_ix = ix;
|
||||
cx.listener(move |this, dragged_tab: &DraggedWindowTab, _window, cx| {
|
||||
this.last_dragged_tab = None;
|
||||
Self::handle_tab_drop(dragged_tab, tab_ix, cx);
|
||||
})
|
||||
})
|
||||
.on_click(move |_, _, cx| {
|
||||
let _ = item.handle.update(cx, |_, window, _| {
|
||||
window.activate_window();
|
||||
});
|
||||
})
|
||||
.child(label)
|
||||
.map(|this| match show_close_button {
|
||||
ShowCloseButton::Hidden => this,
|
||||
_ => this.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_2()
|
||||
.w_4()
|
||||
.h_4()
|
||||
.map(|this| match close_side {
|
||||
ClosePosition::Left => this.left_1(),
|
||||
ClosePosition::Right => this.right_1(),
|
||||
})
|
||||
.child(
|
||||
IconButton::new("close", IconName::Close)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.on_click({
|
||||
move |_, window, cx| {
|
||||
if item.handle.window_id()
|
||||
== window.window_handle().window_id()
|
||||
{
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
} else {
|
||||
let _ = item.handle.update(cx, |_, window, cx| {
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|this| match show_close_button {
|
||||
ShowCloseButton::Hover => this.visible_on_hover("tab"),
|
||||
_ => this,
|
||||
}),
|
||||
),
|
||||
),
|
||||
})
|
||||
.into_any();
|
||||
|
||||
let menu = right_click_menu(ix)
|
||||
.trigger(|_, _, _| tab)
|
||||
.menu(move |window, cx| {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let tabs = tabs.clone();
|
||||
let other_tabs = tabs.clone();
|
||||
let move_tabs = tabs.clone();
|
||||
let merge_tabs = tabs.clone();
|
||||
|
||||
ContextMenu::build(window, cx, move |mut menu, _window_, _cx| {
|
||||
menu = menu.entry("Close Tab", None, move |window, cx| {
|
||||
Self::handle_right_click_action(
|
||||
cx,
|
||||
window,
|
||||
&tabs,
|
||||
|tab| tab.id == item.id,
|
||||
|window, cx| {
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
menu = menu.entry("Close Other Tabs", None, move |window, cx| {
|
||||
Self::handle_right_click_action(
|
||||
cx,
|
||||
window,
|
||||
&other_tabs,
|
||||
|tab| tab.id != item.id,
|
||||
|window, cx| {
|
||||
window.dispatch_action(Box::new(CloseWindow), cx);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
menu = menu.entry("Move Tab to New Window", None, move |window, cx| {
|
||||
Self::handle_right_click_action(
|
||||
cx,
|
||||
window,
|
||||
&move_tabs,
|
||||
|tab| tab.id == item.id,
|
||||
|window, cx| {
|
||||
SystemWindowTabController::move_tab_to_new_window(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
);
|
||||
window.move_tab_to_new_window();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
menu = menu.entry("Show All Tabs", None, move |window, cx| {
|
||||
Self::handle_right_click_action(
|
||||
cx,
|
||||
window,
|
||||
&merge_tabs,
|
||||
|tab| tab.id == item.id,
|
||||
|window, _cx| {
|
||||
window.toggle_window_tab_overview();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
menu.context(focus_handle)
|
||||
})
|
||||
});
|
||||
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w(rem_size * 10)
|
||||
.when(is_active, |this| this.bg(active_background_color))
|
||||
.border_t_1()
|
||||
.border_color(if is_active {
|
||||
active_background_color
|
||||
} else {
|
||||
cx.theme().colors().border
|
||||
})
|
||||
.child(menu)
|
||||
}
|
||||
|
||||
fn handle_tab_drop(dragged_tab: &DraggedWindowTab, ix: usize, cx: &mut Context<Self>) {
|
||||
SystemWindowTabController::update_tab_position(cx, dragged_tab.id, ix);
|
||||
}
|
||||
|
||||
fn handle_right_click_action<F, P>(
|
||||
cx: &mut App,
|
||||
window: &mut Window,
|
||||
tabs: &Vec<SystemWindowTab>,
|
||||
predicate: P,
|
||||
mut action: F,
|
||||
) where
|
||||
P: Fn(&SystemWindowTab) -> bool,
|
||||
F: FnMut(&mut Window, &mut App),
|
||||
{
|
||||
for tab in tabs {
|
||||
if predicate(tab) {
|
||||
if tab.id == window.window_handle().window_id() {
|
||||
action(window, cx);
|
||||
} else {
|
||||
let _ = tab.handle.update(cx, |_view, window, cx| {
|
||||
action(window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for SystemWindowTabs {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let active_background_color = cx.theme().colors().title_bar_background;
|
||||
let inactive_background_color = cx.theme().colors().tab_bar_background;
|
||||
let entity = cx.entity();
|
||||
|
||||
let controller = cx.global::<SystemWindowTabController>();
|
||||
let visible = controller.is_visible();
|
||||
let current_window_tab = vec![SystemWindowTab::new(
|
||||
SharedString::from(window.window_title()),
|
||||
window.window_handle(),
|
||||
)];
|
||||
let tabs = controller
|
||||
.tabs(window.window_handle().window_id())
|
||||
.unwrap_or(¤t_window_tab)
|
||||
.clone();
|
||||
|
||||
let tab_items = tabs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(ix, item)| {
|
||||
self.render_tab(
|
||||
ix,
|
||||
item.clone(),
|
||||
tabs.clone(),
|
||||
active_background_color,
|
||||
inactive_background_color,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let number_of_tabs = tab_items.len().max(1);
|
||||
if !window.tab_bar_visible() && !visible {
|
||||
return h_flex().into_any_element();
|
||||
}
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.h(Tab::container_height(cx))
|
||||
.bg(inactive_background_color)
|
||||
.on_mouse_up_out(
|
||||
MouseButton::Left,
|
||||
cx.listener(|this, _event, window, cx| {
|
||||
if let Some(tab) = this.last_dragged_tab.take() {
|
||||
SystemWindowTabController::move_tab_to_new_window(cx, tab.id);
|
||||
if tab.id == window.window_handle().window_id() {
|
||||
window.move_tab_to_new_window();
|
||||
} else {
|
||||
let _ = tab.handle.update(cx, |_, window, _cx| {
|
||||
window.move_tab_to_new_window();
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id("window tabs")
|
||||
.w_full()
|
||||
.h(Tab::container_height(cx))
|
||||
.bg(inactive_background_color)
|
||||
.overflow_x_scroll()
|
||||
.track_scroll(&self.tab_bar_scroll_handle)
|
||||
.children(tab_items)
|
||||
.child(
|
||||
canvas(
|
||||
|_, _, _| (),
|
||||
move |bounds, _, _, cx| {
|
||||
let entity = entity.clone();
|
||||
entity.update(cx, |this, cx| {
|
||||
let width = bounds.size.width / number_of_tabs as f32;
|
||||
if width != this.measured_tab_width {
|
||||
this.measured_tab_width = width;
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
.absolute()
|
||||
.size_full(),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.px(DynamicSpacing::Base06.rems(cx))
|
||||
.border_t_1()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
IconButton::new("plus", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(
|
||||
Box::new(zed_actions::OpenRecent {
|
||||
create_new_window: true,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DraggedWindowTab {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
|
||||
let label = Label::new(self.title.clone())
|
||||
.size(LabelSize::Small)
|
||||
.truncate()
|
||||
.color(if self.is_active {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.h(Tab::container_height(cx))
|
||||
.w(self.width)
|
||||
.px(DynamicSpacing::Base16.px(cx))
|
||||
.justify_center()
|
||||
.bg(if self.is_active {
|
||||
self.active_background_color
|
||||
} else {
|
||||
self.inactive_background_color
|
||||
})
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.font(ui_font)
|
||||
.child(label)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ mod collab;
|
|||
mod onboarding_banner;
|
||||
pub mod platform_title_bar;
|
||||
mod platforms;
|
||||
mod system_window_tabs;
|
||||
mod title_bar_settings;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
|
@ -11,6 +12,7 @@ mod stories;
|
|||
use crate::{
|
||||
application_menu::{ApplicationMenu, show_menus},
|
||||
platform_title_bar::PlatformTitleBar,
|
||||
system_window_tabs::SystemWindowTabs,
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
@ -65,6 +67,7 @@ actions!(
|
|||
|
||||
pub fn init(cx: &mut App) {
|
||||
TitleBarSettings::register(cx);
|
||||
SystemWindowTabs::init(cx);
|
||||
|
||||
cx.observe_new(|workspace: &mut Workspace, window, cx| {
|
||||
let Some(window) = window else {
|
||||
|
@ -284,7 +287,7 @@ impl TitleBar {
|
|||
)
|
||||
});
|
||||
|
||||
let platform_titlebar = cx.new(|_| PlatformTitleBar::new(id));
|
||||
let platform_titlebar = cx.new(|cx| PlatformTitleBar::new(id, cx));
|
||||
|
||||
Self {
|
||||
platform_titlebar,
|
||||
|
|
|
@ -42,9 +42,9 @@ use gpui::{
|
|||
Action, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds, Context,
|
||||
CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
|
||||
Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton,
|
||||
PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task,
|
||||
Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId, WindowOptions, actions, canvas,
|
||||
point, relative, size, transparent_black,
|
||||
PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription,
|
||||
SystemWindowTabController, Task, Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId,
|
||||
WindowOptions, actions, canvas, point, relative, size, transparent_black,
|
||||
};
|
||||
pub use history_manager::*;
|
||||
pub use item::{
|
||||
|
@ -4375,6 +4375,11 @@ impl Workspace {
|
|||
return;
|
||||
}
|
||||
window.set_window_title(&title);
|
||||
SystemWindowTabController::update_tab_title(
|
||||
cx,
|
||||
window.window_handle().window_id(),
|
||||
SharedString::from(&title),
|
||||
);
|
||||
self.last_window_title = Some(title);
|
||||
}
|
||||
|
||||
|
@ -5797,17 +5802,22 @@ impl Workspace {
|
|||
return;
|
||||
};
|
||||
let windows = cx.windows();
|
||||
let Some(next_window) = windows
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip_while(|window| window.window_id() != current_window_id)
|
||||
.nth(1)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
next_window
|
||||
.update(cx, |_, window, _| window.activate_window())
|
||||
.ok();
|
||||
let next_window =
|
||||
SystemWindowTabController::get_next_tab_group_window(cx, current_window_id).or_else(
|
||||
|| {
|
||||
windows
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip_while(|window| window.window_id() != current_window_id)
|
||||
.nth(1)
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(window) = next_window {
|
||||
window
|
||||
.update(cx, |_, window, _| window.activate_window())
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_previous_window(&mut self, cx: &mut Context<Self>) {
|
||||
|
@ -5815,18 +5825,23 @@ impl Workspace {
|
|||
return;
|
||||
};
|
||||
let windows = cx.windows();
|
||||
let Some(prev_window) = windows
|
||||
.iter()
|
||||
.rev()
|
||||
.cycle()
|
||||
.skip_while(|window| window.window_id() != current_window_id)
|
||||
.nth(1)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
prev_window
|
||||
.update(cx, |_, window, _| window.activate_window())
|
||||
.ok();
|
||||
let prev_window =
|
||||
SystemWindowTabController::get_prev_tab_group_window(cx, current_window_id).or_else(
|
||||
|| {
|
||||
windows
|
||||
.iter()
|
||||
.rev()
|
||||
.cycle()
|
||||
.skip_while(|window| window.window_id() != current_window_id)
|
||||
.nth(1)
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(window) = prev_window {
|
||||
window
|
||||
.update(cx, |_, window, _| window.activate_window())
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||
|
|
|
@ -29,6 +29,7 @@ pub struct WorkspaceSettings {
|
|||
pub on_last_window_closed: OnLastWindowClosed,
|
||||
pub resize_all_panels_in_dock: Vec<DockPosition>,
|
||||
pub close_on_file_delete: bool,
|
||||
pub use_system_window_tabs: bool,
|
||||
pub zoomed_padding: bool,
|
||||
}
|
||||
|
||||
|
@ -203,6 +204,10 @@ pub struct WorkspaceSettingsContent {
|
|||
///
|
||||
/// Default: false
|
||||
pub close_on_file_delete: Option<bool>,
|
||||
/// Whether to allow windows to tab together based on the user’s tabbing preference (macOS only).
|
||||
///
|
||||
/// Default: false
|
||||
pub use_system_window_tabs: Option<bool>,
|
||||
/// Whether to show padding for zoomed panels.
|
||||
/// When enabled, zoomed bottom panels will have some top padding,
|
||||
/// while zoomed left/right panels will have padding to the right/left (respectively).
|
||||
|
@ -357,6 +362,8 @@ impl Settings for WorkspaceSettings {
|
|||
current.max_tabs = Some(n)
|
||||
}
|
||||
|
||||
vscode.bool_setting("window.nativeTabs", &mut current.use_system_window_tabs);
|
||||
|
||||
// some combination of "window.restoreWindows" and "workbench.startupEditor" might
|
||||
// map to our "restore_on_startup"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ mod reliability;
|
|||
mod zed;
|
||||
|
||||
use agent_ui::AgentPanel;
|
||||
use anyhow::{Context as _, Result};
|
||||
use anyhow::{Context as _, Error, Result};
|
||||
use clap::{Parser, command};
|
||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||
use client::{Client, ProxySettings, UserStore, parse_zed_link};
|
||||
|
@ -947,9 +947,13 @@ async fn installation_id() -> Result<IdType> {
|
|||
|
||||
async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp) -> Result<()> {
|
||||
if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
|
||||
let use_system_window_tabs = cx
|
||||
.update(|cx| WorkspaceSettings::get(None, cx).use_system_window_tabs)
|
||||
.unwrap_or(false);
|
||||
let mut results: Vec<Result<(), Error>> = Vec::new();
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
for (location, paths) in locations {
|
||||
for (index, (location, paths)) in locations.into_iter().enumerate() {
|
||||
match location {
|
||||
SerializedWorkspaceLocation::Local => {
|
||||
let app_state = app_state.clone();
|
||||
|
@ -964,7 +968,14 @@ async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp
|
|||
})?;
|
||||
open_task.await.map(|_| ())
|
||||
});
|
||||
tasks.push(task);
|
||||
|
||||
// If we're using system window tabs and this is the first workspace,
|
||||
// wait for it to finish so that the other windows can be added as tabs.
|
||||
if use_system_window_tabs && index == 0 {
|
||||
results.push(task.await);
|
||||
} else {
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
SerializedWorkspaceLocation::Ssh(ssh) => {
|
||||
let app_state = app_state.clone();
|
||||
|
@ -998,7 +1009,7 @@ async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp
|
|||
}
|
||||
|
||||
// Wait for all workspaces to open concurrently
|
||||
let results = future::join_all(tasks).await;
|
||||
results.extend(future::join_all(tasks).await);
|
||||
|
||||
// Show notifications for any errors that occurred
|
||||
let mut error_count = 0;
|
||||
|
|
|
@ -282,6 +282,8 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut App) -> WindowO
|
|||
_ => gpui::WindowDecorations::Client,
|
||||
};
|
||||
|
||||
let use_system_window_tabs = WorkspaceSettings::get_global(cx).use_system_window_tabs;
|
||||
|
||||
WindowOptions {
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: None,
|
||||
|
@ -301,6 +303,11 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut App) -> WindowO
|
|||
width: px(360.0),
|
||||
height: px(240.0),
|
||||
}),
|
||||
tabbing_identifier: if use_system_window_tabs {
|
||||
Some(String::from("zed"))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1249,6 +1249,16 @@ or
|
|||
|
||||
Each option controls displaying of a particular toolbar element. If all elements are hidden, the editor toolbar is not displayed.
|
||||
|
||||
## Use System Tabs
|
||||
|
||||
- Description: Whether to allow windows to tab together based on the user’s tabbing preference (macOS only).
|
||||
- Setting: `use_system_window_tabs`
|
||||
- Default: `false`
|
||||
|
||||
**Options**
|
||||
|
||||
This setting enables integration with macOS’s native window tabbing feature. When set to `true`, Zed windows can be grouped together as tabs in a single macOS window, following the system-wide tabbing preferences set by the user (such as "Always", "In Full Screen", or "Never"). This setting is only available on macOS.
|
||||
|
||||
## Enable Language Server
|
||||
|
||||
- Description: Whether or not to use language servers to provide code intelligence.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue