dock.rs

  1use gpui::{
  2    actions,
  3    elements::{ChildView, Container, FlexItem, Margin, MouseEventHandler, Svg},
  4    impl_internal_actions, CursorStyle, Element, ElementBox, Entity, MouseButton,
  5    MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
  6};
  7use serde::Deserialize;
  8use settings::{DockAnchor, Settings};
  9use theme::Theme;
 10
 11use crate::{ItemHandle, Pane, StatusItemView, Workspace};
 12
 13#[derive(PartialEq, Clone, Deserialize)]
 14pub struct MoveDock(pub DockAnchor);
 15
 16#[derive(PartialEq, Clone)]
 17pub struct AddDefaultItemToDock;
 18
 19actions!(workspace, [ToggleDock]);
 20impl_internal_actions!(workspace, [MoveDock, AddDefaultItemToDock]);
 21
 22pub fn init(cx: &mut MutableAppContext) {
 23    cx.add_action(Dock::toggle);
 24    cx.add_action(Dock::move_dock);
 25}
 26
 27#[derive(Copy, Clone)]
 28pub enum DockPosition {
 29    Shown(DockAnchor),
 30    Hidden(DockAnchor),
 31}
 32
 33impl Default for DockPosition {
 34    fn default() -> Self {
 35        DockPosition::Hidden(Default::default())
 36    }
 37}
 38
 39impl DockPosition {
 40    fn anchor(&self) -> DockAnchor {
 41        match self {
 42            DockPosition::Shown(anchor) | DockPosition::Hidden(anchor) => *anchor,
 43        }
 44    }
 45
 46    fn toggle(self) -> Self {
 47        match self {
 48            DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
 49            DockPosition::Hidden(anchor) => DockPosition::Shown(anchor),
 50        }
 51    }
 52
 53    fn visible(&self) -> Option<DockAnchor> {
 54        match self {
 55            DockPosition::Shown(anchor) => Some(*anchor),
 56            DockPosition::Hidden(_) => None,
 57        }
 58    }
 59
 60    fn hide(self) -> Self {
 61        match self {
 62            DockPosition::Shown(anchor) => DockPosition::Hidden(anchor),
 63            DockPosition::Hidden(_) => self,
 64        }
 65    }
 66}
 67
 68pub type DefaultItemFactory =
 69    fn(&mut Workspace, &mut ViewContext<Workspace>) -> Box<dyn ItemHandle>;
 70
 71pub struct Dock {
 72    position: DockPosition,
 73    pane: ViewHandle<Pane>,
 74    default_item_factory: DefaultItemFactory,
 75}
 76
 77impl Dock {
 78    pub fn new(cx: &mut ViewContext<Workspace>, default_item_factory: DefaultItemFactory) -> Self {
 79        let anchor = cx.global::<Settings>().default_dock_anchor;
 80        let pane = cx.add_view(|cx| Pane::new(Some(anchor), cx));
 81        let pane_id = pane.id();
 82        cx.subscribe(&pane, move |workspace, _, event, cx| {
 83            workspace.handle_pane_event(pane_id, event, cx);
 84        })
 85        .detach();
 86
 87        Self {
 88            pane,
 89            position: DockPosition::Hidden(anchor),
 90            default_item_factory,
 91        }
 92    }
 93
 94    pub fn pane(&self) -> &ViewHandle<Pane> {
 95        &self.pane
 96    }
 97
 98    pub fn visible_pane(&self) -> Option<&ViewHandle<Pane>> {
 99        self.position.visible().map(|_| self.pane())
100    }
101
102    fn set_dock_position(
103        workspace: &mut Workspace,
104        new_position: DockPosition,
105        cx: &mut ViewContext<Workspace>,
106    ) {
107        workspace.dock.position = new_position;
108        // Tell the pane about the new anchor position
109        workspace.dock.pane.update(cx, |pane, cx| {
110            pane.set_docked(Some(new_position.anchor()), cx)
111        });
112
113        let now_visible = workspace.dock.visible_pane().is_some();
114        if now_visible {
115            // Ensure that the pane has at least one item or construct a default item to put in it
116            let pane = workspace.dock.pane.clone();
117            if pane.read(cx).items().next().is_none() {
118                let item_to_add = (workspace.dock.default_item_factory)(workspace, cx);
119                Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
120            }
121            cx.focus(pane);
122        } else {
123            if let Some(last_active_center_pane) = workspace.last_active_center_pane.clone() {
124                cx.focus(last_active_center_pane);
125            }
126        }
127        cx.notify();
128    }
129
130    pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
131        Self::set_dock_position(workspace, workspace.dock.position.hide(), cx);
132    }
133
134    fn toggle(workspace: &mut Workspace, _: &ToggleDock, cx: &mut ViewContext<Workspace>) {
135        Self::set_dock_position(workspace, workspace.dock.position.toggle(), cx);
136    }
137
138    fn move_dock(
139        workspace: &mut Workspace,
140        &MoveDock(new_anchor): &MoveDock,
141        cx: &mut ViewContext<Workspace>,
142    ) {
143        Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx);
144    }
145
146    pub fn render(
147        &self,
148        theme: &Theme,
149        anchor: DockAnchor,
150        cx: &mut RenderContext<Workspace>,
151    ) -> Option<ElementBox> {
152        let style = &theme.workspace.dock;
153
154        self.position
155            .visible()
156            .filter(|current_anchor| *current_anchor == anchor)
157            .map(|anchor| match anchor {
158                DockAnchor::Bottom | DockAnchor::Right => {
159                    let mut panel_style = style.panel.clone();
160                    if anchor == DockAnchor::Bottom {
161                        panel_style.margin = Margin {
162                            top: panel_style.margin.top,
163                            ..Default::default()
164                        };
165                    } else {
166                        panel_style.margin = Margin {
167                            left: panel_style.margin.left,
168                            ..Default::default()
169                        };
170                    }
171                    FlexItem::new(
172                        Container::new(ChildView::new(self.pane.clone()).boxed())
173                            .with_style(style.panel)
174                            .boxed(),
175                    )
176                    .flex(style.flex, true)
177                    .boxed()
178                }
179                DockAnchor::Expanded => Container::new(
180                    MouseEventHandler::<Dock>::new(0, cx, |_state, _cx| {
181                        Container::new(ChildView::new(self.pane.clone()).boxed())
182                            .with_style(style.maximized)
183                            .boxed()
184                    })
185                    .capture_all()
186                    .with_cursor_style(CursorStyle::Arrow)
187                    .boxed(),
188                )
189                .with_background_color(style.wash_color)
190                .boxed(),
191            })
192    }
193}
194
195pub struct ToggleDockButton {
196    workspace: WeakViewHandle<Workspace>,
197}
198
199impl ToggleDockButton {
200    pub fn new(workspace: WeakViewHandle<Workspace>, _cx: &mut ViewContext<Self>) -> Self {
201        Self { workspace }
202    }
203}
204
205impl Entity for ToggleDockButton {
206    type Event = ();
207}
208
209impl View for ToggleDockButton {
210    fn ui_name() -> &'static str {
211        "Dock Toggle"
212    }
213
214    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
215        let dock_is_open = self
216            .workspace
217            .upgrade(cx)
218            .map(|workspace| workspace.read(cx).dock.position.visible().is_some())
219            .unwrap_or(false);
220
221        MouseEventHandler::<Self>::new(0, cx, |state, cx| {
222            let theme = &cx
223                .global::<Settings>()
224                .theme
225                .workspace
226                .status_bar
227                .sidebar_buttons;
228            let style = theme.item.style_for(state, dock_is_open);
229
230            Svg::new("icons/terminal_16.svg")
231                .with_color(style.icon_color)
232                .constrained()
233                .with_width(style.icon_size)
234                .with_height(style.icon_size)
235                .contained()
236                .with_style(style.container)
237                .boxed()
238        })
239        .with_cursor_style(CursorStyle::PointingHand)
240        .on_click(MouseButton::Left, |_, cx| {
241            cx.dispatch_action(ToggleDock);
242        })
243        // TODO: Add tooltip
244        .boxed()
245    }
246}
247
248impl StatusItemView for ToggleDockButton {
249    fn set_active_pane_item(
250        &mut self,
251        _active_pane_item: Option<&dyn crate::ItemHandle>,
252        _cx: &mut ViewContext<Self>,
253    ) {
254        //Not applicable
255    }
256}