use std::{ any::{TypeId, type_name}, cell::{BorrowMutError, Cell, Ref, RefCell, RefMut}, marker::PhantomData, mem, ops::{Deref, DerefMut}, path::{Path, PathBuf}, rc::{Rc, Weak}, sync::{Arc, atomic::Ordering::SeqCst}, time::{Duration, Instant},}; use anyhow::{Context as _, Result, anyhow};use derive_more::{Deref, DerefMut};use futures::{ Future, FutureExt, channel::oneshot, future::{LocalBoxFuture, Shared},};use itertools::Itertools;use parking_lot::RwLock;use slotmap::SlotMap; pub use async_context::*;use collections::{FxHashMap, FxHashSet, HashMap, VecDeque};pub use context::*;pub use entity_map::*;use http_client::{HttpClient, Url};use smallvec::SmallVec;#[cfg(any(test, feature = "test-support"))]pub use test_context::*;use util::{ResultExt, debug_panic};#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]pub use visual_test_context::*; #[cfg(any(feature = "inspector", debug_assertions))]use crate::InspectorElementRegistry;use crate::{ Action, ActionBuildError, ActionRegistry, Any, AnyView, AnyWindowHandle, AppContext, Asset, AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, Priority, PromptBuilder, PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, colors::{Colors, GlobalColors}, current_platform, hash, init_app_menus,}; mod async_context;mod context;mod entity_map;#[cfg(any(test, feature = "test-support"))]mod test_context;#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]mod visual_test_context; /// The duration for which futures returned from [Context::on_app_quit] can run before the application fully quits.pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100); /// Temporary(?) wrapper around [`RefCell<App>`] to help us debug any double borrows./// Strongly consider removing after stabilization.#[doc(hidden)]pub struct AppCell { app: RefCell<App>,} impl AppCell { #[doc(hidden)] #[track_caller] pub fn borrow(&self) -> AppRef<'_> { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } AppRef(self.app.borrow()) } #[doc(hidden)] #[track_caller] pub fn borrow_mut(&self) -> AppRefMut<'_> { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } AppRefMut(self.app.borrow_mut()) } #[doc(hidden)] #[track_caller] pub fn try_borrow_mut(&self) -> Result<AppRefMut<'_>, BorrowMutError> { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } Ok(AppRefMut(self.app.try_borrow_mut()?)) }} #[doc(hidden)]#[derive(Deref, DerefMut)]pub struct AppRef<'a>(Ref<'a, App>); impl Drop for AppRef<'_> { fn drop(&mut self) { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("dropped borrow from {thread_id:?}"); } }} #[doc(hidden)]#[derive(Deref, DerefMut)]pub struct AppRefMut<'a>(RefMut<'a, App>); impl Drop for AppRefMut<'_> { fn drop(&mut self) { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("dropped {thread_id:?}"); } }} /// A reference to a GPUI application, typically constructed in the `main` function of your app./// You won't interact with this type much outside of initial configuration and startup.pub struct Application(Rc<AppCell>); /// Represents an application before it is fully launched. Once your app is/// configured, you'll start the app with `App::run`.impl Application { /// Builds an app with the given asset source. #[allow(clippy::new_without_default)] pub fn new() -> Self { #[cfg(any(test, feature = "test-support"))] log::info!("GPUI was compiled in test mode"); Self(App::new_app( current_platform(false), Arc::new(()), Arc::new(NullHttpClient), )) } /// Build an app in headless mode. This prevents opening windows, /// but makes it possible to run an application in an context like /// SSH, where GUI applications are not allowed. pub fn headless() -> Self { Self(App::new_app( current_platform(true), Arc::new(()), Arc::new(NullHttpClient), )) } /// Assign pub fn with_assets(self, asset_source: impl AssetSource) -> Self { let mut context_lock = self.0.borrow_mut(); let asset_source = Arc::new(asset_source); context_lock.asset_source = asset_source.clone(); context_lock.svg_renderer = SvgRenderer::new(asset_source); drop(context_lock); self } /// Sets the HTTP client for the application. pub fn with_http_client(self, http_client: Arc<dyn HttpClient>) -> Self { let mut context_lock = self.0.borrow_mut(); context_lock.http_client = http_client; drop(context_lock); self } /// Configures when the application should automatically quit. /// By default, [`QuitMode::Default`] is used. pub fn with_quit_mode(self, mode: QuitMode) -> Self { self.0.borrow_mut().quit_mode = mode; self } /// Start the application. The provided callback will be called once the /// app is fully launched. pub fn run<F>(self, on_finish_launching: F) where F: 'static + FnOnce(&mut App), { let this = self.0.clone(); let platform = self.0.borrow().platform.clone(); platform.run(Box::new(move || { let cx = &mut *this.borrow_mut(); on_finish_launching(cx); })); } /// Register a handler to be invoked when the platform instructs the application /// to open one or more URLs. pub fn on_open_urls<F>(&self, mut callback: F) -> &Self where F: 'static + FnMut(Vec<String>), { self.0.borrow().platform.on_open_urls(Box::new(callback)); self } /// Invokes a handler when an already-running application is launched. /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock. pub fn on_reopen<F>(&self, mut callback: F) -> &Self where F: 'static + FnMut(&mut App), { let this = Rc::downgrade(&self.0); self.0.borrow_mut().platform.on_reopen(Box::new(move || { if let Some(app) = this.upgrade() { callback(&mut app.borrow_mut()); } })); self } /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } /// Returns a reference to the [`TextSystem`] associated with this app. pub fn text_system(&self) -> Arc<TextSystem> { self.0.borrow().text_system.clone() } /// Returns the file URL of the executable with the specified name in the application bundle pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { self.0.borrow().path_for_auxiliary_executable(name) }} type Handler = Box<dyn FnMut(&mut App) -> bool + 'static>;type Listener = Box<dyn FnMut(&dyn Any, &mut App) -> bool + 'static>;pub(crate) type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut Window, &mut App) -> bool + 'static>;type QuitHandler = Box<dyn FnOnce(&mut App) -> LocalBoxFuture<'static, ()> + 'static>;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>; /// Defines when the application should automatically quit.#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum QuitMode { /// Use [`QuitMode::Explicit`] on macOS and [`QuitMode::LastWindowClosed`] on other platforms. #[default] Default, /// Quit automatically when the last window is closed. LastWindowClosed, /// Quit only when requested via [`App::quit`]. Explicit,} #[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?; // TODO: `.keys()` returns arbitrary order, what does "next" mean? 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?; // TODO: `.keys()` returns arbitrary order, what does "previous" mean? 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>> { self.tab_groups .values() .find(|tabs| tabs.iter().any(|tab| tab.id == id)) } /// 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; return; } } } } /// 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.iter().find(|tab| tab.id == id).cloned() 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]); }Lines 1–500 of 2583. Load more
use std::{ any::{TypeId, type_name}, cell::{BorrowMutError, Cell, Ref, RefCell, RefMut}, marker::PhantomData, mem, ops::{Deref, DerefMut}, path::{Path, PathBuf}, rc::{Rc, Weak}, sync::{Arc, atomic::Ordering::SeqCst}, time::{Duration, Instant}, }; use anyhow::{Context as _, Result, anyhow}; use derive_more::{Deref, DerefMut}; use futures::{ Future, FutureExt, channel::oneshot, future::{LocalBoxFuture, Shared}, }; use itertools::Itertools; use parking_lot::RwLock; use slotmap::SlotMap; pub use async_context::*; use collections::{FxHashMap, FxHashSet, HashMap, VecDeque}; pub use context::*; pub use entity_map::*; use http_client::{HttpClient, Url}; use smallvec::SmallVec; #[cfg(any(test, feature = "test-support"))] pub use test_context::*; use util::{ResultExt, debug_panic}; #[cfg(all(target_os = "macos", any(test, feature = "test-support")))] pub use visual_test_context::*; #[cfg(any(feature = "inspector", debug_assertions))] use crate::InspectorElementRegistry; use crate::{ Action, ActionBuildError, ActionRegistry, Any, AnyView, AnyWindowHandle, AppContext, Asset, AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, Priority, PromptBuilder, PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, colors::{Colors, GlobalColors}, current_platform, hash, init_app_menus, }; mod async_context; mod context; mod entity_map; #[cfg(any(test, feature = "test-support"))] mod test_context; #[cfg(all(target_os = "macos", any(test, feature = "test-support")))] mod visual_test_context; /// The duration for which futures returned from [Context::on_app_quit] can run before the application fully quits. pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100); /// Temporary(?) wrapper around [`RefCell<App>`] to help us debug any double borrows. /// Strongly consider removing after stabilization. #[doc(hidden)] pub struct AppCell { app: RefCell<App>, } impl AppCell { #[doc(hidden)] #[track_caller] pub fn borrow(&self) -> AppRef<'_> { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } AppRef(self.app.borrow()) } #[doc(hidden)] #[track_caller] pub fn borrow_mut(&self) -> AppRefMut<'_> { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } AppRefMut(self.app.borrow_mut()) } #[doc(hidden)] #[track_caller] pub fn try_borrow_mut(&self) -> Result<AppRefMut<'_>, BorrowMutError> { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("borrowed {thread_id:?}"); } Ok(AppRefMut(self.app.try_borrow_mut()?)) } } #[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRef<'a>(Ref<'a, App>); impl Drop for AppRef<'_> { fn drop(&mut self) { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("dropped borrow from {thread_id:?}"); } } } #[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRefMut<'a>(RefMut<'a, App>); impl Drop for AppRefMut<'_> { fn drop(&mut self) { if option_env!("TRACK_THREAD_BORROWS").is_some() { let thread_id = std::thread::current().id(); eprintln!("dropped {thread_id:?}"); } } } /// A reference to a GPUI application, typically constructed in the `main` function of your app. /// You won't interact with this type much outside of initial configuration and startup. pub struct Application(Rc<AppCell>); /// Represents an application before it is fully launched. Once your app is /// configured, you'll start the app with `App::run`. impl Application { /// Builds an app with the given asset source. #[allow(clippy::new_without_default)] pub fn new() -> Self { #[cfg(any(test, feature = "test-support"))] log::info!("GPUI was compiled in test mode"); Self(App::new_app( current_platform(false), Arc::new(()), Arc::new(NullHttpClient), )) } /// Build an app in headless mode. This prevents opening windows, /// but makes it possible to run an application in an context like /// SSH, where GUI applications are not allowed. pub fn headless() -> Self { Self(App::new_app( current_platform(true), Arc::new(()), Arc::new(NullHttpClient), )) } /// Assign pub fn with_assets(self, asset_source: impl AssetSource) -> Self { let mut context_lock = self.0.borrow_mut(); let asset_source = Arc::new(asset_source); context_lock.asset_source = asset_source.clone(); context_lock.svg_renderer = SvgRenderer::new(asset_source); drop(context_lock); self } /// Sets the HTTP client for the application. pub fn with_http_client(self, http_client: Arc<dyn HttpClient>) -> Self { let mut context_lock = self.0.borrow_mut(); context_lock.http_client = http_client; drop(context_lock); self } /// Configures when the application should automatically quit. /// By default, [`QuitMode::Default`] is used. pub fn with_quit_mode(self, mode: QuitMode) -> Self { self.0.borrow_mut().quit_mode = mode; self } /// Start the application. The provided callback will be called once the /// app is fully launched. pub fn run<F>(self, on_finish_launching: F) where F: 'static + FnOnce(&mut App), { let this = self.0.clone(); let platform = self.0.borrow().platform.clone(); platform.run(Box::new(move || { let cx = &mut *this.borrow_mut(); on_finish_launching(cx); })); } /// Register a handler to be invoked when the platform instructs the application /// to open one or more URLs. pub fn on_open_urls<F>(&self, mut callback: F) -> &Self where F: 'static + FnMut(Vec<String>), { self.0.borrow().platform.on_open_urls(Box::new(callback)); self } /// Invokes a handler when an already-running application is launched. /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock. pub fn on_reopen<F>(&self, mut callback: F) -> &Self where F: 'static + FnMut(&mut App), { let this = Rc::downgrade(&self.0); self.0.borrow_mut().platform.on_reopen(Box::new(move || { if let Some(app) = this.upgrade() { callback(&mut app.borrow_mut()); } })); self } /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } /// Returns a reference to the [`TextSystem`] associated with this app. pub fn text_system(&self) -> Arc<TextSystem> { self.0.borrow().text_system.clone() } /// Returns the file URL of the executable with the specified name in the application bundle pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { self.0.borrow().path_for_auxiliary_executable(name) } } type Handler = Box<dyn FnMut(&mut App) -> bool + 'static>; type Listener = Box<dyn FnMut(&dyn Any, &mut App) -> bool + 'static>; pub(crate) type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut Window, &mut App) -> bool + 'static>; type QuitHandler = Box<dyn FnOnce(&mut App) -> LocalBoxFuture<'static, ()> + 'static>; 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>; /// Defines when the application should automatically quit. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum QuitMode { /// Use [`QuitMode::Explicit`] on macOS and [`QuitMode::LastWindowClosed`] on other platforms. #[default] Default, /// Quit automatically when the last window is closed. LastWindowClosed, /// Quit only when requested via [`App::quit`]. Explicit, } #[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?; // TODO: `.keys()` returns arbitrary order, what does "next" mean? 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?; // TODO: `.keys()` returns arbitrary order, what does "previous" mean? 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>> { self.tab_groups .values() .find(|tabs| tabs.iter().any(|tab| tab.id == id)) } /// 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; return; } } } } /// 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.iter().find(|tab| tab.id == id).cloned() 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]); }