diff --git a/gpui/src/elements/canvas.rs b/gpui/src/elements/canvas.rs new file mode 100644 index 0000000000..4788ab9b9d --- /dev/null +++ b/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) +where + F: FnMut(RectF, &mut PaintContext); + +impl Canvas +where + F: FnMut(RectF, &mut PaintContext), +{ + pub fn new(f: F) -> Self { + Self(f) + } +} + +impl Element for Canvas +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 + } +} diff --git a/gpui/src/elements/mod.rs b/gpui/src/elements/mod.rs index 121fa9b6a6..1bcfa7f6fa 100644 --- a/gpui/src/elements/mod.rs +++ b/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::*; diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 53b7415d83..d842096c0c 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1181,6 +1181,10 @@ impl workspace::ItemView for BufferView { fn save(&self, ctx: &mut MutableAppContext) -> Option>> { 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 { diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index c900340a04..e2662eab96 100644 --- a/zed/src/workspace/pane.rs +++ b/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 { diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 1f442866d2..fa61bdcb0d 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/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>> { None } @@ -35,6 +38,7 @@ pub trait ItemViewHandle: Send + Sync { fn set_parent_pane(&self, pane: &ViewHandle, 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>>; } @@ -75,6 +79,10 @@ impl ItemViewHandle for ViewHandle { 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() }