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        self.position
154            .visible()
155            .filter(|current_anchor| *current_anchor == anchor)
156            .map(|anchor| match anchor {
157                DockAnchor::Bottom | DockAnchor::Right => {
158                    let mut panel_style = style.panel.clone();
159                    if anchor == DockAnchor::Bottom {
160                        panel_style.margin = Margin {
161                            top: panel_style.margin.top,
162                            ..Default::default()
163                        };
164                    } else {
165                        panel_style.margin = Margin {
166                            left: panel_style.margin.left,
167                            ..Default::default()
168                        };
169                    }
170                    FlexItem::new(
171                        Container::new(ChildView::new(self.pane.clone()).boxed())
172                            .with_style(style.panel)
173                            .boxed(),
174                    )
175                    .flex(style.flex, true)
176                    .boxed()
177                }
178                DockAnchor::Expanded => Container::new(
179                    MouseEventHandler::<Dock>::new(0, cx, |_state, _cx| {
180                        Container::new(ChildView::new(self.pane.clone()).boxed())
181                            .with_style(style.maximized)
182                            .boxed()
183                    })
184                    .capture_all()
185                    .with_cursor_style(CursorStyle::Arrow)
186                    .boxed(),
187                )
188                .with_background_color(style.wash_color)
189                .boxed(),
190            })
191    }
192}
193
194pub struct ToggleDockButton {
195    workspace: WeakViewHandle<Workspace>,
196}
197
198impl ToggleDockButton {
199    pub fn new(workspace: WeakViewHandle<Workspace>, _cx: &mut ViewContext<Self>) -> Self {
200        Self { workspace }
201    }
202}
203
204impl Entity for ToggleDockButton {
205    type Event = ();
206}
207
208impl View for ToggleDockButton {
209    fn ui_name() -> &'static str {
210        "Dock Toggle"
211    }
212
213    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
214        let dock_is_open = self
215            .workspace
216            .upgrade(cx)
217            .map(|workspace| workspace.read(cx).dock.position.visible().is_some())
218            .unwrap_or(false);
219
220        MouseEventHandler::<Self>::new(0, cx, |state, cx| {
221            let theme = &cx
222                .global::<Settings>()
223                .theme
224                .workspace
225                .status_bar
226                .sidebar_buttons;
227            let style = theme.item.style_for(state, dock_is_open);
228
229            Svg::new("icons/terminal_16.svg")
230                .with_color(style.icon_color)
231                .constrained()
232                .with_width(style.icon_size)
233                .with_height(style.icon_size)
234                .contained()
235                .with_style(style.container)
236                .boxed()
237        })
238        .with_cursor_style(CursorStyle::PointingHand)
239        .on_click(MouseButton::Left, |_, cx| {
240            cx.dispatch_action(ToggleDock);
241        })
242        // TODO: Add tooltip
243        .boxed()
244    }
245}
246
247impl StatusItemView for ToggleDockButton {
248    fn set_active_pane_item(
249        &mut self,
250        _active_pane_item: Option<&dyn crate::ItemHandle>,
251        _cx: &mut ViewContext<Self>,
252    ) {
253        //Not applicable
254    }
255}