1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-03 15:20:05 -04:00

Added ability to define colors at UV coordinates along a path (#4353)

<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to add commits to your PR.
* Remember to run `cargo fmt` and `cargo cranky`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

I had to make a couple types not Copy because closures, but it should'nt
be a massive deal.

I tried my best to make the API change as non breaking as possible.
Anywhere a PathStroke is used, you can just use a normal Stroke instead.
As mentioned above, the bezier paths couldn't be copy anymore, but IMO
that's a minor caveat.

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
Joe Sorensen
2024-04-22 10:35:09 -06:00
committed by GitHub
parent ff8cfc2aa0
commit 2ce82cce21
13 changed files with 550 additions and 104 deletions

View File

@@ -9,6 +9,9 @@ use crate::texture_atlas::PreparedDisc;
use crate::*;
use emath::*;
use self::color::ColorMode;
use self::stroke::PathStroke;
// ----------------------------------------------------------------------------
#[allow(clippy::approx_constant)]
@@ -471,16 +474,22 @@ impl Path {
}
/// Open-ended.
pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
pub fn stroke_open(&self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) {
stroke_path(feathering, &self.0, PathType::Open, stroke, out);
}
/// A closed path (returning to the first point).
pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
pub fn stroke_closed(&self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) {
stroke_path(feathering, &self.0, PathType::Closed, stroke, out);
}
pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) {
pub fn stroke(
&self,
feathering: f32,
path_type: PathType,
stroke: &PathStroke,
out: &mut Mesh,
) {
stroke_path(feathering, &self.0, path_type, stroke, out);
}
@@ -864,19 +873,28 @@ fn stroke_path(
feathering: f32,
path: &[PathPoint],
path_type: PathType,
stroke: Stroke,
stroke: &PathStroke,
out: &mut Mesh,
) {
let n = path.len() as u32;
if stroke.width <= 0.0 || stroke.color == Color32::TRANSPARENT || n < 2 {
if stroke.width <= 0.0 || stroke.color == ColorMode::TRANSPARENT || n < 2 {
return;
}
let idx = out.vertices.len() as u32;
// expand the bounding box to include the thickness of the path
let bbox = Rect::from_points(&path.iter().map(|p| p.pos).collect::<Vec<Pos2>>())
.expand((stroke.width / 2.0) + feathering);
let get_color = |col: &ColorMode, pos: Pos2| match col {
ColorMode::Solid(col) => *col,
ColorMode::UV(fun) => fun(bbox, pos),
};
if feathering > 0.0 {
let color_inner = stroke.color;
let color_inner = &stroke.color;
let color_outer = Color32::TRANSPARENT;
let thin_line = stroke.width <= feathering;
@@ -889,9 +907,11 @@ fn stroke_path(
*/
// Fade out as it gets thinner:
let color_inner = mul_color(color_inner, stroke.width / feathering);
if color_inner == Color32::TRANSPARENT {
return;
if let ColorMode::Solid(col) = color_inner {
let color_inner = mul_color(*col, stroke.width / feathering);
if color_inner == Color32::TRANSPARENT {
return;
}
}
out.reserve_triangles(4 * n as usize);
@@ -904,7 +924,10 @@ fn stroke_path(
let p = p1.pos;
let n = p1.normal;
out.colored_vertex(p + n * feathering, color_outer);
out.colored_vertex(p, color_inner);
out.colored_vertex(
p,
mul_color(get_color(color_inner, p), stroke.width / feathering),
);
out.colored_vertex(p - n * feathering, color_outer);
if connect_with_previous {
@@ -943,8 +966,14 @@ fn stroke_path(
let p = p1.pos;
let n = p1.normal;
out.colored_vertex(p + n * outer_rad, color_outer);
out.colored_vertex(p + n * inner_rad, color_inner);
out.colored_vertex(p - n * inner_rad, color_inner);
out.colored_vertex(
p + n * inner_rad,
get_color(color_inner, p + n * inner_rad),
);
out.colored_vertex(
p - n * inner_rad,
get_color(color_inner, p - n * inner_rad),
);
out.colored_vertex(p - n * outer_rad, color_outer);
out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
@@ -983,8 +1012,14 @@ fn stroke_path(
let n = end.normal;
let back_extrude = n.rot90() * feathering;
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
out.colored_vertex(p + n * inner_rad, color_inner);
out.colored_vertex(p - n * inner_rad, color_inner);
out.colored_vertex(
p + n * inner_rad,
get_color(color_inner, p + n * inner_rad),
);
out.colored_vertex(
p - n * inner_rad,
get_color(color_inner, p - n * inner_rad),
);
out.colored_vertex(p - n * outer_rad + back_extrude, color_outer);
out.add_triangle(idx + 0, idx + 1, idx + 2);
@@ -997,8 +1032,14 @@ fn stroke_path(
let p = point.pos;
let n = point.normal;
out.colored_vertex(p + n * outer_rad, color_outer);
out.colored_vertex(p + n * inner_rad, color_inner);
out.colored_vertex(p - n * inner_rad, color_inner);
out.colored_vertex(
p + n * inner_rad,
get_color(color_inner, p + n * inner_rad),
);
out.colored_vertex(
p - n * inner_rad,
get_color(color_inner, p - n * inner_rad),
);
out.colored_vertex(p - n * outer_rad, color_outer);
out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
@@ -1020,8 +1061,14 @@ fn stroke_path(
let n = end.normal;
let back_extrude = -n.rot90() * feathering;
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
out.colored_vertex(p + n * inner_rad, color_inner);
out.colored_vertex(p - n * inner_rad, color_inner);
out.colored_vertex(
p + n * inner_rad,
get_color(color_inner, p + n * inner_rad),
);
out.colored_vertex(
p - n * inner_rad,
get_color(color_inner, p - n * inner_rad),
);
out.colored_vertex(p - n * outer_rad + back_extrude, color_outer);
out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
@@ -1067,19 +1114,39 @@ fn stroke_path(
if thin_line {
// Fade out thin lines rather than making them thinner
let radius = feathering / 2.0;
let color = mul_color(stroke.color, stroke.width / feathering);
if color == Color32::TRANSPARENT {
return;
if let ColorMode::Solid(color) = stroke.color {
let color = mul_color(color, stroke.width / feathering);
if color == Color32::TRANSPARENT {
return;
}
}
for p in path {
out.colored_vertex(p.pos + radius * p.normal, color);
out.colored_vertex(p.pos - radius * p.normal, color);
out.colored_vertex(
p.pos + radius * p.normal,
mul_color(
get_color(&stroke.color, p.pos + radius * p.normal),
stroke.width / feathering,
),
);
out.colored_vertex(
p.pos - radius * p.normal,
mul_color(
get_color(&stroke.color, p.pos - radius * p.normal),
stroke.width / feathering,
),
);
}
} else {
let radius = stroke.width / 2.0;
for p in path {
out.colored_vertex(p.pos + radius * p.normal, stroke.color);
out.colored_vertex(p.pos - radius * p.normal, stroke.color);
out.colored_vertex(
p.pos + radius * p.normal,
get_color(&stroke.color, p.pos + radius * p.normal),
);
out.colored_vertex(
p.pos - radius * p.normal,
get_color(&stroke.color, p.pos - radius * p.normal),
);
}
}
}
@@ -1275,9 +1342,9 @@ impl Tessellator {
self.tessellate_text(&text_shape, out);
}
Shape::QuadraticBezier(quadratic_shape) => {
self.tessellate_quadratic_bezier(quadratic_shape, out);
self.tessellate_quadratic_bezier(&quadratic_shape, out);
}
Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(cubic_shape, out),
Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(&cubic_shape, out),
Shape::Callback(_) => {
panic!("Shape::Callback passed to Tessellator");
}
@@ -1337,7 +1404,7 @@ impl Tessellator {
self.scratchpad_path.add_circle(center, radius);
self.scratchpad_path.fill(self.feathering, fill, out);
self.scratchpad_path
.stroke_closed(self.feathering, stroke, out);
.stroke_closed(self.feathering, &stroke.into(), out);
}
/// Tessellate a single [`EllipseShape`] into a [`Mesh`].
@@ -1404,7 +1471,7 @@ impl Tessellator {
self.scratchpad_path.add_line_loop(&points);
self.scratchpad_path.fill(self.feathering, fill, out);
self.scratchpad_path
.stroke_closed(self.feathering, stroke, out);
.stroke_closed(self.feathering, &stroke.into(), out);
}
/// Tessellate a single [`Mesh`] into a [`Mesh`].
@@ -1430,7 +1497,13 @@ impl Tessellator {
///
/// * `shape`: the mesh to tessellate.
/// * `out`: triangles are appended to this.
pub fn tessellate_line(&mut self, points: [Pos2; 2], stroke: Stroke, out: &mut Mesh) {
pub fn tessellate_line(
&mut self,
points: [Pos2; 2],
stroke: impl Into<PathStroke>,
out: &mut Mesh,
) {
let stroke = stroke.into();
if stroke.is_empty() {
return;
}
@@ -1446,7 +1519,7 @@ impl Tessellator {
self.scratchpad_path.clear();
self.scratchpad_path.add_line_segment(points);
self.scratchpad_path
.stroke_open(self.feathering, stroke, out);
.stroke_open(self.feathering, &stroke, out);
}
/// Tessellate a single [`PathShape`] into a [`Mesh`].
@@ -1493,7 +1566,7 @@ impl Tessellator {
PathType::Open
};
self.scratchpad_path
.stroke(self.feathering, typ, *stroke, out);
.stroke(self.feathering, typ, stroke, out);
}
/// Tessellate a single [`Rect`] into a [`Mesh`].
@@ -1588,7 +1661,7 @@ impl Tessellator {
path.fill(self.feathering, fill, out);
}
path.stroke_closed(self.feathering, stroke, out);
path.stroke_closed(self.feathering, &stroke.into(), out);
}
self.feathering = old_feathering; // restore
@@ -1707,8 +1780,11 @@ impl Tessellator {
self.scratchpad_path.clear();
self.scratchpad_path
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
self.scratchpad_path
.stroke_open(self.feathering, *underline, out);
self.scratchpad_path.stroke_open(
self.feathering,
&PathStroke::from(*underline),
out,
);
}
}
}
@@ -1719,7 +1795,7 @@ impl Tessellator {
/// * `out`: triangles are appended to this.
pub fn tessellate_quadratic_bezier(
&mut self,
quadratic_shape: QuadraticBezierShape,
quadratic_shape: &QuadraticBezierShape,
out: &mut Mesh,
) {
let options = &self.options;
@@ -1737,7 +1813,7 @@ impl Tessellator {
&points,
quadratic_shape.fill,
quadratic_shape.closed,
quadratic_shape.stroke,
&quadratic_shape.stroke,
out,
);
}
@@ -1746,7 +1822,7 @@ impl Tessellator {
///
/// * `cubic_shape`: the shape to tessellate.
/// * `out`: triangles are appended to this.
pub fn tessellate_cubic_bezier(&mut self, cubic_shape: CubicBezierShape, out: &mut Mesh) {
pub fn tessellate_cubic_bezier(&mut self, cubic_shape: &CubicBezierShape, out: &mut Mesh) {
let options = &self.options;
let clip_rect = self.clip_rect;
if options.coarse_tessellation_culling
@@ -1763,7 +1839,7 @@ impl Tessellator {
&points,
cubic_shape.fill,
cubic_shape.closed,
cubic_shape.stroke,
&cubic_shape.stroke,
out,
);
}
@@ -1774,7 +1850,7 @@ impl Tessellator {
points: &[Pos2],
fill: Color32,
closed: bool,
stroke: Stroke,
stroke: &PathStroke,
out: &mut Mesh,
) {
if points.len() < 2 {
@@ -1985,3 +2061,48 @@ fn test_tessellator() {
assert_eq!(primitives.len(), 2);
}
#[test]
fn path_bounding_box() {
use crate::*;
for i in 1..=100 {
let width = i as f32;
let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(10.0, 10.0));
let expected_rect = rect.expand((width / 2.0) + 1.5);
let mut mesh = Mesh::default();
let mut path = Path::default();
path.add_open_points(&[
pos2(0.0, 0.0),
pos2(2.0, 0.0),
pos2(5.0, 5.0),
pos2(0.0, 5.0),
pos2(0.0, 7.0),
pos2(10.0, 10.0),
]);
path.stroke(
1.5,
PathType::Closed,
&PathStroke::new_uv(width, move |r, p| {
assert_eq!(r, expected_rect);
// see https://github.com/emilk/egui/pull/4353#discussion_r1573879940 for why .contains() isn't used here.
// TL;DR rounding errors.
assert!(
r.distance_to_pos(p) <= 0.55,
"passed rect {r:?} didn't contain point {p:?} (distance: {})",
r.distance_to_pos(p)
);
assert!(
expected_rect.distance_to_pos(p) <= 0.55,
"expected rect {expected_rect:?} didn't contain point {p:?}"
);
Color32::WHITE
}),
&mut mesh,
);
}
}