Start work on displaying modified status in tabs

Max Brunsfeld created

Change summary

gpui/src/elements/canvas.rs         | 73 +++++++++++++++++++++++++++++++
gpui/src/elements/mod.rs            |  2 
zed/src/editor/buffer_view.rs       |  4 +
zed/src/workspace/pane.rs           | 53 +++++++++++++++++++++
zed/src/workspace/workspace_view.rs |  8 +++
5 files changed, 138 insertions(+), 2 deletions(-)

Detailed changes

gpui/src/elements/canvas.rs 🔗

@@ -0,0 +1,73 @@
+use super::Element;
+use crate::PaintContext;
+use pathfinder_geometry::{
+    rect::RectF,
+    vector::{vec2f, Vector2F},
+};
+
+pub struct Canvas<F>(F)
+where
+    F: FnMut(RectF, &mut PaintContext);
+
+impl<F> Canvas<F>
+where
+    F: FnMut(RectF, &mut PaintContext),
+{
+    pub fn new(f: F) -> Self {
+        Self(f)
+    }
+}
+
+impl<F> Element for Canvas<F>
+where
+    F: FnMut(RectF, &mut PaintContext),
+{
+    type LayoutState = ();
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: crate::SizeConstraint,
+        _: &mut crate::LayoutContext,
+    ) -> (Vector2F, Self::LayoutState) {
+        let x = if constraint.max.x().is_finite() {
+            constraint.max.x()
+        } else {
+            constraint.min.x()
+        };
+        let y = if constraint.max.y().is_finite() {
+            constraint.max.y()
+        } else {
+            constraint.min.y()
+        };
+        (vec2f(x, y), ())
+    }
+
+    fn paint(
+        &mut self,
+        bounds: RectF,
+        _: &mut Self::LayoutState,
+        ctx: &mut PaintContext,
+    ) -> Self::PaintState {
+        self.0(bounds, ctx)
+    }
+
+    fn after_layout(
+        &mut self,
+        _: Vector2F,
+        _: &mut Self::LayoutState,
+        _: &mut crate::AfterLayoutContext,
+    ) {
+    }
+
+    fn dispatch_event(
+        &mut self,
+        _: &crate::Event,
+        _: RectF,
+        _: &mut Self::LayoutState,
+        _: &mut Self::PaintState,
+        _: &mut crate::EventContext,
+    ) -> bool {
+        false
+    }
+}

gpui/src/elements/mod.rs 🔗

@@ -1,4 +1,5 @@
 mod align;
+mod canvas;
 mod constrained_box;
 mod container;
 mod empty;
@@ -13,6 +14,7 @@ mod uniform_list;
 
 pub use crate::presenter::ChildView;
 pub use align::*;
+pub use canvas::*;
 pub use constrained_box::*;
 pub use container::*;
 pub use empty::*;

zed/src/editor/buffer_view.rs 🔗

@@ -1181,6 +1181,10 @@ impl workspace::ItemView for BufferView {
     fn save(&self, ctx: &mut MutableAppContext) -> Option<Task<Result<()>>> {
         self.buffer.update(ctx, |buffer, ctx| buffer.save(ctx))
     }
+
+    fn is_modified(&self, ctx: &AppContext) -> bool {
+        self.buffer.as_ref(ctx).is_modified()
+    }
 }
 
 impl Selection {

zed/src/workspace/pane.rs 🔗

@@ -1,7 +1,11 @@
 use super::{ItemViewHandle, SplitDirection};
 use crate::{settings::Settings, watch};
 use gpui::{
-    color::ColorU, elements::*, keymap::Binding, App, AppContext, Border, Entity, View, ViewContext,
+    color::{ColorF, ColorU},
+    elements::*,
+    geometry::{rect::RectF, vector::vec2f},
+    keymap::Binding,
+    App, AppContext, Border, Entity, Quad, View, ViewContext,
 };
 use std::cmp;
 
@@ -190,7 +194,32 @@ impl Pane {
             let padding = 6.;
             let mut container = Container::new(
                 Align::new(
-                    Label::new(title, settings.ui_font_family, settings.ui_font_size).boxed(),
+                    Flex::row()
+                        .with_child(
+                            Expanded::new(
+                                1.0,
+                                Label::new(title, settings.ui_font_family, settings.ui_font_size)
+                                    .boxed(),
+                            )
+                            .boxed(),
+                        )
+                        .with_child(
+                            Expanded::new(
+                                1.0,
+                                LineBox::new(
+                                    settings.ui_font_family,
+                                    settings.ui_font_size,
+                                    ConstrainedBox::new(Self::render_modified_icon(
+                                        item.is_modified(app),
+                                    ))
+                                    .with_max_width(12.)
+                                    .boxed(),
+                                )
+                                .boxed(),
+                            )
+                            .boxed(),
+                        )
+                        .boxed(),
                 )
                 .boxed(),
             )
@@ -243,6 +272,26 @@ impl Pane {
 
         row.boxed()
     }
+
+    fn render_modified_icon(is_modified: bool) -> ElementBox {
+        Canvas::new(move |bounds, ctx| {
+            if is_modified {
+                let padding = if bounds.height() < bounds.width() {
+                    vec2f(bounds.width() - bounds.height(), 0.0)
+                } else {
+                    vec2f(0.0, bounds.height() - bounds.width())
+                };
+                let square = RectF::new(bounds.origin() + padding / 2., bounds.size() - padding);
+                ctx.scene.push_quad(Quad {
+                    bounds: square,
+                    background: Some(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8()),
+                    border: Default::default(),
+                    corner_radius: square.width() / 2.,
+                });
+            }
+        })
+        .boxed()
+    }
 }
 
 impl Entity for Pane {

zed/src/workspace/workspace_view.rs 🔗

@@ -22,6 +22,9 @@ pub trait ItemView: View {
     {
         None
     }
+    fn is_modified(&self, _: &AppContext) -> bool {
+        false
+    }
     fn save(&self, _: &mut MutableAppContext) -> Option<Task<anyhow::Result<()>>> {
         None
     }
@@ -35,6 +38,7 @@ pub trait ItemViewHandle: Send + Sync {
     fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext);
     fn id(&self) -> usize;
     fn to_any(&self) -> AnyViewHandle;
+    fn is_modified(&self, ctx: &AppContext) -> bool;
     fn save(&self, ctx: &mut MutableAppContext) -> Option<Task<anyhow::Result<()>>>;
 }
 
@@ -75,6 +79,10 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
         self.update(ctx, |item, ctx| item.save(ctx.app_mut()))
     }
 
+    fn is_modified(&self, ctx: &AppContext) -> bool {
+        self.as_ref(ctx).is_modified(ctx)
+    }
+
     fn id(&self) -> usize {
         self.id()
     }