use std::sync::Arc; use gpui::prelude::FluentBuilder; use gpui::{ div, px, rems, App, AppContext, Context, Corner, DefiniteLength, DismissEvent, DragMoveEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, WeakEntity, Window, }; use theme::{ActiveTheme, CLIENT_SIDE_DECORATION_ROUNDING, TABBAR_HEIGHT}; use crate::button::{Button, ButtonVariants as _}; use crate::dock_area::dock::DockPlacement; use crate::dock_area::panel::{Panel, PanelView}; use crate::dock_area::stack_panel::StackPanel; use crate::dock_area::{ClosePanel, DockArea, PanelEvent, PanelStyle, ToggleZoom}; use crate::menu::{DropdownMenu, PopupMenu}; use crate::tab::tab_bar::TabBar; use crate::tab::Tab; use crate::{h_flex, v_flex, AxisExt, IconName, Placement, Selectable, Sizable, StyledExt}; #[derive(Clone)] struct TabState { closable: bool, zoomable: bool, draggable: bool, droppable: bool, active_panel: Option>, } #[derive(Clone)] pub(crate) struct DragPanel { pub(crate) panel: Arc, pub(crate) tab_panel: Entity, } impl DragPanel { pub(crate) fn new(panel: Arc, tab_panel: Entity) -> Self { Self { panel, tab_panel } } } impl Render for DragPanel { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { div() .id("drag-panel") .cursor_grab() .py_1() .px_2() .w_24() .flex() .items_center() .justify_center() .overflow_hidden() .whitespace_nowrap() .rounded(cx.theme().radius_lg) .text_xs() .when(cx.theme().shadow, |this| this.shadow_lg()) .bg(cx.theme().background) .text_color(cx.theme().text_accent) .child(self.panel.title(cx)) } } pub struct TabPanel { focus_handle: FocusHandle, dock_area: WeakEntity, /// List of panels in the tab panel pub(crate) panels: Vec>, /// Current active panel index pub(crate) active_ix: usize, /// If this is true, the Panel closeable will follow the active panel's closeable, /// otherwise this TabPanel will not able to close pub(crate) closable: bool, /// The stock_panel can be None, if is None, that means the panels can't be split or move stack_panel: Option>, /// Scroll handle for the tab bar tab_bar_scroll_handle: ScrollHandle, /// Whether the tab panel is zoomeds zoomed: bool, /// Whether the tab panel is collapsed collapsed: bool, /// When drag move, will get the placement of the panel to be split will_split_placement: Option, } impl Panel for TabPanel { fn panel_id(&self) -> SharedString { "TabPanel".into() } fn title(&self, cx: &App) -> gpui::AnyElement { self.active_panel(cx) .map(|panel| panel.title(cx)) .unwrap_or("Empty Tab".into_any_element()) } fn closable(&self, cx: &App) -> bool { if !self.closable { return false; } self.active_panel(cx) .map(|panel| panel.closable(cx)) .unwrap_or(true) } fn zoomable(&self, cx: &App) -> bool { self.active_panel(cx) .map(|panel| panel.zoomable(cx)) .unwrap_or(false) } fn visible(&self, cx: &App) -> bool { self.visible_panels(cx).next().is_some() } fn popup_menu(&self, menu: PopupMenu, cx: &App) -> PopupMenu { if let Some(panel) = self.active_panel(cx) { panel.popup_menu(menu, cx) } else { menu } } fn toolbar_buttons(&self, window: &Window, cx: &App) -> Vec