Introduce autoscroll support for elements (#10889)
This pull request introduces the new `ElementContext::request_autoscroll(bounds)` and `ElementContext::take_autoscroll()` methods in GPUI. These new APIs enable container elements such as `List` to change their scroll position if one of their children requested an autoscroll. We plan to use this in the revamped assistant. As a drive-by, we also: - Renamed `Element::before_layout` to `Element::request_layout` - Renamed `Element::after_layout` to `Element::prepaint` - Introduced a new `List::splice_focusable` method to splice focusable elements into the list, which enables rendering offscreen elements that are focused. Release Notes: - N/A --------- Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
co-authored by
Nathan
parent
efcd31c254
commit
bcbf2f2fd3
@@ -69,14 +69,17 @@ impl ParentElement for Anchored {
|
||||
}
|
||||
|
||||
impl Element for Anchored {
|
||||
type BeforeLayout = AnchoredState;
|
||||
type AfterLayout = ();
|
||||
type RequestLayoutState = AnchoredState;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
) -> (crate::LayoutId, Self::RequestLayoutState) {
|
||||
let child_layout_ids = self
|
||||
.children
|
||||
.iter_mut()
|
||||
.map(|child| child.before_layout(cx))
|
||||
.map(|child| child.request_layout(cx))
|
||||
.collect::<SmallVec<_>>();
|
||||
|
||||
let anchored_style = Style {
|
||||
@@ -90,19 +93,19 @@ impl Element for Anchored {
|
||||
(layout_id, AnchoredState { child_layout_ids })
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if before_layout.child_layout_ids.is_empty() {
|
||||
if request_layout.child_layout_ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut child_min = point(Pixels::MAX, Pixels::MAX);
|
||||
let mut child_max = Point::default();
|
||||
for child_layout_id in &before_layout.child_layout_ids {
|
||||
for child_layout_id in &request_layout.child_layout_ids {
|
||||
let child_bounds = cx.layout_bounds(*child_layout_id);
|
||||
child_min = child_min.min(&child_bounds.origin);
|
||||
child_max = child_max.max(&child_bounds.lower_right());
|
||||
@@ -167,7 +170,7 @@ impl Element for Anchored {
|
||||
|
||||
cx.with_element_offset(offset, |cx| {
|
||||
for child in &mut self.children {
|
||||
child.after_layout(cx);
|
||||
child.prepaint(cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -175,8 +178,8 @@ impl Element for Anchored {
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: crate::Bounds<crate::Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_after_layout: &mut Self::AfterLayout,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
_prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
for child in &mut self.children {
|
||||
|
||||
@@ -85,14 +85,14 @@ struct AnimationState {
|
||||
}
|
||||
|
||||
impl<E: IntoElement + 'static> Element for AnimationElement<E> {
|
||||
type BeforeLayout = AnyElement;
|
||||
type RequestLayoutState = AnyElement;
|
||||
|
||||
type AfterLayout = ();
|
||||
type PrepaintState = ();
|
||||
|
||||
fn before_layout(
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut crate::ElementContext,
|
||||
) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||
) -> (crate::LayoutId, Self::RequestLayoutState) {
|
||||
cx.with_element_state(Some(self.id.clone()), |state, cx| {
|
||||
let state = state.unwrap().unwrap_or_else(|| AnimationState {
|
||||
start: Instant::now(),
|
||||
@@ -130,24 +130,24 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
|
||||
})
|
||||
}
|
||||
|
||||
((element.before_layout(cx), element), Some(state))
|
||||
((element.request_layout(cx), element), Some(state))
|
||||
})
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_bounds: crate::Bounds<crate::Pixels>,
|
||||
element: &mut Self::BeforeLayout,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
cx: &mut crate::ElementContext,
|
||||
) -> Self::AfterLayout {
|
||||
element.after_layout(cx);
|
||||
) -> Self::PrepaintState {
|
||||
element.prepaint(cx);
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: crate::Bounds<crate::Pixels>,
|
||||
element: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut crate::ElementContext,
|
||||
) {
|
||||
element.paint(cx);
|
||||
|
||||
@@ -5,11 +5,11 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe
|
||||
/// Construct a canvas element with the given paint callback.
|
||||
/// Useful for adding short term custom drawing to a view.
|
||||
pub fn canvas<T>(
|
||||
after_layout: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
|
||||
prepaint: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
|
||||
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut ElementContext),
|
||||
) -> Canvas<T> {
|
||||
Canvas {
|
||||
after_layout: Some(Box::new(after_layout)),
|
||||
prepaint: Some(Box::new(prepaint)),
|
||||
paint: Some(Box::new(paint)),
|
||||
style: StyleRefinement::default(),
|
||||
}
|
||||
@@ -18,7 +18,7 @@ pub fn canvas<T>(
|
||||
/// A canvas element, meant for accessing the low level paint API without defining a whole
|
||||
/// custom element
|
||||
pub struct Canvas<T> {
|
||||
after_layout: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
|
||||
prepaint: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
|
||||
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
@@ -32,35 +32,38 @@ impl<T: 'static> IntoElement for Canvas<T> {
|
||||
}
|
||||
|
||||
impl<T: 'static> Element for Canvas<T> {
|
||||
type BeforeLayout = Style;
|
||||
type AfterLayout = Option<T>;
|
||||
type RequestLayoutState = Style;
|
||||
type PrepaintState = Option<T>;
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
) -> (crate::LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.style);
|
||||
let layout_id = cx.request_layout(&style, []);
|
||||
(layout_id, style)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Style,
|
||||
_request_layout: &mut Style,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<T> {
|
||||
Some(self.after_layout.take().unwrap()(bounds, cx))
|
||||
Some(self.prepaint.take().unwrap()(bounds, cx))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
style: &mut Style,
|
||||
after_layout: &mut Self::AfterLayout,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let after_layout = after_layout.take().unwrap();
|
||||
let prepaint = prepaint.take().unwrap();
|
||||
style.paint(bounds, cx, |cx| {
|
||||
(self.paint.take().unwrap())(bounds, after_layout, cx)
|
||||
(self.paint.take().unwrap())(bounds, prepaint, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,18 +26,18 @@ impl Deferred {
|
||||
}
|
||||
|
||||
impl Element for Deferred {
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = ();
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = ();
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, ()) {
|
||||
let layout_id = self.child.as_mut().unwrap().before_layout(cx);
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, ()) {
|
||||
let layout_id = self.child.as_mut().unwrap().request_layout(cx);
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let child = self.child.take().unwrap();
|
||||
@@ -48,8 +48,8 @@ impl Element for Deferred {
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_after_layout: &mut Self::AfterLayout,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
_prepaint: &mut Self::PrepaintState,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -1120,17 +1120,17 @@ impl ParentElement for Div {
|
||||
}
|
||||
|
||||
impl Element for Div {
|
||||
type BeforeLayout = DivFrameState;
|
||||
type AfterLayout = Option<Hitbox>;
|
||||
type RequestLayoutState = DivFrameState;
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut child_layout_ids = SmallVec::new();
|
||||
let layout_id = self.interactivity.before_layout(cx, |style, cx| {
|
||||
let layout_id = self.interactivity.request_layout(cx, |style, cx| {
|
||||
cx.with_text_style(style.text_style().cloned(), |cx| {
|
||||
child_layout_ids = self
|
||||
.children
|
||||
.iter_mut()
|
||||
.map(|child| child.before_layout(cx))
|
||||
.map(|child| child.request_layout(cx))
|
||||
.collect::<SmallVec<_>>();
|
||||
cx.request_layout(&style, child_layout_ids.iter().copied())
|
||||
})
|
||||
@@ -1138,23 +1138,23 @@ impl Element for Div {
|
||||
(layout_id, DivFrameState { child_layout_ids })
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<Hitbox> {
|
||||
let mut child_min = point(Pixels::MAX, Pixels::MAX);
|
||||
let mut child_max = Point::default();
|
||||
let content_size = if before_layout.child_layout_ids.is_empty() {
|
||||
let content_size = if request_layout.child_layout_ids.is_empty() {
|
||||
bounds.size
|
||||
} else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
|
||||
let mut state = scroll_handle.0.borrow_mut();
|
||||
state.child_bounds = Vec::with_capacity(before_layout.child_layout_ids.len());
|
||||
state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
|
||||
state.bounds = bounds;
|
||||
let requested = state.requested_scroll_top.take();
|
||||
|
||||
for (ix, child_layout_id) in before_layout.child_layout_ids.iter().enumerate() {
|
||||
for (ix, child_layout_id) in request_layout.child_layout_ids.iter().enumerate() {
|
||||
let child_bounds = cx.layout_bounds(*child_layout_id);
|
||||
child_min = child_min.min(&child_bounds.origin);
|
||||
child_max = child_max.max(&child_bounds.lower_right());
|
||||
@@ -1169,7 +1169,7 @@ impl Element for Div {
|
||||
}
|
||||
(child_max - child_min).into()
|
||||
} else {
|
||||
for child_layout_id in &before_layout.child_layout_ids {
|
||||
for child_layout_id in &request_layout.child_layout_ids {
|
||||
let child_bounds = cx.layout_bounds(*child_layout_id);
|
||||
child_min = child_min.min(&child_bounds.origin);
|
||||
child_max = child_max.max(&child_bounds.lower_right());
|
||||
@@ -1177,14 +1177,14 @@ impl Element for Div {
|
||||
(child_max - child_min).into()
|
||||
};
|
||||
|
||||
self.interactivity.after_layout(
|
||||
self.interactivity.prepaint(
|
||||
bounds,
|
||||
content_size,
|
||||
cx,
|
||||
|_style, scroll_offset, hitbox, cx| {
|
||||
cx.with_element_offset(scroll_offset, |cx| {
|
||||
for child in &mut self.children {
|
||||
child.after_layout(cx);
|
||||
child.prepaint(cx);
|
||||
}
|
||||
});
|
||||
hitbox
|
||||
@@ -1195,7 +1195,7 @@ impl Element for Div {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
@@ -1274,7 +1274,7 @@ pub struct Interactivity {
|
||||
|
||||
impl Interactivity {
|
||||
/// Layout this element according to this interactivity state's configured styles
|
||||
pub fn before_layout(
|
||||
pub fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(Style, &mut ElementContext) -> LayoutId,
|
||||
@@ -1337,7 +1337,7 @@ impl Interactivity {
|
||||
}
|
||||
|
||||
/// Commit the bounds of this element according to this interactivity state's configured styles.
|
||||
pub fn after_layout<R>(
|
||||
pub fn prepaint<R>(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
content_size: Size<Pixels>,
|
||||
@@ -2261,30 +2261,30 @@ impl<E> Element for Focusable<E>
|
||||
where
|
||||
E: Element,
|
||||
{
|
||||
type BeforeLayout = E::BeforeLayout;
|
||||
type AfterLayout = E::AfterLayout;
|
||||
type RequestLayoutState = E::RequestLayoutState;
|
||||
type PrepaintState = E::PrepaintState;
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
self.element.before_layout(cx)
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
self.element.request_layout(cx)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::BeforeLayout,
|
||||
state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> E::AfterLayout {
|
||||
self.element.after_layout(bounds, state, cx)
|
||||
) -> E::PrepaintState {
|
||||
self.element.prepaint(bounds, state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
after_layout: &mut Self::AfterLayout,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.element.paint(bounds, before_layout, after_layout, cx)
|
||||
self.element.paint(bounds, request_layout, prepaint, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2344,30 +2344,30 @@ impl<E> Element for Stateful<E>
|
||||
where
|
||||
E: Element,
|
||||
{
|
||||
type BeforeLayout = E::BeforeLayout;
|
||||
type AfterLayout = E::AfterLayout;
|
||||
type RequestLayoutState = E::RequestLayoutState;
|
||||
type PrepaintState = E::PrepaintState;
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
self.element.before_layout(cx)
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
self.element.request_layout(cx)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::BeforeLayout,
|
||||
state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> E::AfterLayout {
|
||||
self.element.after_layout(bounds, state, cx)
|
||||
) -> E::PrepaintState {
|
||||
self.element.prepaint(bounds, state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
after_layout: &mut Self::AfterLayout,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.element.paint(bounds, before_layout, after_layout, cx);
|
||||
self.element.paint(bounds, request_layout, prepaint, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,11 +229,11 @@ impl Img {
|
||||
}
|
||||
|
||||
impl Element for Img {
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = Option<Hitbox>;
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let layout_id = self.interactivity.before_layout(cx, |mut style, cx| {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let layout_id = self.interactivity.request_layout(cx, |mut style, cx| {
|
||||
if let Some(data) = self.source.data(cx) {
|
||||
let image_size = data.size();
|
||||
match (style.size.width, style.size.height) {
|
||||
@@ -256,21 +256,21 @@ impl Element for Img {
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<Hitbox> {
|
||||
self.interactivity
|
||||
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||
.prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::BeforeLayout,
|
||||
hitbox: &mut Self::AfterLayout,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let source = self.source.clone();
|
||||
|
||||
+240
-103
@@ -8,8 +8,8 @@
|
||||
|
||||
use crate::{
|
||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
|
||||
Element, ElementContext, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
||||
StyleRefinement, Styled, WindowContext,
|
||||
Element, ElementContext, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent,
|
||||
Size, Style, StyleRefinement, Styled, WindowContext,
|
||||
};
|
||||
use collections::VecDeque;
|
||||
use refineable::Refineable as _;
|
||||
@@ -92,20 +92,58 @@ pub enum ListSizingBehavior {
|
||||
struct LayoutItemsResponse {
|
||||
max_item_width: Pixels,
|
||||
scroll_top: ListOffset,
|
||||
available_item_space: Size<AvailableSpace>,
|
||||
item_elements: VecDeque<AnyElement>,
|
||||
item_layouts: VecDeque<ItemLayout>,
|
||||
}
|
||||
|
||||
struct ItemLayout {
|
||||
index: usize,
|
||||
element: AnyElement,
|
||||
size: Size<Pixels>,
|
||||
}
|
||||
|
||||
/// Frame state used by the [List] element after layout.
|
||||
pub struct ListAfterLayoutState {
|
||||
pub struct ListPrepaintState {
|
||||
hitbox: Hitbox,
|
||||
layout: LayoutItemsResponse,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ListItem {
|
||||
Unrendered,
|
||||
Rendered { size: Size<Pixels> },
|
||||
Unmeasured {
|
||||
focus_handle: Option<FocusHandle>,
|
||||
},
|
||||
Measured {
|
||||
size: Size<Pixels>,
|
||||
focus_handle: Option<FocusHandle>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ListItem {
|
||||
fn size(&self) -> Option<Size<Pixels>> {
|
||||
if let ListItem::Measured { size, .. } = self {
|
||||
Some(*size)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_handle(&self) -> Option<FocusHandle> {
|
||||
match self {
|
||||
ListItem::Unmeasured { focus_handle } | ListItem::Measured { focus_handle, .. } => {
|
||||
focus_handle.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_focused(&self, cx: &WindowContext) -> bool {
|
||||
match self {
|
||||
ListItem::Unmeasured { focus_handle } | ListItem::Measured { focus_handle, .. } => {
|
||||
focus_handle
|
||||
.as_ref()
|
||||
.is_some_and(|handle| handle.contains_focused(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
@@ -114,6 +152,7 @@ struct ListItemSummary {
|
||||
rendered_count: usize,
|
||||
unrendered_count: usize,
|
||||
height: Pixels,
|
||||
has_focus_handles: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -131,45 +170,45 @@ struct Height(Pixels);
|
||||
impl ListState {
|
||||
/// Construct a new list state, for storage on a view.
|
||||
///
|
||||
/// the overdraw parameter controls how much extra space is rendered
|
||||
/// above and below the visible area. This can help ensure that the list
|
||||
/// doesn't flicker or pop in when scrolling.
|
||||
pub fn new<F>(
|
||||
element_count: usize,
|
||||
orientation: ListAlignment,
|
||||
/// The overdraw parameter controls how much extra space is rendered
|
||||
/// above and below the visible area. Elements within this area will
|
||||
/// be measured even though they are not visible. This can help ensure
|
||||
/// that the list doesn't flicker or pop in when scrolling.
|
||||
pub fn new<R>(
|
||||
item_count: usize,
|
||||
alignment: ListAlignment,
|
||||
overdraw: Pixels,
|
||||
render_item: F,
|
||||
render_item: R,
|
||||
) -> Self
|
||||
where
|
||||
F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
|
||||
R: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
|
||||
{
|
||||
let mut items = SumTree::new();
|
||||
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
Self(Rc::new(RefCell::new(StateInner {
|
||||
let this = Self(Rc::new(RefCell::new(StateInner {
|
||||
last_layout_bounds: None,
|
||||
last_padding: None,
|
||||
render_item: Box::new(render_item),
|
||||
items,
|
||||
items: SumTree::new(),
|
||||
logical_scroll_top: None,
|
||||
alignment: orientation,
|
||||
alignment,
|
||||
overdraw,
|
||||
scroll_handler: None,
|
||||
reset: false,
|
||||
})))
|
||||
})));
|
||||
this.splice(0..0, item_count);
|
||||
this
|
||||
}
|
||||
|
||||
/// Reset this instantiation of the list state.
|
||||
///
|
||||
/// Note that this will cause scroll events to be dropped until the next paint.
|
||||
pub fn reset(&self, element_count: usize) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
state.reset = true;
|
||||
{
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
state.reset = true;
|
||||
state.logical_scroll_top = None;
|
||||
}
|
||||
|
||||
state.logical_scroll_top = None;
|
||||
state.items = SumTree::new();
|
||||
state
|
||||
.items
|
||||
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
self.splice(0..element_count, element_count);
|
||||
}
|
||||
|
||||
/// The number of items in this list.
|
||||
@@ -177,11 +216,39 @@ impl ListState {
|
||||
self.0.borrow().items.summary().count
|
||||
}
|
||||
|
||||
/// Register with the list state that the items in `old_range` have been replaced
|
||||
/// Inform the list state that the items in `old_range` have been replaced
|
||||
/// by `count` new items that must be recalculated.
|
||||
pub fn splice(&self, old_range: Range<usize>, count: usize) {
|
||||
self.splice_focusable(old_range, (0..count).map(|_| None))
|
||||
}
|
||||
|
||||
/// Register with the list state that the items in `old_range` have been replaced
|
||||
/// by new items. As opposed to [`splice`], this method allows an iterator of optional focus handles
|
||||
/// to be supplied to properly integrate with items in the list that can be focused. If a focused item
|
||||
/// is scrolled out of view, the list will continue to render it to allow keyboard interaction.
|
||||
pub fn splice_focusable(
|
||||
&self,
|
||||
old_range: Range<usize>,
|
||||
focus_handles: impl IntoIterator<Item = Option<FocusHandle>>,
|
||||
) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
|
||||
let mut old_items = state.items.cursor::<Count>();
|
||||
let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right, &());
|
||||
old_items.seek_forward(&Count(old_range.end), Bias::Right, &());
|
||||
|
||||
let mut spliced_count = 0;
|
||||
new_items.extend(
|
||||
focus_handles.into_iter().map(|focus_handle| {
|
||||
spliced_count += 1;
|
||||
ListItem::Unmeasured { focus_handle }
|
||||
}),
|
||||
&(),
|
||||
);
|
||||
new_items.append(old_items.suffix(&()), &());
|
||||
drop(old_items);
|
||||
state.items = new_items;
|
||||
|
||||
if let Some(ListOffset {
|
||||
item_ix,
|
||||
offset_in_item,
|
||||
@@ -191,18 +258,9 @@ impl ListState {
|
||||
*item_ix = old_range.start;
|
||||
*offset_in_item = px(0.);
|
||||
} else if old_range.end <= *item_ix {
|
||||
*item_ix = *item_ix - (old_range.end - old_range.start) + count;
|
||||
*item_ix = *item_ix - (old_range.end - old_range.start) + spliced_count;
|
||||
}
|
||||
}
|
||||
|
||||
let mut old_heights = state.items.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, &());
|
||||
|
||||
new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
|
||||
new_heights.append(old_heights.suffix(&()), &());
|
||||
drop(old_heights);
|
||||
state.items = new_heights;
|
||||
}
|
||||
|
||||
/// Set a handler that will be called when the list is scrolled.
|
||||
@@ -279,7 +337,7 @@ impl ListState {
|
||||
let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
|
||||
|
||||
cursor.seek_forward(&Count(ix), Bias::Right, &());
|
||||
if let Some(&ListItem::Rendered { size }) = cursor.item() {
|
||||
if let Some(&ListItem::Measured { size, .. }) = cursor.item() {
|
||||
let &(Count(count), Height(top)) = cursor.start();
|
||||
if count == ix {
|
||||
let top = bounds.top() + top - scroll_top;
|
||||
@@ -379,10 +437,11 @@ impl StateInner {
|
||||
) -> LayoutItemsResponse {
|
||||
let old_items = self.items.clone();
|
||||
let mut measured_items = VecDeque::new();
|
||||
let mut item_elements = VecDeque::new();
|
||||
let mut item_layouts = VecDeque::new();
|
||||
let mut rendered_height = padding.top;
|
||||
let mut max_item_width = px(0.);
|
||||
let mut scroll_top = self.logical_scroll_top();
|
||||
let mut rendered_focused_item = false;
|
||||
|
||||
let available_item_space = size(
|
||||
available_width.map_or(AvailableSpace::MinContent, |width| {
|
||||
@@ -401,27 +460,34 @@ impl StateInner {
|
||||
break;
|
||||
}
|
||||
|
||||
// Use the previously cached height if available
|
||||
let mut size = if let ListItem::Rendered { size } = item {
|
||||
Some(*size)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Use the previously cached height and focus handle if available
|
||||
let mut size = item.size();
|
||||
|
||||
// If we're within the visible area or the height wasn't cached, render and measure the item's element
|
||||
if visible_height < available_height || size.is_none() {
|
||||
let mut element = (self.render_item)(scroll_top.item_ix + ix, cx);
|
||||
let element_size = element.measure(available_item_space, cx);
|
||||
let item_index = scroll_top.item_ix + ix;
|
||||
let mut element = (self.render_item)(item_index, cx);
|
||||
let element_size = element.layout_as_root(available_item_space, cx);
|
||||
size = Some(element_size);
|
||||
if visible_height < available_height {
|
||||
item_elements.push_back(element);
|
||||
item_layouts.push_back(ItemLayout {
|
||||
index: item_index,
|
||||
element,
|
||||
size: element_size,
|
||||
});
|
||||
if item.contains_focused(cx) {
|
||||
rendered_focused_item = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let size = size.unwrap();
|
||||
rendered_height += size.height;
|
||||
max_item_width = max_item_width.max(size.width);
|
||||
measured_items.push_back(ListItem::Rendered { size });
|
||||
measured_items.push_back(ListItem::Measured {
|
||||
size,
|
||||
focus_handle: item.focus_handle(),
|
||||
});
|
||||
}
|
||||
rendered_height += padding.bottom;
|
||||
|
||||
@@ -433,13 +499,24 @@ impl StateInner {
|
||||
if rendered_height - scroll_top.offset_in_item < available_height {
|
||||
while rendered_height < available_height {
|
||||
cursor.prev(&());
|
||||
if cursor.item().is_some() {
|
||||
let mut element = (self.render_item)(cursor.start().0, cx);
|
||||
let element_size = element.measure(available_item_space, cx);
|
||||
|
||||
if let Some(item) = cursor.item() {
|
||||
let item_index = cursor.start().0;
|
||||
let mut element = (self.render_item)(item_index, cx);
|
||||
let element_size = element.layout_as_root(available_item_space, cx);
|
||||
let focus_handle = item.focus_handle();
|
||||
rendered_height += element_size.height;
|
||||
measured_items.push_front(ListItem::Rendered { size: element_size });
|
||||
item_elements.push_front(element)
|
||||
measured_items.push_front(ListItem::Measured {
|
||||
size: element_size,
|
||||
focus_handle,
|
||||
});
|
||||
item_layouts.push_front(ItemLayout {
|
||||
index: item_index,
|
||||
element,
|
||||
size: element_size,
|
||||
});
|
||||
if item.contains_focused(cx) {
|
||||
rendered_focused_item = true;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -470,15 +547,18 @@ impl StateInner {
|
||||
while leading_overdraw < self.overdraw {
|
||||
cursor.prev(&());
|
||||
if let Some(item) = cursor.item() {
|
||||
let size = if let ListItem::Rendered { size } = item {
|
||||
let size = if let ListItem::Measured { size, .. } = item {
|
||||
*size
|
||||
} else {
|
||||
let mut element = (self.render_item)(cursor.start().0, cx);
|
||||
element.measure(available_item_space, cx)
|
||||
element.layout_as_root(available_item_space, cx)
|
||||
};
|
||||
|
||||
leading_overdraw += size.height;
|
||||
measured_items.push_front(ListItem::Rendered { size });
|
||||
measured_items.push_front(ListItem::Measured {
|
||||
size,
|
||||
focus_handle: item.focus_handle(),
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -490,23 +570,83 @@ impl StateInner {
|
||||
new_items.extend(measured_items, &());
|
||||
cursor.seek(&Count(measured_range.end), Bias::Right, &());
|
||||
new_items.append(cursor.suffix(&()), &());
|
||||
|
||||
self.items = new_items;
|
||||
|
||||
// If none of the visible items are focused, check if an off-screen item is focused
|
||||
// and include it to be rendered after the visible items so keyboard interaction continues
|
||||
// to work for it.
|
||||
if !rendered_focused_item {
|
||||
let mut cursor = self
|
||||
.items
|
||||
.filter::<_, Count>(|summary| summary.has_focus_handles);
|
||||
cursor.next(&());
|
||||
while let Some(item) = cursor.item() {
|
||||
if item.contains_focused(cx) {
|
||||
let item_index = cursor.start().0;
|
||||
let mut element = (self.render_item)(cursor.start().0, cx);
|
||||
let size = element.layout_as_root(available_item_space, cx);
|
||||
item_layouts.push_back(ItemLayout {
|
||||
index: item_index,
|
||||
element,
|
||||
size,
|
||||
});
|
||||
break;
|
||||
}
|
||||
cursor.next(&());
|
||||
}
|
||||
}
|
||||
|
||||
LayoutItemsResponse {
|
||||
max_item_width,
|
||||
scroll_top,
|
||||
available_item_space,
|
||||
item_elements,
|
||||
item_layouts,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepaint_items(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
padding: Edges<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
) -> Result<LayoutItemsResponse, ListOffset> {
|
||||
cx.transact(|cx| {
|
||||
let mut layout_response =
|
||||
self.layout_items(Some(bounds.size.width), bounds.size.height, &padding, cx);
|
||||
|
||||
// Only paint the visible items, if there is actually any space for them (taking padding into account)
|
||||
if bounds.size.height > padding.top + padding.bottom {
|
||||
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
|
||||
item_origin.y -= layout_response.scroll_top.offset_in_item;
|
||||
for item in &mut layout_response.item_layouts {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
item.element.prepaint_at(item_origin, cx);
|
||||
});
|
||||
|
||||
if let Some(autoscroll_bounds) = cx.take_autoscroll() {
|
||||
if bounds.intersect(&autoscroll_bounds) != autoscroll_bounds {
|
||||
return Err(ListOffset {
|
||||
item_ix: item.index,
|
||||
offset_in_item: autoscroll_bounds.origin.y - item_origin.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
item_origin.y += item.size.height;
|
||||
}
|
||||
} else {
|
||||
layout_response.item_layouts.clear();
|
||||
}
|
||||
|
||||
Ok(layout_response)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ListItem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Unrendered => write!(f, "Unrendered"),
|
||||
Self::Rendered { size, .. } => f.debug_struct("Rendered").field("size", size).finish(),
|
||||
Self::Unmeasured { .. } => write!(f, "Unrendered"),
|
||||
Self::Measured { size, .. } => f.debug_struct("Rendered").field("size", size).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -522,13 +662,13 @@ pub struct ListOffset {
|
||||
}
|
||||
|
||||
impl Element for List {
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = ListAfterLayoutState;
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = ListPrepaintState;
|
||||
|
||||
fn before_layout(
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut crate::ElementContext,
|
||||
) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||
) -> (crate::LayoutId, Self::RequestLayoutState) {
|
||||
let layout_id = match self.sizing_behavior {
|
||||
ListSizingBehavior::Infer => {
|
||||
let mut style = Style::default();
|
||||
@@ -589,12 +729,12 @@ impl Element for List {
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::BeforeLayout,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> ListAfterLayoutState {
|
||||
) -> ListPrepaintState {
|
||||
let state = &mut *self.state.0.borrow_mut();
|
||||
state.reset = false;
|
||||
|
||||
@@ -607,55 +747,47 @@ impl Element for List {
|
||||
if state.last_layout_bounds.map_or(true, |last_bounds| {
|
||||
last_bounds.size.width != bounds.size.width
|
||||
}) {
|
||||
state.items = SumTree::from_iter(
|
||||
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
|
||||
let new_items = SumTree::from_iter(
|
||||
state.items.iter().map(|item| ListItem::Unmeasured {
|
||||
focus_handle: item.focus_handle(),
|
||||
}),
|
||||
&(),
|
||||
)
|
||||
);
|
||||
|
||||
state.items = new_items;
|
||||
}
|
||||
|
||||
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
||||
let mut layout_response =
|
||||
state.layout_items(Some(bounds.size.width), bounds.size.height, &padding, cx);
|
||||
|
||||
// Only paint the visible items, if there is actually any space for them (taking padding into account)
|
||||
if bounds.size.height > padding.top + padding.bottom {
|
||||
// Paint the visible items
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
|
||||
item_origin.y -= layout_response.scroll_top.offset_in_item;
|
||||
for mut item_element in &mut layout_response.item_elements {
|
||||
let item_size = item_element.measure(layout_response.available_item_space, cx);
|
||||
item_element.layout(item_origin, layout_response.available_item_space, cx);
|
||||
item_origin.y += item_size.height;
|
||||
}
|
||||
});
|
||||
}
|
||||
let layout = match state.prepaint_items(bounds, padding, cx) {
|
||||
Ok(layout) => layout,
|
||||
Err(autoscroll_request) => {
|
||||
state.logical_scroll_top = Some(autoscroll_request);
|
||||
state.prepaint_items(bounds, padding, cx).unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
state.last_layout_bounds = Some(bounds);
|
||||
state.last_padding = Some(padding);
|
||||
ListAfterLayoutState {
|
||||
hitbox,
|
||||
layout: layout_response,
|
||||
}
|
||||
ListPrepaintState { hitbox, layout }
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
_: &mut Self::BeforeLayout,
|
||||
after_layout: &mut Self::AfterLayout,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut crate::ElementContext,
|
||||
) {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
for item in &mut after_layout.layout.item_elements {
|
||||
item.paint(cx);
|
||||
for item in &mut prepaint.layout.item_layouts {
|
||||
item.element.paint(cx);
|
||||
}
|
||||
});
|
||||
|
||||
let list_state = self.state.clone();
|
||||
let height = bounds.size.height;
|
||||
let scroll_top = after_layout.layout.scroll_top;
|
||||
let hitbox_id = after_layout.hitbox.id;
|
||||
let scroll_top = prepaint.layout.scroll_top;
|
||||
let hitbox_id = prepaint.hitbox.id;
|
||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(cx) {
|
||||
list_state.0.borrow_mut().scroll(
|
||||
@@ -688,17 +820,21 @@ impl sum_tree::Item for ListItem {
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
match self {
|
||||
ListItem::Unrendered => ListItemSummary {
|
||||
ListItem::Unmeasured { focus_handle } => ListItemSummary {
|
||||
count: 1,
|
||||
rendered_count: 0,
|
||||
unrendered_count: 1,
|
||||
height: px(0.),
|
||||
has_focus_handles: focus_handle.is_some(),
|
||||
},
|
||||
ListItem::Rendered { size } => ListItemSummary {
|
||||
ListItem::Measured {
|
||||
size, focus_handle, ..
|
||||
} => ListItemSummary {
|
||||
count: 1,
|
||||
rendered_count: 1,
|
||||
unrendered_count: 0,
|
||||
height: size.height,
|
||||
has_focus_handles: focus_handle.is_some(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -712,6 +848,7 @@ impl sum_tree::Summary for ListItemSummary {
|
||||
self.rendered_count += summary.rendered_count;
|
||||
self.unrendered_count += summary.unrendered_count;
|
||||
self.height += summary.height;
|
||||
self.has_focus_handles |= summary.has_focus_handles;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,30 +37,30 @@ impl Svg {
|
||||
}
|
||||
|
||||
impl Element for Svg {
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = Option<Hitbox>;
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let layout_id = self
|
||||
.interactivity
|
||||
.before_layout(cx, |style, cx| cx.request_layout(&style, None));
|
||||
.request_layout(cx, |style, cx| cx.request_layout(&style, None));
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<Hitbox> {
|
||||
self.interactivity
|
||||
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||
.prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
) where
|
||||
|
||||
@@ -17,19 +17,19 @@ use std::{
|
||||
use util::ResultExt;
|
||||
|
||||
impl Element for &'static str {
|
||||
type BeforeLayout = TextState;
|
||||
type AfterLayout = ();
|
||||
type RequestLayoutState = TextState;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(SharedString::from(*self), None, cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_text_state: &mut Self::BeforeLayout,
|
||||
_text_state: &mut Self::RequestLayoutState,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
@@ -62,19 +62,19 @@ impl IntoElement for String {
|
||||
}
|
||||
|
||||
impl Element for SharedString {
|
||||
type BeforeLayout = TextState;
|
||||
type AfterLayout = ();
|
||||
type RequestLayoutState = TextState;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(self.clone(), None, cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_text_state: &mut Self::BeforeLayout,
|
||||
_text_state: &mut Self::RequestLayoutState,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
@@ -82,8 +82,8 @@ impl Element for SharedString {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
text_state: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let text_str: &str = self.as_ref();
|
||||
@@ -148,19 +148,19 @@ impl StyledText {
|
||||
}
|
||||
|
||||
impl Element for StyledText {
|
||||
type BeforeLayout = TextState;
|
||||
type AfterLayout = ();
|
||||
type RequestLayoutState = TextState;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::BeforeLayout,
|
||||
_state: &mut Self::RequestLayoutState,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
@@ -168,8 +168,8 @@ impl Element for StyledText {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
text_state: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
text_state.paint(bounds, &self.text, cx)
|
||||
@@ -402,17 +402,17 @@ impl InteractiveText {
|
||||
}
|
||||
|
||||
impl Element for InteractiveText {
|
||||
type BeforeLayout = TextState;
|
||||
type AfterLayout = Hitbox;
|
||||
type RequestLayoutState = TextState;
|
||||
type PrepaintState = Hitbox;
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
self.text.before_layout(cx)
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
self.text.request_layout(cx)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::BeforeLayout,
|
||||
state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> Hitbox {
|
||||
cx.with_element_state::<InteractiveTextState, _>(
|
||||
@@ -430,7 +430,7 @@ impl Element for InteractiveText {
|
||||
}
|
||||
}
|
||||
|
||||
self.text.after_layout(bounds, state, cx);
|
||||
self.text.prepaint(bounds, state, cx);
|
||||
let hitbox = cx.insert_hitbox(bounds, false);
|
||||
(hitbox, interactive_state)
|
||||
},
|
||||
@@ -440,7 +440,7 @@ impl Element for InteractiveText {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::BeforeLayout,
|
||||
text_state: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Hitbox,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
|
||||
@@ -104,13 +104,13 @@ impl Styled for UniformList {
|
||||
}
|
||||
|
||||
impl Element for UniformList {
|
||||
type BeforeLayout = UniformListFrameState;
|
||||
type AfterLayout = Option<Hitbox>;
|
||||
type RequestLayoutState = UniformListFrameState;
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let max_items = self.item_count;
|
||||
let item_size = self.measure_item(None, cx);
|
||||
let layout_id = self.interactivity.before_layout(cx, |style, cx| {
|
||||
let layout_id = self.interactivity.request_layout(cx, |style, cx| {
|
||||
cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| {
|
||||
let desired_height = item_size.height * max_items;
|
||||
let width = known_dimensions
|
||||
@@ -137,10 +137,10 @@ impl Element for UniformList {
|
||||
)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
frame_state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<Hitbox> {
|
||||
let style = self.interactivity.compute_style(None, cx);
|
||||
@@ -155,7 +155,7 @@ impl Element for UniformList {
|
||||
|
||||
let content_size = Size {
|
||||
width: padded_bounds.size.width,
|
||||
height: before_layout.item_size.height * self.item_count + padding.top + padding.bottom,
|
||||
height: frame_state.item_size.height * self.item_count + padding.top + padding.bottom,
|
||||
};
|
||||
|
||||
let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap();
|
||||
@@ -166,7 +166,7 @@ impl Element for UniformList {
|
||||
.as_mut()
|
||||
.and_then(|handle| handle.deferred_scroll_to_item.take());
|
||||
|
||||
self.interactivity.after_layout(
|
||||
self.interactivity.prepaint(
|
||||
bounds,
|
||||
content_size,
|
||||
cx,
|
||||
@@ -222,8 +222,9 @@ impl Element for UniformList {
|
||||
AvailableSpace::Definite(padded_bounds.size.width),
|
||||
AvailableSpace::Definite(item_height),
|
||||
);
|
||||
item.layout(item_origin, available_space, cx);
|
||||
before_layout.items.push(item);
|
||||
item.layout_as_root(available_space, cx);
|
||||
item.prepaint_at(item_origin, cx);
|
||||
frame_state.items.push(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -236,13 +237,13 @@ impl Element for UniformList {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.interactivity
|
||||
.paint(bounds, hitbox.as_ref(), cx, |_, cx| {
|
||||
for item in &mut before_layout.items {
|
||||
for item in &mut request_layout.items {
|
||||
item.paint(cx);
|
||||
}
|
||||
})
|
||||
@@ -278,7 +279,7 @@ impl UniformList {
|
||||
}),
|
||||
AvailableSpace::MinContent,
|
||||
);
|
||||
item_to_measure.measure(available_space, cx)
|
||||
item_to_measure.layout_as_root(available_space, cx)
|
||||
}
|
||||
|
||||
/// Track and render scroll state of this list with reference to the given scroll handle.
|
||||
|
||||
Reference in New Issue
Block a user