Detailed changes
@@ -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
@@ -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 {
@@ -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!()
+ }
+ }
}
@@ -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;
@@ -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;
}
}
@@ -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(),
)