Re-render all list elements when refreshing windows

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2021-08-26 16:36:56 +02:00
parent 5fe5685641
commit 4388c45029
6 changed files with 248 additions and 115 deletions

View File

@ -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<ElementBox> {
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::<HashMap<_, ElementBox>>()
}
pub fn render_cx<V: View>(
&self,
window_id: usize,
view_id: usize,
titlebar_height: f32,
refreshing: bool,
) -> RenderContext<V> {
RenderContext {
app: self,
titlebar_height,
refreshing,
window_id,
view_id,
view_type: PhantomData,
}
}
pub fn background(&self) -> &Arc<executor::Background> {
&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::<T>,
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<T>,
@ -2255,6 +2272,12 @@ impl<V: View> Deref for RenderContext<'_, V> {
}
}
impl<V: View> ReadModel for RenderContext<'_, V> {
fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
self.app.read_model(handle)
}
}
impl<M> AsRef<AppContext> for ViewContext<'_, M> {
fn as_ref(&self) -> &AppContext {
&self.app.cx

View File

@ -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<Cow<'static, str>>) -> 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<T: Element> {
paint: T::PaintState,
},
}
pub struct ElementBox {
pub struct ElementBox(ElementRc);
#[derive(Clone)]
pub struct ElementRc {
name: Option<Cow<'static, str>>,
element: Box<dyn AnyElement>,
element: Rc<RefCell<dyn AnyElement>>,
}
impl<T: Element> AnyElement for Lifecycle<T> {
@ -262,28 +272,51 @@ impl<T: Element> Default for Lifecycle<T> {
}
impl ElementBox {
pub fn metadata(&self) -> Option<&dyn Any> {
let element = unsafe { &*self.0.element.as_ptr() };
element.metadata()
}
}
impl Into<ElementRc> 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 {

View File

@ -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<Mutex<StateInner>>);
pub struct ListState(Rc<RefCell<StateInner>>);
#[derive(Eq, PartialEq)]
pub enum Orientation {
@ -27,7 +25,7 @@ pub enum Orientation {
struct StateInner {
last_layout_width: f32,
elements: Vec<ElementBox>,
elements: Vec<Option<ElementRc>>,
heights: SumTree<ElementHeight>,
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<F, I, V>(state: ListState, cx: &RenderContext<V>, build_items: F) -> Self
where
F: Fn(Range<usize>) -> I,
I: IntoIterator<Item = ElementBox>,
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::<PendingCount, Count>();
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<ElementRc>;
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::<Vec<_>>();
@ -207,40 +262,29 @@ impl Element for List {
}
impl ListState {
pub fn new(elements: Vec<ElementBox>, 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<usize>,
new_elements: impl IntoIterator<Item = ElementBox>,
) {
let state = &mut *self.0.lock();
pub fn splice(&self, old_range: Range<usize>, count: usize) {
let state = &mut *self.0.borrow_mut();
let mut old_heights = state.heights.cursor::<Count, ()>();
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::<TestView>(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::<TestView>(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!()
}
}
}

View File

@ -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;

View File

@ -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<ChannelMessage> {
&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;
}
}

View File

@ -14,7 +14,7 @@ use time::{OffsetDateTime, UtcOffset};
pub struct ChatPanel {
channel_list: ModelHandle<ChannelList>,
active_channel: Option<(ModelHandle<Channel>, Subscription)>,
messages: ListState,
message_list: ListState,
input_editor: ViewHandle<Editor>,
settings: watch::Receiver<Settings>,
}
@ -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<Channel>, cx: &mut ViewContext<Self>) {
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<Channel>,
_: ModelHandle<Channel>,
event: &ChannelEvent,
cx: &mut ViewContext<Self>,
) {
@ -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<Self>) -> 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<Self>) -> ElementBox {
fn render(&self, cx: &RenderContext<Self>) -> 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(),
)