Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Nathan Sobo
74ed2f7f70 Reverse div child paint order 2024-02-23 19:37:44 -07:00
Antonio Scandurra
eba5db3a6e Finish flipping paint order in EditorElement 2024-02-23 18:54:08 +01:00
Antonio Scandurra
cd6bdd8b1c WIP: start painting elements front-to-back
Co-Authored-By: Nathan <nathan@zed.dev>
2024-02-23 18:22:03 +01:00
Antonio Scandurra
a3ce933b04 Use BoundsTree in scene
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2024-02-23 17:51:06 +01:00
Antonio Scandurra
816c48b7d6 Introduce a BoundsTree structure
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2024-02-23 17:33:22 +01:00
9 changed files with 944 additions and 547 deletions

4
Cargo.lock generated
View file

@ -8548,9 +8548,9 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.3.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "signal-hook"

View file

@ -727,6 +727,51 @@ impl EditorElement {
let gutter_settings = EditorSettings::get_global(cx).gutter;
if let Some(indicator) = layout.code_actions_indicator.take() {
debug_assert!(gutter_settings.code_actions);
let mut button = indicator.button.into_any_element();
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(line_height),
);
let indicator_size = button.measure(available_space, cx);
let mut x = Pixels::ZERO;
let mut y = indicator.row as f32 * line_height - scroll_top;
// Center indicator.
x += (layout.gutter_dimensions.margin + layout.gutter_dimensions.left_padding
- indicator_size.width)
/ 2.;
y += (line_height - indicator_size.height) / 2.;
button.draw(bounds.origin + point(x, y), available_space, cx);
}
for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
if let Some(fold_indicator) = fold_indicator {
debug_assert!(gutter_settings.folds);
let mut fold_indicator = fold_indicator.into_any_element();
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(line_height * 0.55),
);
let fold_indicator_size = fold_indicator.measure(available_space, cx);
let position = point(
bounds.size.width - layout.gutter_dimensions.right_padding,
ix as f32 * line_height - (scroll_top % line_height),
);
let centering_offset = point(
(layout.gutter_dimensions.right_padding + layout.gutter_dimensions.margin
- fold_indicator_size.width)
/ 2.,
(line_height - fold_indicator_size.height) / 2.,
);
let origin = bounds.origin + position + centering_offset;
fold_indicator.draw(origin, available_space, cx);
}
}
for (ix, line) in layout.line_numbers.iter().enumerate() {
if let Some(line) = line {
let line_origin = bounds.origin
@ -738,53 +783,6 @@ impl EditorElement {
line.paint(line_origin, line_height, cx).log_err();
}
}
cx.with_z_index(1, |cx| {
for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
if let Some(fold_indicator) = fold_indicator {
debug_assert!(gutter_settings.folds);
let mut fold_indicator = fold_indicator.into_any_element();
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(line_height * 0.55),
);
let fold_indicator_size = fold_indicator.measure(available_space, cx);
let position = point(
bounds.size.width - layout.gutter_dimensions.right_padding,
ix as f32 * line_height - (scroll_top % line_height),
);
let centering_offset = point(
(layout.gutter_dimensions.right_padding + layout.gutter_dimensions.margin
- fold_indicator_size.width)
/ 2.,
(line_height - fold_indicator_size.height) / 2.,
);
let origin = bounds.origin + position + centering_offset;
fold_indicator.draw(origin, available_space, cx);
}
}
if let Some(indicator) = layout.code_actions_indicator.take() {
debug_assert!(gutter_settings.code_actions);
let mut button = indicator.button.into_any_element();
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(line_height),
);
let indicator_size = button.measure(available_space, cx);
let mut x = Pixels::ZERO;
let mut y = indicator.row as f32 * line_height - scroll_top;
// Center indicator.
x += (layout.gutter_dimensions.margin + layout.gutter_dimensions.left_padding
- indicator_size.width)
/ 2.;
y += (line_height - indicator_size.height) / 2.;
button.draw(bounds.origin + point(x, y), available_space, cx);
}
});
}
fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut ElementContext) {
@ -928,6 +926,153 @@ impl EditorElement {
}
}
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
for (participant_ix, (player_color, selections)) in
layout.selections.iter().enumerate()
{
for selection in selections {
if selection.is_local && !selection.range.is_empty() {
invisible_display_ranges.push(selection.range.clone());
}
if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
let cursor_position = selection.head;
if layout
.visible_display_row_range
.contains(&cursor_position.row())
{
let cursor_row_layout = &layout.position_map.line_layouts
[(cursor_position.row() - start_row) as usize]
.line;
let cursor_column = cursor_position.column() as usize;
let cursor_character_x =
cursor_row_layout.x_for_index(cursor_column);
let mut block_width = cursor_row_layout
.x_for_index(cursor_column + 1)
- cursor_character_x;
if block_width == Pixels::ZERO {
block_width = layout.position_map.em_width;
}
let block_text = if let CursorShape::Block = selection.cursor_shape
{
layout
.position_map
.snapshot
.chars_at(cursor_position)
.next()
.and_then(|(character, _)| {
let text = if character == '\n' {
SharedString::from(" ")
} else {
SharedString::from(character.to_string())
};
let len = text.len();
cx.text_system()
.shape_line(
text,
cursor_row_layout.font_size,
&[TextRun {
len,
font: self.style.text.font(),
color: self.style.background,
background_color: None,
strikethrough: None,
underline: None,
}],
)
.log_err()
})
} else {
None
};
let x = cursor_character_x - layout.position_map.scroll_position.x;
let y = cursor_position.row() as f32
* layout.position_map.line_height
- layout.position_map.scroll_position.y;
if selection.is_newest {
self.editor.update(cx, |editor, _| {
editor.pixel_position_of_newest_cursor = Some(point(
text_bounds.origin.x + x + block_width / 2.,
text_bounds.origin.y
+ y
+ layout.position_map.line_height / 2.,
))
});
}
let cursor = Cursor {
color: player_color.cursor,
block_width,
origin: point(x, y),
line_height: layout.position_map.line_height,
shape: selection.cursor_shape,
block_text,
cursor_name: selection.user_name.clone().map(|name| {
CursorName {
string: name,
color: self.style.background,
is_top_row: cursor_position.row() == 0,
z_index: (participant_ix % 256).try_into().unwrap(),
}
}),
};
cursor.paint(content_origin, cx);
}
}
}
}
self.paint_redactions(text_bounds, &layout, cx);
for (ix, line_with_invisibles) in
layout.position_map.line_layouts.iter().enumerate()
{
let row = start_row + ix as u32;
line_with_invisibles.draw(
layout,
row,
content_origin,
whitespace_setting,
&invisible_display_ranges,
cx,
)
}
let corner_radius = 0.15 * layout.position_map.line_height;
for (player_color, selections) in &layout.selections {
for selection in selections.into_iter() {
self.paint_highlighted_range(
selection.range.clone(),
player_color.selection,
corner_radius,
corner_radius * 2.,
layout,
content_origin,
text_bounds,
cx,
);
if selection.is_local && !selection.range.is_empty() {
invisible_display_ranges.push(selection.range.clone());
}
}
}
for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range(
range.clone(),
*color,
Pixels::ZERO,
line_end_overshoot,
layout,
content_origin,
text_bounds,
cx,
);
}
let fold_corner_radius = 0.15 * layout.position_map.line_height;
cx.with_element_id(Some("folds"), |cx| {
let snapshot = &layout.position_map.snapshot;
@ -1008,152 +1153,6 @@ impl EditorElement {
);
}
});
for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range(
range.clone(),
*color,
Pixels::ZERO,
line_end_overshoot,
layout,
content_origin,
text_bounds,
cx,
);
}
let mut cursors = SmallVec::<[Cursor; 32]>::new();
let corner_radius = 0.15 * layout.position_map.line_height;
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
for (participant_ix, (player_color, selections)) in
layout.selections.iter().enumerate()
{
for selection in selections.into_iter() {
self.paint_highlighted_range(
selection.range.clone(),
player_color.selection,
corner_radius,
corner_radius * 2.,
layout,
content_origin,
text_bounds,
cx,
);
if selection.is_local && !selection.range.is_empty() {
invisible_display_ranges.push(selection.range.clone());
}
if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
let cursor_position = selection.head;
if layout
.visible_display_row_range
.contains(&cursor_position.row())
{
let cursor_row_layout = &layout.position_map.line_layouts
[(cursor_position.row() - start_row) as usize]
.line;
let cursor_column = cursor_position.column() as usize;
let cursor_character_x =
cursor_row_layout.x_for_index(cursor_column);
let mut block_width = cursor_row_layout
.x_for_index(cursor_column + 1)
- cursor_character_x;
if block_width == Pixels::ZERO {
block_width = layout.position_map.em_width;
}
let block_text = if let CursorShape::Block = selection.cursor_shape
{
layout
.position_map
.snapshot
.chars_at(cursor_position)
.next()
.and_then(|(character, _)| {
let text = if character == '\n' {
SharedString::from(" ")
} else {
SharedString::from(character.to_string())
};
let len = text.len();
cx.text_system()
.shape_line(
text,
cursor_row_layout.font_size,
&[TextRun {
len,
font: self.style.text.font(),
color: self.style.background,
background_color: None,
strikethrough: None,
underline: None,
}],
)
.log_err()
})
} else {
None
};
let x = cursor_character_x - layout.position_map.scroll_position.x;
let y = cursor_position.row() as f32
* layout.position_map.line_height
- layout.position_map.scroll_position.y;
if selection.is_newest {
self.editor.update(cx, |editor, _| {
editor.pixel_position_of_newest_cursor = Some(point(
text_bounds.origin.x + x + block_width / 2.,
text_bounds.origin.y
+ y
+ layout.position_map.line_height / 2.,
))
});
}
cursors.push(Cursor {
color: player_color.cursor,
block_width,
origin: point(x, y),
line_height: layout.position_map.line_height,
shape: selection.cursor_shape,
block_text,
cursor_name: selection.user_name.clone().map(|name| {
CursorName {
string: name,
color: self.style.background,
is_top_row: cursor_position.row() == 0,
z_index: (participant_ix % 256).try_into().unwrap(),
}
}),
});
}
}
}
}
for (ix, line_with_invisibles) in
layout.position_map.line_layouts.iter().enumerate()
{
let row = start_row + ix as u32;
line_with_invisibles.draw(
layout,
row,
content_origin,
whitespace_setting,
&invisible_display_ranges,
cx,
)
}
cx.with_z_index(0, |cx| self.paint_redactions(text_bounds, &layout, cx));
cx.with_z_index(1, |cx| {
for cursor in cursors {
cursor.paint(content_origin, cx);
}
});
},
)
}
@ -1346,132 +1345,20 @@ impl EditorElement {
let thumb_bounds = Bounds::from_corners(point(left, thumb_top), point(right, thumb_bottom));
if layout.show_scrollbars {
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
cx.paint_quad(quad(
track_bounds,
thumb_bounds,
Corners::default(),
cx.theme().colors().scrollbar_track_background,
cx.theme().colors().scrollbar_thumb_background,
Edges {
top: Pixels::ZERO,
right: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_track_border,
cx.theme().colors().scrollbar_thumb_border,
));
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min();
let end_anchor = Anchor::max();
let background_ranges = self
.editor
.read(cx)
.background_highlight_row_ranges::<BufferSearchHighlights>(
start_anchor..end_anchor,
&layout.position_map.snapshot,
50000,
);
for range in background_ranges {
let start_y = y_for_row(range.start().row() as f32);
let mut end_y = y_for_row(range.end().row() as f32);
if end_y - start_y < px(1.) {
end_y = start_y + px(1.);
}
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
cx.paint_quad(quad(
bounds,
Corners::default(),
cx.theme().status().info,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
}
}
if layout.is_singleton && scrollbar_settings.symbols_selections {
let selection_ranges = self.editor.read(cx).background_highlights_in_range(
Anchor::min()..Anchor::max(),
&layout.position_map.snapshot,
cx.theme().colors(),
);
for hunk in selection_ranges {
let start_display = Point::new(hunk.0.start.row(), 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.0.end.row(), 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_y = y_for_row(start_display.row() as f32);
let mut end_y = if hunk.0.start == hunk.0.end {
y_for_row((end_display.row() + 1) as f32)
} else {
y_for_row((end_display.row()) as f32)
};
if end_y - start_y < px(1.) {
end_y = start_y + px(1.);
}
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
cx.paint_quad(quad(
bounds,
Corners::default(),
cx.theme().status().info,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
}
}
if layout.is_singleton && scrollbar_settings.git_diff {
for hunk in layout
.position_map
.snapshot
.buffer_snapshot
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
{
let start_display = Point::new(hunk.buffer_range.start, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.buffer_range.end, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_y = y_for_row(start_display.row() as f32);
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
y_for_row((end_display.row() + 1) as f32)
} else {
y_for_row((end_display.row()) as f32)
};
if end_y - start_y < px(1.) {
end_y = start_y + px(1.);
}
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
let color = match hunk.status() {
DiffHunkStatus::Added => cx.theme().status().created,
DiffHunkStatus::Modified => cx.theme().status().modified,
DiffHunkStatus::Removed => cx.theme().status().deleted,
};
cx.paint_quad(quad(
bounds,
Corners::default(),
color,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
}
}
if layout.is_singleton && scrollbar_settings.diagnostics {
let max_point = layout
@ -1531,17 +1418,131 @@ impl EditorElement {
}
}
if layout.is_singleton && scrollbar_settings.git_diff {
for hunk in layout
.position_map
.snapshot
.buffer_snapshot
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
{
let start_display = Point::new(hunk.buffer_range.start, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.buffer_range.end, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_y = y_for_row(start_display.row() as f32);
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
y_for_row((end_display.row() + 1) as f32)
} else {
y_for_row((end_display.row()) as f32)
};
if end_y - start_y < px(1.) {
end_y = start_y + px(1.);
}
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
let color = match hunk.status() {
DiffHunkStatus::Added => cx.theme().status().created,
DiffHunkStatus::Modified => cx.theme().status().modified,
DiffHunkStatus::Removed => cx.theme().status().deleted,
};
cx.paint_quad(quad(
bounds,
Corners::default(),
color,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
}
}
if layout.is_singleton && scrollbar_settings.symbols_selections {
let selection_ranges = self.editor.read(cx).background_highlights_in_range(
Anchor::min()..Anchor::max(),
&layout.position_map.snapshot,
cx.theme().colors(),
);
for hunk in selection_ranges {
let start_display = Point::new(hunk.0.start.row(), 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.0.end.row(), 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_y = y_for_row(start_display.row() as f32);
let mut end_y = if hunk.0.start == hunk.0.end {
y_for_row((end_display.row() + 1) as f32)
} else {
y_for_row((end_display.row()) as f32)
};
if end_y - start_y < px(1.) {
end_y = start_y + px(1.);
}
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
cx.paint_quad(quad(
bounds,
Corners::default(),
cx.theme().status().info,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
}
}
if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min();
let end_anchor = Anchor::max();
let background_ranges = self
.editor
.read(cx)
.background_highlight_row_ranges::<BufferSearchHighlights>(
start_anchor..end_anchor,
&layout.position_map.snapshot,
50000,
);
for range in background_ranges {
let start_y = y_for_row(range.start().row() as f32);
let mut end_y = y_for_row(range.end().row() as f32);
if end_y - start_y < px(1.) {
end_y = start_y + px(1.);
}
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
cx.paint_quad(quad(
bounds,
Corners::default(),
cx.theme().status().info,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
}
}
cx.paint_quad(quad(
thumb_bounds,
track_bounds,
Corners::default(),
cx.theme().colors().scrollbar_thumb_background,
cx.theme().colors().scrollbar_track_background,
Edges {
top: Pixels::ZERO,
right: px(1.),
right: Pixels::ZERO,
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
cx.theme().colors().scrollbar_track_border,
));
}
@ -3126,34 +3127,25 @@ impl Element for EditorElement {
ElementInputHandler::new(bounds, self.editor.clone()),
);
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
self.paint_scrollbar(bounds, &mut layout, cx);
self.paint_overlays(text_bounds, &mut layout, cx);
if !layout.blocks.is_empty() {
cx.with_element_id(Some("editor_blocks"), |cx| {
self.paint_blocks(bounds, &mut layout, cx);
});
}
self.paint_mouse_listeners(
bounds,
gutter_bounds,
text_bounds,
&layout,
cx,
);
self.paint_text(text_bounds, &mut layout, cx);
if layout.gutter_size.width > Pixels::ZERO {
self.paint_gutter(gutter_bounds, &mut layout, cx);
}
self.paint_text(text_bounds, &mut layout, cx);
cx.with_z_index(0, |cx| {
self.paint_mouse_listeners(
bounds,
gutter_bounds,
text_bounds,
&layout,
cx,
);
});
if !layout.blocks.is_empty() {
cx.with_z_index(0, |cx| {
cx.with_element_id(Some("editor_blocks"), |cx| {
self.paint_blocks(bounds, &mut layout, cx);
});
})
}
cx.with_z_index(1, |cx| {
self.paint_overlays(text_bounds, &mut layout, cx);
});
cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx));
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
});
})
},

View file

@ -0,0 +1,435 @@
use crate::{Bounds, Half};
use std::{
cmp,
fmt::Debug,
ops::{Add, Sub},
};
#[derive(Debug, Default)]
pub struct BoundsTree<U: Default + Clone + Debug> {
root: Option<usize>,
nodes: Vec<Node<U>>,
stack: Vec<usize>,
}
impl<U> BoundsTree<U>
where
U: Clone + Debug + PartialOrd + Add<U, Output = U> + Sub<Output = U> + Half + Default,
{
pub fn new() -> Self {
BoundsTree::default()
}
pub fn clear(&mut self) {
self.root = None;
self.nodes.clear();
self.stack.clear();
}
pub fn insert(&mut self, new_bounds: Bounds<U>) -> u32 {
// If the tree is empty, make the root the new leaf.
if self.root.is_none() {
let new_node = self.push_leaf(new_bounds, 1);
self.root = Some(new_node);
return 1;
}
// Search for the best place to add the new leaf based on heuristics.
let mut max_intersecting_ordering = 0;
let mut index = self.root.unwrap();
while let Node::Internal {
left,
right,
bounds: node_bounds,
..
} = self.node_mut(index)
{
let left = *left;
let right = *right;
*node_bounds = node_bounds.union(&new_bounds);
self.stack.push(index);
// Descend to the best-fit child, based on which one would increase
// the surface area the least. This attempts to keep the tree balanced
// in terms of surface area. If there is an intersection with the other child,
// add its keys to the intersections vector.
let left_cost = new_bounds.union(self.node(left).bounds()).half_perimeter();
let right_cost = new_bounds.union(self.node(right).bounds()).half_perimeter();
if left_cost < right_cost {
max_intersecting_ordering =
self.find_max_ordering(right, &new_bounds, max_intersecting_ordering);
index = left;
} else {
max_intersecting_ordering =
self.find_max_ordering(left, &new_bounds, max_intersecting_ordering);
index = right;
}
}
// We've found a leaf ('index' now refers to a leaf node).
// We'll insert a new parent node above the leaf and attach our new leaf to it.
let sibling = index;
// Check for collision with the located leaf node
let Node::Leaf {
bounds: sibling_bounds,
order: sibling_ordering,
..
} = self.node(index)
else {
unreachable!();
};
if sibling_bounds.intersects(&new_bounds) {
max_intersecting_ordering = cmp::max(max_intersecting_ordering, *sibling_ordering);
}
let ordering = max_intersecting_ordering + 1;
let new_node = self.push_leaf(new_bounds, ordering);
let new_parent = self.push_internal(sibling, new_node);
// If there was an old parent, we need to update its children indices.
if let Some(old_parent) = self.stack.last().copied() {
let Node::Internal { left, right, .. } = self.node_mut(old_parent) else {
unreachable!();
};
if *left == sibling {
*left = new_parent;
} else {
*right = new_parent;
}
} else {
// If the old parent was the root, the new parent is the new root.
self.root = Some(new_parent);
}
for node_index in self.stack.drain(..) {
let Node::Internal { max_ordering, .. } = &mut self.nodes[node_index] else {
unreachable!()
};
*max_ordering = cmp::max(*max_ordering, ordering);
}
ordering
}
fn find_max_ordering(&self, index: usize, bounds: &Bounds<U>, mut max_ordering: u32) -> u32 {
match self.node(index) {
Node::Leaf {
bounds: node_bounds,
order: ordering,
..
} => {
if bounds.intersects(node_bounds) {
max_ordering = cmp::max(*ordering, max_ordering);
}
}
Node::Internal {
left,
right,
bounds: node_bounds,
max_ordering: node_max_ordering,
..
} => {
if bounds.intersects(node_bounds) && max_ordering < *node_max_ordering {
let left_max_ordering = self.node(*left).max_ordering();
let right_max_ordering = self.node(*right).max_ordering();
if left_max_ordering > right_max_ordering {
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
} else {
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
}
}
}
}
max_ordering
}
fn push_leaf(&mut self, bounds: Bounds<U>, order: u32) -> usize {
self.nodes.push(Node::Leaf { bounds, order });
self.nodes.len() - 1
}
fn push_internal(&mut self, left: usize, right: usize) -> usize {
let left_node = self.node(left);
let right_node = self.node(right);
let new_bounds = left_node.bounds().union(right_node.bounds());
let max_ordering = cmp::max(left_node.max_ordering(), right_node.max_ordering());
self.nodes.push(Node::Internal {
bounds: new_bounds,
left,
right,
max_ordering,
});
self.nodes.len() - 1
}
#[inline(always)]
fn node(&self, index: usize) -> &Node<U> {
&self.nodes[index]
}
#[inline(always)]
fn node_mut(&mut self, index: usize) -> &mut Node<U> {
&mut self.nodes[index]
}
}
#[derive(Default, Debug, Clone, PartialEq)]
pub struct Primitive<U: Clone + Default + Debug> {
bounds: Bounds<U>,
order: u32,
}
#[derive(Debug)]
enum Node<U: Clone + Default + Debug> {
Leaf {
bounds: Bounds<U>,
order: u32,
},
Internal {
left: usize,
right: usize,
bounds: Bounds<U>,
max_ordering: u32,
},
}
impl<U> Node<U>
where
U: Clone + Default + Debug,
{
fn bounds(&self) -> &Bounds<U> {
match self {
Node::Leaf { bounds, .. } => bounds,
Node::Internal { bounds, .. } => bounds,
}
}
fn max_ordering(&self) -> u32 {
match self {
Node::Leaf {
order: ordering, ..
} => *ordering,
Node::Internal { max_ordering, .. } => *max_ordering,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Point, Size};
use rand::{Rng, SeedableRng};
use std::{fs, path::Path};
#[test]
fn test_bounds_insertion_with_two_bounds() {
let mut tree = BoundsTree::new();
let bounds1 = Bounds {
origin: Point { x: 0.0, y: 0.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
let bounds2 = Bounds {
origin: Point { x: 5.0, y: 5.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
// Insert the first Bounds.
assert_eq!(tree.insert(bounds1), 1);
// Insert the second Bounds, which overlaps with the first.
assert_eq!(tree.insert(bounds2), 2);
}
#[test]
fn test_adjacent_bounds() {
let mut tree = BoundsTree::new();
let bounds1 = Bounds {
origin: Point { x: 0.0, y: 0.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
let bounds2 = Bounds {
origin: Point { x: 10.0, y: 0.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
// Insert the first bounds.
assert_eq!(tree.insert(bounds1), 1);
// Insert the second bounds, which is adjacent to the first but not overlapping.
assert_eq!(tree.insert(bounds2), 1);
}
#[test]
fn test_random_iterations() {
let max_bounds = 100;
let mut actual_intersections: Vec<usize> = Vec::new();
for seed in 1..=1000 {
// let seed = 44;
let debug = false;
if debug {
let svg_path = Path::new("./svg");
if svg_path.exists() {
fs::remove_dir_all("./svg").unwrap();
}
fs::create_dir_all("./svg").unwrap();
}
dbg!(seed);
let mut tree = BoundsTree::new();
let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64);
let mut expected_quads: Vec<Primitive<f32>> = Vec::new();
let mut insert_time = std::time::Duration::ZERO;
// Insert a random number of random Bounds into the tree.
let num_bounds = rng.gen_range(1..=max_bounds);
for quad_id in 0..num_bounds {
let min_x: f32 = rng.gen_range(-100.0..100.0);
let min_y: f32 = rng.gen_range(-100.0..100.0);
let max_x: f32 = rng.gen_range(min_x..min_x + 50.0);
let max_y: f32 = rng.gen_range(min_y..min_y + 50.0);
let bounds = Bounds {
origin: Point { x: min_x, y: min_y },
size: Size {
width: max_x - min_x,
height: max_y - min_y,
},
};
let expected_ordering = expected_quads
.iter()
.filter_map(|quad| {
(quad.bounds.origin.x < bounds.origin.x + bounds.size.width
&& quad.bounds.origin.x + quad.bounds.size.width > bounds.origin.x
&& quad.bounds.origin.y < bounds.origin.y + bounds.size.height
&& quad.bounds.origin.y + quad.bounds.size.height > bounds.origin.y)
.then_some(quad.order)
})
.max()
.unwrap_or(0)
+ 1;
expected_quads.push(Primitive {
bounds,
order: expected_ordering,
});
if debug {
println!("inserting {} with Bounds: {:?}", quad_id, bounds);
draw_bounds(
format!("./svg/expected_bounds_after_{}.svg", quad_id),
&expected_quads,
);
}
// Insert the Bounds into the tree and collect intersections.
actual_intersections.clear();
let t0 = std::time::Instant::now();
let actual_ordering = tree.insert(bounds);
insert_time += t0.elapsed();
assert_eq!(actual_ordering, expected_ordering);
if debug {
tree.draw(format!("./svg/bounds_tree_after_{}.svg", quad_id));
}
}
}
}
fn draw_bounds(svg_path: impl AsRef<Path>, bounds: &[Primitive<f32>]) {
let mut svg_content = String::from(
r#"<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-100 -100 200 200" style="border:1px solid black;">"#,
);
for quad in bounds {
svg_content.push_str(&format!(
r#"<rect x="{}" y="{}" width="{}" height="{}" style="fill:none;stroke:black;stroke-width:1" />"#,
quad.bounds.origin.x,
quad.bounds.origin.y,
quad.bounds.size.width,
quad.bounds.size.height
));
svg_content.push_str(&format!(
r#"<text x="{}" y="{}" font-size="3" text-anchor="middle" alignment-baseline="central"></text>"#,
quad.bounds.origin.x + quad.bounds.size.width / 2.0,
quad.bounds.origin.y + quad.bounds.size.height / 2.0,
));
}
svg_content.push_str("</svg>");
fs::write(svg_path, &svg_content).unwrap();
}
impl BoundsTree<f32> {
fn draw(&self, svg_path: impl AsRef<std::path::Path>) {
let root_bounds = self.node(self.root.unwrap()).bounds();
let mut svg_content = format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="border:1px solid black;" viewBox="{} {} {} {}">"#,
root_bounds.origin.x,
root_bounds.origin.y,
root_bounds.size.width,
root_bounds.size.height
);
fn draw_node(svg_content: &mut String, nodes: &[Node<f32>], index: usize) {
match &nodes[index] {
Node::Internal {
bounds,
left,
right,
..
} => {
svg_content.push_str(&format!(
r#"<rect x="{}" y="{}" width="{}" height="{}" style="fill:rgba({},{},{},0.1);stroke:rgba({},{},{},1);stroke-width:1" />"#,
bounds.origin.x,
bounds.origin.y,
bounds.size.width,
bounds.size.height,
(index * 50) % 255, // Red component
(index * 120) % 255, // Green component
(index * 180) % 255, // Blue component
(index * 50) % 255, // Red stroke
(index * 120) % 255, // Green stroke
(index * 180) % 255 // Blue stroke
));
draw_node(svg_content, nodes, *left);
draw_node(svg_content, nodes, *right);
}
Node::Leaf { bounds, .. } => {
svg_content.push_str(&format!(
r#"<rect x="{}" y="{}" width="{}" height="{}" style="fill:none;stroke:black;stroke-width:1" />"#,
bounds.origin.x,
bounds.origin.y,
bounds.size.width,
bounds.size.height
));
}
}
}
if let Some(root) = self.root {
draw_node(&mut svg_content, &self.nodes, root);
}
svg_content.push_str("</svg>");
std::fs::write(svg_path, &svg_content).unwrap();
}
}
}

View file

@ -1127,7 +1127,7 @@ impl Element for Div {
cx,
|_style, scroll_offset, cx| {
cx.with_element_offset(scroll_offset, |cx| {
for child in &mut self.children {
for child in self.children.iter_mut().rev() {
child.paint(cx);
}
})

View file

@ -828,6 +828,28 @@ where
y: self.origin.y.clone() + self.size.height.clone().half(),
}
}
/// Calculates the half perimeter of a rectangle defined by the bounds.
///
/// The half perimeter is calculated as the sum of the width and the height of the rectangle.
/// This method is generic over the type `T` which must implement the `Sub` trait to allow
/// calculation of the width and height from the bounds' origin and size, as well as the `Add` trait
/// to sum the width and height for the half perimeter.
///
/// # Examples
///
/// ```
/// # use zed::{Bounds, Point, Size};
/// let bounds = Bounds {
/// origin: Point { x: 0, y: 0 },
/// size: Size { width: 10, height: 20 },
/// };
/// let half_perimeter = bounds.half_perimeter();
/// assert_eq!(half_perimeter, 30);
/// ```
pub fn half_perimeter(&self) -> T {
self.size.width.clone() + self.size.height.clone()
}
}
impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {

View file

@ -70,6 +70,7 @@ mod app;
mod arena;
mod assets;
mod bounds_tree;
mod color;
mod element;
mod elements;
@ -117,6 +118,7 @@ pub use anyhow::Result;
pub use app::*;
pub(crate) use arena::*;
pub use assets::*;
pub(crate) use bounds_tree::*;
pub use color::*;
pub use ctor::ctor;
pub use element::*;

View file

@ -1,6 +1,6 @@
use crate::{
point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels,
Point, ScaledPixels, StackingOrder,
point, AtlasTextureId, AtlasTile, Bounds, BoundsTree, ContentMask, Corners, Edges, EntityId,
Hsla, Pixels, Point, ScaledPixels, StackingOrder,
};
use collections::{BTreeMap, FxHashSet};
use std::{fmt::Debug, iter::Peekable, slice};
@ -37,9 +37,6 @@ impl From<ViewId> for EntityId {
#[derive(Default)]
pub(crate) struct Scene {
last_layer: Option<(StackingOrder, LayerId)>,
layers_by_order: BTreeMap<StackingOrder, LayerId>,
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
pub(crate) shadows: Vec<Shadow>,
pub(crate) quads: Vec<Quad>,
pub(crate) paths: Vec<Path<ScaledPixels>>,
@ -47,13 +44,11 @@ pub(crate) struct Scene {
pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
pub(crate) surfaces: Vec<Surface>,
bounds_tree: BoundsTree<ScaledPixels>,
}
impl Scene {
pub fn clear(&mut self) {
self.last_layer = None;
self.layers_by_order.clear();
self.orders_by_layer.clear();
self.shadows.clear();
self.quads.clear();
self.paths.clear();
@ -61,6 +56,7 @@ impl Scene {
self.monochrome_sprites.clear();
self.polychrome_sprites.clear();
self.surfaces.clear();
self.bounds_tree.clear();
}
pub fn paths(&self) -> &[Path<ScaledPixels>] {
@ -93,7 +89,11 @@ impl Scene {
}
}
pub(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
pub(crate) fn insert(
&mut self,
order: &StackingOrder,
primitive: impl Into<Primitive>,
) -> Option<u32> {
let primitive = primitive.into();
let clipped_bounds = primitive
.bounds()
@ -101,153 +101,108 @@ impl Scene {
if clipped_bounds.size.width <= ScaledPixels(0.)
|| clipped_bounds.size.height <= ScaledPixels(0.)
{
return;
return None;
}
let layer_id = self.layer_id_for_order(order);
let order = u32::MAX - self.bounds_tree.insert(clipped_bounds);
match primitive {
Primitive::Shadow(mut shadow) => {
shadow.layer_id = layer_id;
shadow.order = order;
self.shadows.push(shadow);
}
Primitive::Quad(mut quad) => {
quad.layer_id = layer_id;
quad.order = order;
self.quads.push(quad);
}
Primitive::Path(mut path) => {
path.layer_id = layer_id;
path.order = order;
path.id = PathId(self.paths.len());
self.paths.push(path);
}
Primitive::Underline(mut underline) => {
underline.layer_id = layer_id;
underline.order = order;
self.underlines.push(underline);
}
Primitive::MonochromeSprite(mut sprite) => {
sprite.layer_id = layer_id;
sprite.order = order;
self.monochrome_sprites.push(sprite);
}
Primitive::PolychromeSprite(mut sprite) => {
sprite.layer_id = layer_id;
sprite.order = order;
self.polychrome_sprites.push(sprite);
}
Primitive::Surface(mut surface) => {
surface.layer_id = layer_id;
surface.order = order;
self.surfaces.push(surface);
}
}
}
fn layer_id_for_order(&mut self, order: &StackingOrder) -> LayerId {
if let Some((last_order, last_layer_id)) = self.last_layer.as_ref() {
if order == last_order {
return *last_layer_id;
}
}
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
*layer_id
} else {
let next_id = self.layers_by_order.len() as LayerId;
self.layers_by_order.insert(order.clone(), next_id);
self.orders_by_layer.insert(next_id, order.clone());
next_id
};
self.last_layer = Some((order.clone(), layer_id));
layer_id
Some(order)
}
pub fn reuse_views(&mut self, views: &FxHashSet<EntityId>, prev_scene: &mut Self) {
for shadow in prev_scene.shadows.drain(..) {
if views.contains(&shadow.view_id.into()) {
let order = &prev_scene.orders_by_layer[&shadow.layer_id];
self.insert(order, shadow);
}
}
// todo!()
// for shadow in prev_scene.shadows.drain(..) {
// if views.contains(&shadow.view_id.into()) {
// let order = &prev_scene.orders_by_layer[&shadow.layer_id];
// self.insert(order, shadow);
// }
// }
for quad in prev_scene.quads.drain(..) {
if views.contains(&quad.view_id.into()) {
let order = &prev_scene.orders_by_layer[&quad.layer_id];
self.insert(order, quad);
}
}
// for quad in prev_scene.quads.drain(..) {
// if views.contains(&quad.view_id.into()) {
// let order = &prev_scene.orders_by_layer[&quad.layer_id];
// self.insert(order, quad);
// }
// }
for path in prev_scene.paths.drain(..) {
if views.contains(&path.view_id.into()) {
let order = &prev_scene.orders_by_layer[&path.layer_id];
self.insert(order, path);
}
}
// for path in prev_scene.paths.drain(..) {
// if views.contains(&path.view_id.into()) {
// let order = &prev_scene.orders_by_layer[&path.layer_id];
// self.insert(order, path);
// }
// }
for underline in prev_scene.underlines.drain(..) {
if views.contains(&underline.view_id.into()) {
let order = &prev_scene.orders_by_layer[&underline.layer_id];
self.insert(order, underline);
}
}
// for underline in prev_scene.underlines.drain(..) {
// if views.contains(&underline.view_id.into()) {
// let order = &prev_scene.orders_by_layer[&underline.layer_id];
// self.insert(order, underline);
// }
// }
for sprite in prev_scene.monochrome_sprites.drain(..) {
if views.contains(&sprite.view_id.into()) {
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
self.insert(order, sprite);
}
}
// for sprite in prev_scene.monochrome_sprites.drain(..) {
// if views.contains(&sprite.view_id.into()) {
// let order = &prev_scene.orders_by_layer[&sprite.layer_id];
// self.insert(order, sprite);
// }
// }
for sprite in prev_scene.polychrome_sprites.drain(..) {
if views.contains(&sprite.view_id.into()) {
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
self.insert(order, sprite);
}
}
// for sprite in prev_scene.polychrome_sprites.drain(..) {
// if views.contains(&sprite.view_id.into()) {
// let order = &prev_scene.orders_by_layer[&sprite.layer_id];
// self.insert(order, sprite);
// }
// }
for surface in prev_scene.surfaces.drain(..) {
if views.contains(&surface.view_id.into()) {
let order = &prev_scene.orders_by_layer[&surface.layer_id];
self.insert(order, surface);
}
}
// for surface in prev_scene.surfaces.drain(..) {
// if views.contains(&surface.view_id.into()) {
// let order = &prev_scene.orders_by_layer[&surface.layer_id];
// self.insert(order, surface);
// }
// }
}
pub fn finish(&mut self) {
let mut orders = vec![0; self.layers_by_order.len()];
for (ix, layer_id) in self.layers_by_order.values().enumerate() {
orders[*layer_id as usize] = ix as u32;
}
for shadow in &mut self.shadows {
shadow.order = orders[shadow.layer_id as usize];
}
self.shadows.sort_by_key(|shadow| shadow.order);
for quad in &mut self.quads {
quad.order = orders[quad.layer_id as usize];
}
self.quads.sort_by_key(|quad| quad.order);
for path in &mut self.paths {
path.order = orders[path.layer_id as usize];
}
self.paths.sort_by_key(|path| path.order);
for underline in &mut self.underlines {
underline.order = orders[underline.layer_id as usize];
}
self.underlines.sort_by_key(|underline| underline.order);
for monochrome_sprite in &mut self.monochrome_sprites {
monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize];
}
self.monochrome_sprites.sort_by_key(|sprite| sprite.order);
for polychrome_sprite in &mut self.polychrome_sprites {
polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize];
}
self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
for surface in &mut self.surfaces {
surface.order = orders[surface.layer_id as usize];
}
self.surfaces.sort_by_key(|surface| surface.order);
self.shadows.sort_unstable_by_key(|shadow| shadow.order);
self.quads.sort_unstable_by_key(|quad| quad.order);
self.paths.sort_unstable_by_key(|path| path.order);
self.underlines
.sort_unstable_by_key(|underline| underline.order);
self.monochrome_sprites
.sort_unstable_by_key(|sprite| sprite.order);
self.polychrome_sprites
.sort_unstable_by_key(|sprite| sprite.order);
self.surfaces.sort_unstable_by_key(|surface| surface.order);
}
}

View file

@ -402,98 +402,88 @@ impl Style {
let rem_size = cx.rem_size();
cx.with_z_index(0, |cx| {
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
&self.box_shadow,
if self.is_border_visible() {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
let max_corner_radius = corner_radii.max();
let top_bounds = Bounds::from_corners(
bounds.origin,
bounds.upper_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
);
});
let bottom_bounds = Bounds::from_corners(
bounds.lower_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
bounds.lower_right(),
);
let left_bounds = Bounds::from_corners(
top_bounds.lower_left(),
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
);
let right_bounds = Bounds::from_corners(
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
bottom_bounds.upper_right(),
);
let mut background = self.border_color.unwrap_or_default();
background.a = 0.;
let quad = quad(
bounds,
corner_radii,
background,
border_widths,
self.border_color.unwrap_or_default(),
);
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(quad.clone());
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(quad);
},
);
}
continuation(cx);
let background_color = self.background.as_ref().and_then(Fill::color);
if background_color.map_or(false, |color| !color.is_transparent()) {
cx.with_z_index(1, |cx| {
let mut border_color = background_color.unwrap_or_default();
border_color.a = 0.;
cx.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
Edges::default(),
border_color,
));
});
let mut border_color = background_color.unwrap_or_default();
border_color.a = 0.;
cx.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
Edges::default(),
border_color,
));
}
cx.with_z_index(2, |cx| {
continuation(cx);
});
if self.is_border_visible() {
cx.with_z_index(3, |cx| {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
let max_corner_radius = corner_radii.max();
let top_bounds = Bounds::from_corners(
bounds.origin,
bounds.upper_right()
+ point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
);
let bottom_bounds = Bounds::from_corners(
bounds.lower_left()
- point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
bounds.lower_right(),
);
let left_bounds = Bounds::from_corners(
top_bounds.lower_left(),
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
);
let right_bounds = Bounds::from_corners(
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
bottom_bounds.upper_right(),
);
let mut background = self.border_color.unwrap_or_default();
background.a = 0.;
let quad = quad(
bounds,
corner_radii,
background,
border_widths,
self.border_color.unwrap_or_default(),
);
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(quad.clone());
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(quad);
},
);
});
}
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
&self.box_shadow,
);
#[cfg(debug_assertions)]
if self.debug_below {

View file

@ -212,7 +212,8 @@ impl AnyView {
/// When using this method, the view's previous layout and paint will be recycled from the previous frame if [ViewContext::notify] has not been called since it was rendered.
/// The one exception is when [WindowContext::refresh] is called, in which case caching is ignored.
pub fn cached(mut self) -> Self {
self.cache = true;
// TODO!: ENABLE ME!
// self.cache = true;
self
}