diff --git a/gpui/src/app.rs b/gpui/src/app.rs index af5746043e63e114076bf14f5cb404ff88a1f2aa..1e531d3692ea41af3444e55e91d41c873cde568b 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -1305,7 +1305,7 @@ impl MutableAppContext { if !self.flushing_effects && self.pending_flushes == 0 { self.flushing_effects = true; - let mut full_refresh = false; + let mut refreshing = false; loop { if let Some(effect) = self.pending_effects.pop_front() { match effect { @@ -1320,13 +1320,13 @@ impl MutableAppContext { self.focus(window_id, view_id); } Effect::RefreshWindows => { - full_refresh = true; + refreshing = true; } } self.remove_dropped_entities(); } else { self.remove_dropped_entities(); - if full_refresh { + if refreshing { self.perform_window_refresh(); } else { self.update_windows(); @@ -1336,7 +1336,7 @@ impl MutableAppContext { self.flushing_effects = false; break; } else { - full_refresh = false; + refreshing = false; } } } @@ -1638,11 +1638,11 @@ impl AppContext { window_id: usize, view_id: usize, titlebar_height: f32, - refresh: bool, + refreshing: bool, ) -> Result { self.views .get(&(window_id, view_id)) - .map(|v| v.render(window_id, view_id, titlebar_height, refresh, self)) + .map(|v| v.render(window_id, view_id, titlebar_height, refreshing, self)) .ok_or(anyhow!("view not found")) } @@ -1666,6 +1666,23 @@ impl AppContext { .collect::>() } + pub fn render_cx( + &self, + window_id: usize, + view_id: usize, + titlebar_height: f32, + refreshing: bool, + ) -> RenderContext { + RenderContext { + app: self, + titlebar_height, + refreshing, + window_id, + view_id, + view_type: PhantomData, + } + } + pub fn background(&self) -> &Arc { &self.background } @@ -1816,7 +1833,7 @@ pub trait AnyView { window_id: usize, view_id: usize, titlebar_height: f32, - refresh: bool, + refreshing: bool, cx: &AppContext, ) -> ElementBox; fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize); @@ -1849,7 +1866,7 @@ where window_id: usize, view_id: usize, titlebar_height: f32, - refresh: bool, + refreshing: bool, cx: &AppContext, ) -> ElementBox { View::render( @@ -1860,7 +1877,7 @@ where app: cx, view_type: PhantomData::, titlebar_height, - refresh, + refreshing, }, ) } @@ -2229,7 +2246,7 @@ impl<'a, T: View> ViewContext<'a, T> { pub struct RenderContext<'a, T: View> { pub app: &'a AppContext, pub titlebar_height: f32, - pub refresh: bool, + pub refreshing: bool, window_id: usize, view_id: usize, view_type: PhantomData, @@ -2255,6 +2272,12 @@ impl Deref for RenderContext<'_, V> { } } +impl ReadModel for RenderContext<'_, V> { + fn read_model(&self, handle: &ModelHandle) -> &T { + self.app.read_model(handle) + } +} + impl AsRef for ViewContext<'_, M> { fn as_ref(&self) -> &AppContext { &self.app.cx diff --git a/gpui/src/elements.rs b/gpui/src/elements.rs index d7d042617fe7f8a74b09bc1d28a98aef8fb7a19c..c533b938a3cc170ae02ba536b964c0e1bcff0306 100644 --- a/gpui/src/elements.rs +++ b/gpui/src/elements.rs @@ -37,7 +37,14 @@ use crate::{ }; use core::panic; use json::ToJson; -use std::{any::Any, borrow::Cow, mem}; +use std::{ + any::Any, + borrow::Cow, + cell::RefCell, + mem, + ops::{Deref, DerefMut}, + rc::Rc, +}; trait AnyElement { fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F; @@ -91,20 +98,20 @@ pub trait Element { where Self: 'static + Sized, { - ElementBox { + ElementBox(ElementRc { name: None, - element: Box::new(Lifecycle::Init { element: self }), - } + element: Rc::new(RefCell::new(Lifecycle::Init { element: self })), + }) } fn named(self, name: impl Into>) -> ElementBox where Self: 'static + Sized, { - ElementBox { + ElementBox(ElementRc { name: Some(name.into()), - element: Box::new(Lifecycle::Init { element: self }), - } + element: Rc::new(RefCell::new(Lifecycle::Init { element: self })), + }) } } @@ -127,9 +134,12 @@ pub enum Lifecycle { paint: T::PaintState, }, } -pub struct ElementBox { +pub struct ElementBox(ElementRc); + +#[derive(Clone)] +pub struct ElementRc { name: Option>, - element: Box, + element: Rc>, } impl AnyElement for Lifecycle { @@ -262,28 +272,51 @@ impl Default for Lifecycle { } impl ElementBox { + pub fn metadata(&self) -> Option<&dyn Any> { + let element = unsafe { &*self.0.element.as_ptr() }; + element.metadata() + } +} + +impl Into for ElementBox { + fn into(self) -> ElementRc { + self.0 + } +} + +impl Deref for ElementBox { + type Target = ElementRc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ElementBox { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl ElementRc { pub fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F { - self.element.layout(constraint, cx) + self.element.borrow_mut().layout(constraint, cx) } pub fn paint(&mut self, origin: Vector2F, cx: &mut PaintContext) { - self.element.paint(origin, cx); + self.element.borrow_mut().paint(origin, cx); } pub fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool { - self.element.dispatch_event(event, cx) + self.element.borrow_mut().dispatch_event(event, cx) } pub fn size(&self) -> Vector2F { - self.element.size() - } - - pub fn metadata(&self) -> Option<&dyn Any> { - self.element.metadata() + self.element.borrow().size() } pub fn debug(&self, cx: &DebugContext) -> json::Value { - let mut value = self.element.debug(cx); + let mut value = self.element.borrow().debug(cx); if let Some(name) = &self.name { if let json::Value::Object(map) = &mut value { diff --git a/gpui/src/elements/list.rs b/gpui/src/elements/list.rs index b4213fc5a50c825c9ae353dbb6fdef460f4fb170..98ab96c427b9c6741c833dec81e6499886e652d8 100644 --- a/gpui/src/elements/list.rs +++ b/gpui/src/elements/list.rs @@ -5,19 +5,17 @@ use crate::{ }, json::json, sum_tree::{self, Bias, SumTree}, - DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, + DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext, + RenderContext, SizeConstraint, View, }; -use parking_lot::Mutex; -use std::{ops::Range, sync::Arc}; - -use crate::ElementBox; +use std::{cell::RefCell, ops::Range, rc::Rc}; pub struct List { state: ListState, } #[derive(Clone)] -pub struct ListState(Arc>); +pub struct ListState(Rc>); #[derive(Eq, PartialEq)] pub enum Orientation { @@ -27,7 +25,7 @@ pub enum Orientation { struct StateInner { last_layout_width: f32, - elements: Vec, + elements: Vec>, heights: SumTree, scroll_position: f32, orientation: Orientation, @@ -56,13 +54,55 @@ struct PendingCount(usize); struct Height(f32); impl List { - pub fn new(state: ListState) -> Self { + pub fn new(state: ListState, cx: &RenderContext, build_items: F) -> Self + where + F: Fn(Range) -> I, + I: IntoIterator, + V: View, + { + { + let state = &mut *state.0.borrow_mut(); + if cx.refreshing { + let elements = (build_items)(0..state.elements.len()); + state.elements.clear(); + state + .elements + .extend(elements.into_iter().map(|e| Some(e.into()))); + state.heights = SumTree::new(); + state.heights.extend( + (0..state.elements.len()).map(|_| ElementHeight::Pending), + &(), + ); + } else { + let mut cursor = state.heights.cursor::(); + cursor.seek(&PendingCount(1), sum_tree::Bias::Left, &()); + + while cursor.item().is_some() { + let start_ix = cursor.sum_start().0; + while cursor.item().map_or(false, |h| h.is_pending()) { + cursor.next(&()); + } + let end_ix = cursor.sum_start().0; + if end_ix > start_ix { + state.elements.splice( + start_ix..end_ix, + (build_items)(start_ix..end_ix) + .into_iter() + .map(|e| Some(e.into())), + ); + } + + cursor.seek(&PendingCount(cursor.seek_start().0 + 1), Bias::Left, &()); + } + } + } + Self { state } } } impl Element for List { - type LayoutState = (); + type LayoutState = Vec; type PaintState = (); @@ -71,7 +111,7 @@ impl Element for List { constraint: SizeConstraint, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { - let state = &mut *self.state.0.lock(); + let state = &mut *self.state.0.borrow_mut(); let mut item_constraint = constraint; item_constraint.min.set_y(0.); item_constraint.max.set_y(f32::INFINITY); @@ -87,8 +127,8 @@ impl Element for List { while let Some(height) = old_heights.item() { if height.is_pending() { - let size = - state.elements[old_heights.sum_start().count].layout(item_constraint, cx); + let element = &mut state.elements[old_heights.sum_start().count]; + let size = element.as_mut().unwrap().layout(item_constraint, cx); new_heights.push(ElementHeight::Ready(size.y()), &()); // Adjust scroll position to keep visible elements stable @@ -123,18 +163,28 @@ impl Element for List { } else { state.heights = SumTree::new(); for element in &mut state.elements { + let element = element.as_mut().unwrap(); let size = element.layout(item_constraint, cx); state.heights.push(ElementHeight::Ready(size.y()), &()); } state.last_layout_width = constraint.max.x(); } - (size, ()) + let visible_elements = state.elements[state.visible_range(size.y())] + .iter() + .map(|e| e.clone().unwrap()) + .collect(); + (size, visible_elements) } - fn paint(&mut self, bounds: RectF, _: &mut (), cx: &mut PaintContext) { + fn paint( + &mut self, + bounds: RectF, + visible_elements: &mut Self::LayoutState, + cx: &mut PaintContext, + ) { cx.scene.push_layer(Some(bounds)); - let state = &mut *self.state.0.lock(); + let state = &mut *self.state.0.borrow_mut(); let visible_range = state.visible_range(bounds.height()); let mut item_top = { @@ -149,7 +199,7 @@ impl Element for List { } let scroll_top = state.scroll_top(bounds.height()); - for element in &mut state.elements[visible_range] { + for element in visible_elements { let origin = bounds.origin() + vec2f(0., item_top - scroll_top); element.paint(origin, cx); item_top += element.size().y(); @@ -161,16 +211,15 @@ impl Element for List { &mut self, event: &Event, bounds: RectF, - _: &mut (), + visible_elements: &mut Self::LayoutState, _: &mut (), cx: &mut EventContext, ) -> bool { let mut handled = false; - let mut state = self.state.0.lock(); - let visible_range = state.visible_range(bounds.height()); - for item in &mut state.elements[visible_range] { - handled = item.dispatch_event(event, cx) || handled; + let mut state = self.state.0.borrow_mut(); + for element in visible_elements { + handled = element.dispatch_event(event, cx) || handled; } match event { @@ -191,10 +240,16 @@ impl Element for List { handled } - fn debug(&self, bounds: RectF, _: &(), _: &(), cx: &DebugContext) -> serde_json::Value { - let state = self.state.0.lock(); + fn debug( + &self, + bounds: RectF, + visible_elements: &Self::LayoutState, + _: &(), + cx: &DebugContext, + ) -> serde_json::Value { + let state = self.state.0.borrow_mut(); let visible_range = state.visible_range(bounds.height()); - let visible_elements = state.elements[visible_range.clone()] + let visible_elements = visible_elements .iter() .map(|e| e.debug(cx)) .collect::>(); @@ -207,40 +262,29 @@ impl Element for List { } impl ListState { - pub fn new(elements: Vec, orientation: Orientation) -> Self { + pub fn new(element_count: usize, orientation: Orientation) -> Self { let mut heights = SumTree::new(); - heights.extend(elements.iter().map(|_| ElementHeight::Pending), &()); - Self(Arc::new(Mutex::new(StateInner { + heights.extend((0..element_count).map(|_| ElementHeight::Pending), &()); + Self(Rc::new(RefCell::new(StateInner { last_layout_width: 0., - elements, + elements: (0..element_count).map(|_| None).collect(), heights, scroll_position: 0., orientation, }))) } - pub fn splice( - &self, - old_range: Range, - new_elements: impl IntoIterator, - ) { - let state = &mut *self.0.lock(); + pub fn splice(&self, old_range: Range, count: usize) { + let state = &mut *self.0.borrow_mut(); let mut old_heights = state.heights.cursor::(); let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &()); old_heights.seek_forward(&Count(old_range.end), Bias::Right, &()); - let mut len = 0; - let old_elements = state.elements.splice( - old_range, - new_elements.into_iter().map(|e| { - len += 1; - e - }), - ); + let old_elements = state.elements.splice(old_range, (0..count).map(|_| None)); drop(old_elements); - new_heights.extend((0..len).map(|_| ElementHeight::Pending), &()); + new_heights.extend((0..count).map(|_| ElementHeight::Pending), &()); new_heights.push_tree(old_heights.suffix(&()), &()); drop(old_heights); state.heights = new_heights; @@ -370,22 +414,28 @@ impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Height { #[cfg(test)] mod tests { use super::*; - use crate::{elements::*, geometry::vector::vec2f}; + use crate::{elements::*, geometry::vector::vec2f, Entity}; #[crate::test(self)] fn test_layout(cx: &mut crate::MutableAppContext) { - let mut presenter = cx.build_presenter(0, 20.0); - let mut layout_cx = presenter.layout_cx(cx); - let state = ListState::new(vec![item(20.), item(30.), item(10.)], Orientation::Top); - let mut list = List::new(state.clone()).boxed(); + let mut presenter = cx.build_presenter(0, 0.); + + let mut elements = vec![20., 30., 10.]; + let state = ListState::new(elements.len(), Orientation::Top); + let mut list = List::new( + state.clone(), + &cx.render_cx::(0, 0, 0., false), + |range| elements[range].iter().copied().map(item), + ) + .boxed(); let size = list.layout( SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)), - &mut layout_cx, + &mut presenter.layout_cx(cx), ); assert_eq!(size, vec2f(100., 40.)); assert_eq!( - state.0.lock().heights.summary(), + state.0.borrow().heights.summary(), ElementHeightSummary { count: 3, pending_count: 0, @@ -393,23 +443,32 @@ mod tests { } ); - state.splice(1..2, vec![item(40.), item(50.)]); - state.splice(3..3, vec![item(60.)]); + elements.splice(1..2, vec![40., 50.]); + elements.push(60.); + state.splice(1..2, 2); + state.splice(4..4, 1); assert_eq!( - state.0.lock().heights.summary(), + state.0.borrow().heights.summary(), ElementHeightSummary { count: 5, pending_count: 3, height: 30. } ); + + let mut list = List::new( + state.clone(), + &cx.render_cx::(0, 0, 0., false), + |range| elements[range].iter().copied().map(item), + ) + .boxed(); let size = list.layout( SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)), - &mut layout_cx, + &mut presenter.layout_cx(cx), ); assert_eq!(size, vec2f(100., 40.)); assert_eq!( - state.0.lock().heights.summary(), + state.0.borrow().heights.summary(), ElementHeightSummary { count: 5, pending_count: 0, @@ -424,4 +483,20 @@ mod tests { .with_width(100.) .boxed() } + + struct TestView; + + impl Entity for TestView { + type Event = (); + } + + impl View for TestView { + fn ui_name() -> &'static str { + "TestView" + } + + fn render(&self, _: &RenderContext<'_, Self>) -> ElementBox { + unimplemented!() + } + } } diff --git a/gpui/src/lib.rs b/gpui/src/lib.rs index e7cfa3b1776ab416be5df8fed36f98aac36080bf..10877e33a00542b248a8ef84ae4b50f016005bf4 100644 --- a/gpui/src/lib.rs +++ b/gpui/src/lib.rs @@ -18,7 +18,7 @@ pub use scene::{Border, Quad, Scene}; pub mod text_layout; pub use text_layout::TextLayoutCache; mod util; -pub use elements::{Element, ElementBox}; +pub use elements::{Element, ElementBox, ElementRc}; pub mod executor; pub use executor::Task; pub mod color; diff --git a/zed/src/channel.rs b/zed/src/channel.rs index 37d75a4c912ff7c7dee85ec0ab09c08fd228227a..02352ec00d0d0b03fcbd874ee8c538fcc847f4b5 100644 --- a/zed/src/channel.rs +++ b/zed/src/channel.rs @@ -60,7 +60,7 @@ pub struct PendingChannelMessage { #[derive(Clone, Debug, Default)] pub struct ChannelMessageSummary { max_id: u64, - count: Count, + count: usize, } #[derive(Copy, Clone, Debug, Default)] @@ -208,7 +208,7 @@ impl Channel { } channel.update(&mut cx, |channel, cx| { - let old_count = channel.messages.summary().count.0; + let old_count = channel.messages.summary().count; let new_count = messages.len(); channel.messages = SumTree::new(); @@ -282,6 +282,10 @@ impl Channel { Ok(()) } + pub fn message_count(&self) -> usize { + self.messages.summary().count + } + pub fn messages(&self) -> &SumTree { &self.messages } @@ -380,7 +384,7 @@ impl sum_tree::Item for ChannelMessage { fn summary(&self) -> Self::Summary { ChannelMessageSummary { max_id: self.id, - count: Count(1), + count: 1, } } } @@ -390,7 +394,7 @@ impl sum_tree::Summary for ChannelMessageSummary { fn add_summary(&mut self, summary: &Self, _: &()) { self.max_id = summary.max_id; - self.count.0 += summary.count.0; + self.count += summary.count; } } @@ -403,7 +407,7 @@ impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for u64 { impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count { fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) { - self.0 += summary.count.0; + self.0 += summary.count; } } diff --git a/zed/src/chat_panel.rs b/zed/src/chat_panel.rs index 582c5b436a0ecc37bfa32cfb989f750978438071..0573666f9dc9b90579c34682575b989b97b5afbc 100644 --- a/zed/src/chat_panel.rs +++ b/zed/src/chat_panel.rs @@ -14,7 +14,7 @@ use time::{OffsetDateTime, UtcOffset}; pub struct ChatPanel { channel_list: ModelHandle, active_channel: Option<(ModelHandle, Subscription)>, - messages: ListState, + message_list: ListState, input_editor: ViewHandle, settings: watch::Receiver, } @@ -38,8 +38,8 @@ impl ChatPanel { let input_editor = cx.add_view(|cx| Editor::auto_height(settings.clone(), cx)); let mut this = Self { channel_list, - active_channel: None, - messages: ListState::new(Vec::new(), Orientation::Bottom), + active_channel: Default::default(), + message_list: ListState::new(0, Orientation::Bottom), input_editor, settings, }; @@ -76,23 +76,15 @@ impl ChatPanel { fn set_active_channel(&mut self, channel: ModelHandle, cx: &mut ViewContext) { if self.active_channel.as_ref().map(|e| &e.0) != Some(&channel) { let subscription = cx.subscribe(&channel, Self::channel_did_change); - let now = OffsetDateTime::now_utc(); - self.messages = ListState::new( - channel - .read(cx) - .messages() - .cursor::<(), ()>() - .map(|m| self.render_message(m, now)) - .collect(), - Orientation::Bottom, - ); + self.message_list = + ListState::new(channel.read(cx).message_count(), Orientation::Bottom); self.active_channel = Some((channel, subscription)); } } fn channel_did_change( &mut self, - channel: ModelHandle, + _: ModelHandle, event: &ChannelEvent, cx: &mut ViewContext, ) { @@ -101,21 +93,27 @@ impl ChatPanel { old_range, new_count, } => { - let now = OffsetDateTime::now_utc(); - self.messages.splice( - old_range.clone(), - channel - .read(cx) - .messages_in_range(old_range.start..(old_range.start + new_count)) - .map(|message| self.render_message(message, now)), - ); + self.message_list.splice(old_range.clone(), *new_count); } } cx.notify(); } - fn render_active_channel_messages(&self) -> ElementBox { - Expanded::new(1., List::new(self.messages.clone()).boxed()).boxed() + fn render_active_channel_messages(&self, cx: &RenderContext) -> ElementBox { + let messages = if let Some((channel, _)) = self.active_channel.as_ref() { + let channel = channel.read(cx); + let now = OffsetDateTime::now_utc(); + List::new(self.message_list.clone(), cx, |range| { + channel + .messages_in_range(range) + .map(|message| self.render_message(message, now)) + }) + .boxed() + } else { + Empty::new().boxed() + }; + + Expanded::new(1., messages).boxed() } fn render_message(&self, message: &ChannelMessage, now: OffsetDateTime) -> ElementBox { @@ -194,11 +192,11 @@ impl View for ChatPanel { "ChatPanel" } - fn render(&self, _: &RenderContext) -> ElementBox { + fn render(&self, cx: &RenderContext) -> ElementBox { let theme = &self.settings.borrow().theme; Container::new( Flex::column() - .with_child(self.render_active_channel_messages()) + .with_child(self.render_active_channel_messages(cx)) .with_child(self.render_input_box()) .boxed(), )