From a47b0b4ca6827803558e53bfa17c0ece443e412d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 26 Apr 2021 21:24:23 -0600 Subject: [PATCH 01/11] Add a mechanism for storing ephemeral values I'll use this in the mouse event handler to track hover and click state. --- gpui/src/app.rs | 123 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 30 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 7c396a51e7c8ff926d5f9719c31dc0724d35a3e5..5352b94c0772cab859c60d6664a7544104dfa51d 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -407,8 +407,9 @@ impl MutableAppContext { font_cache: Arc::new(FontCache::new(fonts)), assets: Arc::new(AssetCache::new(asset_source)), ctx: AppContext { - models: HashMap::new(), - windows: HashMap::new(), + models: Default::default(), + windows: Default::default(), + values: Default::default(), ref_counts: Arc::new(Mutex::new(RefCounts::default())), background: Arc::new(executor::Background::new()), thread_pool: scoped_pool::Pool::new(num_cpus::get(), "app"), @@ -865,8 +866,8 @@ impl MutableAppContext { fn remove_dropped_entities(&mut self) { loop { - let (dropped_models, dropped_views) = self.ctx.ref_counts.lock().take_dropped(); - if dropped_models.is_empty() && dropped_views.is_empty() { + let (dropped_models, dropped_views, dropped_values) = self.ctx.ref_counts.lock().take_dropped(); + if dropped_models.is_empty() && dropped_views.is_empty() && dropped_values.is_empty() { break; } @@ -890,6 +891,11 @@ impl MutableAppContext { window.views.remove(&view_id); } } + + let mut values = self.ctx.values.lock(); + for key in dropped_values { + values.remove(&key); + } } } @@ -1308,6 +1314,7 @@ impl AsRef for MutableAppContext { pub struct AppContext { models: HashMap>, windows: HashMap, + values: Mutex>>, background: Arc, ref_counts: Arc>, thread_pool: scoped_pool::Pool, @@ -1353,6 +1360,13 @@ impl AppContext { pub fn thread_pool(&self) -> &scoped_pool::Pool { &self.thread_pool } + + pub fn value(&self, id: usize) -> ValueHandle { + let key = (TypeId::of::(), id); + let mut values = self.values.lock(); + values.entry(key).or_insert_with(|| Box::new(T::default())); + ValueHandle::new(TypeId::of::(), id, &self.ref_counts) + } } impl ReadModel for AppContext { @@ -1958,7 +1972,7 @@ pub struct ModelHandle { impl ModelHandle { fn new(model_id: usize, ref_counts: &Arc>) -> Self { - ref_counts.lock().inc(model_id); + ref_counts.lock().inc_entity(model_id); Self { model_id, model_type: PhantomData, @@ -2031,7 +2045,7 @@ impl ModelHandle { impl Clone for ModelHandle { fn clone(&self) -> Self { if let Some(ref_counts) = self.ref_counts.upgrade() { - ref_counts.lock().inc(self.model_id); + ref_counts.lock().inc_entity(self.model_id); } Self { @@ -2122,7 +2136,7 @@ pub struct ViewHandle { impl ViewHandle { fn new(window_id: usize, view_id: usize, ref_counts: &Arc>) -> Self { - ref_counts.lock().inc(view_id); + ref_counts.lock().inc_entity(view_id); Self { window_id, view_id, @@ -2205,7 +2219,7 @@ impl ViewHandle { impl Clone for ViewHandle { fn clone(&self) -> Self { if let Some(ref_counts) = self.ref_counts.upgrade() { - ref_counts.lock().inc(self.view_id); + ref_counts.lock().inc_entity(self.view_id); } Self { @@ -2282,7 +2296,7 @@ impl AnyViewHandle { impl From<&ViewHandle> for AnyViewHandle { fn from(handle: &ViewHandle) -> Self { if let Some(ref_counts) = handle.ref_counts.upgrade() { - ref_counts.lock().inc(handle.view_id); + ref_counts.lock().inc_entity(handle.view_id); } AnyViewHandle { window_id: handle.window_id, @@ -2342,48 +2356,97 @@ impl Clone for WeakViewHandle { } } +pub struct ValueHandle { + value_type: PhantomData, + tag_type_id: TypeId, + id: usize, + ref_counts: Weak>, +} + +impl ValueHandle { + fn new(tag_type_id: TypeId, id: usize, ref_counts: &Arc>) -> Self { + ref_counts.lock().inc_value(tag_type_id, id); + Self { + value_type: PhantomData, + tag_type_id, + id, + ref_counts: Arc::downgrade(ref_counts), + } + } + + pub fn map(&self, ctx: &AppContext, f: impl FnOnce(&mut T) -> R) -> R { + f(ctx + .values + .lock() + .get_mut(&(self.tag_type_id, self.id)) + .unwrap() + .downcast_mut() + .unwrap()) + } +} + +impl Drop for ValueHandle { + fn drop(&mut self) { + if let Some(ref_counts) = self.ref_counts.upgrade() { + ref_counts.lock().dec_value(self.tag_type_id, self.id); + } + } +} + #[derive(Default)] struct RefCounts { - counts: HashMap, + entity_counts: HashMap, + value_counts: HashMap<(TypeId, usize), usize>, dropped_models: HashSet, dropped_views: HashSet<(usize, usize)>, + dropped_values: HashSet<(TypeId, usize)>, } impl RefCounts { - fn inc(&mut self, model_id: usize) { - *self.counts.entry(model_id).or_insert(0) += 1; + fn inc_entity(&mut self, model_id: usize) { + *self.entity_counts.entry(model_id).or_insert(0) += 1; + } + + fn inc_value(&mut self, tag_type_id: TypeId, id: usize) { + *self.value_counts.entry((tag_type_id, id)).or_insert(0) += 1; } fn dec_model(&mut self, model_id: usize) { - if let Some(count) = self.counts.get_mut(&model_id) { - *count -= 1; - if *count == 0 { - self.counts.remove(&model_id); - self.dropped_models.insert(model_id); - } - } else { - panic!("Expected ref count to be positive") + let count = self.entity_counts.get_mut(&model_id).unwrap(); + *count -= 1; + if *count == 0 { + self.entity_counts.remove(&model_id); + self.dropped_models.insert(model_id); } } fn dec_view(&mut self, window_id: usize, view_id: usize) { - if let Some(count) = self.counts.get_mut(&view_id) { - *count -= 1; - if *count == 0 { - self.counts.remove(&view_id); - self.dropped_views.insert((window_id, view_id)); - } - } else { - panic!("Expected ref count to be positive") + let count = self.entity_counts.get_mut(&view_id).unwrap(); + *count -= 1; + if *count == 0 { + self.entity_counts.remove(&view_id); + self.dropped_views.insert((window_id, view_id)); + } + } + + fn dec_value(&mut self, tag_type_id: TypeId, id: usize) { + let key = (tag_type_id, id); + let count = self.value_counts.get_mut(&key).unwrap(); + *count -= 1; + if *count == 0 { + self.value_counts.remove(&key); + self.dropped_values.insert(key); } } - fn take_dropped(&mut self) -> (HashSet, HashSet<(usize, usize)>) { + fn take_dropped(&mut self) -> (HashSet, HashSet<(usize, usize)>, HashSet<(TypeId, usize)>) { let mut dropped_models = HashSet::new(); let mut dropped_views = HashSet::new(); + let mut dropped_values = HashSet::new(); std::mem::swap(&mut self.dropped_models, &mut dropped_models); std::mem::swap(&mut self.dropped_views, &mut dropped_views); - (dropped_models, dropped_views) + std::mem::swap(&mut self.dropped_values, &mut dropped_values); + (dropped_models, dropped_views, dropped_values) } } From fc4b7e2a2a35b856f240bdb632d673a30d2aa76b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 26 Apr 2021 21:52:18 -0600 Subject: [PATCH 02/11] Introduce MouseEventHandler Still need to give elements the ability to re-render their parent view. Once that is in place, I think we can implement hoverable close tab buttons. --- gpui/src/app.rs | 13 ++- gpui/src/elements/mod.rs | 1 + gpui/src/elements/mouse_event_handler.rs | 118 +++++++++++++++++++++++ gpui/src/platform/event.rs | 3 + gpui/src/platform/mac/event.rs | 6 ++ 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 gpui/src/elements/mouse_event_handler.rs diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 5352b94c0772cab859c60d6664a7544104dfa51d..425f940e6ab3380283a7371a27fca5985af0e25a 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -866,7 +866,8 @@ impl MutableAppContext { fn remove_dropped_entities(&mut self) { loop { - let (dropped_models, dropped_views, dropped_values) = self.ctx.ref_counts.lock().take_dropped(); + let (dropped_models, dropped_views, dropped_values) = + self.ctx.ref_counts.lock().take_dropped(); if dropped_models.is_empty() && dropped_views.is_empty() && dropped_values.is_empty() { break; } @@ -1361,7 +1362,7 @@ impl AppContext { &self.thread_pool } - pub fn value(&self, id: usize) -> ValueHandle { + pub fn value(&self, id: usize) -> ValueHandle { let key = (TypeId::of::(), id); let mut values = self.values.lock(); values.entry(key).or_insert_with(|| Box::new(T::default())); @@ -2439,7 +2440,13 @@ impl RefCounts { } } - fn take_dropped(&mut self) -> (HashSet, HashSet<(usize, usize)>, HashSet<(TypeId, usize)>) { + fn take_dropped( + &mut self, + ) -> ( + HashSet, + HashSet<(usize, usize)>, + HashSet<(TypeId, usize)>, + ) { let mut dropped_models = HashSet::new(); let mut dropped_views = HashSet::new(); let mut dropped_values = HashSet::new(); diff --git a/gpui/src/elements/mod.rs b/gpui/src/elements/mod.rs index 1bcfa7f6fa58a02e62c63beac0ad5c9d696e2e9e..4564acaed3f009fb15860138b6ca96a91cb331db 100644 --- a/gpui/src/elements/mod.rs +++ b/gpui/src/elements/mod.rs @@ -7,6 +7,7 @@ mod event_handler; mod flex; mod label; mod line_box; +mod mouse_event_handler; mod new; mod stack; mod svg; diff --git a/gpui/src/elements/mouse_event_handler.rs b/gpui/src/elements/mouse_event_handler.rs new file mode 100644 index 0000000000000000000000000000000000000000..6991d9eea31d55d5a0b97d473ac0161daa90a018 --- /dev/null +++ b/gpui/src/elements/mouse_event_handler.rs @@ -0,0 +1,118 @@ +use crate::{ + geometry::{rect::RectF, vector::Vector2F}, + AfterLayoutContext, AppContext, DebugContext, Element, ElementBox, Event, EventContext, + LayoutContext, PaintContext, SizeConstraint, ValueHandle, +}; +use serde_json::json; + +pub struct MouseEventHandler { + state: ValueHandle, + child: ElementBox, +} + +#[derive(Clone, Copy, Default)] +pub struct MouseState { + hovered: bool, + clicked: bool, +} + +impl MouseEventHandler { + pub fn new( + id: usize, + ctx: &AppContext, + render_child: impl FnOnce(MouseState) -> ElementBox, + ) -> Self { + let state = ctx.value::(id); + let child = state.map(ctx, |state| render_child(*state)); + Self { state, child } + } +} + +impl Element for MouseEventHandler { + type LayoutState = (); + + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + (self.child.layout(constraint, ctx), ()) + } + + fn after_layout( + &mut self, + _: Vector2F, + _: &mut Self::LayoutState, + ctx: &mut AfterLayoutContext, + ) { + self.child.after_layout(ctx); + } + + fn paint( + &mut self, + bounds: RectF, + _: &mut Self::LayoutState, + ctx: &mut PaintContext, + ) -> Self::PaintState { + self.child.paint(bounds.origin(), ctx); + } + + fn dispatch_event( + &mut self, + event: &Event, + bounds: RectF, + _: &mut Self::LayoutState, + _: &mut Self::PaintState, + ctx: &mut EventContext, + ) -> bool { + self.state.map(ctx.app, |state| match event { + Event::MouseMoved { position } => { + let mouse_in = bounds.contains_point(*position); + if state.hovered != mouse_in { + state.hovered = mouse_in; + log::info!("hovered {}", state.hovered); + // ctx.notify(); + true + } else { + false + } + } + Event::LeftMouseDown { position, .. } => { + if bounds.contains_point(*position) { + log::info!("clicked"); + state.clicked = true; + // ctx.notify(); + true + } else { + false + } + } + Event::LeftMouseUp { .. } => { + if state.clicked { + log::info!("unclicked"); + state.clicked = false; + // ctx.notify(); + true + } else { + false + } + } + _ => false, + }) + } + + fn debug( + &self, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + ctx: &DebugContext, + ) -> serde_json::Value { + json!({ + "type": "MouseEventHandler", + "child": self.child.debug(ctx), + }) + } +} diff --git a/gpui/src/platform/event.rs b/gpui/src/platform/event.rs index 6a60bd31203004c8b30b95839a9889423c323701..2020a92aec3eb7b415522cc6cfc2666d3c88d551 100644 --- a/gpui/src/platform/event.rs +++ b/gpui/src/platform/event.rs @@ -21,4 +21,7 @@ pub enum Event { LeftMouseDragged { position: Vector2F, }, + MouseMoved { + position: Vector2F, + }, } diff --git a/gpui/src/platform/mac/event.rs b/gpui/src/platform/mac/event.rs index 694c160784ad7d9fece3b4c4575e396074b854c9..b6c27960f0305684a602b432d6c5e3fed69bd5e9 100644 --- a/gpui/src/platform/mac/event.rs +++ b/gpui/src/platform/mac/event.rs @@ -108,6 +108,12 @@ impl Event { ), precise: native_event.hasPreciseScrollingDeltas() == YES, }), + NSEventType::NSMouseMoved => window_height.map(|window_height| Self::MouseMoved { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + }), _ => None, } } From 3d1e44ca29e77e28d691944fdca20734c4f21aff Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 27 Apr 2021 09:23:44 -0600 Subject: [PATCH 03/11] Capture hover events on tabs Co-Authored-By: Antonio Scandurra --- gpui/src/elements/mod.rs | 1 + gpui/src/elements/mouse_event_handler.rs | 14 ++++------- gpui/src/platform/mac/window.rs | 4 +++ zed/src/editor/buffer_element.rs | 1 + zed/src/workspace/pane.rs | 31 ++++++++++++++---------- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/gpui/src/elements/mod.rs b/gpui/src/elements/mod.rs index 4564acaed3f009fb15860138b6ca96a91cb331db..ab56cf280258a3cf0430c53d61a653c549e83631 100644 --- a/gpui/src/elements/mod.rs +++ b/gpui/src/elements/mod.rs @@ -23,6 +23,7 @@ pub use event_handler::*; pub use flex::*; pub use label::*; pub use line_box::*; +pub use mouse_event_handler::*; pub use new::*; pub use stack::*; pub use svg::*; diff --git a/gpui/src/elements/mouse_event_handler.rs b/gpui/src/elements/mouse_event_handler.rs index 6991d9eea31d55d5a0b97d473ac0161daa90a018..2cd41ca0c449d6bf8165958db5da041f16d9ef94 100644 --- a/gpui/src/elements/mouse_event_handler.rs +++ b/gpui/src/elements/mouse_event_handler.rs @@ -17,11 +17,11 @@ pub struct MouseState { } impl MouseEventHandler { - pub fn new( - id: usize, - ctx: &AppContext, - render_child: impl FnOnce(MouseState) -> ElementBox, - ) -> Self { + pub fn new(id: usize, ctx: &AppContext, render_child: F) -> Self + where + Tag: 'static, + F: FnOnce(MouseState) -> ElementBox, + { let state = ctx.value::(id); let child = state.map(ctx, |state| render_child(*state)); Self { state, child } @@ -30,7 +30,6 @@ impl MouseEventHandler { impl Element for MouseEventHandler { type LayoutState = (); - type PaintState = (); fn layout( @@ -72,7 +71,6 @@ impl Element for MouseEventHandler { let mouse_in = bounds.contains_point(*position); if state.hovered != mouse_in { state.hovered = mouse_in; - log::info!("hovered {}", state.hovered); // ctx.notify(); true } else { @@ -81,7 +79,6 @@ impl Element for MouseEventHandler { } Event::LeftMouseDown { position, .. } => { if bounds.contains_point(*position) { - log::info!("clicked"); state.clicked = true; // ctx.notify(); true @@ -91,7 +88,6 @@ impl Element for MouseEventHandler { } Event::LeftMouseUp { .. } => { if state.clicked { - log::info!("unclicked"); state.clicked = false; // ctx.notify(); true diff --git a/gpui/src/platform/mac/window.rs b/gpui/src/platform/mac/window.rs index d45077e3b522c9fb391c9e7eb3152aa6760a67b5..73196346e351ac823212248f0e5bb05931af81a2 100644 --- a/gpui/src/platform/mac/window.rs +++ b/gpui/src/platform/mac/window.rs @@ -82,6 +82,10 @@ unsafe fn build_classes() { sel!(mouseUp:), handle_view_event as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(mouseMoved:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(mouseDragged:), handle_view_event as extern "C" fn(&Object, Sel, id), diff --git a/zed/src/editor/buffer_element.rs b/zed/src/editor/buffer_element.rs index eabd89179144f84902443db42846b63ed9102fb3..8a49c208f30f220129de161ce8fdc0b4cd5b4254 100644 --- a/zed/src/editor/buffer_element.rs +++ b/zed/src/editor/buffer_element.rs @@ -474,6 +474,7 @@ impl Element for BufferElement { precise, } => self.scroll(*position, *delta, *precise, layout, paint, ctx), Event::KeyDown { chars, .. } => self.key_down(chars, ctx), + _ => false, } } else { false diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index 076373cf88fd85098c9e60f0201f56c6add9c769..bac92f5ab44a5f602feb7e65d9c5e1dfa6203abd 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -176,14 +176,14 @@ impl Pane { ctx.emit(Event::Split(direction)); } - fn render_tabs(&self, app: &AppContext) -> ElementBox { + fn render_tabs(&self, ctx: &AppContext) -> ElementBox { let settings = smol::block_on(self.settings.read()); let border_color = ColorU::from_u32(0xdbdbdcff); let mut row = Flex::row(); let last_item_ix = self.items.len() - 1; for (ix, item) in self.items.iter().enumerate() { - let title = item.title(app); + let title = item.title(ctx); let mut border = Border::new(1.0, border_color); border.left = ix > 0; @@ -204,7 +204,7 @@ impl Pane { LineBox::new( settings.ui_font_family, settings.ui_font_size, - Align::new(Self::render_modified_icon(item.is_dirty(app))) + Align::new(Self::render_modified_icon(item.is_dirty(ctx))) .right() .boxed(), ) @@ -224,19 +224,24 @@ impl Pane { container = container.with_background_color(ColorU::from_u32(0xeaeaebff)); } + enum Tab {} + row.add_child( Expanded::new( 1.0, - ConstrainedBox::new( - EventHandler::new(container.boxed()) - .on_mouse_down(move |ctx| { - ctx.dispatch_action("pane:activate_item", ix); - true - }) - .boxed(), - ) - .with_min_width(80.0) - .with_max_width(264.0) + MouseEventHandler::new::(0, ctx, |mouse_state| { + ConstrainedBox::new( + EventHandler::new(container.boxed()) + .on_mouse_down(move |ctx| { + ctx.dispatch_action("pane:activate_item", ix); + true + }) + .boxed(), + ) + .with_min_width(80.0) + .with_max_width(264.0) + .boxed() + }) .boxed(), ) .named("tab"), From bb95d58c7942bc96d681e815e3e99e271a933fe9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 27 Apr 2021 11:37:14 -0600 Subject: [PATCH 04/11] Relay hover events to tabs Co-Authored-By: Max Brunsfeld --- gpui/src/app.rs | 10 ++++++++- gpui/src/elements/mouse_event_handler.rs | 22 ++++++++++--------- gpui/src/presenter.rs | 27 ++++++++++++++++++------ zed/src/workspace/pane.rs | 3 ++- 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 425f940e6ab3380283a7371a27fca5985af0e25a..a8c636b0602aee37124f4fef84c30d08a038baed 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -788,7 +788,15 @@ impl MutableAppContext { } } - let actions = presenter.borrow_mut().dispatch_event(event, ctx.as_ref()); + let (actions, invalidated_views) = + presenter.borrow_mut().dispatch_event(event, ctx.as_ref()); + + ctx.window_invalidations + .entry(window_id) + .or_default() + .updated + .extend(invalidated_views.into_iter()); + for action in actions { ctx.dispatch_action_any( window_id, diff --git a/gpui/src/elements/mouse_event_handler.rs b/gpui/src/elements/mouse_event_handler.rs index 2cd41ca0c449d6bf8165958db5da041f16d9ef94..7554b53a6bff0e884709d203244e8ff230f310be 100644 --- a/gpui/src/elements/mouse_event_handler.rs +++ b/gpui/src/elements/mouse_event_handler.rs @@ -10,7 +10,7 @@ pub struct MouseEventHandler { child: ElementBox, } -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct MouseState { hovered: bool, clicked: bool, @@ -66,36 +66,38 @@ impl Element for MouseEventHandler { _: &mut Self::PaintState, ctx: &mut EventContext, ) -> bool { + let handled_in_child = self.child.dispatch_event(event, ctx); + self.state.map(ctx.app, |state| match event { Event::MouseMoved { position } => { let mouse_in = bounds.contains_point(*position); if state.hovered != mouse_in { state.hovered = mouse_in; - // ctx.notify(); + ctx.notify(); true } else { - false + handled_in_child } } Event::LeftMouseDown { position, .. } => { - if bounds.contains_point(*position) { + if !handled_in_child && bounds.contains_point(*position) { state.clicked = true; - // ctx.notify(); + ctx.notify(); true } else { - false + handled_in_child } } Event::LeftMouseUp { .. } => { - if state.clicked { + if !handled_in_child && state.clicked { state.clicked = false; - // ctx.notify(); + ctx.notify(); true } else { - false + handled_in_child } } - _ => false, + _ => handled_in_child, }) } diff --git a/gpui/src/presenter.rs b/gpui/src/presenter.rs index 6efa21499565d40fc96f7e203b614c04bcd0ef71..bbf106979017787168a63515e68d19b4f2a97e29 100644 --- a/gpui/src/presenter.rs +++ b/gpui/src/presenter.rs @@ -9,7 +9,11 @@ use crate::{ }; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; -use std::{any::Any, collections::HashMap, sync::Arc}; +use std::{ + any::Any, + collections::{HashMap, HashSet}, + sync::Arc, +}; pub struct Presenter { window_id: usize, @@ -114,20 +118,25 @@ impl Presenter { } } - pub fn dispatch_event(&mut self, event: Event, app: &AppContext) -> Vec { + pub fn dispatch_event( + &mut self, + event: Event, + app: &AppContext, + ) -> (Vec, HashSet) { if let Some(root_view_id) = app.root_view_id(self.window_id) { let mut ctx = EventContext { rendered_views: &mut self.rendered_views, - actions: Vec::new(), + actions: Default::default(), font_cache: &self.font_cache, text_layout_cache: &self.text_layout_cache, - view_stack: Vec::new(), + view_stack: Default::default(), + invalidated_views: Default::default(), app, }; ctx.dispatch_event(root_view_id, &event); - ctx.actions + (ctx.actions, ctx.invalidated_views) } else { - Vec::new() + Default::default() } } @@ -214,6 +223,7 @@ pub struct EventContext<'a> { pub text_layout_cache: &'a TextLayoutCache, pub app: &'a AppContext, view_stack: Vec, + invalidated_views: HashSet, } impl<'a> EventContext<'a> { @@ -236,6 +246,11 @@ impl<'a> EventContext<'a> { arg: Box::new(arg), }); } + + pub fn notify(&mut self) { + self.invalidated_views + .insert(*self.view_stack.last().unwrap()); + } } pub struct DebugContext<'a> { diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index bac92f5ab44a5f602feb7e65d9c5e1dfa6203abd..4767f30fda520b732ff030deefaaf81abbe22927 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -229,7 +229,8 @@ impl Pane { row.add_child( Expanded::new( 1.0, - MouseEventHandler::new::(0, ctx, |mouse_state| { + MouseEventHandler::new::(item.id(), ctx, |mouse_state| { + log::info!("mouse event handler {:?}", mouse_state); ConstrainedBox::new( EventHandler::new(container.boxed()) .on_mouse_down(move |ctx| { From eca9f495a1211684146f845e81ea1375690d28e5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 27 Apr 2021 11:58:59 -0600 Subject: [PATCH 05/11] Render a close tab button on tab hover Co-Authored-By: Max Brunsfeld --- gpui/src/elements/mouse_event_handler.rs | 4 +- gpui/src/elements/svg.rs | 8 +- gpui/src/platform/mac/sprite_cache.rs | 6 +- gpui/src/scene.rs | 4 +- zed/assets/.gitkeep | 0 zed/assets/icons/x.svg | 3 + zed/src/file_finder.rs | 2 +- zed/src/workspace/pane.rs | 140 +++++++++++++---------- 8 files changed, 96 insertions(+), 71 deletions(-) delete mode 100644 zed/assets/.gitkeep create mode 100644 zed/assets/icons/x.svg diff --git a/gpui/src/elements/mouse_event_handler.rs b/gpui/src/elements/mouse_event_handler.rs index 7554b53a6bff0e884709d203244e8ff230f310be..6d15a9709ef639ade7f8e5013a8df5f087034cec 100644 --- a/gpui/src/elements/mouse_event_handler.rs +++ b/gpui/src/elements/mouse_event_handler.rs @@ -12,8 +12,8 @@ pub struct MouseEventHandler { #[derive(Clone, Copy, Debug, Default)] pub struct MouseState { - hovered: bool, - clicked: bool, + pub hovered: bool, + pub clicked: bool, } impl MouseEventHandler { diff --git a/gpui/src/elements/svg.rs b/gpui/src/elements/svg.rs index 12b08b154c9a572e1ee2743cbf6317a50a38d352..b52112f65c664234e81db93bdce647f8291d5769 100644 --- a/gpui/src/elements/svg.rs +++ b/gpui/src/elements/svg.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde_json::json; use crate::{ @@ -11,14 +13,14 @@ use crate::{ }; pub struct Svg { - path: String, + path: Cow<'static, str>, color: ColorU, } impl Svg { - pub fn new(path: String) -> Self { + pub fn new(path: impl Into>) -> Self { Self { - path, + path: path.into(), color: ColorU::black(), } } diff --git a/gpui/src/platform/mac/sprite_cache.rs b/gpui/src/platform/mac/sprite_cache.rs index f73ebc6292a3928ee9d26e76cb5d6ac7f170dc40..bfb1c1f6013e2c6f64aca1c253974a7c320a1a07 100644 --- a/gpui/src/platform/mac/sprite_cache.rs +++ b/gpui/src/platform/mac/sprite_cache.rs @@ -9,7 +9,7 @@ use crate::{ use etagere::BucketedAtlasAllocator; use metal::{MTLPixelFormat, TextureDescriptor}; use ordered_float::OrderedFloat; -use std::{collections::HashMap, sync::Arc}; +use std::{borrow::Cow, collections::HashMap, sync::Arc}; #[derive(Hash, Eq, PartialEq)] struct GlyphDescriptor { @@ -29,7 +29,7 @@ pub struct GlyphSprite { #[derive(Hash, Eq, PartialEq)] struct IconDescriptor { - path: String, + path: Cow<'static, str>, width: i32, height: i32, } @@ -138,7 +138,7 @@ impl SpriteCache { pub fn render_icon( &mut self, size: Vector2F, - path: String, + path: Cow<'static, str>, svg: usvg::Tree, scale_factor: f32, ) -> IconSprite { diff --git a/gpui/src/scene.rs b/gpui/src/scene.rs index 44f9fcd1a06c3aec1a922dd2c74782a26a7b7bc6..10460ed2e0a526af5c4de1a7b6edac3bf823531f 100644 --- a/gpui/src/scene.rs +++ b/gpui/src/scene.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use serde_json::json; use crate::{ @@ -51,7 +53,7 @@ pub struct Glyph { pub struct Icon { pub bounds: RectF, pub svg: usvg::Tree, - pub path: String, + pub path: Cow<'static, str>, pub color: ColorU, } diff --git a/zed/assets/.gitkeep b/zed/assets/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/zed/assets/icons/x.svg b/zed/assets/icons/x.svg new file mode 100644 index 0000000000000000000000000000000000000000..c47eb9848c87acc04297e2abe6ccb5ab6eb3e7a7 --- /dev/null +++ b/zed/assets/icons/x.svg @@ -0,0 +1,3 @@ + + + diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index d6ec299593b4cbce237369304a8a028731042794..dcefdc7f8d780f931739333a2b194b4c120af59a 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -187,7 +187,7 @@ impl FileFinder { LineBox::new( settings.ui_font_family, settings.ui_font_size, - Svg::new("icons/file-16.svg".into()).boxed(), + Svg::new("icons/file-16.svg").boxed(), ) .boxed(), ) diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index 4767f30fda520b732ff030deefaaf81abbe22927..353a4f13e397d46c3507642e8a5d9d4272f426c4 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -183,54 +183,61 @@ impl Pane { let mut row = Flex::row(); let last_item_ix = self.items.len() - 1; for (ix, item) in self.items.iter().enumerate() { - let title = item.title(ctx); - - let mut border = Border::new(1.0, border_color); - border.left = ix > 0; - border.right = ix == last_item_ix; - border.bottom = ix != self.active_item; - - let padding = 6.; - let mut container = Container::new( - Stack::new() - .with_child( - Align::new( - Label::new(title, settings.ui_font_family, settings.ui_font_size) - .boxed(), - ) - .boxed(), - ) - .with_child( - LineBox::new( - settings.ui_font_family, - settings.ui_font_size, - Align::new(Self::render_modified_icon(item.is_dirty(ctx))) - .right() - .boxed(), - ) - .boxed(), - ) - .boxed(), - ) - .with_vertical_padding(padding) - .with_horizontal_padding(10.) - .with_border(border); - - if ix == self.active_item { - container = container - .with_background_color(ColorU::white()) - .with_padding_bottom(padding + border.width); - } else { - container = container.with_background_color(ColorU::from_u32(0xeaeaebff)); - } - enum Tab {} row.add_child( Expanded::new( 1.0, MouseEventHandler::new::(item.id(), ctx, |mouse_state| { - log::info!("mouse event handler {:?}", mouse_state); + let title = item.title(ctx); + + let mut border = Border::new(1.0, border_color); + border.left = ix > 0; + border.right = ix == last_item_ix; + border.bottom = ix != self.active_item; + + let padding = 6.; + let mut container = Container::new( + Stack::new() + .with_child( + Align::new( + Label::new( + title, + settings.ui_font_family, + settings.ui_font_size, + ) + .boxed(), + ) + .boxed(), + ) + .with_child( + LineBox::new( + settings.ui_font_family, + settings.ui_font_size, + Align::new(Self::render_tab_icon( + mouse_state.hovered, + item.is_dirty(ctx), + )) + .right() + .boxed(), + ) + .boxed(), + ) + .boxed(), + ) + .with_vertical_padding(padding) + .with_horizontal_padding(10.) + .with_border(border); + + if ix == self.active_item { + container = container + .with_background_color(ColorU::white()) + .with_padding_bottom(padding + border.width); + } else { + container = + container.with_background_color(ColorU::from_u32(0xeaeaebff)); + } + ConstrainedBox::new( EventHandler::new(container.boxed()) .on_mouse_down(move |ctx| { @@ -290,25 +297,36 @@ impl Pane { row.named("tabs") } - fn render_modified_icon(is_modified: bool) -> ElementBox { - let diameter = 8.; - ConstrainedBox::new( - Canvas::new(move |bounds, ctx| { - if is_modified { - let square = RectF::new(bounds.origin(), vec2f(diameter, diameter)); - 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: diameter / 2., - }); - } - }) - .boxed(), - ) - .with_width(diameter) - .with_height(diameter) - .named("tab-right-icon") + fn render_tab_icon(tab_hovered: bool, is_modified: bool) -> ElementBox { + let modified_color = ColorU::from_u32(0x556de8ff); + if tab_hovered { + let mut icon = Svg::new("icons/x.svg"); + if is_modified { + icon = icon.with_color(modified_color); + } + ConstrainedBox::new(icon.boxed()) + .with_width(10.) + .named("close-tab-icon") + } else { + let diameter = 8.; + ConstrainedBox::new( + Canvas::new(move |bounds, ctx| { + if is_modified { + let square = RectF::new(bounds.origin(), vec2f(diameter, diameter)); + ctx.scene.push_quad(Quad { + bounds: square, + background: Some(modified_color), + border: Default::default(), + corner_radius: diameter / 2., + }); + } + }) + .boxed(), + ) + .with_width(diameter) + .with_height(diameter) + .named("unsaved-tab-icon") + } } } From 58e336354ec9793c4655c49615fa96ad80069bcb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 27 Apr 2021 16:51:23 -0600 Subject: [PATCH 06/11] Align close tab icon cleanly with unsaved indicator --- gpui/src/app.rs | 16 +++++---- zed/assets/icons/x.svg | 4 +-- zed/src/workspace/pane.rs | 68 ++++++++++++++++----------------------- 3 files changed, 39 insertions(+), 49 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index a8c636b0602aee37124f4fef84c30d08a038baed..d403af3615d5e9321bdeac6ffa1f12e66d04ec3b 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -216,7 +216,7 @@ impl App { } pub fn font_cache(&self) -> Arc { - self.0.borrow().font_cache.clone() + self.0.borrow().ctx.font_cache.clone() } fn update T>(&mut self, callback: F) -> T { @@ -326,7 +326,7 @@ impl TestAppContext { } pub fn font_cache(&self) -> Arc { - self.0.borrow().font_cache.clone() + self.0.borrow().ctx.font_cache.clone() } pub fn platform(&self) -> Rc { @@ -370,7 +370,6 @@ type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext); pub struct MutableAppContext { weak_self: Option>>, platform: Rc, - font_cache: Arc, assets: Arc, ctx: AppContext, actions: HashMap>>>, @@ -404,7 +403,6 @@ impl MutableAppContext { Self { weak_self: None, platform, - font_cache: Arc::new(FontCache::new(fonts)), assets: Arc::new(AssetCache::new(asset_source)), ctx: AppContext { models: Default::default(), @@ -413,6 +411,7 @@ impl MutableAppContext { ref_counts: Arc::new(Mutex::new(RefCounts::default())), background: Arc::new(executor::Background::new()), thread_pool: scoped_pool::Pool::new(num_cpus::get(), "app"), + font_cache: Arc::new(FontCache::new(fonts)), }, actions: HashMap::new(), global_actions: HashMap::new(), @@ -444,7 +443,7 @@ impl MutableAppContext { } pub fn font_cache(&self) -> &Arc { - &self.font_cache + &self.ctx.font_cache } pub fn foreground_executor(&self) -> &Rc { @@ -764,7 +763,7 @@ impl MutableAppContext { let text_layout_cache = TextLayoutCache::new(self.platform.fonts()); let presenter = Rc::new(RefCell::new(Presenter::new( window_id, - self.font_cache.clone(), + self.ctx.font_cache.clone(), text_layout_cache, self.assets.clone(), self, @@ -1327,6 +1326,7 @@ pub struct AppContext { background: Arc, ref_counts: Arc>, thread_pool: scoped_pool::Pool, + font_cache: Arc, } impl AppContext { @@ -1366,6 +1366,10 @@ impl AppContext { &self.background } + pub fn font_cache(&self) -> &FontCache { + &self.font_cache + } + pub fn thread_pool(&self) -> &scoped_pool::Pool { &self.thread_pool } diff --git a/zed/assets/icons/x.svg b/zed/assets/icons/x.svg index c47eb9848c87acc04297e2abe6ccb5ab6eb3e7a7..f194b99c098a261a687018ead8284354cb3f89d4 100644 --- a/zed/assets/icons/x.svg +++ b/zed/assets/icons/x.svg @@ -1,3 +1,3 @@ - - + + diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index 353a4f13e397d46c3507642e8a5d9d4272f426c4..d0f26aa7373da4b8b46602f594723fdc8873f2f4 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -1,7 +1,7 @@ use super::{ItemViewHandle, SplitDirection}; use crate::{settings::Settings, watch}; use gpui::{ - color::{ColorF, ColorU}, + color::ColorU, elements::*, geometry::{rect::RectF, vector::vec2f}, keymap::Binding, @@ -179,6 +179,10 @@ impl Pane { fn render_tabs(&self, ctx: &AppContext) -> ElementBox { let settings = smol::block_on(self.settings.read()); let border_color = ColorU::from_u32(0xdbdbdcff); + let line_height = ctx.font_cache().line_height( + ctx.font_cache().default_font(settings.ui_font_family), + settings.ui_font_size, + ); let mut row = Flex::row(); let last_item_ix = self.items.len() - 1; @@ -196,7 +200,6 @@ impl Pane { border.right = ix == last_item_ix; border.bottom = ix != self.active_item; - let padding = 6.; let mut container = Container::new( Stack::new() .with_child( @@ -211,28 +214,23 @@ impl Pane { .boxed(), ) .with_child( - LineBox::new( - settings.ui_font_family, - settings.ui_font_size, - Align::new(Self::render_tab_icon( - mouse_state.hovered, - item.is_dirty(ctx), - )) - .right() - .boxed(), - ) + Align::new(Self::render_tab_icon( + line_height - 2., + mouse_state.hovered, + item.is_dirty(ctx), + )) + .right() .boxed(), ) .boxed(), ) - .with_vertical_padding(padding) .with_horizontal_padding(10.) .with_border(border); if ix == self.active_item { container = container .with_background_color(ColorU::white()) - .with_padding_bottom(padding + border.width); + .with_padding_bottom(border.width); } else { container = container.with_background_color(ColorU::from_u32(0xeaeaebff)); @@ -260,17 +258,9 @@ impl Pane { // so that the tab's border doesn't abut the window's border. row.add_child( ConstrainedBox::new( - Container::new( - LineBox::new( - settings.ui_font_family, - settings.ui_font_size, - Empty::new().boxed(), - ) + Container::new(Empty::new().boxed()) + .with_border(Border::bottom(1.0, border_color)) .boxed(), - ) - .with_uniform_padding(6.0) - .with_border(Border::bottom(1.0, border_color)) - .boxed(), ) .with_min_width(20.) .named("fixed-filler"), @@ -279,34 +269,26 @@ impl Pane { row.add_child( Expanded::new( 0.0, - Container::new( - LineBox::new( - settings.ui_font_family, - settings.ui_font_size, - Empty::new().boxed(), - ) + Container::new(Empty::new().boxed()) + .with_border(Border::bottom(1.0, border_color)) .boxed(), - ) - .with_uniform_padding(6.0) - .with_border(Border::bottom(1.0, border_color)) - .boxed(), ) .named("filler"), ); - row.named("tabs") + ConstrainedBox::new(row.boxed()) + .with_height(line_height + 16.) + .named("tabs") } - fn render_tab_icon(tab_hovered: bool, is_modified: bool) -> ElementBox { + fn render_tab_icon(close_icon_size: f32, tab_hovered: bool, is_modified: bool) -> ElementBox { let modified_color = ColorU::from_u32(0x556de8ff); - if tab_hovered { + let icon = if tab_hovered { let mut icon = Svg::new("icons/x.svg"); if is_modified { icon = icon.with_color(modified_color); } - ConstrainedBox::new(icon.boxed()) - .with_width(10.) - .named("close-tab-icon") + icon.named("close-tab-icon") } else { let diameter = 8.; ConstrainedBox::new( @@ -326,7 +308,11 @@ impl Pane { .with_width(diameter) .with_height(diameter) .named("unsaved-tab-icon") - } + }; + + ConstrainedBox::new(Align::new(icon).boxed()) + .with_width(close_icon_size) + .named("tab-icon") } } From 69cc2b0483ef792c46e67918c9a68d30fc4e6dc9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 27 Apr 2021 18:35:24 -0600 Subject: [PATCH 07/11] WIP --- 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(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index d403af3615d5e9321bdeac6ffa1f12e66d04ec3b..ffe695519409a4c3beff6d10dfb5a20708ad5b8d 100644 --- a/gpui/src/app.rs +++ b/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 for MutableAppContext { pub struct AppContext { models: HashMap>, windows: HashMap, - values: Mutex>>, + values: RwLock>>, background: Arc, ref_counts: Arc>, thread_pool: scoped_pool::Pool, @@ -1376,7 +1376,7 @@ impl AppContext { pub fn value(&self, id: usize) -> ValueHandle { let key = (TypeId::of::(), 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::(), id, &self.ref_counts) } @@ -2387,10 +2387,20 @@ impl ValueHandle { } } - pub fn map(&self, ctx: &AppContext, f: impl FnOnce(&mut T) -> R) -> R { + pub fn read(&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(&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() diff --git a/gpui/src/elements/mouse_event_handler.rs b/gpui/src/elements/mouse_event_handler.rs index 6d15a9709ef639ade7f8e5013a8df5f087034cec..89f7ba87e63d722a33afdd827ec52942ebb903e8 100644 --- a/gpui/src/elements/mouse_event_handler.rs +++ b/gpui/src/elements/mouse_event_handler.rs @@ -22,9 +22,13 @@ impl MouseEventHandler { Tag: 'static, F: FnOnce(MouseState) -> ElementBox, { - let state = ctx.value::(id); - let child = state.map(ctx, |state| render_child(*state)); - Self { state, child } + let state_handle = ctx.value::(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 { diff --git a/gpui/src/platform/mac/renderer.rs b/gpui/src/platform/mac/renderer.rs index 52296c69c3adc6703f1606c0e462a287aeb60e59..bbe361a9c96389e4d6c20e1a7bb52cf851ef0557 100644 --- a/gpui/src/platform/mac/renderer.rs +++ b/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) diff --git a/gpui/src/platform/mac/shaders/shaders.h b/gpui/src/platform/mac/shaders/shaders.h index 2241a25c2a4fd07ec6c19265b29317f60e2b398a..5f49bfca64004ec67abc9c58c641b631cfd58375 100644 --- a/gpui/src/platform/mac/shaders/shaders.h +++ b/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; diff --git a/gpui/src/platform/mac/shaders/shaders.metal b/gpui/src/platform/mac/shaders/shaders.metal index 8250a8783602644c2bed0bdac6b0de834994d966..91e5ea129577d9443ee0f395c0e01df72f37b702 100644 --- a/gpui/src/platform/mac/shaders/shaders.metal +++ b/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, diff --git a/gpui/src/platform/mac/sprite_cache.rs b/gpui/src/platform/mac/sprite_cache.rs index bfb1c1f6013e2c6f64aca1c253974a7c320a1a07..c78928deca334b966f06118937c4c05bb455fb53 100644 --- a/gpui/src/platform/mac/sprite_cache.rs +++ b/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; diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index d0f26aa7373da4b8b46602f594723fdc8873f2f4..a09c3ea556c1b01106eda81240319937959f01ed 100644 --- a/zed/src/workspace/pane.rs +++ b/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::(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( From 8c51bb253d30bc1fc54e843d683eea2e0ab93985 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 28 Apr 2021 09:32:38 -0600 Subject: [PATCH 08/11] Render sprited icons at 2x resolution since we don't align to pixel grid This provides the sampler with a bit more data when positioning sprites at subpixel positions. Co-Authored-By: Antonio Scandurra --- gpui/src/platform/mac/renderer.rs | 24 +++++++++++++----------- gpui/src/platform/mac/sprite_cache.rs | 19 +------------------ 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/gpui/src/platform/mac/renderer.rs b/gpui/src/platform/mac/renderer.rs index bbe361a9c96389e4d6c20e1a7bb52cf851ef0557..82521cdf86464ddb548c0da1a9a64f611d6801f9 100644 --- a/gpui/src/platform/mac/renderer.rs +++ b/gpui/src/platform/mac/renderer.rs @@ -153,7 +153,8 @@ impl Renderer { atlas_id, shader_data: shaders::GPUISprite { origin: origin.floor().to_float2(), - size: size.to_float2(), + target_size: size.to_float2(), + source_size: size.to_float2(), atlas_origin: atlas_origin.to_float2(), color: path.color.to_uchar4(), compute_winding: 1, @@ -493,7 +494,8 @@ impl Renderer { .or_insert_with(Vec::new) .push(shaders::GPUISprite { origin: origin.to_float2(), - size: sprite.size.to_float2(), + target_size: sprite.size.to_float2(), + source_size: sprite.size.to_float2(), atlas_origin: sprite.atlas_origin.to_float2(), color: glyph.color.to_uchar4(), compute_winding: 0, @@ -502,21 +504,21 @@ impl Renderer { } for icon in layer.icons() { - let sprite = self.sprite_cache.render_icon( - icon.bounds.size(), - icon.path.clone(), - icon.svg.clone(), - scene.scale_factor(), - ); + let origin = icon.bounds.origin() * scene.scale_factor(); + let target_size = icon.bounds.size() * scene.scale_factor(); + let source_size = (target_size * 2.).ceil().to_i32(); + + let sprite = + self.sprite_cache + .render_icon(source_size, icon.path.clone(), icon.svg.clone()); - // Snap sprite to pixel grid. - let origin = (icon.bounds.origin() * scene.scale_factor()); //.floor(); sprites_by_atlas .entry(sprite.atlas_id) .or_insert_with(Vec::new) .push(shaders::GPUISprite { origin: origin.to_float2(), - size: sprite.size.to_float2(), + target_size: target_size.to_float2(), + source_size: sprite.size.to_float2(), atlas_origin: sprite.atlas_origin.to_float2(), color: icon.color.to_uchar4(), compute_winding: 0, diff --git a/gpui/src/platform/mac/sprite_cache.rs b/gpui/src/platform/mac/sprite_cache.rs index c78928deca334b966f06118937c4c05bb455fb53..4c764ae1ca518a52e6f45b6bd80093345576fe26 100644 --- a/gpui/src/platform/mac/sprite_cache.rs +++ b/gpui/src/platform/mac/sprite_cache.rs @@ -137,30 +137,13 @@ impl SpriteCache { pub fn render_icon( &mut self, - size: Vector2F, + size: Vector2I, 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; - let size = (size * scale_factor).round().to_i32(); assert!(size.x() < atlas_size.x()); assert!(size.y() < atlas_size.y()); self.icons From bc1f758b661fe97f7f7738660bb19923558c0e0a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 28 Apr 2021 09:39:13 -0600 Subject: [PATCH 09/11] Lighten tab close button when clicked Co-Authored-By: Antonio Scandurra --- zed/src/workspace/pane.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index a09c3ea556c1b01106eda81240319937959f01ed..9b5fae6ad963ead5aac0e9ccf98bb2522aaad79b 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -293,13 +293,20 @@ impl Pane { enum TabCloseButton {} let modified_color = ColorU::from_u32(0x556de8ff); + let mut clicked_color = modified_color; + clicked_color.a = 180; + let icon = if tab_hovered { let mut icon = Svg::new("icons/x.svg"); MouseEventHandler::new::(item_id, ctx, |mouse_state| { if mouse_state.hovered { Container::new(icon.with_color(ColorU::white()).boxed()) - .with_background_color(modified_color) + .with_background_color(if mouse_state.clicked { + clicked_color + } else { + modified_color + }) .with_corner_radius(close_icon_size / 2.) .boxed() } else { From b5463dcd1e1158764f78928f7cb9029f25aa67b2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 28 Apr 2021 10:00:45 -0600 Subject: [PATCH 10/11] Close tabs when X is clicked Co-Authored-By: Antonio Scandurra --- gpui/src/elements/mouse_event_handler.rs | 16 +++++++++++++++- zed/src/workspace/pane.rs | 19 ++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/gpui/src/elements/mouse_event_handler.rs b/gpui/src/elements/mouse_event_handler.rs index 89f7ba87e63d722a33afdd827ec52942ebb903e8..aed9779f81a21b2e7794ba93ac51612f0ade865c 100644 --- a/gpui/src/elements/mouse_event_handler.rs +++ b/gpui/src/elements/mouse_event_handler.rs @@ -8,6 +8,7 @@ use serde_json::json; pub struct MouseEventHandler { state: ValueHandle, child: ElementBox, + click_handler: Option>, } #[derive(Clone, Copy, Debug, Default)] @@ -28,8 +29,14 @@ impl MouseEventHandler { Self { state: state_handle, child, + click_handler: None, } } + + pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self { + self.click_handler = Some(Box::new(handler)); + self + } } impl Element for MouseEventHandler { @@ -70,6 +77,8 @@ impl Element for MouseEventHandler { _: &mut Self::PaintState, ctx: &mut EventContext, ) -> bool { + let click_handler = self.click_handler.as_mut(); + let handled_in_child = self.child.dispatch_event(event, ctx); self.state.update(ctx.app, |state| match event { @@ -92,10 +101,15 @@ impl Element for MouseEventHandler { handled_in_child } } - Event::LeftMouseUp { .. } => { + Event::LeftMouseUp { position, .. } => { if !handled_in_child && state.clicked { state.clicked = false; ctx.notify(); + if let Some(handler) = click_handler { + if bounds.contains_point(*position) { + handler(ctx); + } + } true } else { handled_in_child diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index 9b5fae6ad963ead5aac0e9ccf98bb2522aaad79b..f17b439d013c07868cab0c75f6ba1d30afb1a755 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -25,6 +25,12 @@ pub fn init(app: &mut MutableAppContext) { app.add_action("pane:close_active_item", |pane: &mut Pane, _: &(), ctx| { pane.close_active_item(ctx); }); + app.add_action( + "pane:close_item", + |pane: &mut Pane, item_id: &usize, ctx| { + pane.close_item(*item_id, ctx); + }, + ); app.add_action("pane:split_up", |pane: &mut Pane, _: &(), ctx| { pane.split(SplitDirection::Up, ctx); }); @@ -155,15 +161,17 @@ impl Pane { pub fn close_active_item(&mut self, ctx: &mut ViewContext) { if !self.items.is_empty() { - self.items.remove(self.active_item); - if self.active_item >= self.items.len() { - self.active_item = self.items.len().saturating_sub(1); - } - ctx.notify(); + self.close_item(self.items[self.active_item].id(), ctx) } + } + + pub fn close_item(&mut self, item_id: usize, ctx: &mut ViewContext) { + self.items.retain(|item| item.id() != item_id); + self.active_item = cmp::min(self.active_item, self.items.len().saturating_sub(1)); if self.items.is_empty() { ctx.emit(Event::Remove); } + ctx.notify(); } fn focus_active_item(&mut self, ctx: &mut ViewContext) { @@ -316,6 +324,7 @@ impl Pane { icon.boxed() } }) + .on_click(move |ctx| ctx.dispatch_action("pane:close_item", item_id)) .named("close-tab-icon") } else { let diameter = 8.; From 1cb3fdbf7d26a51839cdab8bd86c419753ad9506 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 28 Apr 2021 13:02:39 -0600 Subject: [PATCH 11/11] Synthesize a mouse moved event in the previous position after painting a scene This ensures that we correctly update the hover state of elements whose position has changed relative to the mouse cursor even though the mouse hasn't actually moved. Co-Authored-By: Antonio Scandurra Co-Authored-By: Max Brunsfeld --- gpui/src/app.rs | 39 +++++++++++++------------------------- gpui/src/platform/event.rs | 2 +- gpui/src/presenter.rs | 36 ++++++++++++++++++++++++++--------- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index ffe695519409a4c3beff6d10dfb5a20708ad5b8d..43e422ac12a4dad95e7eb3a89e692405eb373397 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -584,6 +584,11 @@ impl MutableAppContext { ); } + pub(crate) fn notify_view(&mut self, window_id: usize, view_id: usize) { + self.pending_effects + .push_back(Effect::ViewNotification { window_id, view_id }); + } + pub fn dispatch_action( &mut self, window_id: usize, @@ -594,7 +599,7 @@ impl MutableAppContext { self.dispatch_action_any(window_id, &responder_chain, name, Box::new(arg).as_ref()); } - fn dispatch_action_any( + pub(crate) fn dispatch_action_any( &mut self, window_id: usize, path: &[usize], @@ -787,23 +792,7 @@ impl MutableAppContext { } } - let (actions, invalidated_views) = - presenter.borrow_mut().dispatch_event(event, ctx.as_ref()); - - ctx.window_invalidations - .entry(window_id) - .or_default() - .updated - .extend(invalidated_views.into_iter()); - - for action in actions { - ctx.dispatch_action_any( - window_id, - &action.path, - action.name, - action.arg.as_ref(), - ); - } + presenter.borrow_mut().dispatch_event(event, ctx); }) })); } @@ -924,11 +913,14 @@ impl MutableAppContext { self.focus(window_id, view_id); } } + + if self.pending_effects.is_empty() { + self.remove_dropped_entities(); + self.update_windows(); + } } self.flushing_effects = false; - self.remove_dropped_entities(); - self.update_windows(); } } @@ -1850,12 +1842,7 @@ impl<'a, T: View> ViewContext<'a, T> { } pub fn notify(&mut self) { - self.app - .pending_effects - .push_back(Effect::ViewNotification { - window_id: self.window_id, - view_id: self.view_id, - }); + self.app.notify_view(self.window_id, self.view_id); } pub fn propagate_action(&mut self) { diff --git a/gpui/src/platform/event.rs b/gpui/src/platform/event.rs index 2020a92aec3eb7b415522cc6cfc2666d3c88d551..032088041691336162f271201a8b6de35d83bac3 100644 --- a/gpui/src/platform/event.rs +++ b/gpui/src/platform/event.rs @@ -1,6 +1,6 @@ use crate::{geometry::vector::Vector2F, keymap::Keystroke}; -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Event { KeyDown { keystroke: Keystroke, diff --git a/gpui/src/presenter.rs b/gpui/src/presenter.rs index bbf106979017787168a63515e68d19b4f2a97e29..1a4f39607c1ce0d8bb13c223f1f20767ecd73b52 100644 --- a/gpui/src/presenter.rs +++ b/gpui/src/presenter.rs @@ -22,6 +22,7 @@ pub struct Presenter { font_cache: Arc, text_layout_cache: TextLayoutCache, asset_cache: Arc, + last_mouse_moved_event: Option, } impl Presenter { @@ -39,6 +40,7 @@ impl Presenter { font_cache, text_layout_cache, asset_cache, + last_mouse_moved_event: None, } } @@ -84,6 +86,10 @@ impl Presenter { }; ctx.paint(root_view_id, Vector2F::zero()); self.text_layout_cache.finish_frame(); + + if let Some(event) = self.last_mouse_moved_event.clone() { + self.dispatch_event(event, app) + } } else { log::error!("could not find root_view_id for window {}", self.window_id); } @@ -118,12 +124,12 @@ impl Presenter { } } - pub fn dispatch_event( - &mut self, - event: Event, - app: &AppContext, - ) -> (Vec, HashSet) { + pub fn dispatch_event(&mut self, event: Event, app: &mut MutableAppContext) { if let Some(root_view_id) = app.root_view_id(self.window_id) { + if matches!(event, Event::MouseMoved { .. }) { + self.last_mouse_moved_event = Some(event.clone()); + } + let mut ctx = EventContext { rendered_views: &mut self.rendered_views, actions: Default::default(), @@ -131,12 +137,24 @@ impl Presenter { text_layout_cache: &self.text_layout_cache, view_stack: Default::default(), invalidated_views: Default::default(), - app, + app: app.as_ref(), }; ctx.dispatch_event(root_view_id, &event); - (ctx.actions, ctx.invalidated_views) - } else { - Default::default() + + let invalidated_views = ctx.invalidated_views; + let actions = ctx.actions; + + for view_id in invalidated_views { + app.notify_view(self.window_id, view_id); + } + for action in actions { + app.dispatch_action_any( + self.window_id, + &action.path, + action.name, + action.arg.as_ref(), + ); + } } }