WIP
This commit is contained in:
parent
6638407ff9
commit
3de8fe0f87
21 changed files with 675 additions and 694 deletions
|
@ -1,3 +1,5 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use super::Element;
|
||||
use crate::{
|
||||
json::{self, json},
|
||||
|
@ -9,18 +11,19 @@ use pathfinder_geometry::{
|
|||
vector::{vec2f, Vector2F},
|
||||
};
|
||||
|
||||
pub struct Canvas<V, F>(F);
|
||||
pub struct Canvas<V, F>(F, PhantomData<V>);
|
||||
|
||||
impl<V: View, F> Canvas<V, F>
|
||||
impl<V, F> Canvas<V, F>
|
||||
where
|
||||
V: View,
|
||||
F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
|
||||
{
|
||||
pub fn new(f: F) -> Self {
|
||||
Self(f)
|
||||
Self(f, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, F> Element<V> for Canvas<V, F>
|
||||
impl<V: View, F> Element<V> for Canvas<V, F>
|
||||
where
|
||||
F: FnMut(RectF, RectF, &mut ViewContext<V>),
|
||||
{
|
||||
|
@ -30,7 +33,8 @@ where
|
|||
fn layout(
|
||||
&mut self,
|
||||
constraint: crate::SizeConstraint,
|
||||
_: &mut crate::LayoutContext,
|
||||
_: &mut V,
|
||||
_: &mut crate::ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let x = if constraint.max.x().is_finite() {
|
||||
constraint.max.x()
|
||||
|
|
|
@ -308,7 +308,7 @@ impl<V: View> Element<V> for Container<V> {
|
|||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
view: &V,
|
||||
cx: &crate::DebugContext,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "Container",
|
||||
|
|
|
@ -101,7 +101,7 @@ impl<V: View> Flex<V> {
|
|||
vec2f(constraint.max.x(), child_max),
|
||||
),
|
||||
};
|
||||
let child_size = child.layout(child_constraint, cx);
|
||||
let child_size = child.layout(child_constraint, view, cx);
|
||||
*remaining_space -= child_size.along(self.axis);
|
||||
*remaining_flex -= flex;
|
||||
*cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
|
||||
|
@ -434,7 +434,7 @@ impl<V: View> Element<V> for FlexItem<V> {
|
|||
view: &V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let size = self.child.layout(constraint, cx);
|
||||
let size = self.child.layout(constraint, view, cx);
|
||||
(size, ())
|
||||
}
|
||||
|
||||
|
@ -445,7 +445,7 @@ impl<V: View> Element<V> for FlexItem<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &V,
|
||||
cx: &mut PaintContext,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
json::json,
|
||||
Element, ElementBox, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
|
||||
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
|
||||
pub struct List<V: View> {
|
||||
|
@ -24,7 +24,7 @@ pub enum Orientation {
|
|||
|
||||
struct StateInner<V: View> {
|
||||
last_layout_width: Option<f32>,
|
||||
render_item: Box<dyn FnMut(usize, &V, &mut ViewContext<V>) -> Option<ElementBox<V>>>,
|
||||
render_item: Box<dyn FnMut(&mut V, usize, &mut ViewContext<V>) -> ElementBox<V>>,
|
||||
rendered_range: Range<usize>,
|
||||
items: SumTree<ListItem<V>>,
|
||||
logical_scroll_top: Option<ListOffset>,
|
||||
|
@ -40,14 +40,23 @@ pub struct ListOffset {
|
|||
pub offset_in_item: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ListItem<V: View> {
|
||||
Unrendered,
|
||||
Rendered(Rc<ElementBox<V>>),
|
||||
Removed(f32),
|
||||
}
|
||||
|
||||
impl<V: View> std::fmt::Debug for ListItem<V> {
|
||||
impl<V: View> Clone for ListItem<V> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Unrendered => Self::Unrendered,
|
||||
Self::Rendered(element) => Self::Rendered(element.clone()),
|
||||
Self::Removed(height) => Self::Removed(*height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> Debug for ListItem<V> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Unrendered => write!(f, "Unrendered"),
|
||||
|
@ -245,14 +254,13 @@ impl<V: View> Element<V> for List<V> {
|
|||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
scroll_top: &mut ListOffset,
|
||||
view: &V,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
|
||||
cx.scene.push_layer(Some(visible_bounds));
|
||||
|
||||
cx.scene.push_mouse_region(
|
||||
MouseRegion::new::<Self>(cx.current_view_id(), 0, bounds).on_scroll({
|
||||
scene.push_layer(Some(visible_bounds));
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<Self>(cx.view_id(), 0, bounds).on_scroll({
|
||||
let state = self.state.clone();
|
||||
let height = bounds.height();
|
||||
let scroll_top = scroll_top.clone();
|
||||
|
@ -273,7 +281,7 @@ impl<V: View> Element<V> for List<V> {
|
|||
element.paint(scene, origin, visible_bounds, view, cx);
|
||||
}
|
||||
|
||||
cx.scene.pop_layer();
|
||||
scene.pop_layer();
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
@ -349,9 +357,10 @@ impl<V: View> ListState<V> {
|
|||
let handle = cx.weak_handle();
|
||||
Self(Rc::new(RefCell::new(StateInner {
|
||||
last_layout_width: None,
|
||||
render_item: Box::new(move |ix, cx| {
|
||||
let handle = handle.upgrade(cx)?;
|
||||
Some(cx.render(&handle, |view, cx| render_item(view, ix, cx)))
|
||||
render_item: Box::new(move |ix, view, cx| {
|
||||
render_item(view, ix, cx)
|
||||
// let handle = handle.upgrade(cx)?;
|
||||
// Some(cx.render(&handle, |view, cx| render_item(view, ix, cx)))
|
||||
}),
|
||||
rendered_range: 0..0,
|
||||
items,
|
||||
|
@ -442,8 +451,8 @@ impl<V: View> StateInner<V> {
|
|||
if let Some(ListItem::Rendered(element)) = existing_element {
|
||||
Some(element.clone())
|
||||
} else {
|
||||
let mut element = (self.render_item)(ix, cx)?;
|
||||
element.layout(constraint, cx);
|
||||
let mut element = (self.render_item)(view, ix, cx);
|
||||
element.layout(constraint, view, cx);
|
||||
Some(element.into())
|
||||
}
|
||||
}
|
||||
|
@ -460,7 +469,7 @@ impl<V: View> StateInner<V> {
|
|||
&'a self,
|
||||
bounds: RectF,
|
||||
scroll_top: &ListOffset,
|
||||
) -> impl Iterator<Item = (Rc<ElementBox>, Vector2F)> + 'a {
|
||||
) -> impl Iterator<Item = (Rc<ElementBox<V>>, Vector2F)> + 'a {
|
||||
let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
|
||||
let mut cursor = self.items.cursor::<Count>();
|
||||
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||
|
@ -637,262 +646,264 @@ mod tests {
|
|||
|
||||
#[crate::test(self)]
|
||||
fn test_layout(cx: &mut crate::AppContext) {
|
||||
let (_, view) = cx.add_window(Default::default(), |_| TestView);
|
||||
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
|
||||
todo!()
|
||||
// let (_, view) = cx.add_window(Default::default(), |_| TestView);
|
||||
// let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
|
||||
|
||||
let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
|
||||
// let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
|
||||
|
||||
let state = view.update(cx, |_, cx| {
|
||||
ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, cx, {
|
||||
let elements = elements.clone();
|
||||
move |_, ix, _| {
|
||||
let (id, height) = elements.borrow()[ix];
|
||||
TestElement::new(id, height).boxed()
|
||||
}
|
||||
})
|
||||
});
|
||||
// let state = view.update(cx, |_, cx| {
|
||||
// ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, cx, {
|
||||
// let elements = elements.clone();
|
||||
// move |_, ix, _| {
|
||||
// let (id, height) = elements.borrow()[ix];
|
||||
// TestElement::new(id, height).boxed()
|
||||
// }
|
||||
// })
|
||||
// });
|
||||
|
||||
let mut list = List::new(state.clone());
|
||||
let (size, _) = list.layout(
|
||||
constraint,
|
||||
&mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
);
|
||||
assert_eq!(size, vec2f(100., 40.));
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 3,
|
||||
rendered_count: 3,
|
||||
unrendered_count: 0,
|
||||
height: 150.
|
||||
}
|
||||
);
|
||||
// let mut list = List::new(state.clone());
|
||||
// let (size, _) = list.layout(
|
||||
// constraint,
|
||||
// &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
// );
|
||||
// assert_eq!(size, vec2f(100., 40.));
|
||||
// assert_eq!(
|
||||
// state.0.borrow().items.summary().clone(),
|
||||
// ListItemSummary {
|
||||
// count: 3,
|
||||
// rendered_count: 3,
|
||||
// unrendered_count: 0,
|
||||
// height: 150.
|
||||
// }
|
||||
// );
|
||||
|
||||
state.0.borrow_mut().scroll(
|
||||
&ListOffset {
|
||||
item_ix: 0,
|
||||
offset_in_item: 0.,
|
||||
},
|
||||
40.,
|
||||
vec2f(0., -54.),
|
||||
true,
|
||||
&mut presenter.build_event_context(&mut Default::default(), cx),
|
||||
);
|
||||
let (_, logical_scroll_top) = list.layout(
|
||||
constraint,
|
||||
&mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
);
|
||||
assert_eq!(
|
||||
logical_scroll_top,
|
||||
ListOffset {
|
||||
item_ix: 2,
|
||||
offset_in_item: 4.
|
||||
}
|
||||
);
|
||||
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
|
||||
// state.0.borrow_mut().scroll(
|
||||
// &ListOffset {
|
||||
// item_ix: 0,
|
||||
// offset_in_item: 0.,
|
||||
// },
|
||||
// 40.,
|
||||
// vec2f(0., -54.),
|
||||
// true,
|
||||
// &mut presenter.build_event_context(&mut Default::default(), cx),
|
||||
// );
|
||||
// let (_, logical_scroll_top) = list.layout(
|
||||
// constraint,
|
||||
// &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// logical_scroll_top,
|
||||
// ListOffset {
|
||||
// item_ix: 2,
|
||||
// offset_in_item: 4.
|
||||
// }
|
||||
// );
|
||||
// assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
|
||||
|
||||
elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
|
||||
elements.borrow_mut().push((5, 60.));
|
||||
state.splice(1..2, 2);
|
||||
state.splice(4..4, 1);
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 5,
|
||||
rendered_count: 2,
|
||||
unrendered_count: 3,
|
||||
height: 120.
|
||||
}
|
||||
);
|
||||
// elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
|
||||
// elements.borrow_mut().push((5, 60.));
|
||||
// state.splice(1..2, 2);
|
||||
// state.splice(4..4, 1);
|
||||
// assert_eq!(
|
||||
// state.0.borrow().items.summary().clone(),
|
||||
// ListItemSummary {
|
||||
// count: 5,
|
||||
// rendered_count: 2,
|
||||
// unrendered_count: 3,
|
||||
// height: 120.
|
||||
// }
|
||||
// );
|
||||
|
||||
let (size, logical_scroll_top) = list.layout(
|
||||
constraint,
|
||||
&mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
);
|
||||
assert_eq!(size, vec2f(100., 40.));
|
||||
assert_eq!(
|
||||
state.0.borrow().items.summary().clone(),
|
||||
ListItemSummary {
|
||||
count: 5,
|
||||
rendered_count: 5,
|
||||
unrendered_count: 0,
|
||||
height: 270.
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
logical_scroll_top,
|
||||
ListOffset {
|
||||
item_ix: 3,
|
||||
offset_in_item: 4.
|
||||
}
|
||||
);
|
||||
assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
|
||||
// let (size, logical_scroll_top) = list.layout(
|
||||
// constraint,
|
||||
// &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
|
||||
// );
|
||||
// assert_eq!(size, vec2f(100., 40.));
|
||||
// assert_eq!(
|
||||
// state.0.borrow().items.summary().clone(),
|
||||
// ListItemSummary {
|
||||
// count: 5,
|
||||
// rendered_count: 5,
|
||||
// unrendered_count: 0,
|
||||
// height: 270.
|
||||
// }
|
||||
// );
|
||||
// assert_eq!(
|
||||
// logical_scroll_top,
|
||||
// ListOffset {
|
||||
// item_ix: 3,
|
||||
// offset_in_item: 4.
|
||||
// }
|
||||
// );
|
||||
// assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
|
||||
}
|
||||
|
||||
#[crate::test(self, iterations = 10, seed = 0)]
|
||||
fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
todo!()
|
||||
// let operations = env::var("OPERATIONS")
|
||||
// .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
// .unwrap_or(10);
|
||||
|
||||
let (window_id, view) = cx.add_window(Default::default(), |_| TestView);
|
||||
let mut next_id = 0;
|
||||
let elements = Rc::new(RefCell::new(
|
||||
(0..rng.gen_range(0..=20))
|
||||
.map(|_| {
|
||||
let id = next_id;
|
||||
next_id += 1;
|
||||
(id, rng.gen_range(0..=200) as f32 / 2.0)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
));
|
||||
let orientation = *[Orientation::Top, Orientation::Bottom]
|
||||
.choose(&mut rng)
|
||||
.unwrap();
|
||||
let overdraw = rng.gen_range(1..=100) as f32;
|
||||
// let (window_id, view) = cx.add_window(Default::default(), |_| TestView);
|
||||
// let mut next_id = 0;
|
||||
// let elements = Rc::new(RefCell::new(
|
||||
// (0..rng.gen_range(0..=20))
|
||||
// .map(|_| {
|
||||
// let id = next_id;
|
||||
// next_id += 1;
|
||||
// (id, rng.gen_range(0..=200) as f32 / 2.0)
|
||||
// })
|
||||
// .collect::<Vec<_>>(),
|
||||
// ));
|
||||
// let orientation = *[Orientation::Top, Orientation::Bottom]
|
||||
// .choose(&mut rng)
|
||||
// .unwrap();
|
||||
// let overdraw = rng.gen_range(1..=100) as f32;
|
||||
|
||||
let state = view.update(cx, |_, cx| {
|
||||
ListState::new(elements.borrow().len(), orientation, overdraw, cx, {
|
||||
let elements = elements.clone();
|
||||
move |_, ix, _| {
|
||||
let (id, height) = elements.borrow()[ix];
|
||||
TestElement::new(id, height).boxed()
|
||||
}
|
||||
})
|
||||
});
|
||||
// let state = view.update(cx, |_, cx| {
|
||||
// ListState::new(elements.borrow().len(), orientation, overdraw, cx, {
|
||||
// let elements = elements.clone();
|
||||
// move |_, ix, _| {
|
||||
// let (id, height) = elements.borrow()[ix];
|
||||
// TestElement::new(id, height).boxed()
|
||||
// }
|
||||
// })
|
||||
// });
|
||||
|
||||
let mut width = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
let mut height = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
log::info!("orientation: {:?}", orientation);
|
||||
log::info!("overdraw: {}", overdraw);
|
||||
log::info!("elements: {:?}", elements.borrow());
|
||||
log::info!("size: ({:?}, {:?})", width, height);
|
||||
log::info!("==================");
|
||||
// let mut width = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
// let mut height = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
// log::info!("orientation: {:?}", orientation);
|
||||
// log::info!("overdraw: {}", overdraw);
|
||||
// log::info!("elements: {:?}", elements.borrow());
|
||||
// log::info!("size: ({:?}, {:?})", width, height);
|
||||
// log::info!("==================");
|
||||
|
||||
let mut last_logical_scroll_top = None;
|
||||
for _ in 0..operations {
|
||||
match rng.gen_range(0..=100) {
|
||||
0..=29 if last_logical_scroll_top.is_some() => {
|
||||
let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
|
||||
log::info!(
|
||||
"Scrolling by {:?}, previous scroll top: {:?}",
|
||||
delta,
|
||||
last_logical_scroll_top.unwrap()
|
||||
);
|
||||
state.0.borrow_mut().scroll(
|
||||
last_logical_scroll_top.as_ref().unwrap(),
|
||||
height,
|
||||
delta,
|
||||
true,
|
||||
&mut presenter.build_event_context(&mut Default::default(), cx),
|
||||
);
|
||||
}
|
||||
30..=34 => {
|
||||
width = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
log::info!("changing width: {:?}", width);
|
||||
}
|
||||
35..=54 => {
|
||||
height = rng.gen_range(0..=1000) as f32 / 2.;
|
||||
log::info!("changing height: {:?}", height);
|
||||
}
|
||||
_ => {
|
||||
let mut elements = elements.borrow_mut();
|
||||
let end_ix = rng.gen_range(0..=elements.len());
|
||||
let start_ix = rng.gen_range(0..=end_ix);
|
||||
let new_elements = (0..rng.gen_range(0..10))
|
||||
.map(|_| {
|
||||
let id = next_id;
|
||||
next_id += 1;
|
||||
(id, rng.gen_range(0..=200) as f32 / 2.)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
|
||||
state.splice(start_ix..end_ix, new_elements.len());
|
||||
elements.splice(start_ix..end_ix, new_elements);
|
||||
for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
|
||||
if let ListItem::Rendered(element) = item {
|
||||
let (expected_id, _) = elements[ix];
|
||||
element.with_metadata(|metadata: Option<&usize>| {
|
||||
assert_eq!(*metadata.unwrap(), expected_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// let mut last_logical_scroll_top = None;
|
||||
// for _ in 0..operations {
|
||||
// match rng.gen_range(0..=100) {
|
||||
// 0..=29 if last_logical_scroll_top.is_some() => {
|
||||
// let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
|
||||
// log::info!(
|
||||
// "Scrolling by {:?}, previous scroll top: {:?}",
|
||||
// delta,
|
||||
// last_logical_scroll_top.unwrap()
|
||||
// );
|
||||
// state.0.borrow_mut().scroll(
|
||||
// last_logical_scroll_top.as_ref().unwrap(),
|
||||
// height,
|
||||
// delta,
|
||||
// true,
|
||||
// &mut presenter.build_event_context(&mut Default::default(), cx),
|
||||
// );
|
||||
// }
|
||||
// 30..=34 => {
|
||||
// width = rng.gen_range(0..=2000) as f32 / 2.;
|
||||
// log::info!("changing width: {:?}", width);
|
||||
// }
|
||||
// 35..=54 => {
|
||||
// height = rng.gen_range(0..=1000) as f32 / 2.;
|
||||
// log::info!("changing height: {:?}", height);
|
||||
// }
|
||||
// _ => {
|
||||
// let mut elements = elements.borrow_mut();
|
||||
// let end_ix = rng.gen_range(0..=elements.len());
|
||||
// let start_ix = rng.gen_range(0..=end_ix);
|
||||
// let new_elements = (0..rng.gen_range(0..10))
|
||||
// .map(|_| {
|
||||
// let id = next_id;
|
||||
// next_id += 1;
|
||||
// (id, rng.gen_range(0..=200) as f32 / 2.)
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
// log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
|
||||
// state.splice(start_ix..end_ix, new_elements.len());
|
||||
// elements.splice(start_ix..end_ix, new_elements);
|
||||
// for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
|
||||
// if let ListItem::Rendered(element) = item {
|
||||
// let (expected_id, _) = elements[ix];
|
||||
// element.with_metadata(|metadata: Option<&usize>| {
|
||||
// assert_eq!(*metadata.unwrap(), expected_id);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let mut list = List::new(state.clone());
|
||||
let window_size = vec2f(width, height);
|
||||
let (size, logical_scroll_top) = list.layout(
|
||||
SizeConstraint::new(vec2f(0., 0.), window_size),
|
||||
&mut presenter.build_layout_context(window_size, false, cx),
|
||||
);
|
||||
assert_eq!(size, window_size);
|
||||
last_logical_scroll_top = Some(logical_scroll_top);
|
||||
// let mut list = List::new(state.clone());
|
||||
// let window_size = vec2f(width, height);
|
||||
// let (size, logical_scroll_top) = list.layout(
|
||||
// SizeConstraint::new(vec2f(0., 0.), window_size),
|
||||
// &mut presenter.build_layout_context(window_size, false, cx),
|
||||
// );
|
||||
// assert_eq!(size, window_size);
|
||||
// last_logical_scroll_top = Some(logical_scroll_top);
|
||||
|
||||
let state = state.0.borrow();
|
||||
log::info!("items {:?}", state.items.items(&()));
|
||||
// let state = state.0.borrow();
|
||||
// log::info!("items {:?}", state.items.items(&()));
|
||||
|
||||
let scroll_top = state.scroll_top(&logical_scroll_top);
|
||||
let rendered_top = (scroll_top - overdraw).max(0.);
|
||||
let rendered_bottom = scroll_top + height + overdraw;
|
||||
let mut item_top = 0.;
|
||||
// let scroll_top = state.scroll_top(&logical_scroll_top);
|
||||
// let rendered_top = (scroll_top - overdraw).max(0.);
|
||||
// let rendered_bottom = scroll_top + height + overdraw;
|
||||
// let mut item_top = 0.;
|
||||
|
||||
log::info!(
|
||||
"rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
|
||||
rendered_top,
|
||||
rendered_bottom,
|
||||
scroll_top,
|
||||
);
|
||||
// log::info!(
|
||||
// "rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
|
||||
// rendered_top,
|
||||
// rendered_bottom,
|
||||
// scroll_top,
|
||||
// );
|
||||
|
||||
let mut first_rendered_element_top = None;
|
||||
let mut last_rendered_element_bottom = None;
|
||||
assert_eq!(state.items.summary().count, elements.borrow().len());
|
||||
for (ix, item) in state.items.cursor::<()>().enumerate() {
|
||||
match item {
|
||||
ListItem::Unrendered => {
|
||||
let item_bottom = item_top;
|
||||
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
ListItem::Removed(height) => {
|
||||
let (id, expected_height) = elements.borrow()[ix];
|
||||
assert_eq!(
|
||||
*height, expected_height,
|
||||
"element {} height didn't match",
|
||||
id
|
||||
);
|
||||
let item_bottom = item_top + height;
|
||||
assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
ListItem::Rendered(element) => {
|
||||
let (expected_id, expected_height) = elements.borrow()[ix];
|
||||
element.with_metadata(|metadata: Option<&usize>| {
|
||||
assert_eq!(*metadata.unwrap(), expected_id);
|
||||
});
|
||||
assert_eq!(element.size().y(), expected_height);
|
||||
let item_bottom = item_top + element.size().y();
|
||||
first_rendered_element_top.get_or_insert(item_top);
|
||||
last_rendered_element_bottom = Some(item_bottom);
|
||||
assert!(item_bottom > rendered_top || item_top < rendered_bottom);
|
||||
item_top = item_bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
// let mut first_rendered_element_top = None;
|
||||
// let mut last_rendered_element_bottom = None;
|
||||
// assert_eq!(state.items.summary().count, elements.borrow().len());
|
||||
// for (ix, item) in state.items.cursor::<()>().enumerate() {
|
||||
// match item {
|
||||
// ListItem::Unrendered => {
|
||||
// let item_bottom = item_top;
|
||||
// assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
|
||||
// item_top = item_bottom;
|
||||
// }
|
||||
// ListItem::Removed(height) => {
|
||||
// let (id, expected_height) = elements.borrow()[ix];
|
||||
// assert_eq!(
|
||||
// *height, expected_height,
|
||||
// "element {} height didn't match",
|
||||
// id
|
||||
// );
|
||||
// let item_bottom = item_top + height;
|
||||
// assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
|
||||
// item_top = item_bottom;
|
||||
// }
|
||||
// ListItem::Rendered(element) => {
|
||||
// let (expected_id, expected_height) = elements.borrow()[ix];
|
||||
// element.with_metadata(|metadata: Option<&usize>| {
|
||||
// assert_eq!(*metadata.unwrap(), expected_id);
|
||||
// });
|
||||
// assert_eq!(element.size().y(), expected_height);
|
||||
// let item_bottom = item_top + element.size().y();
|
||||
// first_rendered_element_top.get_or_insert(item_top);
|
||||
// last_rendered_element_bottom = Some(item_bottom);
|
||||
// assert!(item_bottom > rendered_top || item_top < rendered_bottom);
|
||||
// item_top = item_bottom;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
match orientation {
|
||||
Orientation::Top => {
|
||||
if let Some(first_rendered_element_top) = first_rendered_element_top {
|
||||
assert!(first_rendered_element_top <= scroll_top);
|
||||
}
|
||||
}
|
||||
Orientation::Bottom => {
|
||||
if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
|
||||
assert!(last_rendered_element_bottom >= scroll_top + height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// match orientation {
|
||||
// Orientation::Top => {
|
||||
// if let Some(first_rendered_element_top) = first_rendered_element_top {
|
||||
// assert!(first_rendered_element_top <= scroll_top);
|
||||
// }
|
||||
// }
|
||||
// Orientation::Bottom => {
|
||||
// if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
|
||||
// assert!(last_rendered_element_bottom >= scroll_top + height);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
struct TestView;
|
||||
|
|
|
@ -233,14 +233,16 @@ impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
|
|||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
if self.above {
|
||||
self.child.paint(bounds.origin(), visible_bounds, view, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
|
||||
cx.paint_layer(None, |cx| {
|
||||
self.paint_regions(bounds, visible_bounds, cx);
|
||||
self.paint_regions(scene, bounds, visible_bounds, cx);
|
||||
});
|
||||
} else {
|
||||
self.paint_regions(bounds, visible_bounds, cx);
|
||||
self.child.paint(bounds.origin(), visible_bounds, view, cx);
|
||||
self.paint_regions(scene, bounds, visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ impl<V: View> Element<V> for Overlay<V> {
|
|||
} else {
|
||||
constraint
|
||||
};
|
||||
let size = self.child.layout(constraint, cx);
|
||||
let size = self.child.layout(constraint, view, cx);
|
||||
(Vector2F::zero(), size)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
geometry::rect::RectF,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
scene::MouseDrag,
|
||||
Axis, Element, ElementBox, ElementStateHandle, MouseRegion, RenderContext, View,
|
||||
Axis, Element, ElementBox, ElementStateHandle, MouseRegion, SceneBuilder, View, ViewContext,
|
||||
};
|
||||
|
||||
use super::{ConstrainedBox, Hook};
|
||||
|
@ -75,22 +75,22 @@ struct ResizeHandleState {
|
|||
custom_dimension: Cell<f32>,
|
||||
}
|
||||
|
||||
pub struct Resizable {
|
||||
pub struct Resizable<V: View> {
|
||||
side: Side,
|
||||
handle_size: f32,
|
||||
child: ElementBox,
|
||||
child: ElementBox<V>,
|
||||
state: Rc<ResizeHandleState>,
|
||||
_state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
|
||||
}
|
||||
|
||||
impl Resizable {
|
||||
impl<V: View> Resizable<V> {
|
||||
pub fn new<Tag: 'static, T: View>(
|
||||
child: ElementBox,
|
||||
child: ElementBox<V>,
|
||||
element_id: usize,
|
||||
side: Side,
|
||||
handle_size: f32,
|
||||
initial_size: f32,
|
||||
cx: &mut RenderContext<T>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self {
|
||||
let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>(
|
||||
element_id,
|
||||
|
@ -132,24 +132,27 @@ impl Resizable {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Resizable {
|
||||
impl<V: View> Element<V> for Resizable<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: crate::SizeConstraint,
|
||||
cx: &mut crate::LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
(self.child.layout(constraint, cx), ())
|
||||
(self.child.layout(constraint, view, cx), ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: pathfinder_geometry::rect::RectF,
|
||||
visible_bounds: pathfinder_geometry::rect::RectF,
|
||||
_child_size: &mut Self::LayoutState,
|
||||
cx: &mut crate::PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
cx.scene.push_stacking_context(None, None);
|
||||
|
||||
|
@ -186,7 +189,8 @@ impl Element for Resizable {
|
|||
|
||||
cx.scene.pop_stacking_context();
|
||||
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
@ -196,9 +200,10 @@ impl Element for Resizable {
|
|||
_visible_bounds: pathfinder_geometry::rect::RectF,
|
||||
_layout: &Self::LayoutState,
|
||||
_paint: &Self::PaintState,
|
||||
cx: &crate::MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<pathfinder_geometry::rect::RectF> {
|
||||
self.child.rect_for_text_range(range_utf16, cx)
|
||||
self.child.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
|
@ -206,10 +211,11 @@ impl Element for Resizable {
|
|||
_bounds: pathfinder_geometry::rect::RectF,
|
||||
_layout: &Self::LayoutState,
|
||||
_paint: &Self::PaintState,
|
||||
cx: &crate::DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"child": self.child.debug(cx),
|
||||
"child": self.child.debug(view, cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,41 +3,41 @@ use std::ops::Range;
|
|||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::{self, json, ToJson},
|
||||
window::MeasurementContext,
|
||||
DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
|
||||
Element, ElementBox, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
|
||||
/// Element which renders it's children in a stack on top of each other.
|
||||
/// The first child determines the size of the others.
|
||||
#[derive(Default)]
|
||||
pub struct Stack {
|
||||
children: Vec<ElementBox>,
|
||||
pub struct Stack<V: View> {
|
||||
children: Vec<ElementBox<V>>,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
impl<V: View> Stack<V> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Stack {
|
||||
impl<V: View> Element<V> for Stack<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
mut constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let mut size = constraint.min;
|
||||
let mut children = self.children.iter_mut();
|
||||
if let Some(bottom_child) = children.next() {
|
||||
size = bottom_child.layout(constraint, cx);
|
||||
size = bottom_child.layout(constraint, view, cx);
|
||||
constraint = SizeConstraint::strict(size);
|
||||
}
|
||||
|
||||
for child in children {
|
||||
child.layout(constraint, cx);
|
||||
child.layout(constraint, view, cx);
|
||||
}
|
||||
|
||||
(size, ())
|
||||
|
@ -45,14 +45,16 @@ impl Element for Stack {
|
|||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
for child in &mut self.children {
|
||||
cx.paint_layer(None, |cx| {
|
||||
child.paint(bounds.origin(), visible_bounds, cx);
|
||||
child.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -64,12 +66,13 @@ impl Element for Stack {
|
|||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.children
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
|
||||
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
|
||||
}
|
||||
|
||||
fn debug(
|
||||
|
@ -77,18 +80,19 @@ impl Element for Stack {
|
|||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> json::Value {
|
||||
json!({
|
||||
"type": "Stack",
|
||||
"bounds": bounds.to_json(),
|
||||
"children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
|
||||
"children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<ElementBox> for Stack {
|
||||
fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
|
||||
impl<V: View> Extend<ElementBox<V>> for Stack<V> {
|
||||
fn extend<T: IntoIterator<Item = ElementBox<V>>>(&mut self, children: T) {
|
||||
self.children.extend(children)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,7 @@ use crate::{
|
|||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
scene,
|
||||
window::MeasurementContext,
|
||||
DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
|
||||
scene, Element, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
|
||||
pub struct Svg {
|
||||
|
@ -32,14 +30,15 @@ impl Svg {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Svg {
|
||||
impl<V: View> Element<V> for Svg {
|
||||
type LayoutState = Option<usvg::Tree>;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
match cx.asset_cache.svg(&self.path) {
|
||||
Ok(tree) => {
|
||||
|
@ -59,13 +58,15 @@ impl Element for Svg {
|
|||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
_visible_bounds: RectF,
|
||||
svg: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) {
|
||||
if let Some(svg) = svg.clone() {
|
||||
cx.scene.push_icon(scene::Icon {
|
||||
scene.push_icon(scene::Icon {
|
||||
bounds,
|
||||
svg,
|
||||
path: self.path.clone(),
|
||||
|
@ -81,7 +82,8 @@ impl Element for Svg {
|
|||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
|
@ -91,7 +93,8 @@ impl Element for Svg {
|
|||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "Svg",
|
||||
|
|
|
@ -7,8 +7,7 @@ use crate::{
|
|||
},
|
||||
json::{ToJson, Value},
|
||||
text_layout::{Line, RunStyle, ShapedBoundary},
|
||||
window::MeasurementContext,
|
||||
DebugContext, Element, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache,
|
||||
Element, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View, ViewContext,
|
||||
};
|
||||
use log::warn;
|
||||
use serde_json::json;
|
||||
|
@ -53,14 +52,15 @@ impl Text {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Text {
|
||||
impl<V: View> Element<V> for Text {
|
||||
type LayoutState = LayoutState;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
// Convert the string and highlight ranges into an iterator of highlighted chunks.
|
||||
|
||||
|
@ -99,7 +99,7 @@ impl Element for Text {
|
|||
chunks,
|
||||
&self.style,
|
||||
cx.text_layout_cache,
|
||||
cx.font_cache,
|
||||
&cx.font_cache,
|
||||
usize::MAX,
|
||||
self.text.matches('\n').count() + 1,
|
||||
);
|
||||
|
@ -143,10 +143,12 @@ impl Element for Text {
|
|||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let mut origin = bounds.origin();
|
||||
let empty = Vec::new();
|
||||
|
@ -163,6 +165,7 @@ impl Element for Text {
|
|||
if boundaries.intersects(visible_bounds) {
|
||||
if self.soft_wrap {
|
||||
line.paint_wrapped(
|
||||
scene,
|
||||
origin,
|
||||
visible_bounds,
|
||||
layout.line_height,
|
||||
|
@ -170,7 +173,7 @@ impl Element for Text {
|
|||
cx,
|
||||
);
|
||||
} else {
|
||||
line.paint(origin, visible_bounds, layout.line_height, cx);
|
||||
line.paint(scene, origin, visible_bounds, layout.line_height, cx);
|
||||
}
|
||||
}
|
||||
origin.set_y(boundaries.max_y());
|
||||
|
@ -184,7 +187,8 @@ impl Element for Text {
|
|||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &MeasurementContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
None
|
||||
}
|
||||
|
@ -194,7 +198,8 @@ impl Element for Text {
|
|||
bounds: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
_: &DebugContext,
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Value {
|
||||
json!({
|
||||
"type": "Text",
|
||||
|
@ -272,7 +277,7 @@ pub fn layout_highlighted_chunks<'a>(
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
elements::Empty, fonts, platform, AppContext, ElementBox, Entity, RenderContext, View,
|
||||
elements::Empty, fonts, platform, AppContext, ElementBox, Entity, View, ViewContext,
|
||||
};
|
||||
|
||||
#[crate::test(self)]
|
||||
|
@ -305,7 +310,7 @@ mod tests {
|
|||
"TestView"
|
||||
}
|
||||
|
||||
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> ElementBox<Self> {
|
||||
Empty::new().boxed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,7 @@ use crate::{
|
|||
fonts::TextStyle,
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::json,
|
||||
window::MeasurementContext,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
|
||||
Task, View,
|
||||
Action, Axis, ElementStateHandle, SceneBuilder, SizeConstraint, Task, View, ViewContext,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
|
@ -20,9 +18,9 @@ use std::{
|
|||
|
||||
const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
|
||||
|
||||
pub struct Tooltip {
|
||||
child: ElementBox,
|
||||
tooltip: Option<ElementBox>,
|
||||
pub struct Tooltip<V: View> {
|
||||
child: ElementBox<V>,
|
||||
tooltip: Option<ElementBox<V>>,
|
||||
_state: ElementStateHandle<Rc<TooltipState>>,
|
||||
}
|
||||
|
||||
|
@ -50,18 +48,19 @@ pub struct KeystrokeStyle {
|
|||
text: TextStyle,
|
||||
}
|
||||
|
||||
impl Tooltip {
|
||||
impl<V: View> Tooltip<V> {
|
||||
pub fn new<Tag: 'static, T: View>(
|
||||
id: usize,
|
||||
text: String,
|
||||
action: Option<Box<dyn Action>>,
|
||||
style: TooltipStyle,
|
||||
child: ElementBox,
|
||||
cx: &mut RenderContext<T>,
|
||||
child: ElementBox<V>,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self {
|
||||
struct ElementState<Tag>(Tag);
|
||||
struct MouseEventHandlerState<Tag>(Tag);
|
||||
let focused_view_id = cx.focused_view_id(cx.window_id);
|
||||
let focused_view_id = cx.focused_view_id();
|
||||
|
||||
let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id);
|
||||
let state = state_handle.read(cx).clone();
|
||||
|
@ -94,35 +93,36 @@ impl Tooltip {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let child = MouseEventHandler::<MouseEventHandlerState<Tag>>::new(id, cx, |_, _| child)
|
||||
.on_hover(move |e, cx| {
|
||||
let position = e.position;
|
||||
let window_id = cx.window_id();
|
||||
if let Some(view_id) = cx.view_id() {
|
||||
if e.started {
|
||||
if !state.visible.get() {
|
||||
state.position.set(position);
|
||||
let child =
|
||||
MouseEventHandler::<MouseEventHandlerState<Tag>>::new(id, view, cx, |_, _| child)
|
||||
.on_hover(move |e, cx| {
|
||||
let position = e.position;
|
||||
let window_id = cx.window_id();
|
||||
if let Some(view_id) = cx.view_id() {
|
||||
if e.started {
|
||||
if !state.visible.get() {
|
||||
state.position.set(position);
|
||||
|
||||
let mut debounce = state.debounce.borrow_mut();
|
||||
if debounce.is_none() {
|
||||
*debounce = Some(cx.spawn({
|
||||
let state = state.clone();
|
||||
|mut cx| async move {
|
||||
cx.background().timer(DEBOUNCE_TIMEOUT).await;
|
||||
state.visible.set(true);
|
||||
cx.update(|cx| cx.notify_view(window_id, view_id));
|
||||
}
|
||||
}));
|
||||
let mut debounce = state.debounce.borrow_mut();
|
||||
if debounce.is_none() {
|
||||
*debounce = Some(cx.spawn({
|
||||
let state = state.clone();
|
||||
|mut cx| async move {
|
||||
cx.background().timer(DEBOUNCE_TIMEOUT).await;
|
||||
state.visible.set(true);
|
||||
cx.update(|cx| cx.notify_view(window_id, view_id));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.visible.set(false);
|
||||
state.debounce.take();
|
||||
cx.notify();
|
||||
}
|
||||
} else {
|
||||
state.visible.set(false);
|
||||
state.debounce.take();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
})
|
||||
.boxed();
|
||||
})
|
||||
.boxed();
|
||||
Self {
|
||||
child,
|
||||
tooltip,
|
||||
|
@ -168,32 +168,40 @@ impl Tooltip {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Tooltip {
|
||||
impl<V: View> Element<V> for Tooltip<V> {
|
||||
type LayoutState = ();
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let size = self.child.layout(constraint, cx);
|
||||
let size = self.child.layout(constraint, view, cx);
|
||||
if let Some(tooltip) = self.tooltip.as_mut() {
|
||||
tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
|
||||
tooltip.layout(
|
||||
SizeConstraint::new(Vector2F::zero(), cx.window_size),
|
||||
view,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
(size, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
cx: &mut PaintContext,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
if let Some(tooltip) = self.tooltip.as_mut() {
|
||||
tooltip.paint(bounds.origin(), visible_bounds, cx);
|
||||
tooltip.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,9 +212,10 @@ impl Element for Tooltip {
|
|||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &MeasurementContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
self.child.rect_for_text_range(range, cx)
|
||||
self.child.rect_for_text_range(range, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
|
@ -214,11 +223,12 @@ impl Element for Tooltip {
|
|||
_: RectF,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
cx: &crate::DebugContext,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"child": self.child.debug(cx),
|
||||
"tooltip": self.tooltip.as_ref().map(|t| t.debug(cx)),
|
||||
"child": self.child.debug(view, cx),
|
||||
"tooltip": self.tooltip.as_ref().map(|t| t.debug(view, cx)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ impl<V: View> UniformList<V> {
|
|||
mut delta: Vector2F,
|
||||
precise: bool,
|
||||
scroll_max: f32,
|
||||
cx: &mut EventContext,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> bool {
|
||||
if !precise {
|
||||
delta *= 20.;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue