Compare commits

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

11 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
Antonio Scandurra
a3ce933b04 Use BoundsTree in scene
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2024-02-23 17:51:06 +01:00
Antonio Scandurra
816c48b7d6 Introduce a BoundsTree structure
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2024-02-23 17:33:22 +01:00
20 changed files with 2356 additions and 1442 deletions

4
Cargo.lock generated
View file

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

View file

@ -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,
);
}
}
}
@ -804,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;
}
@ -837,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;
}
@ -877,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,
);
}
}
@ -1346,18 +1366,22 @@ 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,
));
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();
@ -1377,18 +1401,22 @@ impl EditorElement {
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(
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,
);
}
}
@ -1415,18 +1443,22 @@ impl EditorElement {
}
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(
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,
);
}
}
@ -1458,18 +1490,22 @@ impl EditorElement {
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,
));
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,
);
}
}
@ -1516,33 +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,
));
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,
);
}
}
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,
));
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 {
@ -3421,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

@ -0,0 +1,363 @@
use crate::{Bounds, Half, Point};
use std::{
cmp,
fmt::Debug,
ops::{Add, Sub},
};
#[derive(Debug)]
pub struct BoundsTree<U, T>
where
U: Default + Clone + Debug,
T: Clone + Debug,
{
root: Option<usize>,
nodes: Vec<Node<U, T>>,
stack: Vec<usize>,
}
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 clear(&mut self) {
self.root = None;
self.nodes.clear();
self.stack.clear();
}
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, payload, 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,
..
} = &mut self.nodes[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.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);
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.nodes[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, 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, .. } = &mut self.nodes[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_order: max_ordering,
..
} = &mut self.nodes[node_index]
else {
unreachable!()
};
*max_ordering = cmp::max(*max_ordering, ordering);
}
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 {
let this = &self;
&this.nodes[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_order: node_max_ordering,
..
} => {
if bounds.intersects(node_bounds) && max_ordering < *node_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);
} 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>, 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.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_order: max_ordering,
});
self.nodes.len() - 1
}
}
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(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_order: u32,
},
}
impl<U, T> Node<U, T>
where
U: Clone + Default + Debug,
T: Clone + 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_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::{Bounds, Point, Size};
#[test]
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 {
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,
},
};
let bounds3 = Bounds {
origin: Point { x: 10.0, y: 10.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
// 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());
// 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 };
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));
// 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");
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"));
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"));
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

@ -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> {
@ -2617,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.

View file

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

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,7 +399,7 @@ 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();
@ -410,101 +412,229 @@ impl Style {
);
});
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 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();
cx.with_z_index(2, |cx| {
continuation(cx);
});
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);
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 mut border_color = background_color;
border_color.a = 0.;
let base_quad = quad(
bounds,
corner_radii,
background_color,
Edges::default(),
border_color,
);
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 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 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(),
);
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.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(quad.clone());
cx.paint_quad(base_quad, hover_quad, group_hover_quad);
});
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);
},
);
});
}
}
#[cfg(debug_assertions)]
if self.debug_below {
cx.remove_global::<DebugBelow>();
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

@ -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
}

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();