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.
|
// Whether to show code action buttons in the editor toolbar.
|
||||||
"code_actions": false
|
"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
|
// Titlebar related settings
|
||||||
"title_bar": {
|
"title_bar": {
|
||||||
// Whether to show the branch icon beside branch switcher in the titlebar.
|
// 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()),
|
app_id: Some(app_id.to_owned()),
|
||||||
window_min_size: None,
|
window_min_size: None,
|
||||||
window_decorations: Some(WindowDecorations::Client),
|
window_decorations: Some(WindowDecorations::Client),
|
||||||
|
tabbing_identifier: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,5 +66,6 @@ fn notification_window_options(
|
||||||
app_id: Some(app_id.to_owned()),
|
app_id: Some(app_id.to_owned()),
|
||||||
window_min_size: None,
|
window_min_size: None,
|
||||||
window_decorations: Some(WindowDecorations::Client),
|
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,
|
app_id: None,
|
||||||
window_min_size: None,
|
window_min_size: None,
|
||||||
window_decorations: None,
|
window_decorations: None,
|
||||||
|
tabbing_identifier: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
sync::{Arc, atomic::Ordering::SeqCst},
|
sync::{Arc, atomic::Ordering::SeqCst},
|
||||||
time::Duration,
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
|
@ -17,6 +17,7 @@ use futures::{
|
||||||
channel::oneshot,
|
channel::oneshot,
|
||||||
future::{LocalBoxFuture, Shared},
|
future::{LocalBoxFuture, Shared},
|
||||||
};
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
|
|
||||||
|
@ -39,8 +40,8 @@ use crate::{
|
||||||
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
||||||
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
|
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
|
||||||
PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
|
PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
|
||||||
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
|
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window,
|
||||||
WindowHandle, WindowId, WindowInvalidator,
|
WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
|
||||||
colors::{Colors, GlobalColors},
|
colors::{Colors, GlobalColors},
|
||||||
current_platform, hash, init_app_menus,
|
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 ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut App) + 'static>;
|
||||||
type NewEntityListener = Box<dyn FnMut(AnyEntity, &mut Option<&mut Window>, &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.
|
/// Contains the state of the full application, and passed as a reference to a variety of callbacks.
|
||||||
/// Other [Context] derefs to this type.
|
/// Other [Context] derefs to this type.
|
||||||
/// You need a reference to an `App` to access the state of a [Entity].
|
/// 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());
|
init_app_menus(platform.as_ref(), &app.borrow());
|
||||||
|
SystemWindowTabController::init(&mut app.borrow_mut());
|
||||||
|
|
||||||
platform.on_keyboard_layout_change(Box::new({
|
platform.on_keyboard_layout_change(Box::new({
|
||||||
let app = Rc::downgrade(&app);
|
let app = Rc::downgrade(&app);
|
||||||
|
|
|
@ -40,8 +40,8 @@ use crate::{
|
||||||
DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
|
DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
|
||||||
ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
|
ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
|
||||||
Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
|
Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
|
||||||
ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer, SvgSize, Task, TaskLabel, Window,
|
ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer, SvgSize, SystemWindowTab, Task,
|
||||||
WindowControlArea, hash, point, px, size,
|
TaskLabel, Window, WindowControlArea, hash, point, px, size,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
@ -500,9 +500,26 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||||
|
|
||||||
// macOS specific methods
|
// 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 set_edited(&mut self, _edited: bool) {}
|
||||||
fn show_character_palette(&self) {}
|
fn show_character_palette(&self) {}
|
||||||
fn titlebar_double_click(&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")]
|
#[cfg(target_os = "windows")]
|
||||||
fn get_raw_handle(&self) -> windows::HWND;
|
fn get_raw_handle(&self) -> windows::HWND;
|
||||||
|
@ -1105,6 +1122,9 @@ pub struct WindowOptions {
|
||||||
/// Whether to use client or server side decorations. Wayland only
|
/// Whether to use client or server side decorations. Wayland only
|
||||||
/// Note that this may be ignored.
|
/// Note that this may be ignored.
|
||||||
pub window_decorations: Option<WindowDecorations>,
|
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
|
/// 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 display_id: Option<DisplayId>,
|
||||||
|
|
||||||
pub window_min_size: Option<Size<Pixels>>,
|
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.
|
/// Represents the status of how a window should be opened.
|
||||||
|
@ -1194,6 +1216,7 @@ impl Default for WindowOptions {
|
||||||
app_id: None,
|
app_id: None,
|
||||||
window_min_size: None,
|
window_min_size: None,
|
||||||
window_decorations: None,
|
window_decorations: None,
|
||||||
|
tabbing_identifier: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ use crate::{
|
||||||
ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
|
ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
|
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
|
||||||
PlatformInput, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions,
|
PlatformInput, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions,
|
||||||
ScaledPixels, Size, Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
|
ScaledPixels, SharedString, Size, SystemWindowTab, Timer, WindowAppearance,
|
||||||
WindowControlArea, WindowKind, WindowParams, platform::PlatformInputHandler, point, px, size,
|
WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowKind, WindowParams,
|
||||||
|
dispatch_get_main_queue, dispatch_sys::dispatch_async_f, platform::PlatformInputHandler, point,
|
||||||
|
px, size,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -24,6 +26,7 @@ use cocoa::{
|
||||||
NSUserDefaults,
|
NSUserDefaults,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use core_graphics::display::{CGDirectDisplayID, CGPoint, CGRect};
|
use core_graphics::display::{CGDirectDisplayID, CGPoint, CGRect};
|
||||||
use ctor::ctor;
|
use ctor::ctor;
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
|
@ -82,6 +85,12 @@ type NSDragOperation = NSUInteger;
|
||||||
const NSDragOperationNone: NSDragOperation = 0;
|
const NSDragOperationNone: NSDragOperation = 0;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
const NSDragOperationCopy: NSDragOperation = 1;
|
const NSDragOperationCopy: NSDragOperation = 1;
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum UserTabbingPreference {
|
||||||
|
Never,
|
||||||
|
Always,
|
||||||
|
InFullScreen,
|
||||||
|
}
|
||||||
|
|
||||||
#[link(name = "CoreGraphics", kind = "framework")]
|
#[link(name = "CoreGraphics", kind = "framework")]
|
||||||
unsafe extern "C" {
|
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),
|
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()
|
decl.register()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,6 +414,11 @@ struct MacWindowState {
|
||||||
// Whether the next left-mouse click is also the focusing click.
|
// Whether the next left-mouse click is also the focusing click.
|
||||||
first_mouse: bool,
|
first_mouse: bool,
|
||||||
fullscreen_restore_bounds: Bounds<Pixels>,
|
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 {
|
impl MacWindowState {
|
||||||
|
@ -534,6 +578,7 @@ impl MacWindow {
|
||||||
show,
|
show,
|
||||||
display_id,
|
display_id,
|
||||||
window_min_size,
|
window_min_size,
|
||||||
|
tabbing_identifier,
|
||||||
}: WindowParams,
|
}: WindowParams,
|
||||||
executor: ForegroundExecutor,
|
executor: ForegroundExecutor,
|
||||||
renderer_context: renderer::Context,
|
renderer_context: renderer::Context,
|
||||||
|
@ -541,7 +586,12 @@ impl MacWindow {
|
||||||
unsafe {
|
unsafe {
|
||||||
let pool = NSAutoreleasePool::new(nil);
|
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;
|
let mut style_mask;
|
||||||
if let Some(titlebar) = titlebar.as_ref() {
|
if let Some(titlebar) = titlebar.as_ref() {
|
||||||
|
@ -660,6 +710,11 @@ impl MacWindow {
|
||||||
external_files_dragged: false,
|
external_files_dragged: false,
|
||||||
first_mouse: false,
|
first_mouse: false,
|
||||||
fullscreen_restore_bounds: Bounds::default(),
|
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(
|
(*native_window).set_ivar(
|
||||||
|
@ -714,6 +769,11 @@ impl MacWindow {
|
||||||
WindowKind::Normal => {
|
WindowKind::Normal => {
|
||||||
native_window.setLevel_(NSNormalWindowLevel);
|
native_window.setLevel_(NSNormalWindowLevel);
|
||||||
native_window.setAcceptsMouseMovedEvents_(YES);
|
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 => {
|
WindowKind::PopUp => {
|
||||||
// Use a tracking area to allow receiving MouseMoved events even when
|
// 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 {
|
if focus && show {
|
||||||
native_window.makeKeyAndOrderFront_(nil);
|
native_window.makeKeyAndOrderFront_(nil);
|
||||||
} else if show {
|
} else if show {
|
||||||
|
@ -796,6 +888,33 @@ impl MacWindow {
|
||||||
window_handles
|
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 {
|
impl Drop for MacWindow {
|
||||||
|
@ -851,6 +970,46 @@ impl PlatformWindow for MacWindow {
|
||||||
.detach();
|
.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 {
|
fn scale_factor(&self) -> f32 {
|
||||||
self.0.as_ref().lock().scale_factor()
|
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_app_id(&mut self, _app_id: &str) {}
|
||||||
|
|
||||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
||||||
|
@ -1212,6 +1382,62 @@ impl PlatformWindow for MacWindow {
|
||||||
self.0.lock().appearance_changed_callback = Some(callback);
|
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) {
|
fn draw(&self, scene: &crate::Scene) {
|
||||||
let mut this = self.0.lock();
|
let mut this = self.0.lock();
|
||||||
this.renderer.draw(scene);
|
this.renderer.draw(scene);
|
||||||
|
@ -1653,6 +1879,7 @@ extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
|
||||||
.occlusionState()
|
.occlusionState()
|
||||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
|
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
|
||||||
{
|
{
|
||||||
|
lock.move_traffic_light();
|
||||||
lock.start_display_link();
|
lock.start_display_link();
|
||||||
} else {
|
} else {
|
||||||
lock.stop_display_link();
|
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) {
|
extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
|
||||||
let window_state = unsafe { get_window_state(this) };
|
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 };
|
let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
|
||||||
|
|
||||||
// When opening a pop-up while the application isn't active, Cocoa sends a spurious
|
// 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();
|
let executor = lock.executor.clone();
|
||||||
drop(lock);
|
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
|
executor
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let mut lock = window_state.as_ref().lock();
|
let mut lock = window_state.as_ref().lock();
|
||||||
|
if is_active {
|
||||||
|
lock.move_traffic_light();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(mut callback) = lock.activate_callback.take() {
|
if let Some(mut callback) = lock.activate_callback.take() {
|
||||||
drop(lock);
|
drop(lock);
|
||||||
callback(is_active);
|
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,
|
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptButton, PromptLevel, Quad,
|
||||||
Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge,
|
Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge,
|
||||||
SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS, ScaledPixels, Scene, Shadow, SharedString, Size,
|
SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS, ScaledPixels, Scene, Shadow, SharedString, Size,
|
||||||
StrikethroughStyle, Style, SubscriberSet, Subscription, TabHandles, TaffyLayoutEngine, Task,
|
StrikethroughStyle, Style, SubscriberSet, Subscription, SystemWindowTab,
|
||||||
TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle,
|
SystemWindowTabController, TabHandles, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
|
||||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
|
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
|
||||||
WindowOptions, WindowParams, WindowTextSystem, point, prelude::*, px, rems, size,
|
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
|
||||||
transparent_black,
|
point, prelude::*, px, rems, size, transparent_black,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use collections::{FxHashMap, FxHashSet};
|
use collections::{FxHashMap, FxHashSet};
|
||||||
|
@ -944,6 +944,8 @@ impl Window {
|
||||||
app_id,
|
app_id,
|
||||||
window_min_size,
|
window_min_size,
|
||||||
window_decorations,
|
window_decorations,
|
||||||
|
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))]
|
||||||
|
tabbing_identifier,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
let bounds = window_bounds
|
let bounds = window_bounds
|
||||||
|
@ -960,8 +962,17 @@ impl Window {
|
||||||
show,
|
show,
|
||||||
display_id,
|
display_id,
|
||||||
window_min_size,
|
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 display_id = platform_window.display().map(|display| display.id());
|
||||||
let sprite_atlas = platform_window.sprite_atlas();
|
let sprite_atlas = platform_window.sprite_atlas();
|
||||||
let mouse_position = platform_window.mouse_position();
|
let mouse_position = platform_window.mouse_position();
|
||||||
|
@ -991,9 +1002,13 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
platform_window.on_close(Box::new({
|
platform_window.on_close(Box::new({
|
||||||
|
let window_id = handle.window_id();
|
||||||
let mut cx = cx.to_async();
|
let mut cx = cx.to_async();
|
||||||
move || {
|
move || {
|
||||||
let _ = handle.update(&mut cx, |_, window, _| window.remove_window());
|
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({
|
platform_window.on_request_frame(Box::new({
|
||||||
|
@ -1082,7 +1097,11 @@ impl Window {
|
||||||
.activation_observers
|
.activation_observers
|
||||||
.clone()
|
.clone()
|
||||||
.retain(&(), |callback| callback(window, cx));
|
.retain(&(), |callback| callback(window, cx));
|
||||||
|
|
||||||
|
window.bounds_changed(cx);
|
||||||
window.refresh();
|
window.refresh();
|
||||||
|
|
||||||
|
SystemWindowTabController::update_last_active(cx, window.handle.id);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
@ -1123,6 +1142,57 @@ impl Window {
|
||||||
.unwrap_or(None)
|
.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 {
|
if let Some(app_id) = app_id {
|
||||||
platform_window.set_app_id(&app_id);
|
platform_window.set_app_id(&app_id);
|
||||||
|
@ -4275,11 +4345,47 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform titlebar double-click action.
|
/// Perform titlebar double-click action.
|
||||||
/// This is MacOS specific.
|
/// This is macOS specific.
|
||||||
pub fn titlebar_double_click(&self) {
|
pub fn titlebar_double_click(&self) {
|
||||||
self.platform_window.titlebar_double_click();
|
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.
|
/// Toggles the inspector mode on this window.
|
||||||
#[cfg(any(feature = "inspector", debug_assertions))]
|
#[cfg(any(feature = "inspector", debug_assertions))]
|
||||||
pub fn toggle_inspector(&mut self, cx: &mut App) {
|
pub fn toggle_inspector(&mut self, cx: &mut App) {
|
||||||
|
|
|
@ -414,7 +414,7 @@ impl RulesLibrary {
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
title_bar: if !cfg!(target_os = "macos") {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,28 +1,35 @@
|
||||||
use gpui::{
|
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,
|
ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use ui::prelude::*;
|
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 {
|
pub struct PlatformTitleBar {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
platform_style: PlatformStyle,
|
platform_style: PlatformStyle,
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
should_move: bool,
|
should_move: bool,
|
||||||
|
system_window_tabs: Entity<SystemWindowTabs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformTitleBar {
|
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 platform_style = PlatformStyle::platform();
|
||||||
|
let system_window_tabs = cx.new(|_cx| SystemWindowTabs::new());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
platform_style,
|
platform_style,
|
||||||
children: SmallVec::new(),
|
children: SmallVec::new(),
|
||||||
should_move: false,
|
should_move: false,
|
||||||
|
system_window_tabs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +73,7 @@ impl Render for PlatformTitleBar {
|
||||||
let close_action = Box::new(workspace::CloseWindow);
|
let close_action = Box::new(workspace::CloseWindow);
|
||||||
let children = mem::take(&mut self.children);
|
let children = mem::take(&mut self.children);
|
||||||
|
|
||||||
h_flex()
|
let title_bar = h_flex()
|
||||||
.window_control_area(WindowControlArea::Drag)
|
.window_control_area(WindowControlArea::Drag)
|
||||||
.w_full()
|
.w_full()
|
||||||
.h(height)
|
.h(height)
|
||||||
|
@ -162,7 +169,12 @@ impl Render for PlatformTitleBar {
|
||||||
title_bar.child(platform_windows::WindowsWindowControls::new(height))
|
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;
|
mod onboarding_banner;
|
||||||
pub mod platform_title_bar;
|
pub mod platform_title_bar;
|
||||||
mod platforms;
|
mod platforms;
|
||||||
|
mod system_window_tabs;
|
||||||
mod title_bar_settings;
|
mod title_bar_settings;
|
||||||
|
|
||||||
#[cfg(feature = "stories")]
|
#[cfg(feature = "stories")]
|
||||||
|
@ -11,6 +12,7 @@ mod stories;
|
||||||
use crate::{
|
use crate::{
|
||||||
application_menu::{ApplicationMenu, show_menus},
|
application_menu::{ApplicationMenu, show_menus},
|
||||||
platform_title_bar::PlatformTitleBar,
|
platform_title_bar::PlatformTitleBar,
|
||||||
|
system_window_tabs::SystemWindowTabs,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
@ -65,6 +67,7 @@ actions!(
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
TitleBarSettings::register(cx);
|
TitleBarSettings::register(cx);
|
||||||
|
SystemWindowTabs::init(cx);
|
||||||
|
|
||||||
cx.observe_new(|workspace: &mut Workspace, window, cx| {
|
cx.observe_new(|workspace: &mut Workspace, window, cx| {
|
||||||
let Some(window) = window else {
|
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 {
|
Self {
|
||||||
platform_titlebar,
|
platform_titlebar,
|
||||||
|
|
|
@ -42,9 +42,9 @@ use gpui::{
|
||||||
Action, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds, Context,
|
Action, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds, Context,
|
||||||
CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
|
CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
|
||||||
Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton,
|
Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton,
|
||||||
PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task,
|
PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription,
|
||||||
Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId, WindowOptions, actions, canvas,
|
SystemWindowTabController, Task, Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId,
|
||||||
point, relative, size, transparent_black,
|
WindowOptions, actions, canvas, point, relative, size, transparent_black,
|
||||||
};
|
};
|
||||||
pub use history_manager::*;
|
pub use history_manager::*;
|
||||||
pub use item::{
|
pub use item::{
|
||||||
|
@ -4375,6 +4375,11 @@ impl Workspace {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.set_window_title(&title);
|
window.set_window_title(&title);
|
||||||
|
SystemWindowTabController::update_tab_title(
|
||||||
|
cx,
|
||||||
|
window.window_handle().window_id(),
|
||||||
|
SharedString::from(&title),
|
||||||
|
);
|
||||||
self.last_window_title = Some(title);
|
self.last_window_title = Some(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5797,17 +5802,22 @@ impl Workspace {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let windows = cx.windows();
|
let windows = cx.windows();
|
||||||
let Some(next_window) = windows
|
let next_window =
|
||||||
.iter()
|
SystemWindowTabController::get_next_tab_group_window(cx, current_window_id).or_else(
|
||||||
.cycle()
|
|| {
|
||||||
.skip_while(|window| window.window_id() != current_window_id)
|
windows
|
||||||
.nth(1)
|
.iter()
|
||||||
else {
|
.cycle()
|
||||||
return;
|
.skip_while(|window| window.window_id() != current_window_id)
|
||||||
};
|
.nth(1)
|
||||||
next_window
|
},
|
||||||
.update(cx, |_, window, _| window.activate_window())
|
);
|
||||||
.ok();
|
|
||||||
|
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>) {
|
pub fn activate_previous_window(&mut self, cx: &mut Context<Self>) {
|
||||||
|
@ -5815,18 +5825,23 @@ impl Workspace {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let windows = cx.windows();
|
let windows = cx.windows();
|
||||||
let Some(prev_window) = windows
|
let prev_window =
|
||||||
.iter()
|
SystemWindowTabController::get_prev_tab_group_window(cx, current_window_id).or_else(
|
||||||
.rev()
|
|| {
|
||||||
.cycle()
|
windows
|
||||||
.skip_while(|window| window.window_id() != current_window_id)
|
.iter()
|
||||||
.nth(1)
|
.rev()
|
||||||
else {
|
.cycle()
|
||||||
return;
|
.skip_while(|window| window.window_id() != current_window_id)
|
||||||
};
|
.nth(1)
|
||||||
prev_window
|
},
|
||||||
.update(cx, |_, window, _| window.activate_window())
|
);
|
||||||
.ok();
|
|
||||||
|
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>) {
|
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 on_last_window_closed: OnLastWindowClosed,
|
||||||
pub resize_all_panels_in_dock: Vec<DockPosition>,
|
pub resize_all_panels_in_dock: Vec<DockPosition>,
|
||||||
pub close_on_file_delete: bool,
|
pub close_on_file_delete: bool,
|
||||||
|
pub use_system_window_tabs: bool,
|
||||||
pub zoomed_padding: bool,
|
pub zoomed_padding: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,6 +204,10 @@ pub struct WorkspaceSettingsContent {
|
||||||
///
|
///
|
||||||
/// Default: false
|
/// Default: false
|
||||||
pub close_on_file_delete: Option<bool>,
|
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.
|
/// Whether to show padding for zoomed panels.
|
||||||
/// When enabled, zoomed bottom panels will have some top padding,
|
/// When enabled, zoomed bottom panels will have some top padding,
|
||||||
/// while zoomed left/right panels will have padding to the right/left (respectively).
|
/// 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)
|
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
|
// some combination of "window.restoreWindows" and "workbench.startupEditor" might
|
||||||
// map to our "restore_on_startup"
|
// map to our "restore_on_startup"
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod reliability;
|
||||||
mod zed;
|
mod zed;
|
||||||
|
|
||||||
use agent_ui::AgentPanel;
|
use agent_ui::AgentPanel;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Error, Result};
|
||||||
use clap::{Parser, command};
|
use clap::{Parser, command};
|
||||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||||
use client::{Client, ProxySettings, UserStore, parse_zed_link};
|
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<()> {
|
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 {
|
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();
|
let mut tasks = Vec::new();
|
||||||
|
|
||||||
for (location, paths) in locations {
|
for (index, (location, paths)) in locations.into_iter().enumerate() {
|
||||||
match location {
|
match location {
|
||||||
SerializedWorkspaceLocation::Local => {
|
SerializedWorkspaceLocation::Local => {
|
||||||
let app_state = app_state.clone();
|
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(|_| ())
|
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) => {
|
SerializedWorkspaceLocation::Ssh(ssh) => {
|
||||||
let app_state = app_state.clone();
|
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
|
// 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
|
// Show notifications for any errors that occurred
|
||||||
let mut error_count = 0;
|
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,
|
_ => gpui::WindowDecorations::Client,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let use_system_window_tabs = WorkspaceSettings::get_global(cx).use_system_window_tabs;
|
||||||
|
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
titlebar: Some(TitlebarOptions {
|
titlebar: Some(TitlebarOptions {
|
||||||
title: None,
|
title: None,
|
||||||
|
@ -301,6 +303,11 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut App) -> WindowO
|
||||||
width: px(360.0),
|
width: px(360.0),
|
||||||
height: px(240.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.
|
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
|
## Enable Language Server
|
||||||
|
|
||||||
- Description: Whether or not to use language servers to provide code intelligence.
|
- Description: Whether or not to use language servers to provide code intelligence.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue