Skip to content

Commit

Permalink
Added ability to define colors at UV coordinates along a path (#4353)
Browse files Browse the repository at this point in the history
<!--
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>
  • Loading branch information
murl-digital and emilk authored Apr 22, 2024
1 parent ff8cfc2 commit 2ce82cc
Show file tree
Hide file tree
Showing 13 changed files with 550 additions and 104 deletions.
1 change: 0 additions & 1 deletion crates/ecolor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ extra_debug_asserts = []
## Always enable additional checks.
extra_asserts = []


[dependencies]
#! ### Optional dependencies

Expand Down
16 changes: 8 additions & 8 deletions crates/egui/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -280,21 +280,21 @@ 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(),
})
}

/// 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(
Expand Down Expand Up @@ -513,15 +513,15 @@ 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);
}
});
}

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);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }

Expand Down
28 changes: 25 additions & 3 deletions crates/egui_demo_lib/src/demo/dancing_strings.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
160 changes: 158 additions & 2 deletions crates/epaint/benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -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| {
Expand Down Expand Up @@ -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);
Loading

0 comments on commit 2ce82cc

Please sign in to comment.