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