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:
parent
ff8cfc2aa0
commit
2ce82cce21
|
|
@ -30,7 +30,6 @@ extra_debug_asserts = []
|
||||||
## Always enable additional checks.
|
## Always enable additional checks.
|
||||||
extra_asserts = []
|
extra_asserts = []
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use epaint::{
|
use epaint::{
|
||||||
text::{Fonts, Galley, LayoutJob},
|
text::{Fonts, Galley, LayoutJob},
|
||||||
CircleShape, ClippedShape, RectShape, Rounding, Shape, Stroke,
|
CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper to paint shapes and text to a specific region on a specific layer.
|
/// Helper to paint shapes and text to a specific region on a specific layer.
|
||||||
|
|
@ -280,7 +280,7 @@ impl Painter {
|
||||||
/// # Paint different primitives
|
/// # Paint different primitives
|
||||||
impl Painter {
|
impl Painter {
|
||||||
/// Paints a line from the first point to the second.
|
/// Paints a line from the first point to the second.
|
||||||
pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<Stroke>) -> ShapeIdx {
|
pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<PathStroke>) -> ShapeIdx {
|
||||||
self.add(Shape::LineSegment {
|
self.add(Shape::LineSegment {
|
||||||
points,
|
points,
|
||||||
stroke: stroke.into(),
|
stroke: stroke.into(),
|
||||||
|
|
@ -288,13 +288,13 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paints a horizontal line.
|
/// Paints a horizontal line.
|
||||||
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> ShapeIdx {
|
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<PathStroke>) -> ShapeIdx {
|
||||||
self.add(Shape::hline(x, y, stroke))
|
self.add(Shape::hline(x, y, stroke.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paints a vertical line.
|
/// Paints a vertical line.
|
||||||
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> ShapeIdx {
|
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<PathStroke>) -> ShapeIdx {
|
||||||
self.add(Shape::vline(x, y, stroke))
|
self.add(Shape::vline(x, y, stroke.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn circle(
|
pub fn circle(
|
||||||
|
|
@ -513,7 +513,7 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tint_shape_towards(shape: &mut Shape, target: Color32) {
|
fn tint_shape_towards(shape: &mut Shape, target: Color32) {
|
||||||
epaint::shape_transform::adjust_colors(shape, &|color| {
|
epaint::shape_transform::adjust_colors(shape, move |color| {
|
||||||
if *color != Color32::PLACEHOLDER {
|
if *color != Color32::PLACEHOLDER {
|
||||||
*color = crate::ecolor::tint_color_towards(*color, target);
|
*color = crate::ecolor::tint_color_towards(*color, target);
|
||||||
}
|
}
|
||||||
|
|
@ -521,7 +521,7 @@ fn tint_shape_towards(shape: &mut Shape, target: Color32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn multiply_opacity(shape: &mut Shape, opacity: f32) {
|
fn multiply_opacity(shape: &mut Shape, opacity: f32) {
|
||||||
epaint::shape_transform::adjust_colors(shape, &|color| {
|
epaint::shape_transform::adjust_colors(shape, move |color| {
|
||||||
if *color != Color32::PLACEHOLDER {
|
if *color != Color32::PLACEHOLDER {
|
||||||
*color = color.gamma_multiply(opacity);
|
*color = color.gamma_multiply(opacity);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ syntect = ["egui_extras/syntect"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { workspace = true, default-features = false }
|
egui = { workspace = true, default-features = false, features = ["color-hex"] }
|
||||||
egui_extras = { workspace = true, features = ["default"] }
|
egui_extras = { workspace = true, features = ["default"] }
|
||||||
egui_plot = { workspace = true, features = ["default"] }
|
egui_plot = { workspace = true, features = ["default"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
use egui::{containers::*, *};
|
use egui::{containers::*, epaint::PathStroke, *};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
pub struct DancingStrings {}
|
pub struct DancingStrings {
|
||||||
|
colors: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl super::Demo for DancingStrings {
|
impl super::Demo for DancingStrings {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
|
|
@ -28,6 +30,9 @@ impl super::View for DancingStrings {
|
||||||
Color32::from_black_alpha(240)
|
Color32::from_black_alpha(240)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ui.checkbox(&mut self.colors, "Colored")
|
||||||
|
.on_hover_text("Demonstrates how a path can have varying color across its length.");
|
||||||
|
|
||||||
Frame::canvas(ui.style()).show(ui, |ui| {
|
Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
ui.ctx().request_repaint();
|
ui.ctx().request_repaint();
|
||||||
let time = ui.input(|i| i.time);
|
let time = ui.input(|i| i.time);
|
||||||
|
|
@ -55,7 +60,24 @@ impl super::View for DancingStrings {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let thickness = 10.0 / mode as f32;
|
let thickness = 10.0 / mode as f32;
|
||||||
shapes.push(epaint::Shape::line(points, Stroke::new(thickness, color)));
|
shapes.push(epaint::Shape::line(
|
||||||
|
points,
|
||||||
|
if self.colors {
|
||||||
|
PathStroke::new_uv(thickness, move |rect, p| {
|
||||||
|
let t = remap(p.x, rect.x_range(), -1.0..=1.0).abs();
|
||||||
|
let center_color = hex_color!("#5BCEFA");
|
||||||
|
let outer_color = hex_color!("#F5A9B8");
|
||||||
|
|
||||||
|
Color32::from_rgb(
|
||||||
|
lerp(center_color.r() as f32..=outer_color.r() as f32, t) as u8,
|
||||||
|
lerp(center_color.g() as f32..=outer_color.g() as f32, t) as u8,
|
||||||
|
lerp(center_color.b() as f32..=outer_color.b() as f32, t) as u8,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
PathStroke::new(thickness, color)
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.painter().extend(shapes);
|
ui.painter().extend(shapes);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
use epaint::*;
|
use epaint::{tessellator::Path, *};
|
||||||
|
|
||||||
fn single_dashed_lines(c: &mut Criterion) {
|
fn single_dashed_lines(c: &mut Criterion) {
|
||||||
c.bench_function("single_dashed_lines", move |b| {
|
c.bench_function("single_dashed_lines", move |b| {
|
||||||
|
|
@ -72,10 +72,166 @@ fn tessellate_circles(c: &mut Criterion) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn thick_line_solid(c: &mut Criterion) {
|
||||||
|
c.bench_function("thick_solid_line", move |b| {
|
||||||
|
let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)];
|
||||||
|
let mut path = Path::default();
|
||||||
|
path.add_open_points(&line);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
path.stroke_closed(1.5, &Stroke::new(2.0, Color32::RED).into(), &mut mesh);
|
||||||
|
|
||||||
|
black_box(mesh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thick_large_line_solid(c: &mut Criterion) {
|
||||||
|
c.bench_function("thick_large_solid_line", move |b| {
|
||||||
|
let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::<Vec<_>>();
|
||||||
|
let mut path = Path::default();
|
||||||
|
path.add_open_points(&line);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
path.stroke_closed(1.5, &Stroke::new(2.0, Color32::RED).into(), &mut mesh);
|
||||||
|
|
||||||
|
black_box(mesh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thin_line_solid(c: &mut Criterion) {
|
||||||
|
c.bench_function("thin_solid_line", move |b| {
|
||||||
|
let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)];
|
||||||
|
let mut path = Path::default();
|
||||||
|
path.add_open_points(&line);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
path.stroke_closed(1.5, &Stroke::new(0.5, Color32::RED).into(), &mut mesh);
|
||||||
|
|
||||||
|
black_box(mesh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thin_large_line_solid(c: &mut Criterion) {
|
||||||
|
c.bench_function("thin_large_solid_line", move |b| {
|
||||||
|
let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::<Vec<_>>();
|
||||||
|
let mut path = Path::default();
|
||||||
|
path.add_open_points(&line);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
path.stroke_closed(1.5, &Stroke::new(0.5, Color32::RED).into(), &mut mesh);
|
||||||
|
|
||||||
|
black_box(mesh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thick_line_uv(c: &mut Criterion) {
|
||||||
|
c.bench_function("thick_uv_line", move |b| {
|
||||||
|
let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)];
|
||||||
|
let mut path = Path::default();
|
||||||
|
path.add_open_points(&line);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
path.stroke_closed(
|
||||||
|
1.5,
|
||||||
|
&PathStroke::new_uv(2.0, |_, p| {
|
||||||
|
black_box(p * 2.0);
|
||||||
|
Color32::RED
|
||||||
|
}),
|
||||||
|
&mut mesh,
|
||||||
|
);
|
||||||
|
|
||||||
|
black_box(mesh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thick_large_line_uv(c: &mut Criterion) {
|
||||||
|
c.bench_function("thick_large_uv_line", move |b| {
|
||||||
|
let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::<Vec<_>>();
|
||||||
|
let mut path = Path::default();
|
||||||
|
path.add_open_points(&line);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
path.stroke_closed(
|
||||||
|
1.5,
|
||||||
|
&PathStroke::new_uv(2.0, |_, p| {
|
||||||
|
black_box(p * 2.0);
|
||||||
|
Color32::RED
|
||||||
|
}),
|
||||||
|
&mut mesh,
|
||||||
|
);
|
||||||
|
|
||||||
|
black_box(mesh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thin_line_uv(c: &mut Criterion) {
|
||||||
|
c.bench_function("thin_uv_line", move |b| {
|
||||||
|
let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)];
|
||||||
|
let mut path = Path::default();
|
||||||
|
path.add_open_points(&line);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
path.stroke_closed(
|
||||||
|
1.5,
|
||||||
|
&PathStroke::new_uv(2.0, |_, p| {
|
||||||
|
black_box(p * 2.0);
|
||||||
|
Color32::RED
|
||||||
|
}),
|
||||||
|
&mut mesh,
|
||||||
|
);
|
||||||
|
|
||||||
|
black_box(mesh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thin_large_line_uv(c: &mut Criterion) {
|
||||||
|
c.bench_function("thin_large_uv_line", move |b| {
|
||||||
|
let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::<Vec<_>>();
|
||||||
|
let mut path = Path::default();
|
||||||
|
path.add_open_points(&line);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
path.stroke_closed(
|
||||||
|
1.5,
|
||||||
|
&PathStroke::new_uv(2.0, |_, p| {
|
||||||
|
black_box(p * 2.0);
|
||||||
|
Color32::RED
|
||||||
|
}),
|
||||||
|
&mut mesh,
|
||||||
|
);
|
||||||
|
|
||||||
|
black_box(mesh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(
|
||||||
benches,
|
benches,
|
||||||
single_dashed_lines,
|
single_dashed_lines,
|
||||||
many_dashed_lines,
|
many_dashed_lines,
|
||||||
tessellate_circles
|
tessellate_circles,
|
||||||
|
thick_line_solid,
|
||||||
|
thick_large_line_solid,
|
||||||
|
thin_line_solid,
|
||||||
|
thin_large_line_solid,
|
||||||
|
thick_line_uv,
|
||||||
|
thick_large_line_uv,
|
||||||
|
thin_line_uv,
|
||||||
|
thin_large_line_uv
|
||||||
);
|
);
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{shape::Shape, Color32, PathShape, Stroke};
|
use crate::{shape::Shape, Color32, PathShape, PathStroke};
|
||||||
use emath::*;
|
use emath::*;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
@ -11,7 +11,7 @@ use emath::*;
|
||||||
/// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
/// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
||||||
///
|
///
|
||||||
/// See also [`QuadraticBezierShape`].
|
/// See also [`QuadraticBezierShape`].
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct CubicBezierShape {
|
pub struct CubicBezierShape {
|
||||||
/// The first point is the starting point and the last one is the ending point of the curve.
|
/// The first point is the starting point and the last one is the ending point of the curve.
|
||||||
|
|
@ -20,7 +20,7 @@ pub struct CubicBezierShape {
|
||||||
pub closed: bool,
|
pub closed: bool,
|
||||||
|
|
||||||
pub fill: Color32,
|
pub fill: Color32,
|
||||||
pub stroke: Stroke,
|
pub stroke: PathStroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CubicBezierShape {
|
impl CubicBezierShape {
|
||||||
|
|
@ -32,7 +32,7 @@ impl CubicBezierShape {
|
||||||
points: [Pos2; 4],
|
points: [Pos2; 4],
|
||||||
closed: bool,
|
closed: bool,
|
||||||
fill: Color32,
|
fill: Color32,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<PathStroke>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
points,
|
points,
|
||||||
|
|
@ -52,7 +52,7 @@ impl CubicBezierShape {
|
||||||
points,
|
points,
|
||||||
closed: self.closed,
|
closed: self.closed,
|
||||||
fill: self.fill,
|
fill: self.fill,
|
||||||
stroke: self.stroke,
|
stroke: self.stroke.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ impl CubicBezierShape {
|
||||||
points,
|
points,
|
||||||
closed: self.closed,
|
closed: self.closed,
|
||||||
fill: self.fill,
|
fill: self.fill,
|
||||||
stroke: self.stroke,
|
stroke: self.stroke.clone(),
|
||||||
};
|
};
|
||||||
pathshapes.push(pathshape);
|
pathshapes.push(pathshape);
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ impl CubicBezierShape {
|
||||||
points: [d_from, d_ctrl, d_to],
|
points: [d_from, d_ctrl, d_to],
|
||||||
closed: self.closed,
|
closed: self.closed,
|
||||||
fill: self.fill,
|
fill: self.fill,
|
||||||
stroke: self.stroke,
|
stroke: self.stroke.clone(),
|
||||||
};
|
};
|
||||||
let delta_t = t_range.end - t_range.start;
|
let delta_t = t_range.end - t_range.start;
|
||||||
let q_start = q.sample(t_range.start);
|
let q_start = q.sample(t_range.start);
|
||||||
|
|
@ -168,7 +168,7 @@ impl CubicBezierShape {
|
||||||
points: [from, ctrl1, ctrl2, to],
|
points: [from, ctrl1, ctrl2, to],
|
||||||
closed: self.closed,
|
closed: self.closed,
|
||||||
fill: self.fill,
|
fill: self.fill,
|
||||||
stroke: self.stroke,
|
stroke: self.stroke.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -375,7 +375,7 @@ impl From<CubicBezierShape> for Shape {
|
||||||
/// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
/// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
||||||
///
|
///
|
||||||
/// See also [`CubicBezierShape`].
|
/// See also [`CubicBezierShape`].
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct QuadraticBezierShape {
|
pub struct QuadraticBezierShape {
|
||||||
/// The first point is the starting point and the last one is the ending point of the curve.
|
/// The first point is the starting point and the last one is the ending point of the curve.
|
||||||
|
|
@ -384,7 +384,7 @@ pub struct QuadraticBezierShape {
|
||||||
pub closed: bool,
|
pub closed: bool,
|
||||||
|
|
||||||
pub fill: Color32,
|
pub fill: Color32,
|
||||||
pub stroke: Stroke,
|
pub stroke: PathStroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QuadraticBezierShape {
|
impl QuadraticBezierShape {
|
||||||
|
|
@ -397,7 +397,7 @@ impl QuadraticBezierShape {
|
||||||
points: [Pos2; 3],
|
points: [Pos2; 3],
|
||||||
closed: bool,
|
closed: bool,
|
||||||
fill: Color32,
|
fill: Color32,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<PathStroke>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
points,
|
points,
|
||||||
|
|
@ -417,7 +417,7 @@ impl QuadraticBezierShape {
|
||||||
points,
|
points,
|
||||||
closed: self.closed,
|
closed: self.closed,
|
||||||
fill: self.fill,
|
fill: self.fill,
|
||||||
stroke: self.stroke,
|
stroke: self.stroke.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -429,7 +429,7 @@ impl QuadraticBezierShape {
|
||||||
points,
|
points,
|
||||||
closed: self.closed,
|
closed: self.closed,
|
||||||
fill: self.fill,
|
fill: self.fill,
|
||||||
stroke: self.stroke,
|
stroke: self.stroke.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -688,7 +688,7 @@ fn single_curve_approximation(curve: &CubicBezierShape) -> QuadraticBezierShape
|
||||||
points: [curve.points[0], c, curve.points[3]],
|
points: [curve.points[0], c, curve.points[3]],
|
||||||
closed: curve.closed,
|
closed: curve.closed,
|
||||||
fill: curve.fill,
|
fill: curve.fill,
|
||||||
stroke: curve.stroke,
|
stroke: curve.stroke.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
use std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
|
use ecolor::Color32;
|
||||||
|
use emath::{Pos2, Rect};
|
||||||
|
|
||||||
|
/// How paths will be colored.
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub enum ColorMode {
|
||||||
|
/// The entire path is one solid color, this is the default.
|
||||||
|
Solid(Color32),
|
||||||
|
|
||||||
|
/// Provide a callback which takes in the path's bounding box and a position and converts it to a color.
|
||||||
|
/// When used with a path, the bounding box will have a margin of [`TessellationOptions::feathering_size_in_pixels`](`crate::tessellator::TessellationOptions::feathering_size_in_pixels`)
|
||||||
|
///
|
||||||
|
/// **This cannot be serialized**
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
|
UV(Arc<dyn Fn(Rect, Pos2) -> Color32 + Send + Sync>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ColorMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Solid(Color32::TRANSPARENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for ColorMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Solid(arg0) => f.debug_tuple("Solid").field(arg0).finish(),
|
||||||
|
Self::UV(_arg0) => f.debug_tuple("UV").field(&"<closure>").finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ColorMode {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Solid(l0), Self::Solid(r0)) => l0 == r0,
|
||||||
|
(Self::UV(_l0), Self::UV(_r0)) => false,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorMode {
|
||||||
|
pub const TRANSPARENT: Self = Self::Solid(Color32::TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))]
|
#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))]
|
||||||
|
|
||||||
mod bezier;
|
mod bezier;
|
||||||
|
pub mod color;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
mod margin;
|
mod margin;
|
||||||
mod mesh;
|
mod mesh;
|
||||||
|
|
@ -44,6 +45,7 @@ pub mod util;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
bezier::{CubicBezierShape, QuadraticBezierShape},
|
bezier::{CubicBezierShape, QuadraticBezierShape},
|
||||||
|
color::ColorMode,
|
||||||
image::{ColorImage, FontImage, ImageData, ImageDelta},
|
image::{ColorImage, FontImage, ImageData, ImageDelta},
|
||||||
margin::Margin,
|
margin::Margin,
|
||||||
mesh::{Mesh, Mesh16, Vertex},
|
mesh::{Mesh, Mesh16, Vertex},
|
||||||
|
|
@ -53,7 +55,7 @@ pub use self::{
|
||||||
Rounding, Shape, TextShape,
|
Rounding, Shape, TextShape,
|
||||||
},
|
},
|
||||||
stats::PaintStats,
|
stats::PaintStats,
|
||||||
stroke::Stroke,
|
stroke::{PathStroke, Stroke},
|
||||||
tessellator::{TessellationOptions, Tessellator},
|
tessellator::{TessellationOptions, Tessellator},
|
||||||
text::{FontFamily, FontId, Fonts, Galley},
|
text::{FontFamily, FontId, Fonts, Galley},
|
||||||
texture_atlas::TextureAtlas,
|
texture_atlas::TextureAtlas,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
stroke::PathStroke,
|
||||||
text::{FontId, Fonts, Galley},
|
text::{FontId, Fonts, Galley},
|
||||||
Color32, Mesh, Stroke, TextureId,
|
Color32, Mesh, Stroke, TextureId,
|
||||||
};
|
};
|
||||||
|
|
@ -34,7 +35,10 @@ pub enum Shape {
|
||||||
Ellipse(EllipseShape),
|
Ellipse(EllipseShape),
|
||||||
|
|
||||||
/// A line between two points.
|
/// A line between two points.
|
||||||
LineSegment { points: [Pos2; 2], stroke: Stroke },
|
LineSegment {
|
||||||
|
points: [Pos2; 2],
|
||||||
|
stroke: PathStroke,
|
||||||
|
},
|
||||||
|
|
||||||
/// A series of lines between points.
|
/// A series of lines between points.
|
||||||
/// The path can have a stroke and/or fill (if closed).
|
/// The path can have a stroke and/or fill (if closed).
|
||||||
|
|
@ -88,7 +92,7 @@ impl Shape {
|
||||||
/// A line between two points.
|
/// A line between two points.
|
||||||
/// More efficient than calling [`Self::line`].
|
/// More efficient than calling [`Self::line`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
|
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<PathStroke>) -> Self {
|
||||||
Self::LineSegment {
|
Self::LineSegment {
|
||||||
points,
|
points,
|
||||||
stroke: stroke.into(),
|
stroke: stroke.into(),
|
||||||
|
|
@ -96,7 +100,7 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A horizontal line.
|
/// A horizontal line.
|
||||||
pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> Self {
|
pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<PathStroke>) -> Self {
|
||||||
let x = x.into();
|
let x = x.into();
|
||||||
Self::LineSegment {
|
Self::LineSegment {
|
||||||
points: [pos2(x.min, y), pos2(x.max, y)],
|
points: [pos2(x.min, y), pos2(x.max, y)],
|
||||||
|
|
@ -105,7 +109,7 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A vertical line.
|
/// A vertical line.
|
||||||
pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> Self {
|
pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<PathStroke>) -> Self {
|
||||||
let y = y.into();
|
let y = y.into();
|
||||||
Self::LineSegment {
|
Self::LineSegment {
|
||||||
points: [pos2(x, y.min), pos2(x, y.max)],
|
points: [pos2(x, y.min), pos2(x, y.max)],
|
||||||
|
|
@ -117,13 +121,13 @@ impl Shape {
|
||||||
///
|
///
|
||||||
/// Use [`Self::line_segment`] instead if your line only connects two points.
|
/// Use [`Self::line_segment`] instead if your line only connects two points.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||||
Self::Path(PathShape::line(points, stroke))
|
Self::Path(PathShape::line(points, stroke))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A line that closes back to the start point again.
|
/// A line that closes back to the start point again.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||||
Self::Path(PathShape::closed_line(points, stroke))
|
Self::Path(PathShape::closed_line(points, stroke))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,7 +228,7 @@ impl Shape {
|
||||||
pub fn convex_polygon(
|
pub fn convex_polygon(
|
||||||
points: Vec<Pos2>,
|
points: Vec<Pos2>,
|
||||||
fill: impl Into<Color32>,
|
fill: impl Into<Color32>,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<PathStroke>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::Path(PathShape::convex_polygon(points, fill, stroke))
|
Self::Path(PathShape::convex_polygon(points, fill, stroke))
|
||||||
}
|
}
|
||||||
|
|
@ -586,7 +590,7 @@ pub struct PathShape {
|
||||||
pub fill: Color32,
|
pub fill: Color32,
|
||||||
|
|
||||||
/// Color and thickness of the line.
|
/// Color and thickness of the line.
|
||||||
pub stroke: Stroke,
|
pub stroke: PathStroke,
|
||||||
// TODO(emilk): Add texture support either by supplying uv for each point,
|
// TODO(emilk): Add texture support either by supplying uv for each point,
|
||||||
// or by some transform from points to uv (e.g. a callback or a linear transform matrix).
|
// or by some transform from points to uv (e.g. a callback or a linear transform matrix).
|
||||||
}
|
}
|
||||||
|
|
@ -596,7 +600,7 @@ impl PathShape {
|
||||||
///
|
///
|
||||||
/// Use [`Shape::line_segment`] instead if your line only connects two points.
|
/// Use [`Shape::line_segment`] instead if your line only connects two points.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
points,
|
points,
|
||||||
closed: false,
|
closed: false,
|
||||||
|
|
@ -607,7 +611,7 @@ impl PathShape {
|
||||||
|
|
||||||
/// A line that closes back to the start point again.
|
/// A line that closes back to the start point again.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
points,
|
points,
|
||||||
closed: true,
|
closed: true,
|
||||||
|
|
@ -623,7 +627,7 @@ impl PathShape {
|
||||||
pub fn convex_polygon(
|
pub fn convex_polygon(
|
||||||
points: Vec<Pos2>,
|
points: Vec<Pos2>,
|
||||||
fill: impl Into<Color32>,
|
fill: impl Into<Color32>,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<PathStroke>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
points,
|
points,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Remember to handle [`Color32::PLACEHOLDER`] specially!
|
/// Remember to handle [`Color32::PLACEHOLDER`] specially!
|
||||||
pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
pub fn adjust_colors(
|
||||||
|
shape: &mut Shape,
|
||||||
|
adjust_color: impl Fn(&mut Color32) + Send + Sync + Copy + 'static,
|
||||||
|
) {
|
||||||
#![allow(clippy::match_same_arms)]
|
#![allow(clippy::match_same_arms)]
|
||||||
match shape {
|
match shape {
|
||||||
Shape::Noop => {}
|
Shape::Noop => {}
|
||||||
|
|
@ -10,8 +15,48 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
||||||
adjust_colors(shape, adjust_color);
|
adjust_colors(shape, adjust_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shape::LineSegment { stroke, points: _ } => {
|
Shape::LineSegment { stroke, points: _ } => match &stroke.color {
|
||||||
adjust_color(&mut stroke.color);
|
color::ColorMode::Solid(mut col) => adjust_color(&mut col),
|
||||||
|
color::ColorMode::UV(callback) => {
|
||||||
|
let callback = callback.clone();
|
||||||
|
stroke.color = color::ColorMode::UV(Arc::new(Box::new(move |rect, pos| {
|
||||||
|
let mut col = callback(rect, pos);
|
||||||
|
adjust_color(&mut col);
|
||||||
|
col
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Shape::Path(PathShape {
|
||||||
|
points: _,
|
||||||
|
closed: _,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
})
|
||||||
|
| Shape::QuadraticBezier(QuadraticBezierShape {
|
||||||
|
points: _,
|
||||||
|
closed: _,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
})
|
||||||
|
| Shape::CubicBezier(CubicBezierShape {
|
||||||
|
points: _,
|
||||||
|
closed: _,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
}) => {
|
||||||
|
adjust_color(fill);
|
||||||
|
match &stroke.color {
|
||||||
|
color::ColorMode::Solid(mut col) => adjust_color(&mut col),
|
||||||
|
color::ColorMode::UV(callback) => {
|
||||||
|
let callback = callback.clone();
|
||||||
|
stroke.color = color::ColorMode::UV(Arc::new(Box::new(move |rect, pos| {
|
||||||
|
let mut col = callback(rect, pos);
|
||||||
|
adjust_color(&mut col);
|
||||||
|
col
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape::Circle(CircleShape {
|
Shape::Circle(CircleShape {
|
||||||
|
|
@ -26,12 +71,6 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
})
|
})
|
||||||
| Shape::Path(PathShape {
|
|
||||||
points: _,
|
|
||||||
closed: _,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
})
|
|
||||||
| Shape::Rect(RectShape {
|
| Shape::Rect(RectShape {
|
||||||
rect: _,
|
rect: _,
|
||||||
rounding: _,
|
rounding: _,
|
||||||
|
|
@ -40,18 +79,6 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
||||||
blur_width: _,
|
blur_width: _,
|
||||||
fill_texture_id: _,
|
fill_texture_id: _,
|
||||||
uv: _,
|
uv: _,
|
||||||
})
|
|
||||||
| Shape::QuadraticBezier(QuadraticBezierShape {
|
|
||||||
points: _,
|
|
||||||
closed: _,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
})
|
|
||||||
| Shape::CubicBezier(CubicBezierShape {
|
|
||||||
points: _,
|
|
||||||
closed: _,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
}) => {
|
}) => {
|
||||||
adjust_color(fill);
|
adjust_color(fill);
|
||||||
adjust_color(&mut stroke.color);
|
adjust_color(&mut stroke.color);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(clippy::derived_hash_with_manual_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine
|
#![allow(clippy::derived_hash_with_manual_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine
|
||||||
|
|
||||||
|
use std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Describes the width and color of a line.
|
/// Describes the width and color of a line.
|
||||||
|
|
@ -52,3 +54,68 @@ impl std::hash::Hash for Stroke {
|
||||||
color.hash(state);
|
color.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes the width and color of paths. The color can either be solid or provided by a callback. For more information, see [`ColorMode`]
|
||||||
|
///
|
||||||
|
/// The default stroke is the same as [`Stroke::NONE`].
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub struct PathStroke {
|
||||||
|
pub width: f32,
|
||||||
|
pub color: ColorMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathStroke {
|
||||||
|
/// Same as [`PathStroke::default`].
|
||||||
|
pub const NONE: Self = Self {
|
||||||
|
width: 0.0,
|
||||||
|
color: ColorMode::TRANSPARENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn new(width: impl Into<f32>, color: impl Into<Color32>) -> Self {
|
||||||
|
Self {
|
||||||
|
width: width.into(),
|
||||||
|
color: ColorMode::Solid(color.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `PathStroke` with a UV function
|
||||||
|
///
|
||||||
|
/// The bounding box passed to the callback will have a margin of [`TessellationOptions::feathering_size_in_pixels`](`crate::tessellator::TessellationOptions::feathering_size_in_pixels`)
|
||||||
|
#[inline]
|
||||||
|
pub fn new_uv(
|
||||||
|
width: impl Into<f32>,
|
||||||
|
callback: impl Fn(Rect, Pos2) -> Color32 + Send + Sync + 'static,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
width: width.into(),
|
||||||
|
color: ColorMode::UV(Arc::new(callback)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if width is zero or color is solid and transparent
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.width <= 0.0 || self.color == ColorMode::TRANSPARENT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Color> From<(f32, Color)> for PathStroke
|
||||||
|
where
|
||||||
|
Color: Into<Color32>,
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
fn from((width, color): (f32, Color)) -> Self {
|
||||||
|
Self::new(width, color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Stroke> for PathStroke {
|
||||||
|
fn from(value: Stroke) -> Self {
|
||||||
|
Self {
|
||||||
|
width: value.width,
|
||||||
|
color: ColorMode::Solid(value.color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ use crate::texture_atlas::PreparedDisc;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use emath::*;
|
use emath::*;
|
||||||
|
|
||||||
|
use self::color::ColorMode;
|
||||||
|
use self::stroke::PathStroke;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[allow(clippy::approx_constant)]
|
#[allow(clippy::approx_constant)]
|
||||||
|
|
@ -471,16 +474,22 @@ impl Path {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open-ended.
|
/// 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);
|
stroke_path(feathering, &self.0, PathType::Open, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A closed path (returning to the first point).
|
/// 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);
|
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);
|
stroke_path(feathering, &self.0, path_type, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -864,19 +873,28 @@ fn stroke_path(
|
||||||
feathering: f32,
|
feathering: f32,
|
||||||
path: &[PathPoint],
|
path: &[PathPoint],
|
||||||
path_type: PathType,
|
path_type: PathType,
|
||||||
stroke: Stroke,
|
stroke: &PathStroke,
|
||||||
out: &mut Mesh,
|
out: &mut Mesh,
|
||||||
) {
|
) {
|
||||||
let n = path.len() as u32;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx = out.vertices.len() as u32;
|
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 {
|
if feathering > 0.0 {
|
||||||
let color_inner = stroke.color;
|
let color_inner = &stroke.color;
|
||||||
let color_outer = Color32::TRANSPARENT;
|
let color_outer = Color32::TRANSPARENT;
|
||||||
|
|
||||||
let thin_line = stroke.width <= feathering;
|
let thin_line = stroke.width <= feathering;
|
||||||
|
|
@ -889,9 +907,11 @@ fn stroke_path(
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Fade out as it gets thinner:
|
// Fade out as it gets thinner:
|
||||||
let color_inner = mul_color(color_inner, stroke.width / feathering);
|
if let ColorMode::Solid(col) = color_inner {
|
||||||
if color_inner == Color32::TRANSPARENT {
|
let color_inner = mul_color(*col, stroke.width / feathering);
|
||||||
return;
|
if color_inner == Color32::TRANSPARENT {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out.reserve_triangles(4 * n as usize);
|
out.reserve_triangles(4 * n as usize);
|
||||||
|
|
@ -904,7 +924,10 @@ fn stroke_path(
|
||||||
let p = p1.pos;
|
let p = p1.pos;
|
||||||
let n = p1.normal;
|
let n = p1.normal;
|
||||||
out.colored_vertex(p + n * feathering, color_outer);
|
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);
|
out.colored_vertex(p - n * feathering, color_outer);
|
||||||
|
|
||||||
if connect_with_previous {
|
if connect_with_previous {
|
||||||
|
|
@ -943,8 +966,14 @@ fn stroke_path(
|
||||||
let p = p1.pos;
|
let p = p1.pos;
|
||||||
let n = p1.normal;
|
let n = p1.normal;
|
||||||
out.colored_vertex(p + n * outer_rad, color_outer);
|
out.colored_vertex(p + n * outer_rad, color_outer);
|
||||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
out.colored_vertex(
|
||||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
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.colored_vertex(p - n * outer_rad, color_outer);
|
||||||
|
|
||||||
out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
|
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 n = end.normal;
|
||||||
let back_extrude = n.rot90() * feathering;
|
let back_extrude = n.rot90() * feathering;
|
||||||
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
||||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
out.colored_vertex(
|
||||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
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.colored_vertex(p - n * outer_rad + back_extrude, color_outer);
|
||||||
|
|
||||||
out.add_triangle(idx + 0, idx + 1, idx + 2);
|
out.add_triangle(idx + 0, idx + 1, idx + 2);
|
||||||
|
|
@ -997,8 +1032,14 @@ fn stroke_path(
|
||||||
let p = point.pos;
|
let p = point.pos;
|
||||||
let n = point.normal;
|
let n = point.normal;
|
||||||
out.colored_vertex(p + n * outer_rad, color_outer);
|
out.colored_vertex(p + n * outer_rad, color_outer);
|
||||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
out.colored_vertex(
|
||||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
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.colored_vertex(p - n * outer_rad, color_outer);
|
||||||
|
|
||||||
out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
|
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 n = end.normal;
|
||||||
let back_extrude = -n.rot90() * feathering;
|
let back_extrude = -n.rot90() * feathering;
|
||||||
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
||||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
out.colored_vertex(
|
||||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
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.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);
|
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 {
|
if thin_line {
|
||||||
// Fade out thin lines rather than making them thinner
|
// Fade out thin lines rather than making them thinner
|
||||||
let radius = feathering / 2.0;
|
let radius = feathering / 2.0;
|
||||||
let color = mul_color(stroke.color, stroke.width / feathering);
|
if let ColorMode::Solid(color) = stroke.color {
|
||||||
if color == Color32::TRANSPARENT {
|
let color = mul_color(color, stroke.width / feathering);
|
||||||
return;
|
if color == Color32::TRANSPARENT {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for p in path {
|
for p in path {
|
||||||
out.colored_vertex(p.pos + radius * p.normal, color);
|
out.colored_vertex(
|
||||||
out.colored_vertex(p.pos - radius * p.normal, color);
|
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 {
|
} else {
|
||||||
let radius = stroke.width / 2.0;
|
let radius = stroke.width / 2.0;
|
||||||
for p in path {
|
for p in path {
|
||||||
out.colored_vertex(p.pos + radius * p.normal, stroke.color);
|
out.colored_vertex(
|
||||||
out.colored_vertex(p.pos - radius * p.normal, stroke.color);
|
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);
|
self.tessellate_text(&text_shape, out);
|
||||||
}
|
}
|
||||||
Shape::QuadraticBezier(quadratic_shape) => {
|
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(_) => {
|
Shape::Callback(_) => {
|
||||||
panic!("Shape::Callback passed to Tessellator");
|
panic!("Shape::Callback passed to Tessellator");
|
||||||
}
|
}
|
||||||
|
|
@ -1337,7 +1404,7 @@ impl Tessellator {
|
||||||
self.scratchpad_path.add_circle(center, radius);
|
self.scratchpad_path.add_circle(center, radius);
|
||||||
self.scratchpad_path.fill(self.feathering, fill, out);
|
self.scratchpad_path.fill(self.feathering, fill, out);
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.stroke_closed(self.feathering, stroke, out);
|
.stroke_closed(self.feathering, &stroke.into(), out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`EllipseShape`] into a [`Mesh`].
|
/// Tessellate a single [`EllipseShape`] into a [`Mesh`].
|
||||||
|
|
@ -1404,7 +1471,7 @@ impl Tessellator {
|
||||||
self.scratchpad_path.add_line_loop(&points);
|
self.scratchpad_path.add_line_loop(&points);
|
||||||
self.scratchpad_path.fill(self.feathering, fill, out);
|
self.scratchpad_path.fill(self.feathering, fill, out);
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.stroke_closed(self.feathering, stroke, out);
|
.stroke_closed(self.feathering, &stroke.into(), out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`Mesh`] into a [`Mesh`].
|
/// Tessellate a single [`Mesh`] into a [`Mesh`].
|
||||||
|
|
@ -1430,7 +1497,13 @@ impl Tessellator {
|
||||||
///
|
///
|
||||||
/// * `shape`: the mesh to tessellate.
|
/// * `shape`: the mesh to tessellate.
|
||||||
/// * `out`: triangles are appended to this.
|
/// * `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() {
|
if stroke.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1446,7 +1519,7 @@ impl Tessellator {
|
||||||
self.scratchpad_path.clear();
|
self.scratchpad_path.clear();
|
||||||
self.scratchpad_path.add_line_segment(points);
|
self.scratchpad_path.add_line_segment(points);
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.stroke_open(self.feathering, stroke, out);
|
.stroke_open(self.feathering, &stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`PathShape`] into a [`Mesh`].
|
/// Tessellate a single [`PathShape`] into a [`Mesh`].
|
||||||
|
|
@ -1493,7 +1566,7 @@ impl Tessellator {
|
||||||
PathType::Open
|
PathType::Open
|
||||||
};
|
};
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.stroke(self.feathering, typ, *stroke, out);
|
.stroke(self.feathering, typ, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`Rect`] into a [`Mesh`].
|
/// Tessellate a single [`Rect`] into a [`Mesh`].
|
||||||
|
|
@ -1588,7 +1661,7 @@ impl Tessellator {
|
||||||
path.fill(self.feathering, fill, out);
|
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
|
self.feathering = old_feathering; // restore
|
||||||
|
|
@ -1707,8 +1780,11 @@ impl Tessellator {
|
||||||
self.scratchpad_path.clear();
|
self.scratchpad_path.clear();
|
||||||
self.scratchpad_path
|
self.scratchpad_path
|
||||||
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
|
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
|
||||||
self.scratchpad_path
|
self.scratchpad_path.stroke_open(
|
||||||
.stroke_open(self.feathering, *underline, out);
|
self.feathering,
|
||||||
|
&PathStroke::from(*underline),
|
||||||
|
out,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1719,7 +1795,7 @@ impl Tessellator {
|
||||||
/// * `out`: triangles are appended to this.
|
/// * `out`: triangles are appended to this.
|
||||||
pub fn tessellate_quadratic_bezier(
|
pub fn tessellate_quadratic_bezier(
|
||||||
&mut self,
|
&mut self,
|
||||||
quadratic_shape: QuadraticBezierShape,
|
quadratic_shape: &QuadraticBezierShape,
|
||||||
out: &mut Mesh,
|
out: &mut Mesh,
|
||||||
) {
|
) {
|
||||||
let options = &self.options;
|
let options = &self.options;
|
||||||
|
|
@ -1737,7 +1813,7 @@ impl Tessellator {
|
||||||
&points,
|
&points,
|
||||||
quadratic_shape.fill,
|
quadratic_shape.fill,
|
||||||
quadratic_shape.closed,
|
quadratic_shape.closed,
|
||||||
quadratic_shape.stroke,
|
&quadratic_shape.stroke,
|
||||||
out,
|
out,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1746,7 +1822,7 @@ impl Tessellator {
|
||||||
///
|
///
|
||||||
/// * `cubic_shape`: the shape to tessellate.
|
/// * `cubic_shape`: the shape to tessellate.
|
||||||
/// * `out`: triangles are appended to this.
|
/// * `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 options = &self.options;
|
||||||
let clip_rect = self.clip_rect;
|
let clip_rect = self.clip_rect;
|
||||||
if options.coarse_tessellation_culling
|
if options.coarse_tessellation_culling
|
||||||
|
|
@ -1763,7 +1839,7 @@ impl Tessellator {
|
||||||
&points,
|
&points,
|
||||||
cubic_shape.fill,
|
cubic_shape.fill,
|
||||||
cubic_shape.closed,
|
cubic_shape.closed,
|
||||||
cubic_shape.stroke,
|
&cubic_shape.stroke,
|
||||||
out,
|
out,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1774,7 +1850,7 @@ impl Tessellator {
|
||||||
points: &[Pos2],
|
points: &[Pos2],
|
||||||
fill: Color32,
|
fill: Color32,
|
||||||
closed: bool,
|
closed: bool,
|
||||||
stroke: Stroke,
|
stroke: &PathStroke,
|
||||||
out: &mut Mesh,
|
out: &mut Mesh,
|
||||||
) {
|
) {
|
||||||
if points.len() < 2 {
|
if points.len() < 2 {
|
||||||
|
|
@ -1985,3 +2061,48 @@ fn test_tessellator() {
|
||||||
|
|
||||||
assert_eq!(primitives.len(), 2);
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use emath::*;
|
use emath::*;
|
||||||
|
|
||||||
use crate::{text::font::Font, Color32, Mesh, Stroke, Vertex};
|
use crate::{stroke::PathStroke, text::font::Font, Color32, Mesh, Stroke, Vertex};
|
||||||
|
|
||||||
use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals};
|
use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals};
|
||||||
|
|
||||||
|
|
@ -853,7 +853,7 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke,
|
||||||
let mut path = crate::tessellator::Path::default(); // TODO(emilk): reuse this to avoid re-allocations.
|
let mut path = crate::tessellator::Path::default(); // TODO(emilk): reuse this to avoid re-allocations.
|
||||||
path.add_line_segment([start, stop]);
|
path.add_line_segment([start, stop]);
|
||||||
let feathering = 1.0 / point_scale.pixels_per_point();
|
let feathering = 1.0 / point_scale.pixels_per_point();
|
||||||
path.stroke_open(feathering, stroke, mesh);
|
path.stroke_open(feathering, &PathStroke::from(stroke), mesh);
|
||||||
} else {
|
} else {
|
||||||
// Thin lines often lost, so this is a bad idea
|
// Thin lines often lost, so this is a bad idea
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue