1use anyhow::Result;
2use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt};
3use gpui::{
4 AnyView, App, Context, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
5 ManagedView, MouseButton, Pixels, Render, Subscription, Task, Tiling, Window, WindowId,
6 actions, deferred, px,
7};
8use project::Project;
9use std::future::Future;
10use std::path::PathBuf;
11use ui::prelude::*;
12use util::ResultExt;
13
14const SIDEBAR_RESIZE_HANDLE_SIZE: Pixels = px(6.0);
15
16use crate::{
17 CloseIntent, CloseWindow, DockPosition, Event as WorkspaceEvent, Item, ModalView, Panel, Toast,
18 Workspace, WorkspaceId, client_side_decorations, notifications::NotificationId,
19};
20
21actions!(
22 multi_workspace,
23 [
24 /// Creates a new workspace within the current window.
25 NewWorkspaceInWindow,
26 /// Switches to the next workspace within the current window.
27 NextWorkspaceInWindow,
28 /// Switches to the previous workspace within the current window.
29 PreviousWorkspaceInWindow,
30 /// Toggles the workspace switcher sidebar.
31 ToggleWorkspaceSidebar,
32 /// Moves focus to or from the workspace sidebar without closing it.
33 FocusWorkspaceSidebar,
34 ]
35);
36
37pub enum SidebarEvent {
38 Open,
39 Close,
40}
41
42pub trait Sidebar: EventEmitter<SidebarEvent> + Focusable + Render + Sized {
43 fn width(&self, cx: &App) -> Pixels;
44 fn set_width(&mut self, width: Option<Pixels>, cx: &mut Context<Self>);
45 fn has_notifications(&self, cx: &App) -> bool;
46}
47
48pub trait SidebarHandle: 'static + Send + Sync {
49 fn width(&self, cx: &App) -> Pixels;
50 fn set_width(&self, width: Option<Pixels>, cx: &mut App);
51 fn focus_handle(&self, cx: &App) -> FocusHandle;
52 fn focus(&self, window: &mut Window, cx: &mut App);
53 fn has_notifications(&self, cx: &App) -> bool;
54 fn to_any(&self) -> AnyView;
55 fn entity_id(&self) -> EntityId;
56}
57
58#[derive(Clone)]
59pub struct DraggedSidebar;
60
61impl Render for DraggedSidebar {
62 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
63 gpui::Empty
64 }
65}
66
67impl<T: Sidebar> SidebarHandle for Entity<T> {
68 fn width(&self, cx: &App) -> Pixels {
69 self.read(cx).width(cx)
70 }
71
72 fn set_width(&self, width: Option<Pixels>, cx: &mut App) {
73 self.update(cx, |this, cx| this.set_width(width, cx))
74 }
75
76 fn focus_handle(&self, cx: &App) -> FocusHandle {
77 self.read(cx).focus_handle(cx)
78 }
79
80 fn focus(&self, window: &mut Window, cx: &mut App) {
81 let handle = self.read(cx).focus_handle(cx);
82 window.focus(&handle, cx);
83 }
84
85 fn has_notifications(&self, cx: &App) -> bool {
86 self.read(cx).has_notifications(cx)
87 }
88
89 fn to_any(&self) -> AnyView {
90 self.clone().into()
91 }
92
93 fn entity_id(&self) -> EntityId {
94 Entity::entity_id(self)
95 }
96}
97
98pub struct MultiWorkspace {
99 window_id: WindowId,
100 workspaces: Vec<Entity<Workspace>>,
101 active_workspace_index: usize,
102 sidebar: Option<Box<dyn SidebarHandle>>,
103 sidebar_open: bool,
104 _sidebar_subscription: Option<Subscription>,
105 pending_removal_tasks: Vec<Task<()>>,
106 _serialize_task: Option<Task<()>>,
107 _create_task: Option<Task<()>>,
108 _subscriptions: Vec<Subscription>,
109}
110
111impl MultiWorkspace {
112 pub fn new(workspace: Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
113 let release_subscription = cx.on_release(|this: &mut MultiWorkspace, _cx| {
114 if let Some(task) = this._serialize_task.take() {
115 task.detach();
116 }
117 if let Some(task) = this._create_task.take() {
118 task.detach();
119 }
120 for task in std::mem::take(&mut this.pending_removal_tasks) {
121 task.detach();
122 }
123 });
124 let quit_subscription = cx.on_app_quit(Self::app_will_quit);
125 Self::subscribe_to_workspace(&workspace, cx);
126 Self {
127 window_id: window.window_handle().window_id(),
128 workspaces: vec![workspace],
129 active_workspace_index: 0,
130 sidebar: None,
131 sidebar_open: false,
132 _sidebar_subscription: None,
133 pending_removal_tasks: Vec::new(),
134 _serialize_task: None,
135 _create_task: None,
136 _subscriptions: vec![release_subscription, quit_subscription],
137 }
138 }
139
140 pub fn register_sidebar<T: Sidebar>(
141 &mut self,
142 sidebar: Entity<T>,
143 window: &mut Window,
144 cx: &mut Context<Self>,
145 ) {
146 let subscription =
147 cx.subscribe_in(&sidebar, window, |this, _, event, window, cx| match event {
148 SidebarEvent::Open => this.toggle_sidebar(window, cx),
149 SidebarEvent::Close => {
150 this.close_sidebar(window, cx);
151 }
152 });
153 self.sidebar = Some(Box::new(sidebar));
154 self._sidebar_subscription = Some(subscription);
155 }
156
157 pub fn sidebar(&self) -> Option<&dyn SidebarHandle> {
158 self.sidebar.as_deref()
159 }
160
161 pub fn sidebar_open(&self) -> bool {
162 self.sidebar_open && self.sidebar.is_some()
163 }
164
165 pub fn sidebar_has_notifications(&self, cx: &App) -> bool {
166 self.sidebar
167 .as_ref()
168 .map_or(false, |s| s.has_notifications(cx))
169 }
170
171 pub fn multi_workspace_enabled(&self, cx: &App) -> bool {
172 cx.has_flag::<AgentV2FeatureFlag>()
173 }
174
175 pub fn toggle_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
176 if !self.multi_workspace_enabled(cx) {
177 return;
178 }
179
180 if self.sidebar_open {
181 self.close_sidebar(window, cx);
182 } else {
183 self.open_sidebar(cx);
184 if let Some(sidebar) = &self.sidebar {
185 sidebar.focus(window, cx);
186 }
187 }
188 }
189
190 pub fn focus_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
191 if !self.multi_workspace_enabled(cx) {
192 return;
193 }
194
195 if self.sidebar_open {
196 let sidebar_is_focused = self
197 .sidebar
198 .as_ref()
199 .is_some_and(|s| s.focus_handle(cx).contains_focused(window, cx));
200
201 if sidebar_is_focused {
202 let pane = self.workspace().read(cx).active_pane().clone();
203 let pane_focus = pane.read(cx).focus_handle(cx);
204 window.focus(&pane_focus, cx);
205 } else if let Some(sidebar) = &self.sidebar {
206 sidebar.focus(window, cx);
207 }
208 } else {
209 self.open_sidebar(cx);
210 if let Some(sidebar) = &self.sidebar {
211 sidebar.focus(window, cx);
212 }
213 }
214 }
215
216 pub fn open_sidebar(&mut self, cx: &mut Context<Self>) {
217 self.sidebar_open = true;
218 for workspace in &self.workspaces {
219 workspace.update(cx, |workspace, cx| {
220 workspace.set_workspace_sidebar_open(true, cx);
221 });
222 }
223 self.serialize(cx);
224 cx.notify();
225 }
226
227 fn close_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
228 self.sidebar_open = false;
229 for workspace in &self.workspaces {
230 workspace.update(cx, |workspace, cx| {
231 workspace.set_workspace_sidebar_open(false, cx);
232 });
233 }
234 let pane = self.workspace().read(cx).active_pane().clone();
235 let pane_focus = pane.read(cx).focus_handle(cx);
236 window.focus(&pane_focus, cx);
237 self.serialize(cx);
238 cx.notify();
239 }
240
241 pub fn close_window(&mut self, _: &CloseWindow, window: &mut Window, cx: &mut Context<Self>) {
242 cx.spawn_in(window, async move |this, cx| {
243 let workspaces = this.update(cx, |multi_workspace, _cx| {
244 multi_workspace.workspaces().to_vec()
245 })?;
246
247 for workspace in workspaces {
248 let should_continue = workspace
249 .update_in(cx, |workspace, window, cx| {
250 workspace.prepare_to_close(CloseIntent::CloseWindow, window, cx)
251 })?
252 .await?;
253 if !should_continue {
254 return anyhow::Ok(());
255 }
256 }
257
258 cx.update(|window, _cx| {
259 window.remove_window();
260 })?;
261
262 anyhow::Ok(())
263 })
264 .detach_and_log_err(cx);
265 }
266
267 fn subscribe_to_workspace(workspace: &Entity<Workspace>, cx: &mut Context<Self>) {
268 cx.subscribe(workspace, |this, workspace, event, cx| {
269 if let WorkspaceEvent::Activate = event {
270 this.activate(workspace, cx);
271 }
272 })
273 .detach();
274 }
275
276 pub fn is_sidebar_open(&self) -> bool {
277 self.sidebar_open
278 }
279
280 pub fn workspace(&self) -> &Entity<Workspace> {
281 &self.workspaces[self.active_workspace_index]
282 }
283
284 pub fn workspaces(&self) -> &[Entity<Workspace>] {
285 &self.workspaces
286 }
287
288 pub fn active_workspace_index(&self) -> usize {
289 self.active_workspace_index
290 }
291
292 pub fn activate(&mut self, workspace: Entity<Workspace>, cx: &mut Context<Self>) {
293 if !self.multi_workspace_enabled(cx) {
294 self.workspaces[0] = workspace;
295 self.active_workspace_index = 0;
296 cx.notify();
297 return;
298 }
299
300 let old_index = self.active_workspace_index;
301 let new_index = self.set_active_workspace(workspace, cx);
302 if old_index != new_index {
303 self.serialize(cx);
304 }
305 }
306
307 fn set_active_workspace(
308 &mut self,
309 workspace: Entity<Workspace>,
310 cx: &mut Context<Self>,
311 ) -> usize {
312 let index = self.add_workspace(workspace, cx);
313 self.active_workspace_index = index;
314 cx.notify();
315 index
316 }
317
318 /// Adds a workspace to this window without changing which workspace is active.
319 /// Returns the index of the workspace (existing or newly inserted).
320 pub fn add_workspace(&mut self, workspace: Entity<Workspace>, cx: &mut Context<Self>) -> usize {
321 if let Some(index) = self.workspaces.iter().position(|w| *w == workspace) {
322 index
323 } else {
324 if self.sidebar_open {
325 workspace.update(cx, |workspace, cx| {
326 workspace.set_workspace_sidebar_open(true, cx);
327 });
328 }
329 Self::subscribe_to_workspace(&workspace, cx);
330 self.workspaces.push(workspace);
331 cx.notify();
332 self.workspaces.len() - 1
333 }
334 }
335
336 pub fn activate_index(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
337 debug_assert!(
338 index < self.workspaces.len(),
339 "workspace index out of bounds"
340 );
341 self.active_workspace_index = index;
342 self.serialize(cx);
343 self.focus_active_workspace(window, cx);
344 cx.notify();
345 }
346
347 pub fn activate_next_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
348 if self.workspaces.len() > 1 {
349 let next_index = (self.active_workspace_index + 1) % self.workspaces.len();
350 self.activate_index(next_index, window, cx);
351 }
352 }
353
354 pub fn activate_previous_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
355 if self.workspaces.len() > 1 {
356 let prev_index = if self.active_workspace_index == 0 {
357 self.workspaces.len() - 1
358 } else {
359 self.active_workspace_index - 1
360 };
361 self.activate_index(prev_index, window, cx);
362 }
363 }
364
365 fn serialize(&mut self, cx: &mut App) {
366 let window_id = self.window_id;
367 let state = crate::persistence::model::MultiWorkspaceState {
368 active_workspace_id: self.workspace().read(cx).database_id(),
369 sidebar_open: self.sidebar_open,
370 };
371 self._serialize_task = Some(cx.background_spawn(async move {
372 crate::persistence::write_multi_workspace_state(window_id, state).await;
373 }));
374 }
375
376 /// Returns the in-flight serialization task (if any) so the caller can
377 /// await it. Used by the quit handler to ensure pending DB writes
378 /// complete before the process exits.
379 pub fn flush_serialization(&mut self) -> Task<()> {
380 self._serialize_task.take().unwrap_or(Task::ready(()))
381 }
382
383 fn app_will_quit(&mut self, _cx: &mut Context<Self>) -> impl Future<Output = ()> + use<> {
384 let mut tasks: Vec<Task<()>> = Vec::new();
385 if let Some(task) = self._serialize_task.take() {
386 tasks.push(task);
387 }
388 if let Some(task) = self._create_task.take() {
389 tasks.push(task);
390 }
391 tasks.extend(std::mem::take(&mut self.pending_removal_tasks));
392
393 async move {
394 futures::future::join_all(tasks).await;
395 }
396 }
397
398 fn focus_active_workspace(&self, window: &mut Window, cx: &mut App) {
399 // If a dock panel is zoomed, focus it instead of the center pane.
400 // Otherwise, focusing the center pane triggers dismiss_zoomed_items_to_reveal
401 // which closes the zoomed dock.
402 let focus_handle = {
403 let workspace = self.workspace().read(cx);
404 let mut target = None;
405 for dock in workspace.all_docks() {
406 let dock = dock.read(cx);
407 if dock.is_open() {
408 if let Some(panel) = dock.active_panel() {
409 if panel.is_zoomed(window, cx) {
410 target = Some(panel.panel_focus_handle(cx));
411 break;
412 }
413 }
414 }
415 }
416 target.unwrap_or_else(|| {
417 let pane = workspace.active_pane().clone();
418 pane.read(cx).focus_handle(cx)
419 })
420 };
421 window.focus(&focus_handle, cx);
422 }
423
424 pub fn panel<T: Panel>(&self, cx: &App) -> Option<Entity<T>> {
425 self.workspace().read(cx).panel::<T>(cx)
426 }
427
428 pub fn active_modal<V: ManagedView + 'static>(&self, cx: &App) -> Option<Entity<V>> {
429 self.workspace().read(cx).active_modal::<V>(cx)
430 }
431
432 pub fn add_panel<T: Panel>(
433 &mut self,
434 panel: Entity<T>,
435 window: &mut Window,
436 cx: &mut Context<Self>,
437 ) {
438 self.workspace().update(cx, |workspace, cx| {
439 workspace.add_panel(panel, window, cx);
440 });
441 }
442
443 pub fn focus_panel<T: Panel>(
444 &mut self,
445 window: &mut Window,
446 cx: &mut Context<Self>,
447 ) -> Option<Entity<T>> {
448 self.workspace()
449 .update(cx, |workspace, cx| workspace.focus_panel::<T>(window, cx))
450 }
451
452 pub fn toggle_modal<V: ModalView, B>(
453 &mut self,
454 window: &mut Window,
455 cx: &mut Context<Self>,
456 build: B,
457 ) where
458 B: FnOnce(&mut Window, &mut gpui::Context<V>) -> V,
459 {
460 self.workspace().update(cx, |workspace, cx| {
461 workspace.toggle_modal(window, cx, build);
462 });
463 }
464
465 pub fn toggle_dock(
466 &mut self,
467 dock_side: DockPosition,
468 window: &mut Window,
469 cx: &mut Context<Self>,
470 ) {
471 self.workspace().update(cx, |workspace, cx| {
472 workspace.toggle_dock(dock_side, window, cx);
473 });
474 }
475
476 pub fn active_item_as<I: 'static>(&self, cx: &App) -> Option<Entity<I>> {
477 self.workspace().read(cx).active_item_as::<I>(cx)
478 }
479
480 pub fn items_of_type<'a, T: Item>(
481 &'a self,
482 cx: &'a App,
483 ) -> impl 'a + Iterator<Item = Entity<T>> {
484 self.workspace().read(cx).items_of_type::<T>(cx)
485 }
486
487 pub fn database_id(&self, cx: &App) -> Option<WorkspaceId> {
488 self.workspace().read(cx).database_id()
489 }
490
491 pub fn take_pending_removal_tasks(&mut self) -> Vec<Task<()>> {
492 let mut tasks: Vec<Task<()>> = std::mem::take(&mut self.pending_removal_tasks)
493 .into_iter()
494 .filter(|task| !task.is_ready())
495 .collect();
496 if let Some(task) = self._create_task.take() {
497 if !task.is_ready() {
498 tasks.push(task);
499 }
500 }
501 tasks
502 }
503
504 #[cfg(any(test, feature = "test-support"))]
505 pub fn set_random_database_id(&mut self, cx: &mut Context<Self>) {
506 self.workspace().update(cx, |workspace, _cx| {
507 workspace.set_random_database_id();
508 });
509 }
510
511 #[cfg(any(test, feature = "test-support"))]
512 pub fn test_new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
513 let workspace = cx.new(|cx| Workspace::test_new(project, window, cx));
514 Self::new(workspace, window, cx)
515 }
516
517 #[cfg(any(test, feature = "test-support"))]
518 pub fn test_add_workspace(
519 &mut self,
520 project: Entity<Project>,
521 window: &mut Window,
522 cx: &mut Context<Self>,
523 ) -> Entity<Workspace> {
524 let workspace = cx.new(|cx| Workspace::test_new(project, window, cx));
525 self.activate(workspace.clone(), cx);
526 workspace
527 }
528
529 pub fn create_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
530 if !self.multi_workspace_enabled(cx) {
531 return;
532 }
533 let app_state = self.workspace().read(cx).app_state().clone();
534 let project = Project::local(
535 app_state.client.clone(),
536 app_state.node_runtime.clone(),
537 app_state.user_store.clone(),
538 app_state.languages.clone(),
539 app_state.fs.clone(),
540 None,
541 project::LocalProjectFlags::default(),
542 cx,
543 );
544 let new_workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
545 self.set_active_workspace(new_workspace.clone(), cx);
546 self.focus_active_workspace(window, cx);
547
548 let weak_workspace = new_workspace.downgrade();
549 self._create_task = Some(cx.spawn_in(window, async move |this, cx| {
550 let result = crate::persistence::DB.next_id().await;
551 this.update_in(cx, |this, window, cx| match result {
552 Ok(workspace_id) => {
553 if let Some(workspace) = weak_workspace.upgrade() {
554 let session_id = workspace.read(cx).session_id();
555 let window_id = window.window_handle().window_id().as_u64();
556 workspace.update(cx, |workspace, _cx| {
557 workspace.set_database_id(workspace_id);
558 });
559 cx.background_spawn(async move {
560 crate::persistence::DB
561 .set_session_binding(workspace_id, session_id, Some(window_id))
562 .await
563 .log_err();
564 })
565 .detach();
566 } else {
567 cx.background_spawn(async move {
568 crate::persistence::DB
569 .delete_workspace_by_id(workspace_id)
570 .await
571 .log_err();
572 })
573 .detach();
574 }
575 this.serialize(cx);
576 }
577 Err(error) => {
578 log::error!("Failed to create workspace: {error:#}");
579 if let Some(index) = weak_workspace
580 .upgrade()
581 .and_then(|w| this.workspaces.iter().position(|ws| *ws == w))
582 {
583 this.remove_workspace(index, window, cx);
584 }
585 this.workspace().update(cx, |workspace, cx| {
586 let id = NotificationId::unique::<MultiWorkspace>();
587 workspace.show_toast(
588 Toast::new(id, format!("Failed to create workspace: {error}")),
589 cx,
590 );
591 });
592 }
593 })
594 .log_err();
595 }));
596 }
597
598 pub fn remove_workspace(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
599 if self.workspaces.len() <= 1 || index >= self.workspaces.len() {
600 return;
601 }
602
603 let removed_workspace = self.workspaces.remove(index);
604
605 if self.active_workspace_index >= self.workspaces.len() {
606 self.active_workspace_index = self.workspaces.len() - 1;
607 } else if self.active_workspace_index > index {
608 self.active_workspace_index -= 1;
609 }
610
611 if let Some(workspace_id) = removed_workspace.read(cx).database_id() {
612 self.pending_removal_tasks.retain(|task| !task.is_ready());
613 self.pending_removal_tasks
614 .push(cx.background_spawn(async move {
615 crate::persistence::DB
616 .delete_workspace_by_id(workspace_id)
617 .await
618 .log_err();
619 }));
620 }
621
622 self.serialize(cx);
623 self.focus_active_workspace(window, cx);
624 cx.notify();
625 }
626
627 pub fn open_project(
628 &mut self,
629 paths: Vec<PathBuf>,
630 window: &mut Window,
631 cx: &mut Context<Self>,
632 ) -> Task<Result<()>> {
633 let workspace = self.workspace().clone();
634
635 if self.multi_workspace_enabled(cx) {
636 workspace.update(cx, |workspace, cx| {
637 workspace.open_workspace_for_paths(true, paths, window, cx)
638 })
639 } else {
640 cx.spawn_in(window, async move |_this, cx| {
641 let should_continue = workspace
642 .update_in(cx, |workspace, window, cx| {
643 workspace.prepare_to_close(crate::CloseIntent::ReplaceWindow, window, cx)
644 })?
645 .await?;
646 if should_continue {
647 workspace
648 .update_in(cx, |workspace, window, cx| {
649 workspace.open_workspace_for_paths(true, paths, window, cx)
650 })?
651 .await
652 } else {
653 Ok(())
654 }
655 })
656 }
657 }
658}
659
660impl Render for MultiWorkspace {
661 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
662 let multi_workspace_enabled = self.multi_workspace_enabled(cx);
663 let is_zoomed = self.workspace().read(cx).zoomed_item().is_some();
664
665 let sidebar: Option<AnyElement> = if multi_workspace_enabled && self.sidebar_open {
666 self.sidebar.as_ref().map(|sidebar_handle| {
667 let weak = cx.weak_entity();
668
669 let sidebar_width = sidebar_handle.width(cx);
670 let resize_handle = deferred(
671 div()
672 .id("sidebar-resize-handle")
673 .absolute()
674 .right(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
675 .top(px(0.))
676 .h_full()
677 .w(SIDEBAR_RESIZE_HANDLE_SIZE)
678 .cursor_col_resize()
679 .on_drag(DraggedSidebar, |dragged, _, _, cx| {
680 cx.stop_propagation();
681 cx.new(|_| dragged.clone())
682 })
683 .on_mouse_down(MouseButton::Left, |_, _, cx| {
684 cx.stop_propagation();
685 })
686 .on_mouse_up(MouseButton::Left, move |event, _, cx| {
687 if event.click_count == 2 {
688 weak.update(cx, |this, cx| {
689 if let Some(sidebar) = this.sidebar.as_mut() {
690 sidebar.set_width(None, cx);
691 }
692 })
693 .ok();
694 cx.stop_propagation();
695 }
696 })
697 .occlude(),
698 );
699
700 div()
701 .id("sidebar-container")
702 .relative()
703 .h_full()
704 .w(sidebar_width)
705 .flex_shrink_0()
706 .child(sidebar_handle.to_any())
707 .child(resize_handle)
708 .into_any_element()
709 })
710 } else {
711 None
712 };
713
714 client_side_decorations(
715 h_flex()
716 .key_context("Workspace")
717 .relative()
718 .size_full()
719 .on_action(cx.listener(Self::close_window))
720 .on_action(
721 cx.listener(|this: &mut Self, _: &NewWorkspaceInWindow, window, cx| {
722 this.create_workspace(window, cx);
723 }),
724 )
725 .on_action(
726 cx.listener(|this: &mut Self, _: &NextWorkspaceInWindow, window, cx| {
727 this.activate_next_workspace(window, cx);
728 }),
729 )
730 .on_action(cx.listener(
731 |this: &mut Self, _: &PreviousWorkspaceInWindow, window, cx| {
732 this.activate_previous_workspace(window, cx);
733 },
734 ))
735 .on_action(cx.listener(
736 |this: &mut Self, _: &ToggleWorkspaceSidebar, window, cx| {
737 this.toggle_sidebar(window, cx);
738 },
739 ))
740 .on_action(
741 cx.listener(|this: &mut Self, _: &FocusWorkspaceSidebar, window, cx| {
742 this.focus_sidebar(window, cx);
743 }),
744 )
745 .when(
746 self.sidebar_open() && self.multi_workspace_enabled(cx),
747 |this| {
748 this.on_drag_move(cx.listener(
749 |this: &mut Self, e: &DragMoveEvent<DraggedSidebar>, _window, cx| {
750 if let Some(sidebar) = &this.sidebar {
751 let new_width = e.event.position.x;
752 sidebar.set_width(Some(new_width), cx);
753 }
754 },
755 ))
756 .children(sidebar)
757 },
758 )
759 .child(
760 div()
761 .flex()
762 .flex_1()
763 .size_full()
764 .overflow_hidden()
765 .when(is_zoomed, |this| this.absolute().inset_0())
766 .child(self.workspace().clone()),
767 ),
768 window,
769 cx,
770 Tiling {
771 left: multi_workspace_enabled && self.sidebar_open && !is_zoomed,
772 ..Tiling::default()
773 },
774 )
775 }
776}