Compare commits
5 commits
main
...
bounds-tre
Author | SHA1 | Date | |
---|---|---|---|
![]() |
74ed2f7f70 | ||
![]() |
eba5db3a6e | ||
![]() |
cd6bdd8b1c | ||
![]() |
a3ce933b04 | ||
![]() |
816c48b7d6 |
9 changed files with 944 additions and 547 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
})
|
||||
},
|
||||
|
|
435
crates/gpui/src/bounds_tree.rs
Normal file
435
crates/gpui/src/bounds_tree.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue