WIP

Nathan Sobo created

Change summary

gpui/src/app.rs                             | 22 +++++++++++----
gpui/src/elements/mouse_event_handler.rs    | 12 +++++--
gpui/src/platform/mac/renderer.rs           |  2 
gpui/src/platform/mac/shaders/shaders.h     |  3 +
gpui/src/platform/mac/shaders/shaders.metal |  4 +-
gpui/src/platform/mac/sprite_cache.rs       | 15 +++++++++++
zed/src/workspace/pane.rs                   | 31 +++++++++++++++++++---
7 files changed, 70 insertions(+), 19 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -9,7 +9,7 @@ use crate::{
 };
 use anyhow::{anyhow, Result};
 use keymap::MatchResult;
-use parking_lot::Mutex;
+use parking_lot::{Mutex, RwLock};
 use pathfinder_geometry::{rect::RectF, vector::vec2f};
 use platform::Event;
 use postage::{sink::Sink as _, stream::Stream as _};
@@ -900,7 +900,7 @@ impl MutableAppContext {
                 }
             }
 
-            let mut values = self.ctx.values.lock();
+            let mut values = self.ctx.values.write();
             for key in dropped_values {
                 values.remove(&key);
             }
@@ -1322,7 +1322,7 @@ impl AsRef<AppContext> for MutableAppContext {
 pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
     windows: HashMap<usize, Window>,
-    values: Mutex<HashMap<(TypeId, usize), Box<dyn Any>>>,
+    values: RwLock<HashMap<(TypeId, usize), Box<dyn Any>>>,
     background: Arc<executor::Background>,
     ref_counts: Arc<Mutex<RefCounts>>,
     thread_pool: scoped_pool::Pool,
@@ -1376,7 +1376,7 @@ impl AppContext {
 
     pub fn value<Tag: 'static, T: 'static + Default>(&self, id: usize) -> ValueHandle<T> {
         let key = (TypeId::of::<Tag>(), id);
-        let mut values = self.values.lock();
+        let mut values = self.values.write();
         values.entry(key).or_insert_with(|| Box::new(T::default()));
         ValueHandle::new(TypeId::of::<Tag>(), id, &self.ref_counts)
     }
@@ -2387,10 +2387,20 @@ impl<T: 'static> ValueHandle<T> {
         }
     }
 
-    pub fn map<R>(&self, ctx: &AppContext, f: impl FnOnce(&mut T) -> R) -> R {
+    pub fn read<R>(&self, ctx: &AppContext, f: impl FnOnce(&T) -> R) -> R {
         f(ctx
             .values
-            .lock()
+            .read()
+            .get(&(self.tag_type_id, self.id))
+            .unwrap()
+            .downcast_ref()
+            .unwrap())
+    }
+
+    pub fn update<R>(&self, ctx: &AppContext, f: impl FnOnce(&mut T) -> R) -> R {
+        f(ctx
+            .values
+            .write()
             .get_mut(&(self.tag_type_id, self.id))
             .unwrap()
             .downcast_mut()

gpui/src/elements/mouse_event_handler.rs 🔗

@@ -22,9 +22,13 @@ impl MouseEventHandler {
         Tag: 'static,
         F: FnOnce(MouseState) -> ElementBox,
     {
-        let state = ctx.value::<Tag, _>(id);
-        let child = state.map(ctx, |state| render_child(*state));
-        Self { state, child }
+        let state_handle = ctx.value::<Tag, _>(id);
+        let state = state_handle.read(ctx, |state| *state);
+        let child = render_child(state);
+        Self {
+            state: state_handle,
+            child,
+        }
     }
 }
 
@@ -68,7 +72,7 @@ impl Element for MouseEventHandler {
     ) -> bool {
         let handled_in_child = self.child.dispatch_event(event, ctx);
 
-        self.state.map(ctx.app, |state| match event {
+        self.state.update(ctx.app, |state| match event {
             Event::MouseMoved { position } => {
                 let mouse_in = bounds.contains_point(*position);
                 if state.hovered != mouse_in {

gpui/src/platform/mac/renderer.rs 🔗

@@ -510,7 +510,7 @@ impl Renderer {
             );
 
             // Snap sprite to pixel grid.
-            let origin = (icon.bounds.origin() * scene.scale_factor()).floor();
+            let origin = (icon.bounds.origin() * scene.scale_factor()); //.floor();
             sprites_by_atlas
                 .entry(sprite.atlas_id)
                 .or_insert_with(Vec::new)

gpui/src/platform/mac/shaders/shaders.h 🔗

@@ -49,7 +49,8 @@ typedef enum {
 
 typedef struct {
     vector_float2 origin;
-    vector_float2 size;
+    vector_float2 target_size;
+    vector_float2 source_size;
     vector_float2 atlas_origin;
     vector_uchar4 color;
     uint8_t compute_winding;

gpui/src/platform/mac/shaders/shaders.metal 🔗

@@ -186,9 +186,9 @@ vertex SpriteFragmentInput sprite_vertex(
 ) {
     float2 unit_vertex = unit_vertices[unit_vertex_id];
     GPUISprite sprite = sprites[sprite_id];
-    float2 position = unit_vertex * sprite.size + sprite.origin;
+    float2 position = unit_vertex * sprite.target_size + sprite.origin;
     float4 device_position = to_device_position(position, *viewport_size);
-    float2 atlas_position = (unit_vertex * sprite.size + sprite.atlas_origin) / *atlas_size;
+    float2 atlas_position = (unit_vertex * sprite.source_size + sprite.atlas_origin) / *atlas_size;
 
     return SpriteFragmentInput {
         device_position,

gpui/src/platform/mac/sprite_cache.rs 🔗

@@ -140,8 +140,23 @@ impl SpriteCache {
         size: Vector2F,
         path: Cow<'static, str>,
         svg: usvg::Tree,
+        target_position: Vector2F,
         scale_factor: f32,
     ) -> IconSprite {
+        const SUBPIXEL_VARIANTS: u8 = 4;
+
+        let target_position = target_position * scale_factor;
+        let subpixel_variant = (
+            (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
+                % SUBPIXEL_VARIANTS,
+            (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
+                % SUBPIXEL_VARIANTS,
+        );
+        let subpixel_shift = vec2f(
+            subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
+            subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
+        );
+
         let atlases = &mut self.atlases;
         let atlas_size = self.atlas_size;
         let device = &self.device;

zed/src/workspace/pane.rs 🔗

@@ -215,9 +215,11 @@ impl Pane {
                                 )
                                 .with_child(
                                     Align::new(Self::render_tab_icon(
+                                        item.id(),
                                         line_height - 2.,
                                         mouse_state.hovered,
                                         item.is_dirty(ctx),
+                                        ctx,
                                     ))
                                     .right()
                                     .boxed(),
@@ -281,14 +283,33 @@ impl Pane {
             .named("tabs")
     }
 
-    fn render_tab_icon(close_icon_size: f32, tab_hovered: bool, is_modified: bool) -> ElementBox {
+    fn render_tab_icon(
+        item_id: usize,
+        close_icon_size: f32,
+        tab_hovered: bool,
+        is_modified: bool,
+        ctx: &AppContext,
+    ) -> ElementBox {
+        enum TabCloseButton {}
+
         let modified_color = ColorU::from_u32(0x556de8ff);
         let icon = if tab_hovered {
             let mut icon = Svg::new("icons/x.svg");
-            if is_modified {
-                icon = icon.with_color(modified_color);
-            }
-            icon.named("close-tab-icon")
+
+            MouseEventHandler::new::<TabCloseButton, _>(item_id, ctx, |mouse_state| {
+                if mouse_state.hovered {
+                    Container::new(icon.with_color(ColorU::white()).boxed())
+                        .with_background_color(modified_color)
+                        .with_corner_radius(close_icon_size / 2.)
+                        .boxed()
+                } else {
+                    if is_modified {
+                        icon = icon.with_color(modified_color);
+                    }
+                    icon.boxed()
+                }
+            })
+            .named("close-tab-icon")
         } else {
             let diameter = 8.;
             ConstrainedBox::new(