Add forward and backward navigation buttons to toolbar

Max Brunsfeld created

Change summary

assets/icons/arrow-left.svg       |  3 +
assets/icons/arrow-right.svg      |  3 +
crates/theme/src/theme.rs         |  1 
crates/workspace/src/pane.rs      |  3 
crates/workspace/src/toolbar.rs   | 70 +++++++++++++++++++++++++++++---
styles/src/styleTree/workspace.ts |  9 ++++
6 files changed, 80 insertions(+), 9 deletions(-)

Detailed changes

assets/icons/arrow-left.svg 🔗

@@ -0,0 +1,3 @@
+<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 3.99999C8 4.31671 7.76023 4.57258 7.44352 4.57258H1.95565L3.8416 6.45853C4.06527 6.6822 4.06527 7.04454 3.8416 7.2682C3.72887 7.38004 3.58215 7.43551 3.43542 7.43551C3.2887 7.43551 3.14233 7.37959 3.03068 7.26776L0.16775 4.40483C-0.0559165 4.18116 -0.0559165 3.81883 0.16775 3.59516L3.03068 0.732233C3.25434 0.508567 3.61668 0.508567 3.84035 0.732233C4.06401 0.955899 4.06401 1.31824 3.84035 1.5419L1.95565 3.42741H7.44352C7.76023 3.42741 8 3.68328 8 3.99999Z" fill="#839496"/>
+</svg>

assets/icons/arrow-right.svg 🔗

@@ -0,0 +1,3 @@
+<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M7.83265 4.40382L4.97532 7.26115C4.8646 7.37365 4.71816 7.42901 4.57172 7.42901C4.42528 7.42901 4.2792 7.37321 4.16777 7.26159C3.94454 7.03836 3.94454 6.67673 4.16777 6.4535L6.05039 4.57169H0.571465C0.255909 4.57169 0 4.31631 0 4.00022C0 3.68413 0.255731 3.42876 0.571287 3.42876H6.05021L4.16795 1.54649C3.94472 1.32326 3.94472 0.961634 4.16795 0.738405C4.39117 0.515177 4.75281 0.515177 4.97603 0.738405L7.83336 3.59573C8.0557 3.81985 8.0557 4.18059 7.83247 4.40382H7.83265Z" fill="#FDF6E3"/>
+</svg>

crates/theme/src/theme.rs 🔗

@@ -108,6 +108,7 @@ pub struct Toolbar {
     pub container: ContainerStyle,
     pub height: f32,
     pub item_spacing: f32,
+    pub nav_button: Interactive<IconButton>,
 }
 
 #[derive(Clone, Deserialize, Default)]

crates/workspace/src/pane.rs 🔗

@@ -168,12 +168,13 @@ pub struct NavigationEntry {
 
 impl Pane {
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
+        let handle = cx.weak_handle();
         Self {
             items: Vec::new(),
             active_item_index: 0,
             autoscroll: false,
             nav_history: Default::default(),
-            toolbar: cx.add_view(|_| Toolbar::new()),
+            toolbar: cx.add_view(|_| Toolbar::new(handle)),
         }
     }
 

crates/workspace/src/toolbar.rs 🔗

@@ -1,7 +1,7 @@
-use crate::ItemHandle;
+use crate::{ItemHandle, Pane};
 use gpui::{
-    elements::*, AnyViewHandle, AppContext, ElementBox, Entity, MutableAppContext, RenderContext,
-    View, ViewContext, ViewHandle,
+    elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, Entity,
+    MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use settings::Settings;
 
@@ -42,6 +42,7 @@ pub enum ToolbarItemLocation {
 
 pub struct Toolbar {
     active_pane_item: Option<Box<dyn ItemHandle>>,
+    pane: WeakViewHandle<Pane>,
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
 
@@ -60,6 +61,7 @@ impl View for Toolbar {
         let mut primary_left_items = Vec::new();
         let mut primary_right_items = Vec::new();
         let mut secondary_item = None;
+        let spacing = theme.item_spacing;
 
         for (item, position) in &self.items {
             match *position {
@@ -68,7 +70,7 @@ impl View for Toolbar {
                     let left_item = ChildView::new(item.as_ref())
                         .aligned()
                         .contained()
-                        .with_margin_right(theme.item_spacing);
+                        .with_margin_right(spacing);
                     if let Some((flex, expanded)) = flex {
                         primary_left_items.push(left_item.flex(flex, expanded).boxed());
                     } else {
@@ -79,7 +81,7 @@ impl View for Toolbar {
                     let right_item = ChildView::new(item.as_ref())
                         .aligned()
                         .contained()
-                        .with_margin_left(theme.item_spacing)
+                        .with_margin_left(spacing)
                         .flex_float();
                     if let Some((flex, expanded)) = flex {
                         primary_right_items.push(right_item.flex(flex, expanded).boxed());
@@ -98,26 +100,78 @@ impl View for Toolbar {
             }
         }
 
+        let pane = self.pane.clone();
+        let container_style = theme.container;
+        let height = theme.height;
+        let button_style = theme.nav_button;
+
         Flex::column()
             .with_child(
                 Flex::row()
+                    .with_child(nav_button(
+                        "icons/arrow-left.svg",
+                        button_style,
+                        spacing,
+                        super::GoBack {
+                            pane: Some(pane.clone()),
+                        },
+                        cx,
+                    ))
+                    .with_child(nav_button(
+                        "icons/arrow-right.svg",
+                        button_style,
+                        spacing,
+                        super::GoForward {
+                            pane: Some(pane.clone()),
+                        },
+                        cx,
+                    ))
                     .with_children(primary_left_items)
                     .with_children(primary_right_items)
                     .constrained()
-                    .with_height(theme.height)
+                    .with_height(height)
                     .boxed(),
             )
             .with_children(secondary_item)
             .contained()
-            .with_style(theme.container)
+            .with_style(container_style)
             .boxed()
     }
 }
 
+fn nav_button<A: Action + Clone>(
+    svg_path: &'static str,
+    style: theme::Interactive<theme::IconButton>,
+    spacing: f32,
+    action: A,
+    cx: &mut RenderContext<Toolbar>,
+) -> ElementBox {
+    MouseEventHandler::new::<A, _, _>(0, cx, |state, _| {
+        let style = style.style_for(state, false);
+        Svg::new(svg_path)
+            .with_color(style.color)
+            .constrained()
+            .with_width(style.icon_width)
+            .aligned()
+            .contained()
+            .with_style(style.container)
+            .constrained()
+            .with_width(style.button_width)
+            .with_height(style.button_width)
+            .boxed()
+    })
+    .with_cursor_style(CursorStyle::PointingHand)
+    .on_mouse_down(move |_, cx| cx.dispatch_action(action.clone()))
+    .contained()
+    .with_margin_right(spacing)
+    .boxed()
+}
+
 impl Toolbar {
-    pub fn new() -> Self {
+    pub fn new(pane: WeakViewHandle<Pane>) -> Self {
         Self {
             active_pane_item: None,
+            pane,
             items: Default::default(),
         }
     }

styles/src/styleTree/workspace.ts 🔗

@@ -139,6 +139,15 @@ export default function workspace(theme: Theme) {
       background: backgroundColor(theme, 500),
       border: border(theme, "secondary", { bottom: true }),
       itemSpacing: 8,
+      navButton: {
+        color: iconColor(theme, "secondary"),
+        iconWidth: 8,
+        buttonWidth: 12,
+        margin: { left: 8, right: 8 },
+        hover: {
+          color: iconColor(theme, "active"),
+        },
+      },
       padding: { left: 16, right: 8, top: 4, bottom: 4 },
     },
     breadcrumbs: {