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.
|
||||
extra_asserts = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
#! ### Optional dependencies
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
};
|
||||
use epaint::{
|
||||
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.
|
||||
|
|
@ -280,7 +280,7 @@ impl Painter {
|
|||
/// # Paint different primitives
|
||||
impl Painter {
|
||||
/// 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 {
|
||||
points,
|
||||
stroke: stroke.into(),
|
||||
|
|
@ -288,13 +288,13 @@ impl Painter {
|
|||
}
|
||||
|
||||
/// Paints a horizontal line.
|
||||
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> ShapeIdx {
|
||||
self.add(Shape::hline(x, y, stroke))
|
||||
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<PathStroke>) -> ShapeIdx {
|
||||
self.add(Shape::hline(x, y, stroke.into()))
|
||||
}
|
||||
|
||||
/// Paints a vertical line.
|
||||
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> ShapeIdx {
|
||||
self.add(Shape::vline(x, y, stroke))
|
||||
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<PathStroke>) -> ShapeIdx {
|
||||
self.add(Shape::vline(x, y, stroke.into()))
|
||||
}
|
||||
|
||||
pub fn circle(
|
||||
|
|
@ -513,7 +513,7 @@ impl Painter {
|
|||
}
|
||||
|
||||
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 {
|
||||
*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) {
|
||||
epaint::shape_transform::adjust_colors(shape, &|color| {
|
||||
epaint::shape_transform::adjust_colors(shape, move |color| {
|
||||
if *color != Color32::PLACEHOLDER {
|
||||
*color = color.gamma_multiply(opacity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ syntect = ["egui_extras/syntect"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
egui = { workspace = true, default-features = false }
|
||||
egui = { workspace = true, default-features = false, features = ["color-hex"] }
|
||||
egui_extras = { workspace = true, features = ["default"] }
|
||||
egui_plot = { workspace = true, features = ["default"] }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use egui::{containers::*, *};
|
||||
use egui::{containers::*, epaint::PathStroke, *};
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct DancingStrings {}
|
||||
pub struct DancingStrings {
|
||||
colors: bool,
|
||||
}
|
||||
|
||||
impl super::Demo for DancingStrings {
|
||||
fn name(&self) -> &'static str {
|
||||
|
|
@ -28,6 +30,9 @@ impl super::View for DancingStrings {
|
|||
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| {
|
||||
ui.ctx().request_repaint();
|
||||
let time = ui.input(|i| i.time);
|
||||
|
|
@ -55,7 +60,24 @@ impl super::View for DancingStrings {
|
|||
.collect();
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use epaint::*;
|
||||
use epaint::{tessellator::Path, *};
|
||||
|
||||
fn single_dashed_lines(c: &mut Criterion) {
|
||||
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!(
|
||||
benches,
|
||||
single_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);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{shape::Shape, Color32, PathShape, Stroke};
|
||||
use crate::{shape::Shape, Color32, PathShape, PathStroke};
|
||||
use emath::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -11,7 +11,7 @@ use emath::*;
|
|||
/// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
||||
///
|
||||
/// See also [`QuadraticBezierShape`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct CubicBezierShape {
|
||||
/// 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 fill: Color32,
|
||||
pub stroke: Stroke,
|
||||
pub stroke: PathStroke,
|
||||
}
|
||||
|
||||
impl CubicBezierShape {
|
||||
|
|
@ -32,7 +32,7 @@ impl CubicBezierShape {
|
|||
points: [Pos2; 4],
|
||||
closed: bool,
|
||||
fill: Color32,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke: impl Into<PathStroke>,
|
||||
) -> Self {
|
||||
Self {
|
||||
points,
|
||||
|
|
@ -52,7 +52,7 @@ impl CubicBezierShape {
|
|||
points,
|
||||
closed: self.closed,
|
||||
fill: self.fill,
|
||||
stroke: self.stroke,
|
||||
stroke: self.stroke.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ impl CubicBezierShape {
|
|||
points,
|
||||
closed: self.closed,
|
||||
fill: self.fill,
|
||||
stroke: self.stroke,
|
||||
stroke: self.stroke.clone(),
|
||||
};
|
||||
pathshapes.push(pathshape);
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ impl CubicBezierShape {
|
|||
points: [d_from, d_ctrl, d_to],
|
||||
closed: self.closed,
|
||||
fill: self.fill,
|
||||
stroke: self.stroke,
|
||||
stroke: self.stroke.clone(),
|
||||
};
|
||||
let delta_t = t_range.end - t_range.start;
|
||||
let q_start = q.sample(t_range.start);
|
||||
|
|
@ -168,7 +168,7 @@ impl CubicBezierShape {
|
|||
points: [from, ctrl1, ctrl2, to],
|
||||
closed: self.closed,
|
||||
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).
|
||||
///
|
||||
/// See also [`CubicBezierShape`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct QuadraticBezierShape {
|
||||
/// 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 fill: Color32,
|
||||
pub stroke: Stroke,
|
||||
pub stroke: PathStroke,
|
||||
}
|
||||
|
||||
impl QuadraticBezierShape {
|
||||
|
|
@ -397,7 +397,7 @@ impl QuadraticBezierShape {
|
|||
points: [Pos2; 3],
|
||||
closed: bool,
|
||||
fill: Color32,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke: impl Into<PathStroke>,
|
||||
) -> Self {
|
||||
Self {
|
||||
points,
|
||||
|
|
@ -417,7 +417,7 @@ impl QuadraticBezierShape {
|
|||
points,
|
||||
closed: self.closed,
|
||||
fill: self.fill,
|
||||
stroke: self.stroke,
|
||||
stroke: self.stroke.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -429,7 +429,7 @@ impl QuadraticBezierShape {
|
|||
points,
|
||||
closed: self.closed,
|
||||
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]],
|
||||
closed: curve.closed,
|
||||
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))]
|
||||
|
||||
mod bezier;
|
||||
pub mod color;
|
||||
pub mod image;
|
||||
mod margin;
|
||||
mod mesh;
|
||||
|
|
@ -44,6 +45,7 @@ pub mod util;
|
|||
|
||||
pub use self::{
|
||||
bezier::{CubicBezierShape, QuadraticBezierShape},
|
||||
color::ColorMode,
|
||||
image::{ColorImage, FontImage, ImageData, ImageDelta},
|
||||
margin::Margin,
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
|
|
@ -53,7 +55,7 @@ pub use self::{
|
|||
Rounding, Shape, TextShape,
|
||||
},
|
||||
stats::PaintStats,
|
||||
stroke::Stroke,
|
||||
stroke::{PathStroke, Stroke},
|
||||
tessellator::{TessellationOptions, Tessellator},
|
||||
text::{FontFamily, FontId, Fonts, Galley},
|
||||
texture_atlas::TextureAtlas,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
stroke::PathStroke,
|
||||
text::{FontId, Fonts, Galley},
|
||||
Color32, Mesh, Stroke, TextureId,
|
||||
};
|
||||
|
|
@ -34,7 +35,10 @@ pub enum Shape {
|
|||
Ellipse(EllipseShape),
|
||||
|
||||
/// A line between two points.
|
||||
LineSegment { points: [Pos2; 2], stroke: Stroke },
|
||||
LineSegment {
|
||||
points: [Pos2; 2],
|
||||
stroke: PathStroke,
|
||||
},
|
||||
|
||||
/// A series of lines between points.
|
||||
/// The path can have a stroke and/or fill (if closed).
|
||||
|
|
@ -88,7 +92,7 @@ impl Shape {
|
|||
/// A line between two points.
|
||||
/// More efficient than calling [`Self::line`].
|
||||
#[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 {
|
||||
points,
|
||||
stroke: stroke.into(),
|
||||
|
|
@ -96,7 +100,7 @@ impl Shape {
|
|||
}
|
||||
|
||||
/// 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();
|
||||
Self::LineSegment {
|
||||
points: [pos2(x.min, y), pos2(x.max, y)],
|
||||
|
|
@ -105,7 +109,7 @@ impl Shape {
|
|||
}
|
||||
|
||||
/// 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();
|
||||
Self::LineSegment {
|
||||
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.
|
||||
#[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))
|
||||
}
|
||||
|
||||
/// A line that closes back to the start point again.
|
||||
#[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))
|
||||
}
|
||||
|
||||
|
|
@ -224,7 +228,7 @@ impl Shape {
|
|||
pub fn convex_polygon(
|
||||
points: Vec<Pos2>,
|
||||
fill: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke: impl Into<PathStroke>,
|
||||
) -> Self {
|
||||
Self::Path(PathShape::convex_polygon(points, fill, stroke))
|
||||
}
|
||||
|
|
@ -586,7 +590,7 @@ pub struct PathShape {
|
|||
pub fill: Color32,
|
||||
|
||||
/// Color and thickness of the line.
|
||||
pub stroke: Stroke,
|
||||
pub stroke: PathStroke,
|
||||
// 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).
|
||||
}
|
||||
|
|
@ -596,7 +600,7 @@ impl PathShape {
|
|||
///
|
||||
/// Use [`Shape::line_segment`] instead if your line only connects two points.
|
||||
#[inline]
|
||||
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
||||
pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
|
||||
Self {
|
||||
points,
|
||||
closed: false,
|
||||
|
|
@ -607,7 +611,7 @@ impl PathShape {
|
|||
|
||||
/// A line that closes back to the start point again.
|
||||
#[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 {
|
||||
points,
|
||||
closed: true,
|
||||
|
|
@ -623,7 +627,7 @@ impl PathShape {
|
|||
pub fn convex_polygon(
|
||||
points: Vec<Pos2>,
|
||||
fill: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke: impl Into<PathStroke>,
|
||||
) -> Self {
|
||||
Self {
|
||||
points,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// 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)]
|
||||
match shape {
|
||||
Shape::Noop => {}
|
||||
|
|
@ -10,8 +15,48 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
|||
adjust_colors(shape, adjust_color);
|
||||
}
|
||||
}
|
||||
Shape::LineSegment { stroke, points: _ } => {
|
||||
adjust_color(&mut stroke.color);
|
||||
Shape::LineSegment { stroke, points: _ } => 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::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 {
|
||||
|
|
@ -26,12 +71,6 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
|||
fill,
|
||||
stroke,
|
||||
})
|
||||
| Shape::Path(PathShape {
|
||||
points: _,
|
||||
closed: _,
|
||||
fill,
|
||||
stroke,
|
||||
})
|
||||
| Shape::Rect(RectShape {
|
||||
rect: _,
|
||||
rounding: _,
|
||||
|
|
@ -40,18 +79,6 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
|||
blur_width: _,
|
||||
fill_texture_id: _,
|
||||
uv: _,
|
||||
})
|
||||
| Shape::QuadraticBezier(QuadraticBezierShape {
|
||||
points: _,
|
||||
closed: _,
|
||||
fill,
|
||||
stroke,
|
||||
})
|
||||
| Shape::CubicBezier(CubicBezierShape {
|
||||
points: _,
|
||||
closed: _,
|
||||
fill,
|
||||
stroke,
|
||||
}) => {
|
||||
adjust_color(fill);
|
||||
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
|
||||
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Describes the width and color of a line.
|
||||
|
|
@ -52,3 +54,68 @@ impl std::hash::Hash for Stroke {
|
|||
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 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
|
||||
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};
|
||||
|
||||
|
|
@ -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.
|
||||
path.add_line_segment([start, stop]);
|
||||
let feathering = 1.0 / point_scale.pixels_per_point();
|
||||
path.stroke_open(feathering, stroke, mesh);
|
||||
path.stroke_open(feathering, &PathStroke::from(stroke), mesh);
|
||||
} else {
|
||||
// Thin lines often lost, so this is a bad idea
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue