Compare commits

..

9 Commits

Author SHA1 Message Date
Antonio Scandurra
30239b3cc6 WIP 2024-02-26 17:53:01 +01:00
Antonio Scandurra
180421fe5a WIP 2024-02-26 15:21:43 +01:00
Antonio Scandurra
1a13995b8f Add support for group hovering, still need to wire it up 2024-02-26 13:44:56 +01:00
Antonio Scandurra
b6379a9177 Fix compile errors and render hovered quads correctly 2024-02-26 11:50:28 +01:00
Nathan Sobo
2cb041504b WIP: Start passing hover variants of quad primitives in paint_quad 2024-02-25 20:35:56 -07:00
Nathan Sobo
4d8dc79d7e Implement hover detection for scene elements
- When inserting primitives, optionally specify the hover state and whether the primitive is opaque
- After scene is completed, sort the bounds of all intersecting primitives in descending order (higher is on top)
- Walk through the intersecting bounds and consult the each intersecting primitive's metadata
    - Replace primitive with its hovered variant if present
    - Stop if the primitive was inserted with occludes_hover = true
2024-02-25 19:42:20 -07:00
Nathan Sobo
474c806331 Add BoundsTree::find_containing and try to start using it 2024-02-25 15:25:00 -07:00
Nathan Sobo
2db6ccd803 WIP: Replace primitives based on hover 2024-02-24 18:31:31 -07:00
Nathan Sobo
2d6a227258 Don't take an order in Scene::insert 2024-02-24 10:56:06 -07:00
17 changed files with 2379 additions and 1862 deletions

View File

@@ -2672,24 +2672,32 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) ->
let right = bounds.right();
let top = bounds.top();
cx.paint_quad(fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
cx.paint_quad(
fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
),
),
color,
),
color,
));
cx.paint_quad(fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
));
None,
None,
);
cx.paint_quad(
fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
),
None,
None,
);
})
.w(width)
.h(line_height)

View File

@@ -632,8 +632,8 @@ impl EditorElement {
let scroll_top =
layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
let gutter_bg = cx.theme().colors().editor_gutter_background;
cx.paint_quad(fill(gutter_bounds, gutter_bg));
cx.paint_quad(fill(text_bounds, self.style.background));
cx.paint_quad(fill(gutter_bounds, gutter_bg), None, None);
cx.paint_quad(fill(text_bounds, self.style.background), None, None);
if let EditorMode::Full = layout.mode {
let mut active_rows = layout.active_rows.iter().peekable();
@@ -657,7 +657,7 @@ impl EditorElement {
layout.position_map.line_height * (end_row - start_row + 1) as f32,
);
let active_line_bg = cx.theme().colors().editor_active_line_background;
cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
cx.paint_quad(fill(Bounds { origin, size }, active_line_bg), None, None);
}
}
@@ -673,7 +673,11 @@ impl EditorElement {
layout.position_map.line_height * highlighted_rows.len() as f32,
);
let highlighted_line_bg = cx.theme().colors().editor_highlighted_line_background;
cx.paint_quad(fill(Bounds { origin, size }, highlighted_line_bg));
cx.paint_quad(
fill(Bounds { origin, size }, highlighted_line_bg),
None,
None,
);
}
let scroll_left =
@@ -694,13 +698,17 @@ impl EditorElement {
} else {
cx.theme().colors().editor_wrap_guide
};
cx.paint_quad(fill(
Bounds {
origin: point(x, text_bounds.origin.y),
size: size(px(1.), text_bounds.size.height),
},
color,
));
cx.paint_quad(
fill(
Bounds {
origin: point(x, text_bounds.origin.y),
size: size(px(1.), text_bounds.size.height),
},
color,
),
None,
None,
);
}
}
}
@@ -727,51 +735,6 @@ 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
@@ -783,6 +746,53 @@ 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) {
@@ -802,13 +812,17 @@ impl EditorElement {
let highlight_origin = bounds.origin + point(-width, start_y);
let highlight_size = size(width * 2., end_y - start_y);
let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
cx.paint_quad(quad(
highlight_bounds,
Corners::all(1. * line_height),
cx.theme().status().modified,
Edges::default(),
transparent_black(),
));
cx.paint_quad(
quad(
highlight_bounds,
Corners::all(1. * line_height),
cx.theme().status().modified,
Edges::default(),
transparent_black(),
),
None,
None,
);
continue;
}
@@ -835,13 +849,17 @@ impl EditorElement {
let highlight_origin = bounds.origin + point(-width, start_y);
let highlight_size = size(width * 2., end_y - start_y);
let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
cx.paint_quad(quad(
highlight_bounds,
Corners::all(1. * line_height),
cx.theme().status().deleted,
Edges::default(),
transparent_black(),
));
cx.paint_quad(
quad(
highlight_bounds,
Corners::all(1. * line_height),
cx.theme().status().deleted,
Edges::default(),
transparent_black(),
),
None,
None,
);
continue;
}
@@ -875,13 +893,17 @@ impl EditorElement {
let highlight_origin = bounds.origin + point(-width, start_y);
let highlight_size = size(width * 2., end_y - start_y);
let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
cx.paint_quad(quad(
highlight_bounds,
Corners::all(0.05 * line_height),
color,
Edges::default(),
transparent_black(),
));
cx.paint_quad(
quad(
highlight_bounds,
Corners::all(0.05 * line_height),
color,
Edges::default(),
transparent_black(),
),
None,
None,
);
}
}
@@ -926,153 +948,6 @@ 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;
@@ -1153,6 +1028,152 @@ 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);
}
});
},
)
}
@@ -1345,20 +1366,148 @@ impl EditorElement {
let thumb_bounds = Bounds::from_corners(point(left, thumb_top), point(right, thumb_bottom));
if layout.show_scrollbars {
cx.paint_quad(
quad(
track_bounds,
Corners::default(),
cx.theme().colors().scrollbar_track_background,
Edges {
top: Pixels::ZERO,
right: Pixels::ZERO,
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_track_border,
),
None,
None,
);
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,
),
None,
None,
);
}
}
cx.paint_quad(quad(
thumb_bounds,
Corners::default(),
cx.theme().colors().scrollbar_thumb_background,
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,
),
None,
None,
);
}
}
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,
),
None,
None,
);
}
}
if layout.is_singleton && scrollbar_settings.diagnostics {
let max_point = layout
@@ -1403,147 +1552,41 @@ impl EditorElement {
DiagnosticSeverity::INFORMATION => cx.theme().status().info,
_ => cx.theme().status().hint,
};
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.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,
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,
),
None,
None,
);
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(
track_bounds,
Corners::default(),
cx.theme().colors().scrollbar_track_background,
Edges {
top: Pixels::ZERO,
right: Pixels::ZERO,
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_track_border,
));
cx.paint_quad(
quad(
thumb_bounds,
Corners::default(),
cx.theme().colors().scrollbar_thumb_background,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
),
None,
None,
);
}
let interactive_track_bounds = InteractiveBounds {
@@ -3127,25 +3170,34 @@ impl Element for EditorElement {
ElementInputHandler::new(bounds, self.editor.clone()),
);
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);
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
if layout.gutter_size.width > Pixels::ZERO {
self.paint_gutter(gutter_bounds, &mut layout, cx);
}
self.paint_background(gutter_bounds, text_bounds, &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));
});
})
},
@@ -3413,7 +3465,7 @@ impl Cursor {
})
}
cx.paint_quad(cursor);
cx.paint_quad(cursor, None, None);
if let Some(block_text) = &self.block_text {
block_text

View File

@@ -94,7 +94,7 @@ fn generate_shader_bindings() -> PathBuf {
let mut builder = cbindgen::Builder::new();
let src_paths = [
crate_dir.join("src/scene.rs"),
crate_dir.join("src/scene/primitives.rs"),
crate_dir.join("src/geometry.rs"),
crate_dir.join("src/color.rs"),
crate_dir.join("src/window.rs"),

View File

@@ -1,35 +1,36 @@
use crate::{Bounds, Half};
use crate::{Bounds, Half, Point};
use std::{
cmp,
fmt::Debug,
ops::{Add, Sub},
};
#[derive(Debug, Default)]
pub struct BoundsTree<U: Default + Clone + Debug> {
#[derive(Debug)]
pub struct BoundsTree<U, T>
where
U: Default + Clone + Debug,
T: Clone + Debug,
{
root: Option<usize>,
nodes: Vec<Node<U>>,
nodes: Vec<Node<U, T>>,
stack: Vec<usize>,
}
impl<U> BoundsTree<U>
impl<U, T> BoundsTree<U, T>
where
U: Clone + Debug + PartialOrd + Add<U, Output = U> + Sub<Output = U> + Half + Default,
T: Clone + Debug,
{
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 {
pub fn insert(&mut self, new_bounds: Bounds<U>, payload: T) -> 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);
let new_node = self.push_leaf(new_bounds, payload, 1);
self.root = Some(new_node);
return 1;
}
@@ -42,7 +43,7 @@ where
right,
bounds: node_bounds,
..
} = self.node_mut(index)
} = &mut self.nodes[index]
{
let left = *left;
let right = *right;
@@ -53,8 +54,12 @@ where
// 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();
let left_cost = new_bounds
.union(&self.nodes[left].bounds())
.half_perimeter();
let right_cost = new_bounds
.union(&self.nodes[right].bounds())
.half_perimeter();
if left_cost < right_cost {
max_intersecting_ordering =
self.find_max_ordering(right, &new_bounds, max_intersecting_ordering);
@@ -75,7 +80,7 @@ where
bounds: sibling_bounds,
order: sibling_ordering,
..
} = self.node(index)
} = &self.nodes[index]
else {
unreachable!();
};
@@ -84,12 +89,12 @@ where
}
let ordering = max_intersecting_ordering + 1;
let new_node = self.push_leaf(new_bounds, ordering);
let new_node = self.push_leaf(new_bounds, payload, 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 {
let Node::Internal { left, right, .. } = &mut self.nodes[old_parent] else {
unreachable!();
};
@@ -104,7 +109,11 @@ where
}
for node_index in self.stack.drain(..) {
let Node::Internal { max_ordering, .. } = &mut self.nodes[node_index] else {
let Node::Internal {
max_order: max_ordering,
..
} = &mut self.nodes[node_index]
else {
unreachable!()
};
*max_ordering = cmp::max(*max_ordering, ordering);
@@ -113,8 +122,52 @@ where
ordering
}
/// Finds all nodes whose bounds contain the given point and pushes their (bounds, payload) pairs onto the result vector.
pub(crate) fn find_containing(
&mut self,
point: &Point<U>,
result: &mut Vec<BoundsSearchResult<U, T>>,
) {
if let Some(mut index) = self.root {
self.stack.clear();
self.stack.push(index);
while let Some(current_index) = self.stack.pop() {
match &self.nodes[current_index] {
Node::Leaf {
bounds,
order,
data,
} => {
if bounds.contains(point) {
result.push(BoundsSearchResult {
bounds: bounds.clone(),
order: *order,
data: data.clone(),
});
}
}
Node::Internal {
left,
right,
bounds,
..
} => {
if bounds.contains(point) {
self.stack.push(*left);
self.stack.push(*right);
}
}
}
}
}
}
fn find_max_ordering(&self, index: usize, bounds: &Bounds<U>, mut max_ordering: u32) -> u32 {
match self.node(index) {
match {
let this = &self;
&this.nodes[index]
} {
Node::Leaf {
bounds: node_bounds,
order: ordering,
@@ -128,12 +181,12 @@ where
left,
right,
bounds: node_bounds,
max_ordering: node_max_ordering,
max_order: 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();
let left_max_ordering = self.nodes[*left].max_ordering();
let right_max_ordering = self.nodes[*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);
@@ -147,59 +200,67 @@ where
max_ordering
}
fn push_leaf(&mut self, bounds: Bounds<U>, order: u32) -> usize {
self.nodes.push(Node::Leaf { bounds, order });
fn push_leaf(&mut self, bounds: Bounds<U>, payload: T, order: u32) -> usize {
self.nodes.push(Node::Leaf {
bounds,
data: payload,
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 left_node = &self.nodes[left];
let right_node = &self.nodes[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,
max_order: 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]
impl<U, T> Default for BoundsTree<U, T>
where
U: Default + Clone + Debug,
T: Clone + Debug,
{
fn default() -> Self {
BoundsTree {
root: None,
nodes: Vec::new(),
stack: Vec::new(),
}
}
}
#[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> {
#[derive(Debug, Clone)]
enum Node<U, T>
where
U: Clone + Default + Debug,
T: Clone + Debug,
{
Leaf {
bounds: Bounds<U>,
order: u32,
data: T,
},
Internal {
left: usize,
right: usize,
bounds: Bounds<U>,
max_ordering: u32,
max_order: u32,
},
}
impl<U> Node<U>
impl<U, T> Node<U, T>
where
U: Clone + Default + Debug,
T: Clone + Debug,
{
fn bounds(&self) -> &Bounds<U> {
match self {
@@ -213,21 +274,28 @@ where
Node::Leaf {
order: ordering, ..
} => *ordering,
Node::Internal { max_ordering, .. } => *max_ordering,
Node::Internal {
max_order: max_ordering,
..
} => *max_ordering,
}
}
}
pub struct BoundsSearchResult<U: Clone + Default + Debug, T> {
pub bounds: Bounds<U>,
pub order: u32,
pub data: T,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Point, Size};
use rand::{Rng, SeedableRng};
use std::{fs, path::Path};
use crate::{Bounds, Point, Size};
#[test]
fn test_bounds_insertion_with_two_bounds() {
let mut tree = BoundsTree::new();
fn test_insert_and_find_containing() {
let mut tree = BoundsTree::<f32, String>::default();
let bounds1 = Bounds {
origin: Point { x: 0.0, y: 0.0 },
size: Size {
@@ -242,194 +310,54 @@ mod tests {
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 },
let bounds3 = Bounds {
origin: Point { x: 10.0, y: 10.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
// Insert the first bounds.
assert_eq!(tree.insert(bounds1), 1);
// Insert bounds into the tree
tree.insert(bounds1.clone(), "Payload 1".to_string());
tree.insert(bounds2.clone(), "Payload 2".to_string());
tree.insert(bounds3.clone(), "Payload 3".to_string());
// Insert the second bounds, which is adjacent to the first but not overlapping.
assert_eq!(tree.insert(bounds2), 1);
}
// Points for testing
let point_inside_bounds1 = Point { x: 1.0, y: 1.0 };
let point_inside_bounds1_and_2 = Point { x: 6.0, y: 6.0 };
let point_inside_bounds2_and_3 = Point { x: 12.0, y: 12.0 };
let point_outside_all_bounds = Point { x: 21.0, y: 21.0 };
#[test]
fn test_random_iterations() {
let max_bounds = 100;
assert!(!bounds1.contains(&point_inside_bounds2_and_3));
assert!(!bounds1.contains(&point_outside_all_bounds));
assert!(bounds2.contains(&point_inside_bounds1_and_2));
assert!(bounds2.contains(&point_inside_bounds2_and_3));
assert!(!bounds2.contains(&point_outside_all_bounds));
assert!(!bounds3.contains(&point_inside_bounds1));
assert!(bounds3.contains(&point_inside_bounds2_and_3));
assert!(!bounds3.contains(&point_outside_all_bounds));
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();
}
// Test find_containing for different points
let mut result = Vec::new();
tree.find_containing(&point_inside_bounds1, &mut result);
assert_eq!(result.len(), 1);
assert_eq!(result[0].data, "Payload 1");
dbg!(seed);
result.clear();
tree.find_containing(&point_inside_bounds1_and_2, &mut result);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|r| r.data == "Payload 1"));
assert!(result.iter().any(|r| r.data == "Payload 2"));
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();
result.clear();
tree.find_containing(&point_inside_bounds2_and_3, &mut result);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|r| r.data == "Payload 2"));
assert!(result.iter().any(|r| r.data == "Payload 3"));
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();
}
result.clear();
tree.find_containing(&point_outside_all_bounds, &mut result);
assert_eq!(result.len(), 0);
}
}

View File

@@ -338,6 +338,11 @@ impl Hsla {
self.a == 0.0
}
/// Returns true if the HSLA color is fully opaque, false otherwise.
pub fn is_opaque(&self) -> bool {
self.a == 1.0
}
/// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
///
/// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.

View File

@@ -45,7 +45,7 @@ impl Element for Canvas {
}
fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut ElementContext) {
style.paint(bounds, cx, |cx| {
style.paint(bounds, None, None, cx, |cx| {
(self.paint_callback.take().unwrap())(&bounds, cx)
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -152,7 +152,7 @@ impl Element for Img {
let size = size(surface.width().into(), surface.height().into());
let new_bounds = preserve_aspect_ratio(bounds, size);
// TODO: Add support for corner_radii and grayscale.
cx.paint_surface(new_bounds, surface);
cx.paint_surface(new_bounds, surface, true);
}
};
});

View File

@@ -2639,6 +2639,12 @@ pub trait Half {
fn half(&self) -> Self;
}
impl Half for i32 {
fn half(&self) -> Self {
self / 2
}
}
impl Half for f32 {
fn half(&self) -> Self {
self / 2.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,345 @@
use crate::{
point, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels, Point,
ScaledPixels,
};
use std::fmt::Debug;
#[derive(Default, Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Quad {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub background: Hsla,
pub border_color: Hsla,
pub corner_radii: Corners<ScaledPixels>,
pub border_widths: Edges<ScaledPixels>,
}
impl Ord for Quad {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Quad {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Underline {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub thickness: ScaledPixels,
pub wavy: bool,
}
impl Ord for Underline {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Underline {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Shadow {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub blur_radius: ScaledPixels,
pub pad: u32, // align to 8 bytes
}
impl Ord for Shadow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Shadow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct MonochromeSprite {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
}
impl Ord for MonochromeSprite {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.order.cmp(&other.order) {
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
order => order,
}
}
}
impl PartialOrd for MonochromeSprite {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct PolychromeSprite {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub tile: AtlasTile,
pub grayscale: bool,
pub pad: u32, // align to 8 bytes
}
impl Ord for PolychromeSprite {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.order.cmp(&other.order) {
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
order => order,
}
}
}
impl PartialOrd for PolychromeSprite {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Surface {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
#[cfg(target_os = "macos")]
pub image_buffer: media::core_video::CVImageBuffer,
}
impl Ord for Surface {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Surface {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PathId(pub(crate) usize);
/// A line made up of a series of vertices and control points.
#[derive(Debug)]
pub struct Path<P: Clone + Default + Debug> {
pub(crate) id: PathId,
pub(crate) view_id: ViewId,
pub(crate) order: u32,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>,
pub(crate) color: Hsla,
pub(crate) start: Point<P>,
pub(crate) current: Point<P>,
pub(crate) contour_count: usize,
}
impl Path<Pixels> {
/// Create a new path with the given starting point.
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
view_id: ViewId::default(),
order: u32::default(),
vertices: Vec::new(),
start,
current: start,
bounds: Bounds {
origin: start,
size: Default::default(),
},
content_mask: Default::default(),
color: Default::default(),
contour_count: 0,
}
}
/// Scale this path by the given factor.
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
view_id: self.view_id,
order: self.order,
bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),
vertices: self
.vertices
.iter()
.map(|vertex| vertex.scale(factor))
.collect(),
start: self.start.map(|start| start.scale(factor)),
current: self.current.scale(factor),
contour_count: self.contour_count,
color: self.color,
}
}
/// Draw a straight line from the current point to the given point.
pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.current = to;
}
/// Draw a curve from the current point to the given point, using the given control point.
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.push_triangle(
(self.current, ctrl, to),
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
);
self.current = to;
}
fn push_triangle(
&mut self,
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),
) {
self.bounds = self
.bounds
.union(&Bounds {
origin: xy.0,
size: Default::default(),
})
.union(&Bounds {
origin: xy.1,
size: Default::default(),
})
.union(&Bounds {
origin: xy.2,
size: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.0,
st_position: st.0,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.1,
st_position: st.1,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.2,
st_position: st.2,
content_mask: Default::default(),
});
}
}
impl Eq for Path<ScaledPixels> {}
impl PartialEq for Path<ScaledPixels> {
fn eq(&self, other: &Self) -> bool {
self.order == other.order
}
}
impl Ord for Path<ScaledPixels> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Path<ScaledPixels> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct PathVertex<P: Clone + Default + Debug> {
pub(crate) xy_position: Point<P>,
pub(crate) st_position: Point<f32>,
pub(crate) content_mask: ContentMask<P>,
}
impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex {
xy_position: self.xy_position.scale(factor),
st_position: self.st_position,
content_mask: self.content_mask.scale(factor),
}
}
}
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct ViewId {
low_bits: u32,
high_bits: u32,
}
impl From<EntityId> for ViewId {
fn from(value: EntityId) -> Self {
let value = value.as_u64();
Self {
low_bits: value as u32,
high_bits: (value >> 32) as u32,
}
}
}
impl From<ViewId> for EntityId {
fn from(value: ViewId) -> Self {
let value = (value.low_bits as u64) | ((value.high_bits as u64) << 32);
value.into()
}
}

View File

@@ -383,10 +383,12 @@ impl Style {
}
}
/// Paints the background of an element styled with this style.
/// Paints the background of an element styled with this style, then calls the continuation function, then paints the border.
pub fn paint(
&self,
bounds: Bounds<Pixels>,
hover: Option<Self>,
group_hover: Option<(SharedString, Option<Self>)>,
cx: &mut ElementContext,
continuation: impl FnOnce(&mut ElementContext),
) {
@@ -397,104 +399,242 @@ impl Style {
#[cfg(debug_assertions)]
if self.debug || cx.has_global::<DebugBelow>() {
cx.paint_quad(crate::outline(bounds, crate::red()));
cx.paint_quad(crate::outline(bounds, crate::red()), None, None);
}
let rem_size = cx.rem_size();
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()) {
let mut border_color = background_color.unwrap_or_default();
border_color.a = 0.;
cx.paint_quad(quad(
cx.with_z_index(0, |cx| {
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
Edges::default(),
border_color,
));
}
&self.box_shadow,
);
});
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
&self.box_shadow,
);
let named_hover_group = group_hover
.as_ref()
.map(|(group_name, _)| group_name.clone());
cx.with_hover_group(named_hover_group, |cx| {
let background_color = self.background_color();
let hover_background_color = hover
.as_ref()
.map(|hover_style| hover_style.background_color())
.unwrap_or_default();
let group_hover_background_color = group_hover
.as_ref()
.and_then(|(_, group_hover_style)| {
Some(group_hover_style.as_ref()?.background_color())
})
.unwrap_or_default();
#[cfg(debug_assertions)]
if self.debug_below {
cx.remove_global::<DebugBelow>();
if !background_color.is_transparent()
|| !hover_background_color.is_transparent()
|| !group_hover_background_color.is_transparent()
{
cx.with_z_index(1, |cx| {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let mut border_color = background_color;
border_color.a = 0.;
let base_quad = quad(
bounds,
corner_radii,
background_color,
Edges::default(),
border_color,
);
let hover_quad = if hover_background_color.is_transparent() {
None
} else {
let mut border_color = hover_background_color;
border_color.a = 0.;
Some(quad(
bounds,
corner_radii,
hover_background_color,
Edges::default(),
border_color,
))
};
let group_hover_quad = group_hover.as_ref().map(|(group_id, _)| {
let quad = if group_hover_background_color.is_transparent() {
None
} else {
let mut border_color = group_hover_background_color;
border_color.a = 0.;
Some(quad(
bounds,
corner_radii,
group_hover_background_color,
Edges::default(),
border_color,
))
};
(group_id.clone(), quad)
});
cx.paint_quad(base_quad, hover_quad, group_hover_quad);
});
}
cx.with_z_index(2, |cx| {
continuation(cx);
});
let border_color = self.border_color();
let hover_border_color = hover
.as_ref()
.map(|hover_style| hover_style.border_color())
.unwrap_or_default();
let group_hover_border_color = group_hover
.as_ref()
.and_then(|(_, group_hover_style)| Some(group_hover_style.as_ref()?.border_color()))
.unwrap_or_default();
if self.border_widths.any(|width| !width.is_zero())
&& (!border_color.is_transparent()
|| !hover_border_color.is_transparent()
|| !group_hover_border_color.is_transparent())
{
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 = border_color;
background.a = 0.;
let border_quad = quad(
bounds,
corner_radii,
background,
border_widths,
border_color,
);
let hover_border_quad = if hover_border_color.is_transparent() {
None
} else {
let mut background = hover_border_color;
background.a = 0.;
Some(quad(
bounds,
corner_radii,
background,
border_widths,
hover_border_color,
))
};
let group_hover_border_quad = group_hover.as_ref().map(|(group_id, _)| {
let quad = if group_hover_border_color.is_transparent() {
None
} else {
let mut background = group_hover_border_color;
background.a = 0.;
Some(quad(
bounds,
corner_radii,
background,
border_widths,
group_hover_border_color,
))
};
(group_id.clone(), quad)
});
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(
border_quad.clone(),
hover_border_quad.clone(),
group_hover_border_quad.clone(),
);
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(
border_quad.clone(),
hover_border_quad.clone(),
group_hover_border_quad.clone(),
);
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(
border_quad.clone(),
hover_border_quad.clone(),
group_hover_border_quad.clone(),
);
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(border_quad, hover_border_quad, group_hover_border_quad);
},
);
});
}
#[cfg(debug_assertions)]
if self.debug_below {
cx.remove_global::<DebugBelow>();
}
})
}
/// Returns the background color of the style based on the visibility.
/// If the visibility is `Visible`, it returns the background color of the style if set,
/// otherwise it returns the default color. If the visibility is `Hidden`, it returns
/// a transparent black color.
fn background_color(&self) -> Hsla {
match self.visibility {
Visibility::Visible => self
.background
.as_ref()
.and_then(Fill::color)
.unwrap_or_default(),
Visibility::Hidden => Hsla::transparent_black(),
}
}
fn is_border_visible(&self) -> bool {
self.border_color
.map_or(false, |color| !color.is_transparent())
&& self.border_widths.any(|length| !length.is_zero())
/// Returns the border color of the style based on the visibility.
/// If the visibility is `Visible`, it returns the border color of the style if set,
/// otherwise it returns the default color. If the visibility is `Hidden`, it returns
/// a transparent black color.
fn border_color(&self) -> Hsla {
match self.visibility {
Visibility::Visible => self.border_color.unwrap_or_default(),
Visibility::Hidden => Hsla::transparent_black(),
}
}
}

View File

@@ -130,13 +130,17 @@ fn paint_line(
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
wraps.next();
if let Some((background_origin, background_color)) = current_background.as_mut() {
cx.paint_quad(fill(
Bounds {
origin: *background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
*background_color,
));
cx.paint_quad(
fill(
Bounds {
origin: *background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
*background_color,
),
None,
None,
);
background_origin.x = origin.x;
background_origin.y += line_height;
}
@@ -229,13 +233,17 @@ fn paint_line(
}
if let Some((background_origin, background_color)) = finished_background {
cx.paint_quad(fill(
Bounds {
origin: background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
background_color,
));
cx.paint_quad(
fill(
Bounds {
origin: background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
background_color,
),
None,
None,
);
}
if let Some((underline_origin, underline_style)) = finished_underline {
@@ -289,13 +297,17 @@ fn paint_line(
}
if let Some((background_origin, background_color)) = current_background.take() {
cx.paint_quad(fill(
Bounds {
origin: background_origin,
size: size(last_line_end_x - background_origin.x, line_height),
},
background_color,
));
cx.paint_quad(
fill(
Bounds {
origin: background_origin,
size: size(last_line_end_x - background_origin.x, line_height),
},
background_color,
),
None,
None,
);
}
if let Some((underline_start, underline_style)) = current_underline.take() {

View File

@@ -5,8 +5,8 @@ use crate::{
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
PromptLevel, Quad, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, View, ViewId, VisualContext, WeakView, WindowAppearance, WindowBounds,
WindowOptions, WindowTextSystem,
};
use anyhow::{anyhow, Context as _, Result};
@@ -1043,9 +1043,10 @@ impl<'a> WindowContext<'a> {
self.window.layout_engine.as_mut().unwrap().clear();
self.text_system()
.finish_frame(&self.window.next_frame.reused_views);
let mouse_position = self.window.mouse_position.scale(self.window.scale_factor);
self.window
.next_frame
.finish(&mut self.window.rendered_frame);
.finish(&mut self.window.rendered_frame, mouse_position);
ELEMENT_ARENA.with_borrow_mut(|element_arena| {
let percentage = (element_arena.len() as f32 / element_arena.capacity() as f32) * 100.;
if percentage >= 80. {
@@ -2797,6 +2798,24 @@ impl PaintQuad {
..self
}
}
pub(crate) fn into_primitive(
self,
view_id: impl Into<ViewId>,
scale_factor: f32,
content_mask: ContentMask<Pixels>,
) -> Quad {
Quad {
view_id: view_id.into(),
order: 0,
bounds: self.bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
background: self.background,
border_color: self.border_color,
corner_radii: self.corner_radii.scale(scale_factor),
border_widths: self.border_widths.scale(scale_factor),
}
}
}
/// Creates a quad with the given parameters.

View File

@@ -34,9 +34,9 @@ use crate::{
EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad,
Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams,
RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext,
StackingOrder, StrikethroughStyle, Style, TextStyleRefinement, Underline, UnderlineStyle,
Window, WindowContext, SUBPIXEL_VARIANTS,
RenderImageParams, RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size,
StackingContext, StackingOrder, StrikethroughStyle, Style, TextStyleRefinement, Underline,
UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS,
};
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
@@ -124,7 +124,7 @@ impl Frame {
.unwrap_or_default()
}
pub(crate) fn finish(&mut self, prev_frame: &mut Self) {
pub(crate) fn finish(&mut self, prev_frame: &mut Self, mouse_position: Point<ScaledPixels>) {
// Reuse mouse listeners that didn't change since the last frame.
for (type_id, listeners) in &mut prev_frame.mouse_listeners {
let next_listeners = self.mouse_listeners.entry(*type_id).or_default();
@@ -157,7 +157,7 @@ impl Frame {
// Reuse geometry that didn't change since the last frame.
self.scene
.reuse_views(&self.reused_views, &mut prev_frame.scene);
self.scene.finish();
self.scene.finish(mouse_position);
}
}
@@ -651,6 +651,7 @@ impl<'a> ElementContext<'a> {
}
})
}
/// Paint one or more drop shadows into the scene for the next frame at the current z-index.
pub fn paint_shadows(
&mut self,
@@ -666,11 +667,9 @@ impl<'a> ElementContext<'a> {
let mut shadow_bounds = bounds;
shadow_bounds.origin += shadow.offset;
shadow_bounds.dilate(shadow.spread_radius);
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_shadow(
Shadow {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds: shadow_bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
@@ -679,6 +678,8 @@ impl<'a> ElementContext<'a> {
blur_radius: shadow.blur_radius.scale(scale_factor),
pad: 0,
},
None,
None,
);
}
}
@@ -686,17 +687,20 @@ impl<'a> ElementContext<'a> {
/// Paint one or more quads into the scene for the next frame at the current stacking context.
/// Quads are colored rectangular regions with an optional background, border, and corner radius.
/// see [`fill`](crate::fill), [`outline`](crate::outline), and [`quad`](crate::quad) to construct this type.
pub fn paint_quad(&mut self, quad: PaintQuad) {
pub fn paint_quad(
&mut self,
quad: PaintQuad,
hover: Option<PaintQuad>,
group_hover: Option<(SharedString, Option<PaintQuad>)>,
) {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_quad(
Quad {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds: quad.bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
@@ -705,6 +709,13 @@ impl<'a> ElementContext<'a> {
corner_radii: quad.corner_radii.scale(scale_factor),
border_widths: quad.border_widths.scale(scale_factor),
},
hover.map(|quad| quad.into_primitive(view_id, scale_factor, content_mask.clone())),
group_hover.map(|(group_id, quad)| {
(
group_id,
quad.map(|quad| quad.into_primitive(view_id, scale_factor, content_mask)),
)
}),
);
}
@@ -721,7 +732,7 @@ impl<'a> ElementContext<'a> {
window
.next_frame
.scene
.insert(&window.next_frame.z_index_stack, path.scale(scale_factor));
.insert_path(path.scale(scale_factor), None, None);
}
/// Paint an underline into the scene for the next frame at the current z-index.
@@ -745,11 +756,9 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_underline(
Underline {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds: bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
@@ -757,6 +766,8 @@ impl<'a> ElementContext<'a> {
thickness: style.thickness.scale(scale_factor),
wavy: style.wavy,
},
None,
None,
);
}
@@ -777,11 +788,9 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_underline(
Underline {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds: bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
@@ -789,6 +798,8 @@ impl<'a> ElementContext<'a> {
color: style.color.unwrap_or_default(),
wavy: false,
},
None,
None,
);
}
@@ -837,17 +848,17 @@ impl<'a> ElementContext<'a> {
let content_mask = self.content_mask().scale(scale_factor);
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_monochrome_sprite(
MonochromeSprite {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
content_mask,
color,
tile,
},
None,
None,
);
}
Ok(())
@@ -895,11 +906,9 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_polychrome_sprite(
PolychromeSprite {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
corner_radii: Default::default(),
@@ -908,6 +917,8 @@ impl<'a> ElementContext<'a> {
grayscale: false,
pad: 0,
},
None,
None,
);
}
Ok(())
@@ -941,17 +952,17 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_monochrome_sprite(
MonochromeSprite {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
content_mask,
color,
tile,
},
None,
None,
);
Ok(())
@@ -980,11 +991,9 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_polychrome_sprite(
PolychromeSprite {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
content_mask,
@@ -993,28 +1002,34 @@ impl<'a> ElementContext<'a> {
grayscale,
pad: 0,
},
None,
None,
);
Ok(())
}
/// Paint a surface into the scene for the next frame at the current z-index.
#[cfg(target_os = "macos")]
pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
pub fn paint_surface(
&mut self,
bounds: Bounds<Pixels>,
image_buffer: CVImageBuffer,
occludes_hover: bool,
) {
let scale_factor = self.scale_factor();
let bounds = bounds.scale(scale_factor);
let content_mask = self.content_mask().scale(scale_factor);
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_surface(
crate::Surface {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
content_mask,
image_buffer,
},
occludes_hover,
);
}
@@ -1161,6 +1176,22 @@ impl<'a> ElementContext<'a> {
})
}
/// Invoke the given function with the given hover group id present on the hover stack.
/// This is a fairly low-level method used to paint hover effects for views that share
/// the same hover group.
pub fn with_hover_group<R>(
&mut self,
name: Option<SharedString>,
f: impl FnOnce(&mut Self) -> R,
) -> R {
let window = &mut self.window;
let group = window.next_frame.scene.hover_group(name);
window.hover_group_stack.push(group);
let result = f(self);
window.hover_group_stack.pop();
result
}
/// Sets an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the
/// platform to receive textual input with proper integration with concerns such
/// as IME interactions. This handler will be active for the upcoming frame until the following frame is

View File

@@ -133,7 +133,7 @@ impl LayoutRect {
)
.into();
cx.paint_quad(fill(Bounds::new(position, size), self.color));
cx.paint_quad(fill(Bounds::new(position, size), self.color), None, None);
}
}
@@ -782,7 +782,7 @@ impl Element for TerminalElement {
) {
let mut layout = self.compute_layout(bounds, cx);
cx.paint_quad(fill(bounds, layout.background_color));
cx.paint_quad(fill(bounds, layout.background_color), None, None);
let origin = bounds.origin + Point::new(layout.gutter, px(0.));
let terminal_input_handler = TerminalInputHandler {

View File

@@ -766,7 +766,11 @@ mod element {
}
cx.add_opaque_layer(handle_bounds);
cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
cx.paint_quad(
gpui::fill(divider_bounds, cx.theme().colors().border),
None,
None,
);
cx.on_mouse_event({
let dragged_handle = dragged_handle.clone();