2020-08-21 16:53:43 +00:00
|
|
|
//! Converts graphics primitives into textured triangles.
|
|
|
|
//!
|
2021-01-10 10:43:01 +00:00
|
|
|
//! This module converts lines, circles, text and more represented by [`Shape`]
|
2021-01-25 20:23:24 +00:00
|
|
|
//! into textured triangles represented by [`Mesh`].
|
2020-08-21 16:53:43 +00:00
|
|
|
|
2020-07-30 10:30:20 +00:00
|
|
|
#![allow(clippy::identity_op)]
|
|
|
|
|
2022-05-10 17:31:19 +00:00
|
|
|
use crate::texture_atlas::PreparedDisc;
|
2021-03-30 19:41:39 +00:00
|
|
|
use crate::*;
|
2021-01-10 14:42:46 +00:00
|
|
|
use emath::*;
|
2022-04-30 11:14:33 +00:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#[allow(clippy::approx_constant)]
|
|
|
|
mod precomputed_vertices {
|
|
|
|
/*
|
|
|
|
fn main() {
|
|
|
|
let n = 64;
|
|
|
|
println!("pub const CIRCLE_{}: [Vec2; {}] = [", n, n+1);
|
|
|
|
for i in 0..=n {
|
|
|
|
let a = std::f64::consts::TAU * i as f64 / n as f64;
|
|
|
|
println!(" vec2({:.06}, {:.06}),", a.cos(), a.sin());
|
|
|
|
}
|
|
|
|
println!("];")
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
use emath::{vec2, Vec2};
|
|
|
|
|
|
|
|
pub const CIRCLE_8: [Vec2; 9] = [
|
|
|
|
vec2(1.000000, 0.000000),
|
|
|
|
vec2(0.707107, 0.707107),
|
|
|
|
vec2(0.000000, 1.000000),
|
|
|
|
vec2(-0.707107, 0.707107),
|
|
|
|
vec2(-1.000000, 0.000000),
|
|
|
|
vec2(-0.707107, -0.707107),
|
|
|
|
vec2(0.000000, -1.000000),
|
|
|
|
vec2(0.707107, -0.707107),
|
|
|
|
vec2(1.000000, 0.000000),
|
|
|
|
];
|
|
|
|
|
|
|
|
pub const CIRCLE_16: [Vec2; 17] = [
|
|
|
|
vec2(1.000000, 0.000000),
|
|
|
|
vec2(0.923880, 0.382683),
|
|
|
|
vec2(0.707107, 0.707107),
|
|
|
|
vec2(0.382683, 0.923880),
|
|
|
|
vec2(0.000000, 1.000000),
|
|
|
|
vec2(-0.382684, 0.923880),
|
|
|
|
vec2(-0.707107, 0.707107),
|
|
|
|
vec2(-0.923880, 0.382683),
|
|
|
|
vec2(-1.000000, 0.000000),
|
|
|
|
vec2(-0.923880, -0.382683),
|
|
|
|
vec2(-0.707107, -0.707107),
|
|
|
|
vec2(-0.382684, -0.923880),
|
|
|
|
vec2(0.000000, -1.000000),
|
|
|
|
vec2(0.382684, -0.923879),
|
|
|
|
vec2(0.707107, -0.707107),
|
|
|
|
vec2(0.923880, -0.382683),
|
|
|
|
vec2(1.000000, 0.000000),
|
|
|
|
];
|
|
|
|
|
|
|
|
pub const CIRCLE_32: [Vec2; 33] = [
|
|
|
|
vec2(1.000000, 0.000000),
|
|
|
|
vec2(0.980785, 0.195090),
|
|
|
|
vec2(0.923880, 0.382683),
|
|
|
|
vec2(0.831470, 0.555570),
|
|
|
|
vec2(0.707107, 0.707107),
|
|
|
|
vec2(0.555570, 0.831470),
|
|
|
|
vec2(0.382683, 0.923880),
|
|
|
|
vec2(0.195090, 0.980785),
|
|
|
|
vec2(0.000000, 1.000000),
|
|
|
|
vec2(-0.195090, 0.980785),
|
|
|
|
vec2(-0.382683, 0.923880),
|
|
|
|
vec2(-0.555570, 0.831470),
|
|
|
|
vec2(-0.707107, 0.707107),
|
|
|
|
vec2(-0.831470, 0.555570),
|
|
|
|
vec2(-0.923880, 0.382683),
|
|
|
|
vec2(-0.980785, 0.195090),
|
|
|
|
vec2(-1.000000, 0.000000),
|
|
|
|
vec2(-0.980785, -0.195090),
|
|
|
|
vec2(-0.923880, -0.382683),
|
|
|
|
vec2(-0.831470, -0.555570),
|
|
|
|
vec2(-0.707107, -0.707107),
|
|
|
|
vec2(-0.555570, -0.831470),
|
|
|
|
vec2(-0.382683, -0.923880),
|
|
|
|
vec2(-0.195090, -0.980785),
|
|
|
|
vec2(-0.000000, -1.000000),
|
|
|
|
vec2(0.195090, -0.980785),
|
|
|
|
vec2(0.382683, -0.923880),
|
|
|
|
vec2(0.555570, -0.831470),
|
|
|
|
vec2(0.707107, -0.707107),
|
|
|
|
vec2(0.831470, -0.555570),
|
|
|
|
vec2(0.923880, -0.382683),
|
|
|
|
vec2(0.980785, -0.195090),
|
|
|
|
vec2(1.000000, -0.000000),
|
|
|
|
];
|
|
|
|
|
|
|
|
pub const CIRCLE_64: [Vec2; 65] = [
|
|
|
|
vec2(1.000000, 0.000000),
|
|
|
|
vec2(0.995185, 0.098017),
|
|
|
|
vec2(0.980785, 0.195090),
|
|
|
|
vec2(0.956940, 0.290285),
|
|
|
|
vec2(0.923880, 0.382683),
|
|
|
|
vec2(0.881921, 0.471397),
|
|
|
|
vec2(0.831470, 0.555570),
|
|
|
|
vec2(0.773010, 0.634393),
|
|
|
|
vec2(0.707107, 0.707107),
|
|
|
|
vec2(0.634393, 0.773010),
|
|
|
|
vec2(0.555570, 0.831470),
|
|
|
|
vec2(0.471397, 0.881921),
|
|
|
|
vec2(0.382683, 0.923880),
|
|
|
|
vec2(0.290285, 0.956940),
|
|
|
|
vec2(0.195090, 0.980785),
|
|
|
|
vec2(0.098017, 0.995185),
|
|
|
|
vec2(0.000000, 1.000000),
|
|
|
|
vec2(-0.098017, 0.995185),
|
|
|
|
vec2(-0.195090, 0.980785),
|
|
|
|
vec2(-0.290285, 0.956940),
|
|
|
|
vec2(-0.382683, 0.923880),
|
|
|
|
vec2(-0.471397, 0.881921),
|
|
|
|
vec2(-0.555570, 0.831470),
|
|
|
|
vec2(-0.634393, 0.773010),
|
|
|
|
vec2(-0.707107, 0.707107),
|
|
|
|
vec2(-0.773010, 0.634393),
|
|
|
|
vec2(-0.831470, 0.555570),
|
|
|
|
vec2(-0.881921, 0.471397),
|
|
|
|
vec2(-0.923880, 0.382683),
|
|
|
|
vec2(-0.956940, 0.290285),
|
|
|
|
vec2(-0.980785, 0.195090),
|
|
|
|
vec2(-0.995185, 0.098017),
|
|
|
|
vec2(-1.000000, 0.000000),
|
|
|
|
vec2(-0.995185, -0.098017),
|
|
|
|
vec2(-0.980785, -0.195090),
|
|
|
|
vec2(-0.956940, -0.290285),
|
|
|
|
vec2(-0.923880, -0.382683),
|
|
|
|
vec2(-0.881921, -0.471397),
|
|
|
|
vec2(-0.831470, -0.555570),
|
|
|
|
vec2(-0.773010, -0.634393),
|
|
|
|
vec2(-0.707107, -0.707107),
|
|
|
|
vec2(-0.634393, -0.773010),
|
|
|
|
vec2(-0.555570, -0.831470),
|
|
|
|
vec2(-0.471397, -0.881921),
|
|
|
|
vec2(-0.382683, -0.923880),
|
|
|
|
vec2(-0.290285, -0.956940),
|
|
|
|
vec2(-0.195090, -0.980785),
|
|
|
|
vec2(-0.098017, -0.995185),
|
|
|
|
vec2(-0.000000, -1.000000),
|
|
|
|
vec2(0.098017, -0.995185),
|
|
|
|
vec2(0.195090, -0.980785),
|
|
|
|
vec2(0.290285, -0.956940),
|
|
|
|
vec2(0.382683, -0.923880),
|
|
|
|
vec2(0.471397, -0.881921),
|
|
|
|
vec2(0.555570, -0.831470),
|
|
|
|
vec2(0.634393, -0.773010),
|
|
|
|
vec2(0.707107, -0.707107),
|
|
|
|
vec2(0.773010, -0.634393),
|
|
|
|
vec2(0.831470, -0.555570),
|
|
|
|
vec2(0.881921, -0.471397),
|
|
|
|
vec2(0.923880, -0.382683),
|
|
|
|
vec2(0.956940, -0.290285),
|
|
|
|
vec2(0.980785, -0.195090),
|
|
|
|
vec2(0.995185, -0.098017),
|
|
|
|
vec2(1.000000, -0.000000),
|
|
|
|
];
|
|
|
|
|
|
|
|
pub const CIRCLE_128: [Vec2; 129] = [
|
|
|
|
vec2(1.000000, 0.000000),
|
|
|
|
vec2(0.998795, 0.049068),
|
|
|
|
vec2(0.995185, 0.098017),
|
|
|
|
vec2(0.989177, 0.146730),
|
|
|
|
vec2(0.980785, 0.195090),
|
|
|
|
vec2(0.970031, 0.242980),
|
|
|
|
vec2(0.956940, 0.290285),
|
|
|
|
vec2(0.941544, 0.336890),
|
|
|
|
vec2(0.923880, 0.382683),
|
|
|
|
vec2(0.903989, 0.427555),
|
|
|
|
vec2(0.881921, 0.471397),
|
|
|
|
vec2(0.857729, 0.514103),
|
|
|
|
vec2(0.831470, 0.555570),
|
|
|
|
vec2(0.803208, 0.595699),
|
|
|
|
vec2(0.773010, 0.634393),
|
|
|
|
vec2(0.740951, 0.671559),
|
|
|
|
vec2(0.707107, 0.707107),
|
|
|
|
vec2(0.671559, 0.740951),
|
|
|
|
vec2(0.634393, 0.773010),
|
|
|
|
vec2(0.595699, 0.803208),
|
|
|
|
vec2(0.555570, 0.831470),
|
|
|
|
vec2(0.514103, 0.857729),
|
|
|
|
vec2(0.471397, 0.881921),
|
|
|
|
vec2(0.427555, 0.903989),
|
|
|
|
vec2(0.382683, 0.923880),
|
|
|
|
vec2(0.336890, 0.941544),
|
|
|
|
vec2(0.290285, 0.956940),
|
|
|
|
vec2(0.242980, 0.970031),
|
|
|
|
vec2(0.195090, 0.980785),
|
|
|
|
vec2(0.146730, 0.989177),
|
|
|
|
vec2(0.098017, 0.995185),
|
|
|
|
vec2(0.049068, 0.998795),
|
|
|
|
vec2(0.000000, 1.000000),
|
|
|
|
vec2(-0.049068, 0.998795),
|
|
|
|
vec2(-0.098017, 0.995185),
|
|
|
|
vec2(-0.146730, 0.989177),
|
|
|
|
vec2(-0.195090, 0.980785),
|
|
|
|
vec2(-0.242980, 0.970031),
|
|
|
|
vec2(-0.290285, 0.956940),
|
|
|
|
vec2(-0.336890, 0.941544),
|
|
|
|
vec2(-0.382683, 0.923880),
|
|
|
|
vec2(-0.427555, 0.903989),
|
|
|
|
vec2(-0.471397, 0.881921),
|
|
|
|
vec2(-0.514103, 0.857729),
|
|
|
|
vec2(-0.555570, 0.831470),
|
|
|
|
vec2(-0.595699, 0.803208),
|
|
|
|
vec2(-0.634393, 0.773010),
|
|
|
|
vec2(-0.671559, 0.740951),
|
|
|
|
vec2(-0.707107, 0.707107),
|
|
|
|
vec2(-0.740951, 0.671559),
|
|
|
|
vec2(-0.773010, 0.634393),
|
|
|
|
vec2(-0.803208, 0.595699),
|
|
|
|
vec2(-0.831470, 0.555570),
|
|
|
|
vec2(-0.857729, 0.514103),
|
|
|
|
vec2(-0.881921, 0.471397),
|
|
|
|
vec2(-0.903989, 0.427555),
|
|
|
|
vec2(-0.923880, 0.382683),
|
|
|
|
vec2(-0.941544, 0.336890),
|
|
|
|
vec2(-0.956940, 0.290285),
|
|
|
|
vec2(-0.970031, 0.242980),
|
|
|
|
vec2(-0.980785, 0.195090),
|
|
|
|
vec2(-0.989177, 0.146730),
|
|
|
|
vec2(-0.995185, 0.098017),
|
|
|
|
vec2(-0.998795, 0.049068),
|
|
|
|
vec2(-1.000000, 0.000000),
|
|
|
|
vec2(-0.998795, -0.049068),
|
|
|
|
vec2(-0.995185, -0.098017),
|
|
|
|
vec2(-0.989177, -0.146730),
|
|
|
|
vec2(-0.980785, -0.195090),
|
|
|
|
vec2(-0.970031, -0.242980),
|
|
|
|
vec2(-0.956940, -0.290285),
|
|
|
|
vec2(-0.941544, -0.336890),
|
|
|
|
vec2(-0.923880, -0.382683),
|
|
|
|
vec2(-0.903989, -0.427555),
|
|
|
|
vec2(-0.881921, -0.471397),
|
|
|
|
vec2(-0.857729, -0.514103),
|
|
|
|
vec2(-0.831470, -0.555570),
|
|
|
|
vec2(-0.803208, -0.595699),
|
|
|
|
vec2(-0.773010, -0.634393),
|
|
|
|
vec2(-0.740951, -0.671559),
|
|
|
|
vec2(-0.707107, -0.707107),
|
|
|
|
vec2(-0.671559, -0.740951),
|
|
|
|
vec2(-0.634393, -0.773010),
|
|
|
|
vec2(-0.595699, -0.803208),
|
|
|
|
vec2(-0.555570, -0.831470),
|
|
|
|
vec2(-0.514103, -0.857729),
|
|
|
|
vec2(-0.471397, -0.881921),
|
|
|
|
vec2(-0.427555, -0.903989),
|
|
|
|
vec2(-0.382683, -0.923880),
|
|
|
|
vec2(-0.336890, -0.941544),
|
|
|
|
vec2(-0.290285, -0.956940),
|
|
|
|
vec2(-0.242980, -0.970031),
|
|
|
|
vec2(-0.195090, -0.980785),
|
|
|
|
vec2(-0.146730, -0.989177),
|
|
|
|
vec2(-0.098017, -0.995185),
|
|
|
|
vec2(-0.049068, -0.998795),
|
|
|
|
vec2(-0.000000, -1.000000),
|
|
|
|
vec2(0.049068, -0.998795),
|
|
|
|
vec2(0.098017, -0.995185),
|
|
|
|
vec2(0.146730, -0.989177),
|
|
|
|
vec2(0.195090, -0.980785),
|
|
|
|
vec2(0.242980, -0.970031),
|
|
|
|
vec2(0.290285, -0.956940),
|
|
|
|
vec2(0.336890, -0.941544),
|
|
|
|
vec2(0.382683, -0.923880),
|
|
|
|
vec2(0.427555, -0.903989),
|
|
|
|
vec2(0.471397, -0.881921),
|
|
|
|
vec2(0.514103, -0.857729),
|
|
|
|
vec2(0.555570, -0.831470),
|
|
|
|
vec2(0.595699, -0.803208),
|
|
|
|
vec2(0.634393, -0.773010),
|
|
|
|
vec2(0.671559, -0.740951),
|
|
|
|
vec2(0.707107, -0.707107),
|
|
|
|
vec2(0.740951, -0.671559),
|
|
|
|
vec2(0.773010, -0.634393),
|
|
|
|
vec2(0.803208, -0.595699),
|
|
|
|
vec2(0.831470, -0.555570),
|
|
|
|
vec2(0.857729, -0.514103),
|
|
|
|
vec2(0.881921, -0.471397),
|
|
|
|
vec2(0.903989, -0.427555),
|
|
|
|
vec2(0.923880, -0.382683),
|
|
|
|
vec2(0.941544, -0.336890),
|
|
|
|
vec2(0.956940, -0.290285),
|
|
|
|
vec2(0.970031, -0.242980),
|
|
|
|
vec2(0.980785, -0.195090),
|
|
|
|
vec2(0.989177, -0.146730),
|
|
|
|
vec2(0.995185, -0.098017),
|
|
|
|
vec2(0.998795, -0.049068),
|
|
|
|
vec2(1.000000, -0.000000),
|
|
|
|
];
|
|
|
|
}
|
2019-01-04 13:14:32 +00:00
|
|
|
|
2020-04-20 21:33:16 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-04-19 21:34:34 +00:00
|
|
|
#[derive(Clone, Debug, Default)]
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
struct PathPoint {
|
2020-04-18 23:05:49 +00:00
|
|
|
pos: Pos2,
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2020-09-01 21:54:21 +00:00
|
|
|
/// For filled paths the normal is used for anti-aliasing (both strokes and filled areas).
|
2020-08-21 16:53:43 +00:00
|
|
|
///
|
2020-09-01 21:54:21 +00:00
|
|
|
/// For strokes the normal is also used for giving thickness to the path
|
2020-04-18 22:27:25 +00:00
|
|
|
/// (i.e. in what direction to expand).
|
2020-08-21 16:53:43 +00:00
|
|
|
///
|
2020-04-18 22:27:25 +00:00
|
|
|
/// The normal could be estimated by differences between successive points,
|
|
|
|
/// but that would be less accurate (and in some cases slower).
|
2020-08-21 16:53:43 +00:00
|
|
|
///
|
|
|
|
/// Normals are normally unit-length.
|
2020-04-18 22:27:25 +00:00
|
|
|
normal: Vec2,
|
|
|
|
}
|
|
|
|
|
2020-08-29 14:58:01 +00:00
|
|
|
/// A connected line (without thickness or gaps) which can be tessellated
|
2020-09-01 21:54:21 +00:00
|
|
|
/// to either to a stroke (with thickness) or a filled convex area.
|
2020-12-28 23:51:27 +00:00
|
|
|
/// Used as a scratch-pad during tessellation.
|
2020-04-19 21:34:34 +00:00
|
|
|
#[derive(Clone, Debug, Default)]
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
pub struct Path(Vec<PathPoint>);
|
2020-04-18 22:27:25 +00:00
|
|
|
|
|
|
|
impl Path {
|
2021-03-28 21:16:19 +00:00
|
|
|
#[inline(always)]
|
2020-04-18 22:27:25 +00:00
|
|
|
pub fn clear(&mut self) {
|
|
|
|
self.0.clear();
|
|
|
|
}
|
|
|
|
|
2021-03-28 21:16:19 +00:00
|
|
|
#[inline(always)]
|
2020-05-23 20:10:39 +00:00
|
|
|
pub fn reserve(&mut self, additional: usize) {
|
2021-10-20 14:43:40 +00:00
|
|
|
self.0.reserve(additional);
|
2020-05-23 20:10:39 +00:00
|
|
|
}
|
|
|
|
|
2020-05-11 11:11:01 +00:00
|
|
|
#[inline(always)]
|
2020-04-18 23:05:49 +00:00
|
|
|
pub fn add_point(&mut self, pos: Pos2, normal: Vec2) {
|
2020-04-18 22:27:25 +00:00
|
|
|
self.0.push(PathPoint { pos, normal });
|
|
|
|
}
|
|
|
|
|
2020-04-18 23:05:49 +00:00
|
|
|
pub fn add_circle(&mut self, center: Pos2, radius: f32) {
|
2022-04-30 11:14:33 +00:00
|
|
|
use precomputed_vertices::*;
|
|
|
|
|
|
|
|
// These cutoffs are based on a high-dpi display. TODO: use pixels_per_point here?
|
|
|
|
// same cutoffs as in add_circle_quadrant
|
|
|
|
|
|
|
|
if radius <= 2.0 {
|
|
|
|
self.0.extend(CIRCLE_8.iter().map(|&n| PathPoint {
|
|
|
|
pos: center + radius * n,
|
|
|
|
normal: n,
|
|
|
|
}));
|
|
|
|
} else if radius <= 5.0 {
|
|
|
|
self.0.extend(CIRCLE_16.iter().map(|&n| PathPoint {
|
|
|
|
pos: center + radius * n,
|
|
|
|
normal: n,
|
|
|
|
}));
|
|
|
|
} else if radius < 18.0 {
|
|
|
|
self.0.extend(CIRCLE_32.iter().map(|&n| PathPoint {
|
|
|
|
pos: center + radius * n,
|
|
|
|
normal: n,
|
|
|
|
}));
|
|
|
|
} else if radius < 50.0 {
|
|
|
|
self.0.extend(CIRCLE_64.iter().map(|&n| PathPoint {
|
|
|
|
pos: center + radius * n,
|
|
|
|
normal: n,
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
self.0.extend(CIRCLE_128.iter().map(|&n| PathPoint {
|
|
|
|
pos: center + radius * n,
|
|
|
|
normal: n,
|
|
|
|
}));
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-11 11:11:01 +00:00
|
|
|
pub fn add_line_segment(&mut self, points: [Pos2; 2]) {
|
2020-05-23 20:10:39 +00:00
|
|
|
self.reserve(2);
|
2020-05-11 11:11:01 +00:00
|
|
|
let normal = (points[1] - points[0]).normalized().rot90();
|
|
|
|
self.add_point(points[0], normal);
|
|
|
|
self.add_point(points[1], normal);
|
|
|
|
}
|
|
|
|
|
2020-05-23 12:14:36 +00:00
|
|
|
pub fn add_open_points(&mut self, points: &[Pos2]) {
|
2020-04-18 22:27:25 +00:00
|
|
|
let n = points.len();
|
|
|
|
assert!(n >= 2);
|
|
|
|
|
2020-05-11 11:11:01 +00:00
|
|
|
if n == 2 {
|
|
|
|
// Common case optimization:
|
|
|
|
self.add_line_segment([points[0], points[1]]);
|
|
|
|
} else {
|
2020-05-23 20:10:39 +00:00
|
|
|
self.reserve(n);
|
2020-05-11 11:11:01 +00:00
|
|
|
self.add_point(points[0], (points[1] - points[0]).normalized().rot90());
|
2021-03-30 20:36:45 +00:00
|
|
|
let mut n0 = (points[1] - points[0]).normalized().rot90();
|
2020-05-11 11:11:01 +00:00
|
|
|
for i in 1..n - 1 {
|
2020-09-01 18:29:00 +00:00
|
|
|
let mut n1 = (points[i + 1] - points[i]).normalized().rot90();
|
|
|
|
|
2021-10-13 09:52:34 +00:00
|
|
|
// Handle duplicated points (but not triplicated…):
|
2021-02-05 08:49:21 +00:00
|
|
|
if n0 == Vec2::ZERO {
|
2020-09-01 18:29:00 +00:00
|
|
|
n0 = n1;
|
2021-02-05 08:49:21 +00:00
|
|
|
} else if n1 == Vec2::ZERO {
|
2020-09-01 18:29:00 +00:00
|
|
|
n1 = n0;
|
|
|
|
}
|
|
|
|
|
2021-02-04 17:46:36 +00:00
|
|
|
let normal = (n0 + n1) / 2.0;
|
|
|
|
let length_sq = normal.length_sq();
|
|
|
|
let right_angle_length_sq = 0.5;
|
|
|
|
let sharper_than_a_right_angle = length_sq < right_angle_length_sq;
|
|
|
|
if sharper_than_a_right_angle {
|
|
|
|
// cut off the sharp corner
|
|
|
|
let center_normal = normal.normalized();
|
|
|
|
let n0c = (n0 + center_normal) / 2.0;
|
|
|
|
let n1c = (n1 + center_normal) / 2.0;
|
|
|
|
self.add_point(points[i], n0c / n0c.length_sq());
|
|
|
|
self.add_point(points[i], n1c / n1c.length_sq());
|
|
|
|
} else {
|
|
|
|
// miter join
|
|
|
|
self.add_point(points[i], normal / length_sq);
|
|
|
|
}
|
2021-03-30 20:36:45 +00:00
|
|
|
|
|
|
|
n0 = n1;
|
2020-05-11 11:11:01 +00:00
|
|
|
}
|
|
|
|
self.add_point(
|
|
|
|
points[n - 1],
|
|
|
|
(points[n - 1] - points[n - 2]).normalized().rot90(),
|
|
|
|
);
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-17 07:44:09 +00:00
|
|
|
pub fn add_line_loop(&mut self, points: &[Pos2]) {
|
|
|
|
let n = points.len();
|
|
|
|
assert!(n >= 2);
|
2020-05-23 20:10:39 +00:00
|
|
|
self.reserve(n);
|
2020-05-17 07:44:09 +00:00
|
|
|
|
2021-03-30 20:36:45 +00:00
|
|
|
let mut n0 = (points[0] - points[n - 1]).normalized().rot90();
|
|
|
|
|
2020-05-17 07:44:09 +00:00
|
|
|
for i in 0..n {
|
2021-03-30 20:36:45 +00:00
|
|
|
let next_i = if i + 1 == n { 0 } else { i + 1 };
|
|
|
|
let mut n1 = (points[next_i] - points[i]).normalized().rot90();
|
2020-09-01 18:29:00 +00:00
|
|
|
|
2021-10-13 09:52:34 +00:00
|
|
|
// Handle duplicated points (but not triplicated…):
|
2021-02-05 08:49:21 +00:00
|
|
|
if n0 == Vec2::ZERO {
|
2020-09-01 18:29:00 +00:00
|
|
|
n0 = n1;
|
2021-02-05 08:49:21 +00:00
|
|
|
} else if n1 == Vec2::ZERO {
|
2020-09-01 18:29:00 +00:00
|
|
|
n1 = n0;
|
|
|
|
}
|
|
|
|
|
2021-02-04 17:46:36 +00:00
|
|
|
let normal = (n0 + n1) / 2.0;
|
|
|
|
let length_sq = normal.length_sq();
|
2022-02-19 19:42:42 +00:00
|
|
|
|
|
|
|
// We can't just cut off corners for filled shapes like this,
|
|
|
|
// because the feather will both expand and contract the corner along the provided normals
|
|
|
|
// to make sure it doesn't grow, and the shrinking will make the inner points cross each other.
|
|
|
|
//
|
|
|
|
// A better approach is to shrink the vertices in by half the feather-width here
|
|
|
|
// and then only expand during feathering.
|
|
|
|
//
|
|
|
|
// See https://github.com/emilk/egui/issues/1226
|
|
|
|
const CUT_OFF_SHARP_CORNERS: bool = false;
|
|
|
|
|
2021-02-04 17:46:36 +00:00
|
|
|
let right_angle_length_sq = 0.5;
|
|
|
|
let sharper_than_a_right_angle = length_sq < right_angle_length_sq;
|
2022-02-19 19:42:42 +00:00
|
|
|
if CUT_OFF_SHARP_CORNERS && sharper_than_a_right_angle {
|
2021-02-04 17:46:36 +00:00
|
|
|
// cut off the sharp corner
|
|
|
|
let center_normal = normal.normalized();
|
|
|
|
let n0c = (n0 + center_normal) / 2.0;
|
|
|
|
let n1c = (n1 + center_normal) / 2.0;
|
|
|
|
self.add_point(points[i], n0c / n0c.length_sq());
|
|
|
|
self.add_point(points[i], n1c / n1c.length_sq());
|
|
|
|
} else {
|
|
|
|
// miter join
|
|
|
|
self.add_point(points[i], normal / length_sq);
|
|
|
|
}
|
2021-03-30 20:36:45 +00:00
|
|
|
|
|
|
|
n0 = n1;
|
2020-05-17 07:44:09 +00:00
|
|
|
}
|
|
|
|
}
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
|
|
|
|
/// Open-ended.
|
2022-03-23 10:41:38 +00:00
|
|
|
pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
|
|
|
|
stroke_path(feathering, &self.0, PathType::Open, stroke, out);
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A closed path (returning to the first point).
|
2022-03-23 10:41:38 +00:00
|
|
|
pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
|
|
|
|
stroke_path(feathering, &self.0, PathType::Closed, stroke, out);
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 10:41:38 +00:00
|
|
|
pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) {
|
|
|
|
stroke_path(feathering, &self.0, path_type, stroke, out);
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The path is taken to be closed (i.e. returning to the start again).
|
2022-02-19 19:37:49 +00:00
|
|
|
///
|
|
|
|
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
|
|
|
///
|
|
|
|
/// The preferred winding order is clockwise.
|
2022-03-23 10:41:38 +00:00
|
|
|
pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
|
|
|
|
fill_closed_path(feathering, &mut self.0, color, out);
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
}
|
2020-09-01 18:03:50 +00:00
|
|
|
}
|
2020-05-17 07:44:09 +00:00
|
|
|
|
2021-01-10 14:42:46 +00:00
|
|
|
pub mod path {
|
2020-09-01 18:03:50 +00:00
|
|
|
//! Helpers for constructing paths
|
2022-02-05 16:48:55 +00:00
|
|
|
use crate::shape::Rounding;
|
2022-04-30 11:14:33 +00:00
|
|
|
use emath::*;
|
2020-09-01 18:03:50 +00:00
|
|
|
|
|
|
|
/// overwrites existing points
|
2022-02-05 17:13:46 +00:00
|
|
|
pub fn rounded_rectangle(path: &mut Vec<Pos2>, rect: Rect, rounding: Rounding) {
|
2020-09-01 18:03:50 +00:00
|
|
|
path.clear();
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2020-04-25 13:45:38 +00:00
|
|
|
let min = rect.min;
|
|
|
|
let max = rect.max;
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2022-02-05 17:13:46 +00:00
|
|
|
let r = clamp_radius(rounding, rect);
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2022-02-05 17:13:46 +00:00
|
|
|
if r == Rounding::none() {
|
2020-09-01 18:03:50 +00:00
|
|
|
let min = rect.min;
|
|
|
|
let max = rect.max;
|
|
|
|
path.reserve(4);
|
2022-02-19 19:37:49 +00:00
|
|
|
path.push(pos2(min.x, min.y)); // left top
|
|
|
|
path.push(pos2(max.x, min.y)); // right top
|
|
|
|
path.push(pos2(max.x, max.y)); // right bottom
|
|
|
|
path.push(pos2(min.x, max.y)); // left bottom
|
2020-04-18 22:27:25 +00:00
|
|
|
} else {
|
2022-02-05 17:13:46 +00:00
|
|
|
add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0);
|
|
|
|
add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0);
|
|
|
|
add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0);
|
|
|
|
add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0);
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:53:43 +00:00
|
|
|
/// Add one quadrant of a circle
|
|
|
|
///
|
|
|
|
/// * quadrant 0: right bottom
|
|
|
|
/// * quadrant 1: left bottom
|
|
|
|
/// * quadrant 2: left top
|
|
|
|
/// * quadrant 3: right top
|
|
|
|
//
|
|
|
|
// Derivation:
|
|
|
|
//
|
|
|
|
// * angle 0 * TAU / 4 = right
|
|
|
|
// - quadrant 0: right bottom
|
|
|
|
// * angle 1 * TAU / 4 = bottom
|
|
|
|
// - quadrant 1: left bottom
|
|
|
|
// * angle 2 * TAU / 4 = left
|
|
|
|
// - quadrant 2: left top
|
|
|
|
// * angle 3 * TAU / 4 = top
|
|
|
|
// - quadrant 3: right top
|
|
|
|
// * angle 4 * TAU / 4 = right
|
2020-09-01 18:03:50 +00:00
|
|
|
pub fn add_circle_quadrant(path: &mut Vec<Pos2>, center: Pos2, radius: f32, quadrant: f32) {
|
2022-04-30 11:14:33 +00:00
|
|
|
use super::precomputed_vertices::*;
|
|
|
|
|
|
|
|
// These cutoffs are based on a high-dpi display. TODO: use pixels_per_point here?
|
|
|
|
// same cutoffs as in add_circle
|
|
|
|
|
|
|
|
if radius <= 0.0 {
|
|
|
|
path.push(center);
|
|
|
|
} else if radius <= 2.0 {
|
|
|
|
let offset = quadrant as usize * 2;
|
|
|
|
let quadrant_vertices = &CIRCLE_8[offset..=offset + 2];
|
|
|
|
path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
|
|
|
|
} else if radius <= 5.0 {
|
|
|
|
let offset = quadrant as usize * 4;
|
|
|
|
let quadrant_vertices = &CIRCLE_16[offset..=offset + 4];
|
|
|
|
path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
|
|
|
|
} else if radius < 18.0 {
|
|
|
|
let offset = quadrant as usize * 8;
|
|
|
|
let quadrant_vertices = &CIRCLE_32[offset..=offset + 8];
|
|
|
|
path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
|
|
|
|
} else if radius < 50.0 {
|
|
|
|
let offset = quadrant as usize * 16;
|
|
|
|
let quadrant_vertices = &CIRCLE_64[offset..=offset + 16];
|
|
|
|
path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
|
|
|
|
} else {
|
|
|
|
let offset = quadrant as usize * 32;
|
|
|
|
let quadrant_vertices = &CIRCLE_128[offset..=offset + 32];
|
|
|
|
path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-05 16:48:55 +00:00
|
|
|
|
|
|
|
// Ensures the radius of each corner is within a valid range
|
2022-02-05 17:13:46 +00:00
|
|
|
fn clamp_radius(rounding: Rounding, rect: Rect) -> Rounding {
|
2022-02-05 16:48:55 +00:00
|
|
|
let half_width = rect.width() * 0.5;
|
|
|
|
let half_height = rect.height() * 0.5;
|
|
|
|
let max_cr = half_width.min(half_height);
|
2022-04-27 08:05:09 +00:00
|
|
|
rounding.at_most(max_cr).at_least(0.0)
|
2022-02-05 16:48:55 +00:00
|
|
|
}
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2019-02-24 16:18:30 +00:00
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
|
|
pub enum PathType {
|
|
|
|
Open,
|
|
|
|
Closed,
|
|
|
|
}
|
|
|
|
|
2020-12-28 23:51:27 +00:00
|
|
|
/// Tessellation quality options
|
2021-05-27 17:30:08 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
2021-09-29 06:45:13 +00:00
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
2020-12-28 23:51:27 +00:00
|
|
|
pub struct TessellationOptions {
|
2022-03-23 10:41:38 +00:00
|
|
|
/// Use "feathering" to smooth out the edges of shapes as a form of anti-aliasing.
|
|
|
|
///
|
|
|
|
/// Feathering works by making each edge into a thin gradient into transparency.
|
|
|
|
/// The size of this edge is controlled by [`Self::feathering_size_in_pixels`].
|
|
|
|
///
|
|
|
|
/// This makes shapes appear smoother, but requires more triangles and is therefore slower.
|
|
|
|
///
|
2021-08-25 19:39:00 +00:00
|
|
|
/// This setting does not affect text.
|
2022-03-23 10:41:38 +00:00
|
|
|
///
|
2021-08-25 19:39:00 +00:00
|
|
|
/// Default: `true`.
|
2022-03-23 10:41:38 +00:00
|
|
|
pub feathering: bool,
|
|
|
|
|
|
|
|
/// The size of the the feathering, in physical pixels.
|
|
|
|
///
|
|
|
|
/// The default, and suggested, value for this is `1.0`.
|
|
|
|
/// If you use a larger value, edges will appear blurry.
|
|
|
|
pub feathering_size_in_pixels: f32,
|
2021-10-10 13:35:13 +00:00
|
|
|
|
|
|
|
/// If `true` (default) cull certain primitives before tessellating them.
|
|
|
|
/// This likely makes
|
2020-10-10 05:21:45 +00:00
|
|
|
pub coarse_tessellation_culling: bool,
|
2021-10-10 13:35:13 +00:00
|
|
|
|
2022-05-10 17:31:19 +00:00
|
|
|
/// If `true`, small filled circled will be optimized by using pre-rasterized circled
|
|
|
|
/// from the font atlas.
|
|
|
|
pub prerasterized_discs: bool,
|
|
|
|
|
2021-10-27 06:56:06 +00:00
|
|
|
/// If `true` (default) align text to mesh grid.
|
|
|
|
/// This makes the text sharper on most platforms.
|
|
|
|
pub round_text_to_pixels: bool,
|
|
|
|
|
2021-10-10 13:35:13 +00:00
|
|
|
/// Output the clip rectangles to be painted.
|
2020-04-21 08:33:33 +00:00
|
|
|
pub debug_paint_clip_rects: bool,
|
2021-10-10 13:35:13 +00:00
|
|
|
|
|
|
|
/// Output the text-containing rectangles.
|
2021-01-10 09:42:15 +00:00
|
|
|
pub debug_paint_text_rects: bool,
|
2021-10-10 13:35:13 +00:00
|
|
|
|
|
|
|
/// If true, no clipping will be done.
|
2020-10-10 09:39:39 +00:00
|
|
|
pub debug_ignore_clip_rects: bool,
|
2022-01-31 19:26:31 +00:00
|
|
|
|
|
|
|
/// The maximum distance between the original curve and the flattened curve.
|
2022-02-06 15:30:34 +00:00
|
|
|
pub bezier_tolerance: f32,
|
2022-01-31 19:26:31 +00:00
|
|
|
|
|
|
|
/// The default value will be 1.0e-5, it will be used during float compare.
|
|
|
|
pub epsilon: f32,
|
2020-04-21 08:33:33 +00:00
|
|
|
}
|
|
|
|
|
2020-12-28 23:51:27 +00:00
|
|
|
impl Default for TessellationOptions {
|
2020-04-21 08:33:33 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2022-03-23 10:41:38 +00:00
|
|
|
feathering: true,
|
|
|
|
feathering_size_in_pixels: 1.0,
|
2020-11-07 10:44:32 +00:00
|
|
|
coarse_tessellation_culling: true,
|
2022-05-10 17:31:19 +00:00
|
|
|
prerasterized_discs: true,
|
2021-10-27 06:56:06 +00:00
|
|
|
round_text_to_pixels: true,
|
2021-01-10 09:42:15 +00:00
|
|
|
debug_paint_text_rects: false,
|
2020-04-21 08:33:33 +00:00
|
|
|
debug_paint_clip_rects: false,
|
2020-10-10 09:39:39 +00:00
|
|
|
debug_ignore_clip_rects: false,
|
2022-02-06 15:30:34 +00:00
|
|
|
bezier_tolerance: 0.1,
|
2022-01-31 19:26:31 +00:00
|
|
|
epsilon: 1.0e-5,
|
2020-04-21 08:33:33 +00:00
|
|
|
}
|
|
|
|
}
|
2019-02-24 16:18:30 +00:00
|
|
|
}
|
|
|
|
|
2022-02-19 19:37:49 +00:00
|
|
|
fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
|
|
|
if let Some(last) = path.last() {
|
|
|
|
let mut previous = last.pos;
|
|
|
|
let mut area = 0.0;
|
|
|
|
for p in path {
|
|
|
|
area += (previous.x * p.pos.y - p.pos.x * previous.y) as f64;
|
|
|
|
previous = p.pos;
|
|
|
|
}
|
|
|
|
area
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-28 23:51:27 +00:00
|
|
|
/// Tessellate the given convex area into a polygon.
|
2022-02-19 19:37:49 +00:00
|
|
|
///
|
|
|
|
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
|
|
|
///
|
|
|
|
/// The preferred winding order is clockwise.
|
2022-03-23 10:41:38 +00:00
|
|
|
fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out: &mut Mesh) {
|
2021-01-02 16:18:41 +00:00
|
|
|
if color == Color32::TRANSPARENT {
|
2020-05-11 15:57:11 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-04-18 22:27:25 +00:00
|
|
|
let n = path.len() as u32;
|
2022-03-23 10:41:38 +00:00
|
|
|
if feathering > 0.0 {
|
2022-02-19 19:37:49 +00:00
|
|
|
if cw_signed_area(path) < 0.0 {
|
|
|
|
// Wrong winding order - fix:
|
|
|
|
path.reverse();
|
|
|
|
for point in path.iter_mut() {
|
|
|
|
point.normal = -point.normal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:53:43 +00:00
|
|
|
out.reserve_triangles(3 * n as usize);
|
|
|
|
out.reserve_vertices(2 * n as usize);
|
2021-01-02 16:18:41 +00:00
|
|
|
let color_outer = Color32::TRANSPARENT;
|
2020-08-21 16:53:43 +00:00
|
|
|
let idx_inner = out.vertices.len() as u32;
|
2020-04-18 22:27:25 +00:00
|
|
|
let idx_outer = idx_inner + 1;
|
2022-02-19 19:42:42 +00:00
|
|
|
|
|
|
|
// The fill:
|
2020-04-18 22:27:25 +00:00
|
|
|
for i in 2..n {
|
2020-08-21 16:53:43 +00:00
|
|
|
out.add_triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
2022-02-19 19:42:42 +00:00
|
|
|
|
|
|
|
// The feathering:
|
2020-04-18 22:27:25 +00:00
|
|
|
let mut i0 = n - 1;
|
|
|
|
for i1 in 0..n {
|
|
|
|
let p1 = &path[i1 as usize];
|
2022-03-23 10:41:38 +00:00
|
|
|
let dm = 0.5 * feathering * p1.normal;
|
2020-09-02 19:52:43 +00:00
|
|
|
out.colored_vertex(p1.pos - dm, color);
|
|
|
|
out.colored_vertex(p1.pos + dm, color_outer);
|
2020-08-21 16:53:43 +00:00
|
|
|
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
|
|
|
out.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
|
2020-04-18 22:27:25 +00:00
|
|
|
i0 = i1;
|
|
|
|
}
|
|
|
|
} else {
|
2020-08-21 16:53:43 +00:00
|
|
|
out.reserve_triangles(n as usize);
|
|
|
|
let idx = out.vertices.len() as u32;
|
2020-09-02 19:52:43 +00:00
|
|
|
out.vertices.extend(path.iter().map(|p| Vertex {
|
|
|
|
pos: p.pos,
|
|
|
|
uv: WHITE_UV,
|
|
|
|
color,
|
|
|
|
}));
|
2020-04-18 22:27:25 +00:00
|
|
|
for i in 2..n {
|
2020-08-21 16:53:43 +00:00
|
|
|
out.add_triangle(idx, idx + i - 1, idx + i);
|
2019-02-24 16:18:30 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
2019-01-05 14:28:07 +00:00
|
|
|
|
2020-12-28 23:51:27 +00:00
|
|
|
/// Tessellate the given path as a stroke with thickness.
|
2020-09-01 21:54:21 +00:00
|
|
|
fn stroke_path(
|
2022-03-23 10:41:38 +00:00
|
|
|
feathering: f32,
|
2020-04-18 22:27:25 +00:00
|
|
|
path: &[PathPoint],
|
2020-08-21 16:53:43 +00:00
|
|
|
path_type: PathType,
|
2020-09-01 21:54:21 +00:00
|
|
|
stroke: Stroke,
|
2021-01-25 20:23:24 +00:00
|
|
|
out: &mut Mesh,
|
2020-04-18 22:27:25 +00:00
|
|
|
) {
|
2021-11-14 16:23:51 +00:00
|
|
|
let n = path.len() as u32;
|
|
|
|
|
|
|
|
if stroke.width <= 0.0 || stroke.color == Color32::TRANSPARENT || n < 2 {
|
2020-05-11 15:57:11 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:53:43 +00:00
|
|
|
let idx = out.vertices.len() as u32;
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2022-03-23 10:41:38 +00:00
|
|
|
if feathering > 0.0 {
|
2020-09-01 21:54:21 +00:00
|
|
|
let color_inner = stroke.color;
|
2021-01-02 16:18:41 +00:00
|
|
|
let color_outer = Color32::TRANSPARENT;
|
2020-05-11 15:57:11 +00:00
|
|
|
|
2022-03-23 10:41:38 +00:00
|
|
|
let thin_line = stroke.width <= feathering;
|
2020-04-18 22:27:25 +00:00
|
|
|
if thin_line {
|
2020-05-11 15:57:11 +00:00
|
|
|
/*
|
|
|
|
We paint the line using three edges: outer, inner, outer.
|
|
|
|
|
|
|
|
. o i o outer, inner, outer
|
2022-03-23 10:41:38 +00:00
|
|
|
. |---| feathering (pixel width)
|
2020-05-11 15:57:11 +00:00
|
|
|
*/
|
|
|
|
|
2020-04-18 22:27:25 +00:00
|
|
|
// Fade out as it gets thinner:
|
2022-03-23 10:41:38 +00:00
|
|
|
let color_inner = mul_color(color_inner, stroke.width / feathering);
|
2021-01-02 16:18:41 +00:00
|
|
|
if color_inner == Color32::TRANSPARENT {
|
2020-05-11 15:57:11 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:53:43 +00:00
|
|
|
out.reserve_triangles(4 * n as usize);
|
|
|
|
out.reserve_vertices(3 * n as usize);
|
2020-05-20 19:22:53 +00:00
|
|
|
|
2020-05-11 15:57:11 +00:00
|
|
|
let mut i0 = n - 1;
|
|
|
|
for i1 in 0..n {
|
|
|
|
let connect_with_previous = path_type == PathType::Closed || i1 > 0;
|
2020-04-18 22:27:25 +00:00
|
|
|
let p1 = &path[i1 as usize];
|
|
|
|
let p = p1.pos;
|
|
|
|
let n = p1.normal;
|
2022-03-23 10:41:38 +00:00
|
|
|
out.colored_vertex(p + n * feathering, color_outer);
|
2020-09-02 19:52:43 +00:00
|
|
|
out.colored_vertex(p, color_inner);
|
2022-03-23 10:41:38 +00:00
|
|
|
out.colored_vertex(p - n * feathering, color_outer);
|
2020-04-18 22:27:25 +00:00
|
|
|
|
|
|
|
if connect_with_previous {
|
2020-08-21 16:53:43 +00:00
|
|
|
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
|
|
|
|
out.add_triangle(idx + 3 * i0 + 1, idx + 3 * i1 + 0, idx + 3 * i1 + 1);
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2020-08-21 16:53:43 +00:00
|
|
|
out.add_triangle(idx + 3 * i0 + 1, idx + 3 * i0 + 2, idx + 3 * i1 + 1);
|
|
|
|
out.add_triangle(idx + 3 * i0 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2);
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
2020-05-11 15:57:11 +00:00
|
|
|
i0 = i1;
|
|
|
|
}
|
|
|
|
} else {
|
2021-11-14 16:23:51 +00:00
|
|
|
// thick anti-aliased line
|
2020-05-11 15:57:11 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
We paint the line using four edges: outer, inner, inner, outer
|
|
|
|
|
|
|
|
. o i p i o outer, inner, point, inner, outer
|
2022-03-23 10:41:38 +00:00
|
|
|
. |---| feathering (pixel width)
|
2020-05-11 15:57:11 +00:00
|
|
|
. |--------------| width
|
|
|
|
. |---------| outer_rad
|
|
|
|
. |-----| inner_rad
|
|
|
|
*/
|
|
|
|
|
2022-03-23 10:41:38 +00:00
|
|
|
let inner_rad = 0.5 * (stroke.width - feathering);
|
|
|
|
let outer_rad = 0.5 * (stroke.width + feathering);
|
2020-05-20 19:22:53 +00:00
|
|
|
|
2021-11-14 16:23:51 +00:00
|
|
|
match path_type {
|
|
|
|
PathType::Closed => {
|
|
|
|
out.reserve_triangles(6 * n as usize);
|
|
|
|
out.reserve_vertices(4 * n as usize);
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2021-11-14 16:23:51 +00:00
|
|
|
let mut i0 = n - 1;
|
|
|
|
for i1 in 0..n {
|
|
|
|
let p1 = &path[i1 as usize];
|
|
|
|
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 * 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 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2021-11-14 16:23:51 +00:00
|
|
|
out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
|
|
|
|
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
|
2020-04-18 22:27:25 +00:00
|
|
|
|
2021-11-14 16:23:51 +00:00
|
|
|
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
|
|
|
|
out.add_triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
|
|
|
|
|
|
|
|
i0 = i1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PathType::Open => {
|
|
|
|
// Anti-alias the ends by extruding the outer edge and adding
|
|
|
|
// two more triangles to each end:
|
|
|
|
|
|
|
|
// | aa | | aa |
|
|
|
|
// _________________ ___
|
2022-03-23 10:41:38 +00:00
|
|
|
// | \ added / | feathering
|
2021-11-14 16:23:51 +00:00
|
|
|
// | \ ___p___ / | ___
|
|
|
|
// | | | |
|
|
|
|
// | | opa | |
|
|
|
|
// | | que | |
|
|
|
|
// | | | |
|
|
|
|
|
|
|
|
// (in the future it would be great with an option to add a circular end instead)
|
|
|
|
|
|
|
|
out.reserve_triangles(6 * n as usize + 4);
|
|
|
|
out.reserve_vertices(4 * n as usize);
|
|
|
|
|
|
|
|
{
|
|
|
|
let end = &path[0];
|
|
|
|
let p = end.pos;
|
|
|
|
let n = end.normal;
|
2022-03-23 10:41:38 +00:00
|
|
|
let back_extrude = n.rot90() * feathering;
|
2021-11-14 16:23:51 +00:00
|
|
|
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 * outer_rad + back_extrude, color_outer);
|
|
|
|
|
|
|
|
out.add_triangle(idx + 0, idx + 1, idx + 2);
|
|
|
|
out.add_triangle(idx + 0, idx + 2, idx + 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut i0 = 0;
|
|
|
|
for i1 in 1..n - 1 {
|
|
|
|
let point = &path[i1 as usize];
|
|
|
|
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 * 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 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
|
|
|
|
|
|
|
|
out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
|
|
|
|
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
|
|
|
|
|
|
|
|
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
|
|
|
|
out.add_triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
|
|
|
|
|
|
|
|
i0 = i1;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let i1 = n - 1;
|
|
|
|
let end = &path[i1 as usize];
|
|
|
|
let p = end.pos;
|
|
|
|
let n = end.normal;
|
2022-03-23 10:41:38 +00:00
|
|
|
let back_extrude = -n.rot90() * feathering;
|
2021-11-14 16:23:51 +00:00
|
|
|
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 * 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 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
|
|
|
|
|
|
|
|
out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
|
|
|
|
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
|
|
|
|
|
|
|
|
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
|
|
|
|
out.add_triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
|
|
|
|
|
|
|
|
// The extension:
|
|
|
|
out.add_triangle(idx + 4 * i1 + 0, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
|
|
|
|
out.add_triangle(idx + 4 * i1 + 0, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
|
|
|
|
}
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
2019-01-05 19:14:16 +00:00
|
|
|
}
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-11-14 16:23:51 +00:00
|
|
|
// not anti-aliased:
|
2020-08-21 16:53:43 +00:00
|
|
|
out.reserve_triangles(2 * n as usize);
|
|
|
|
out.reserve_vertices(2 * n as usize);
|
2020-05-20 19:22:53 +00:00
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
let last_index = if path_type == PathType::Closed {
|
|
|
|
n
|
|
|
|
} else {
|
|
|
|
n - 1
|
|
|
|
};
|
2020-04-18 22:27:25 +00:00
|
|
|
for i in 0..last_index {
|
2020-08-21 16:53:43 +00:00
|
|
|
out.add_triangle(
|
2020-04-18 22:27:25 +00:00
|
|
|
idx + (2 * i + 0) % (2 * n),
|
|
|
|
idx + (2 * i + 1) % (2 * n),
|
|
|
|
idx + (2 * i + 2) % (2 * n),
|
|
|
|
);
|
2020-08-21 16:53:43 +00:00
|
|
|
out.add_triangle(
|
2020-04-18 22:27:25 +00:00
|
|
|
idx + (2 * i + 2) % (2 * n),
|
|
|
|
idx + (2 * i + 1) % (2 * n),
|
|
|
|
idx + (2 * i + 3) % (2 * n),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-03-23 10:41:38 +00:00
|
|
|
let thin_line = stroke.width <= feathering;
|
2020-05-11 15:57:11 +00:00
|
|
|
if thin_line {
|
|
|
|
// Fade out thin lines rather than making them thinner
|
2022-03-23 10:41:38 +00:00
|
|
|
let radius = feathering / 2.0;
|
|
|
|
let color = mul_color(stroke.color, stroke.width / feathering);
|
2021-01-02 16:18:41 +00:00
|
|
|
if color == Color32::TRANSPARENT {
|
2020-05-11 15:57:11 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
for p in path {
|
2020-09-02 19:52:43 +00:00
|
|
|
out.colored_vertex(p.pos + radius * p.normal, color);
|
|
|
|
out.colored_vertex(p.pos - radius * p.normal, color);
|
2020-05-11 15:57:11 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-09-01 21:54:21 +00:00
|
|
|
let radius = stroke.width / 2.0;
|
2020-05-11 15:57:11 +00:00
|
|
|
for p in path {
|
2020-09-02 19:52:43 +00:00
|
|
|
out.colored_vertex(p.pos + radius * p.normal, stroke.color);
|
|
|
|
out.colored_vertex(p.pos - radius * p.normal, stroke.color);
|
2020-05-11 15:57:11 +00:00
|
|
|
}
|
2019-01-05 14:28:07 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-18 22:27:25 +00:00
|
|
|
}
|
2019-01-05 14:28:07 +00:00
|
|
|
|
2021-01-02 16:02:18 +00:00
|
|
|
fn mul_color(color: Color32, factor: f32) -> Color32 {
|
2021-05-17 20:34:29 +00:00
|
|
|
crate::epaint_assert!(0.0 <= factor && factor <= 1.0);
|
2021-01-19 23:30:07 +00:00
|
|
|
// As an unfortunate side-effect of using premultiplied alpha
|
|
|
|
// we need a somewhat expensive conversion to linear space and back.
|
|
|
|
color.linear_multiply(factor)
|
2020-05-11 15:57:11 +00:00
|
|
|
}
|
|
|
|
|
2020-04-18 22:27:25 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
2019-01-05 19:14:16 +00:00
|
|
|
|
2021-10-10 13:35:13 +00:00
|
|
|
/// Converts [`Shape`]s into triangles ([`Mesh`]).
|
|
|
|
///
|
2022-04-03 16:18:35 +00:00
|
|
|
/// For performance reasons it is smart to reuse the same [`Tessellator`].
|
2021-10-10 13:35:13 +00:00
|
|
|
///
|
|
|
|
/// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
2020-12-29 00:13:14 +00:00
|
|
|
pub struct Tessellator {
|
2022-03-23 10:41:38 +00:00
|
|
|
pixels_per_point: f32,
|
2021-01-10 13:39:03 +00:00
|
|
|
options: TessellationOptions,
|
2022-04-03 16:14:27 +00:00
|
|
|
font_tex_size: [usize; 2],
|
2022-05-10 17:31:19 +00:00
|
|
|
/// See [`TextureAtlas::prepared_discs`].
|
|
|
|
prepared_discs: Vec<PreparedDisc>,
|
2022-03-23 10:41:38 +00:00
|
|
|
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
|
|
|
|
feathering: f32,
|
2020-12-29 00:13:14 +00:00
|
|
|
/// Only used for culling
|
2021-01-10 13:39:03 +00:00
|
|
|
clip_rect: Rect,
|
2020-12-29 00:13:14 +00:00
|
|
|
scratchpad_points: Vec<Pos2>,
|
|
|
|
scratchpad_path: Path,
|
|
|
|
}
|
2020-10-10 05:21:45 +00:00
|
|
|
|
2020-12-29 00:13:14 +00:00
|
|
|
impl Tessellator {
|
2021-10-10 13:35:13 +00:00
|
|
|
/// Create a new [`Tessellator`].
|
2022-04-03 16:14:27 +00:00
|
|
|
///
|
|
|
|
/// * `font_tex_size`: size of the font texture. Required to normalize glyph uv rectangles when tessellating text.
|
2022-05-10 17:31:19 +00:00
|
|
|
/// * `prepared_discs`: What [`TextureAtlas::prepared_discs`] returns. Can safely be set to an empty vec.
|
2022-04-03 16:14:27 +00:00
|
|
|
pub fn new(
|
|
|
|
pixels_per_point: f32,
|
|
|
|
options: TessellationOptions,
|
|
|
|
font_tex_size: [usize; 2],
|
2022-05-10 17:31:19 +00:00
|
|
|
prepared_discs: Vec<PreparedDisc>,
|
2022-04-03 16:14:27 +00:00
|
|
|
) -> Self {
|
2022-03-23 10:41:38 +00:00
|
|
|
let feathering = if options.feathering {
|
|
|
|
let pixel_size = 1.0 / pixels_per_point;
|
|
|
|
options.feathering_size_in_pixels * pixel_size
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
2020-12-29 00:13:14 +00:00
|
|
|
Self {
|
2022-03-23 10:41:38 +00:00
|
|
|
pixels_per_point,
|
2020-12-29 00:13:14 +00:00
|
|
|
options,
|
2022-04-03 16:14:27 +00:00
|
|
|
font_tex_size,
|
2022-05-10 17:31:19 +00:00
|
|
|
prepared_discs,
|
2022-03-23 10:41:38 +00:00
|
|
|
feathering,
|
2021-02-05 08:49:21 +00:00
|
|
|
clip_rect: Rect::EVERYTHING,
|
2020-12-29 00:13:14 +00:00
|
|
|
scratchpad_points: Default::default(),
|
|
|
|
scratchpad_path: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-03 16:18:35 +00:00
|
|
|
/// Set the [`Rect`] to use for culling.
|
2022-03-19 12:01:33 +00:00
|
|
|
pub fn set_clip_rect(&mut self, clip_rect: Rect) {
|
|
|
|
self.clip_rect = clip_rect;
|
|
|
|
}
|
|
|
|
|
2022-03-23 10:41:38 +00:00
|
|
|
#[inline(always)]
|
|
|
|
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
|
|
|
if self.options.round_text_to_pixels {
|
|
|
|
(point * self.pixels_per_point).round() / self.pixels_per_point
|
|
|
|
} else {
|
|
|
|
point
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-03 16:14:27 +00:00
|
|
|
/// Tessellate a clipped shape into a list of primitives.
|
|
|
|
pub fn tessellate_clipped_shape(
|
|
|
|
&mut self,
|
|
|
|
clipped_shape: ClippedShape,
|
|
|
|
out_primitives: &mut Vec<ClippedPrimitive>,
|
|
|
|
) {
|
|
|
|
let ClippedShape(new_clip_rect, new_shape) = clipped_shape;
|
|
|
|
|
|
|
|
if !new_clip_rect.is_positive() {
|
|
|
|
return; // skip empty clip rectangles
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Shape::Vec(shapes) = new_shape {
|
|
|
|
for shape in shapes {
|
|
|
|
self.tessellate_clipped_shape(ClippedShape(new_clip_rect, shape), out_primitives);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Shape::Callback(callback) = new_shape {
|
|
|
|
out_primitives.push(ClippedPrimitive {
|
|
|
|
clip_rect: new_clip_rect,
|
|
|
|
primitive: Primitive::Callback(callback),
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let start_new_mesh = match out_primitives.last() {
|
|
|
|
None => true,
|
|
|
|
Some(output_clipped_primitive) => {
|
|
|
|
output_clipped_primitive.clip_rect != new_clip_rect
|
|
|
|
|| if let Primitive::Mesh(output_mesh) = &output_clipped_primitive.primitive {
|
|
|
|
output_mesh.texture_id != new_shape.texture_id()
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if start_new_mesh {
|
|
|
|
out_primitives.push(ClippedPrimitive {
|
|
|
|
clip_rect: new_clip_rect,
|
|
|
|
primitive: Primitive::Mesh(Mesh::default()),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let out = out_primitives.last_mut().unwrap();
|
|
|
|
|
|
|
|
if let Primitive::Mesh(out_mesh) = &mut out.primitive {
|
|
|
|
self.clip_rect = new_clip_rect;
|
|
|
|
self.tessellate_shape(new_shape, out_mesh);
|
|
|
|
} else {
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-25 20:23:24 +00:00
|
|
|
/// Tessellate a single [`Shape`] into a [`Mesh`].
|
2020-12-29 00:13:14 +00:00
|
|
|
///
|
2022-04-03 16:14:27 +00:00
|
|
|
/// This call can panic the given shape is of [`Shape::Vec`] or [`Shape::Callback`].
|
|
|
|
/// For that, use [`Self::tessellate_clipped_shape`] instead.
|
2021-10-10 13:35:13 +00:00
|
|
|
/// * `shape`: the shape to tessellate.
|
|
|
|
/// * `out`: triangles are appended to this.
|
2022-04-03 16:14:27 +00:00
|
|
|
pub fn tessellate_shape(&mut self, shape: Shape, out: &mut Mesh) {
|
2021-01-10 10:43:01 +00:00
|
|
|
match shape {
|
|
|
|
Shape::Noop => {}
|
|
|
|
Shape::Vec(vec) => {
|
|
|
|
for shape in vec {
|
2022-04-03 16:14:27 +00:00
|
|
|
self.tessellate_shape(shape, out);
|
2020-12-29 00:13:14 +00:00
|
|
|
}
|
2020-10-10 05:21:45 +00:00
|
|
|
}
|
2022-03-20 19:38:48 +00:00
|
|
|
Shape::Circle(circle) => {
|
|
|
|
self.tessellate_circle(circle, out);
|
2020-10-21 09:04:22 +00:00
|
|
|
}
|
2021-01-25 20:23:24 +00:00
|
|
|
Shape::Mesh(mesh) => {
|
2021-09-20 19:36:56 +00:00
|
|
|
if !mesh.is_valid() {
|
2021-05-17 20:34:29 +00:00
|
|
|
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
|
2021-09-20 19:36:56 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
if self.options.coarse_tessellation_culling
|
|
|
|
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
2021-09-20 19:36:56 +00:00
|
|
|
{
|
|
|
|
return;
|
2020-09-01 18:03:50 +00:00
|
|
|
}
|
2021-09-20 19:36:56 +00:00
|
|
|
out.append(mesh);
|
2020-12-29 00:13:14 +00:00
|
|
|
}
|
2022-03-20 19:38:48 +00:00
|
|
|
Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out),
|
2021-09-20 19:36:56 +00:00
|
|
|
Shape::Path(path_shape) => {
|
2022-03-20 19:38:48 +00:00
|
|
|
self.tessellate_path(&path_shape, out);
|
2020-12-29 00:13:14 +00:00
|
|
|
}
|
2021-09-20 19:36:56 +00:00
|
|
|
Shape::Rect(rect_shape) => {
|
|
|
|
self.tessellate_rect(&rect_shape, out);
|
2020-12-29 00:13:14 +00:00
|
|
|
}
|
2021-09-05 07:06:53 +00:00
|
|
|
Shape::Text(text_shape) => {
|
2022-03-20 19:38:48 +00:00
|
|
|
if self.options.debug_paint_text_rects {
|
2021-09-20 19:36:56 +00:00
|
|
|
let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2());
|
2021-01-10 09:42:15 +00:00
|
|
|
self.tessellate_rect(
|
2021-09-20 19:36:56 +00:00
|
|
|
&RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)),
|
2021-01-10 09:42:15 +00:00
|
|
|
out,
|
|
|
|
);
|
|
|
|
}
|
2022-04-03 16:14:27 +00:00
|
|
|
self.tessellate_text(&text_shape, out);
|
2020-04-20 21:33:16 +00:00
|
|
|
}
|
2022-01-31 19:26:31 +00:00
|
|
|
Shape::QuadraticBezier(quadratic_shape) => {
|
|
|
|
self.tessellate_quadratic_bezier(quadratic_shape, out);
|
|
|
|
}
|
|
|
|
Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(cubic_shape, out),
|
2022-03-14 12:25:11 +00:00
|
|
|
Shape::Callback(_) => {
|
|
|
|
panic!("Shape::Callback passed to Tessellator");
|
|
|
|
}
|
2020-04-20 21:33:16 +00:00
|
|
|
}
|
2020-12-29 00:13:14 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
/// Tessellate a single [`CircleShape`] into a [`Mesh`].
|
|
|
|
///
|
|
|
|
/// * `shape`: the circle to tessellate.
|
|
|
|
/// * `out`: triangles are appended to this.
|
|
|
|
pub fn tessellate_circle(&mut self, shape: CircleShape, out: &mut Mesh) {
|
|
|
|
let CircleShape {
|
|
|
|
center,
|
|
|
|
radius,
|
2022-05-10 17:31:19 +00:00
|
|
|
mut fill,
|
2022-03-20 19:38:48 +00:00
|
|
|
stroke,
|
|
|
|
} = shape;
|
2022-01-31 19:26:31 +00:00
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
if radius <= 0.0 {
|
2022-01-31 19:26:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
if self.options.coarse_tessellation_culling
|
|
|
|
&& !self
|
|
|
|
.clip_rect
|
|
|
|
.expand(radius + stroke.width)
|
|
|
|
.contains(center)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2022-01-31 19:26:31 +00:00
|
|
|
|
2022-05-10 17:31:19 +00:00
|
|
|
if self.options.prerasterized_discs && fill != Color32::TRANSPARENT {
|
|
|
|
let radius_px = radius * self.pixels_per_point;
|
|
|
|
// strike the right balance between some circles becoming too blurry, and some too sharp.
|
|
|
|
let cutoff_radius = radius_px * 2.0_f32.powf(0.25);
|
|
|
|
|
|
|
|
// Find the right disc radius for a crisp edge:
|
|
|
|
// TODO: perhaps we can do something faster than this linear search.
|
|
|
|
for disc in &self.prepared_discs {
|
|
|
|
if cutoff_radius <= disc.r {
|
|
|
|
let side = radius_px * disc.w / (self.pixels_per_point * disc.r);
|
|
|
|
let rect = Rect::from_center_size(center, Vec2::splat(side));
|
|
|
|
out.add_rect_with_uv(rect, disc.uv, fill);
|
|
|
|
|
|
|
|
if stroke.is_empty() {
|
|
|
|
return; // we are done
|
|
|
|
} else {
|
|
|
|
// we still need to do the stroke
|
|
|
|
fill = Color32::TRANSPARENT; // don't fill again below
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
self.scratchpad_path.clear();
|
|
|
|
self.scratchpad_path.add_circle(center, radius);
|
2022-03-23 10:41:38 +00:00
|
|
|
self.scratchpad_path.fill(self.feathering, fill, out);
|
2022-03-20 19:38:48 +00:00
|
|
|
self.scratchpad_path
|
2022-03-23 10:41:38 +00:00
|
|
|
.stroke_closed(self.feathering, stroke, out);
|
2022-01-31 19:26:31 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
/// Tessellate a single [`Mesh`] into a [`Mesh`].
|
|
|
|
///
|
|
|
|
/// * `mesh`: the mesh to tessellate.
|
|
|
|
/// * `out`: triangles are appended to this.
|
|
|
|
pub fn tessellate_mesh(&mut self, mesh: &Mesh, out: &mut Mesh) {
|
|
|
|
if !mesh.is_valid() {
|
|
|
|
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
|
2022-01-31 19:26:31 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
if self.options.coarse_tessellation_culling
|
|
|
|
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
|
|
|
{
|
|
|
|
return;
|
2022-01-31 19:26:31 +00:00
|
|
|
}
|
2022-03-20 19:38:48 +00:00
|
|
|
|
|
|
|
out.append_ref(mesh);
|
2022-01-31 19:26:31 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
/// Tessellate a line segment between the two points with the given stoken into a [`Mesh`].
|
|
|
|
///
|
|
|
|
/// * `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) {
|
|
|
|
if stroke.is_empty() {
|
|
|
|
return;
|
2022-01-31 19:26:31 +00:00
|
|
|
}
|
2022-03-20 19:38:48 +00:00
|
|
|
|
|
|
|
if self.options.coarse_tessellation_culling
|
|
|
|
&& !self
|
|
|
|
.clip_rect
|
|
|
|
.intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width))
|
|
|
|
{
|
|
|
|
return;
|
2022-01-31 19:26:31 +00:00
|
|
|
}
|
2022-03-20 19:38:48 +00:00
|
|
|
|
|
|
|
self.scratchpad_path.clear();
|
|
|
|
self.scratchpad_path.add_line_segment(points);
|
2022-03-23 10:41:38 +00:00
|
|
|
self.scratchpad_path
|
|
|
|
.stroke_open(self.feathering, stroke, out);
|
2022-01-31 19:26:31 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
/// Tessellate a single [`PathShape`] into a [`Mesh`].
|
|
|
|
///
|
|
|
|
/// * `path_shape`: the path to tessellate.
|
|
|
|
/// * `out`: triangles are appended to this.
|
|
|
|
pub fn tessellate_path(&mut self, path_shape: &PathShape, out: &mut Mesh) {
|
2021-09-20 19:36:56 +00:00
|
|
|
if path_shape.points.len() < 2 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.options.coarse_tessellation_culling
|
2022-02-21 20:40:46 +00:00
|
|
|
&& !path_shape.visual_bounding_rect().intersects(self.clip_rect)
|
2021-09-20 19:36:56 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let PathShape {
|
|
|
|
points,
|
|
|
|
closed,
|
|
|
|
fill,
|
|
|
|
stroke,
|
|
|
|
} = path_shape;
|
|
|
|
|
|
|
|
self.scratchpad_path.clear();
|
2022-03-20 19:38:48 +00:00
|
|
|
if *closed {
|
|
|
|
self.scratchpad_path.add_line_loop(points);
|
2021-09-20 19:36:56 +00:00
|
|
|
} else {
|
2022-03-20 19:38:48 +00:00
|
|
|
self.scratchpad_path.add_open_points(points);
|
2021-09-20 19:36:56 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
if *fill != Color32::TRANSPARENT {
|
2021-09-20 19:36:56 +00:00
|
|
|
crate::epaint_assert!(
|
|
|
|
closed,
|
|
|
|
"You asked to fill a path that is not closed. That makes no sense."
|
|
|
|
);
|
2022-03-23 10:41:38 +00:00
|
|
|
self.scratchpad_path.fill(self.feathering, *fill, out);
|
2021-09-20 19:36:56 +00:00
|
|
|
}
|
2022-03-20 19:38:48 +00:00
|
|
|
let typ = if *closed {
|
2021-09-20 19:36:56 +00:00
|
|
|
PathType::Closed
|
|
|
|
} else {
|
|
|
|
PathType::Open
|
|
|
|
};
|
2022-03-20 19:38:48 +00:00
|
|
|
self.scratchpad_path
|
2022-03-23 10:41:38 +00:00
|
|
|
.stroke(self.feathering, typ, *stroke, out);
|
2021-09-20 19:36:56 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
/// Tessellate a single [`Rect`] into a [`Mesh`].
|
|
|
|
///
|
|
|
|
/// * `rect`: the rectangle to tessellate.
|
|
|
|
/// * `out`: triangles are appended to this.
|
|
|
|
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
2021-09-20 19:36:56 +00:00
|
|
|
let RectShape {
|
2020-08-21 16:53:43 +00:00
|
|
|
mut rect,
|
2022-02-05 17:13:46 +00:00
|
|
|
rounding,
|
2020-05-23 12:17:40 +00:00
|
|
|
fill,
|
2020-09-01 21:54:21 +00:00
|
|
|
stroke,
|
2020-12-29 00:13:14 +00:00
|
|
|
} = *rect;
|
2020-10-10 05:21:45 +00:00
|
|
|
|
2020-12-29 00:13:14 +00:00
|
|
|
if self.options.coarse_tessellation_culling
|
|
|
|
&& !rect.expand(stroke.width).intersects(self.clip_rect)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2021-03-21 09:31:18 +00:00
|
|
|
if rect.is_negative() {
|
2020-12-29 00:13:14 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-10-10 05:21:45 +00:00
|
|
|
|
2020-12-29 00:13:14 +00:00
|
|
|
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
|
|
|
|
// Make sure we can handle that:
|
|
|
|
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
|
|
|
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
2020-10-10 05:21:45 +00:00
|
|
|
|
2020-12-29 00:13:14 +00:00
|
|
|
let path = &mut self.scratchpad_path;
|
|
|
|
path.clear();
|
2022-02-05 17:13:46 +00:00
|
|
|
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
2020-12-29 00:13:14 +00:00
|
|
|
path.add_line_loop(&self.scratchpad_points);
|
2022-03-23 10:41:38 +00:00
|
|
|
path.fill(self.feathering, fill, out);
|
|
|
|
path.stroke_closed(self.feathering, stroke, out);
|
2020-12-29 00:13:14 +00:00
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
|
|
|
/// * `text_shape`: the text to tessellate.
|
|
|
|
/// * `out`: triangles are appended to this.
|
2022-04-03 16:14:27 +00:00
|
|
|
pub fn tessellate_text(&mut self, text_shape: &TextShape, out: &mut Mesh) {
|
2021-09-05 07:06:53 +00:00
|
|
|
let TextShape {
|
|
|
|
pos: galley_pos,
|
|
|
|
galley,
|
|
|
|
underline,
|
|
|
|
override_text_color,
|
|
|
|
angle,
|
|
|
|
} = text_shape;
|
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
if galley.is_empty() {
|
2020-12-29 00:13:14 +00:00
|
|
|
return;
|
2020-12-16 20:59:33 +00:00
|
|
|
}
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
|
|
|
|
out.vertices.reserve(galley.num_vertices);
|
|
|
|
out.indices.reserve(galley.num_indices);
|
2020-12-16 20:59:33 +00:00
|
|
|
|
2021-05-12 17:41:45 +00:00
|
|
|
// The contents of the galley is already snapped to pixel coordinates,
|
|
|
|
// but we need to make sure the galley ends up on the start of a physical pixel:
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
let galley_pos = pos2(
|
2022-03-23 10:41:38 +00:00
|
|
|
self.round_to_pixel(galley_pos.x),
|
|
|
|
self.round_to_pixel(galley_pos.y),
|
2021-05-12 17:41:45 +00:00
|
|
|
);
|
|
|
|
|
2022-04-03 16:14:27 +00:00
|
|
|
let uv_normalizer = vec2(
|
|
|
|
1.0 / self.font_tex_size[0] as f32,
|
|
|
|
1.0 / self.font_tex_size[1] as f32,
|
|
|
|
);
|
2020-12-16 20:59:33 +00:00
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
let rotator = Rot2::from_angle(*angle);
|
2021-09-05 07:06:53 +00:00
|
|
|
|
2021-03-30 19:07:19 +00:00
|
|
|
for row in &galley.rows {
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
if row.visuals.mesh.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-09-05 07:06:53 +00:00
|
|
|
let mut row_rect = row.visuals.mesh_bounds;
|
2022-03-20 19:38:48 +00:00
|
|
|
if *angle != 0.0 {
|
2021-09-05 07:06:53 +00:00
|
|
|
row_rect = row_rect.rotate_bb(rotator);
|
|
|
|
}
|
|
|
|
row_rect = row_rect.translate(galley_pos.to_vec2());
|
2020-12-16 20:59:33 +00:00
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) {
|
2021-03-30 19:07:19 +00:00
|
|
|
// culling individual lines of text is important, since a single `Shape::Text`
|
|
|
|
// can span hundreds of lines.
|
|
|
|
continue;
|
|
|
|
}
|
2020-12-16 20:59:33 +00:00
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
let index_offset = out.vertices.len() as u32;
|
2021-01-30 13:48:36 +00:00
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
out.indices.extend(
|
|
|
|
row.visuals
|
|
|
|
.mesh
|
|
|
|
.indices
|
|
|
|
.iter()
|
|
|
|
.map(|index| index + index_offset),
|
|
|
|
);
|
2021-01-30 13:48:36 +00:00
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
out.vertices.extend(
|
|
|
|
row.visuals
|
|
|
|
.mesh
|
|
|
|
.vertices
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(i, vertex)| {
|
2021-09-05 07:06:53 +00:00
|
|
|
let Vertex { pos, uv, mut color } = *vertex;
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
|
|
|
|
if let Some(override_text_color) = override_text_color {
|
|
|
|
if row.visuals.glyph_vertex_range.contains(&i) {
|
2022-03-20 19:38:48 +00:00
|
|
|
color = *override_text_color;
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
let offset = if *angle == 0.0 {
|
2021-09-05 07:06:53 +00:00
|
|
|
pos.to_vec2()
|
|
|
|
} else {
|
|
|
|
rotator * pos.to_vec2()
|
|
|
|
};
|
|
|
|
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
Vertex {
|
2021-09-05 07:06:53 +00:00
|
|
|
pos: galley_pos + offset,
|
|
|
|
uv: (uv.to_vec2() * uv_normalizer).to_pos2(),
|
2021-01-30 13:48:36 +00:00
|
|
|
color,
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2022-03-20 19:38:48 +00:00
|
|
|
if *underline != Stroke::none() {
|
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.
This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.
One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.
## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).
Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.
All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 16:18:00 +00:00
|
|
|
self.scratchpad_path.clear();
|
|
|
|
self.scratchpad_path
|
|
|
|
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
|
|
|
|
self.scratchpad_path
|
2022-03-23 10:41:38 +00:00
|
|
|
.stroke_open(self.feathering, *underline, out);
|
2020-12-29 00:13:14 +00:00
|
|
|
}
|
2020-12-16 20:59:33 +00:00
|
|
|
}
|
2019-01-04 13:14:32 +00:00
|
|
|
}
|
2022-03-20 19:38:48 +00:00
|
|
|
|
|
|
|
/// Tessellate a single [`QuadraticBezierShape`] into a [`Mesh`].
|
|
|
|
///
|
|
|
|
/// * `quadratic_shape`: the shape to tessellate.
|
|
|
|
/// * `out`: triangles are appended to this.
|
|
|
|
pub fn tessellate_quadratic_bezier(
|
|
|
|
&mut self,
|
|
|
|
quadratic_shape: QuadraticBezierShape,
|
|
|
|
out: &mut Mesh,
|
|
|
|
) {
|
|
|
|
let options = &self.options;
|
|
|
|
let clip_rect = self.clip_rect;
|
|
|
|
|
|
|
|
if options.coarse_tessellation_culling
|
|
|
|
&& !quadratic_shape.visual_bounding_rect().intersects(clip_rect)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let points = quadratic_shape.flatten(Some(options.bezier_tolerance));
|
|
|
|
|
|
|
|
self.tessellate_bezier_complete(
|
|
|
|
&points,
|
|
|
|
quadratic_shape.fill,
|
|
|
|
quadratic_shape.closed,
|
|
|
|
quadratic_shape.stroke,
|
|
|
|
out,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Tessellate a single [`CubicBezierShape`] into a [`Mesh`].
|
|
|
|
///
|
|
|
|
/// * `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) {
|
|
|
|
let options = &self.options;
|
|
|
|
let clip_rect = self.clip_rect;
|
|
|
|
if options.coarse_tessellation_culling
|
|
|
|
&& !cubic_shape.visual_bounding_rect().intersects(clip_rect)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let points_vec =
|
|
|
|
cubic_shape.flatten_closed(Some(options.bezier_tolerance), Some(options.epsilon));
|
|
|
|
|
|
|
|
for points in points_vec {
|
|
|
|
self.tessellate_bezier_complete(
|
|
|
|
&points,
|
|
|
|
cubic_shape.fill,
|
|
|
|
cubic_shape.closed,
|
|
|
|
cubic_shape.stroke,
|
|
|
|
out,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tessellate_bezier_complete(
|
|
|
|
&mut self,
|
|
|
|
points: &[Pos2],
|
|
|
|
fill: Color32,
|
|
|
|
closed: bool,
|
|
|
|
stroke: Stroke,
|
|
|
|
out: &mut Mesh,
|
|
|
|
) {
|
|
|
|
self.scratchpad_path.clear();
|
|
|
|
if closed {
|
|
|
|
self.scratchpad_path.add_line_loop(points);
|
|
|
|
} else {
|
|
|
|
self.scratchpad_path.add_open_points(points);
|
|
|
|
}
|
|
|
|
if fill != Color32::TRANSPARENT {
|
|
|
|
crate::epaint_assert!(
|
|
|
|
closed,
|
|
|
|
"You asked to fill a path that is not closed. That makes no sense."
|
|
|
|
);
|
2022-03-23 10:41:38 +00:00
|
|
|
self.scratchpad_path.fill(self.feathering, fill, out);
|
2022-03-20 19:38:48 +00:00
|
|
|
}
|
|
|
|
let typ = if closed {
|
|
|
|
PathType::Closed
|
|
|
|
} else {
|
|
|
|
PathType::Open
|
|
|
|
};
|
2022-03-23 10:41:38 +00:00
|
|
|
self.scratchpad_path
|
|
|
|
.stroke(self.feathering, typ, stroke, out);
|
2022-03-20 19:38:48 +00:00
|
|
|
}
|
2019-01-04 13:14:32 +00:00
|
|
|
}
|
2020-04-20 21:33:16 +00:00
|
|
|
|
2021-01-10 10:43:01 +00:00
|
|
|
/// Turns [`Shape`]:s into sets of triangles.
|
2020-08-21 16:53:43 +00:00
|
|
|
///
|
2021-10-10 13:35:13 +00:00
|
|
|
/// The given shapes will tessellated in the same order as they are given.
|
2020-08-21 16:53:43 +00:00
|
|
|
/// They will be batched together by clip rectangle.
|
|
|
|
///
|
2022-03-23 10:41:38 +00:00
|
|
|
/// * `pixels_per_point`: number of physical pixels to each logical point
|
2020-12-28 23:51:27 +00:00
|
|
|
/// * `options`: tessellation quality
|
2022-03-23 10:41:38 +00:00
|
|
|
/// * `shapes`: what to tessellate
|
2022-05-10 17:31:19 +00:00
|
|
|
/// * `font_tex_size`: size of the font texture. Required to normalize glyph uv rectangles when tessellating text.
|
|
|
|
/// * `prepared_discs`: What [`TextureAtlas::prepared_discs`] returns. Can safely be set to an empty vec.
|
2020-08-21 16:53:43 +00:00
|
|
|
///
|
2021-10-10 13:35:13 +00:00
|
|
|
/// The implementation uses a [`Tessellator`].
|
|
|
|
///
|
2020-08-21 16:53:43 +00:00
|
|
|
/// ## Returns
|
2021-01-25 20:23:24 +00:00
|
|
|
/// A list of clip rectangles with matching [`Mesh`].
|
2021-01-10 10:43:01 +00:00
|
|
|
pub fn tessellate_shapes(
|
2022-03-23 10:41:38 +00:00
|
|
|
pixels_per_point: f32,
|
2020-12-28 23:51:27 +00:00
|
|
|
options: TessellationOptions,
|
2022-04-03 16:14:27 +00:00
|
|
|
font_tex_size: [usize; 2],
|
2022-05-10 17:31:19 +00:00
|
|
|
prepared_discs: Vec<PreparedDisc>,
|
|
|
|
shapes: Vec<ClippedShape>,
|
2022-03-14 12:25:11 +00:00
|
|
|
) -> Vec<ClippedPrimitive> {
|
2022-05-10 17:31:19 +00:00
|
|
|
let mut tessellator =
|
|
|
|
Tessellator::new(pixels_per_point, options, font_tex_size, prepared_discs);
|
2020-05-11 11:11:01 +00:00
|
|
|
|
2022-03-14 12:25:11 +00:00
|
|
|
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
2021-01-25 20:43:17 +00:00
|
|
|
|
2022-04-03 16:14:27 +00:00
|
|
|
for clipped_shape in shapes {
|
|
|
|
tessellator.tessellate_clipped_shape(clipped_shape, &mut clipped_primitives);
|
2020-05-11 16:14:02 +00:00
|
|
|
}
|
|
|
|
|
2022-03-14 12:25:11 +00:00
|
|
|
if options.debug_paint_clip_rects {
|
2022-04-03 16:14:27 +00:00
|
|
|
clipped_primitives = add_clip_rects(&mut tessellator, clipped_primitives);
|
2022-03-14 12:25:11 +00:00
|
|
|
}
|
|
|
|
|
2020-10-10 09:39:39 +00:00
|
|
|
if options.debug_ignore_clip_rects {
|
2022-03-14 12:25:11 +00:00
|
|
|
for clipped_primitive in &mut clipped_primitives {
|
|
|
|
clipped_primitive.clip_rect = Rect::EVERYTHING;
|
2020-10-10 09:39:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-14 12:25:11 +00:00
|
|
|
for clipped_primitive in &clipped_primitives {
|
|
|
|
if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {
|
|
|
|
crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
|
|
|
|
}
|
2020-10-21 09:04:22 +00:00
|
|
|
}
|
|
|
|
|
2022-03-14 12:25:11 +00:00
|
|
|
clipped_primitives
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_clip_rects(
|
|
|
|
tessellator: &mut Tessellator,
|
|
|
|
clipped_primitives: Vec<ClippedPrimitive>,
|
|
|
|
) -> Vec<ClippedPrimitive> {
|
|
|
|
tessellator.clip_rect = Rect::EVERYTHING;
|
|
|
|
let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150));
|
|
|
|
|
|
|
|
clipped_primitives
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(|clipped_primitive| {
|
|
|
|
let mut clip_rect_mesh = Mesh::default();
|
|
|
|
tessellator.tessellate_shape(
|
|
|
|
Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke),
|
|
|
|
&mut clip_rect_mesh,
|
|
|
|
);
|
|
|
|
|
|
|
|
[
|
|
|
|
clipped_primitive,
|
|
|
|
ClippedPrimitive {
|
|
|
|
clip_rect: Rect::EVERYTHING, // whatever
|
|
|
|
primitive: Primitive::Mesh(clip_rect_mesh),
|
|
|
|
},
|
|
|
|
]
|
|
|
|
})
|
|
|
|
.collect()
|
2020-04-20 21:33:16 +00:00
|
|
|
}
|
2022-04-03 16:14:27 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_tessellator() {
|
|
|
|
use crate::*;
|
|
|
|
|
|
|
|
let mut shapes = Vec::with_capacity(2);
|
|
|
|
|
|
|
|
let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
|
|
|
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
|
|
|
|
|
|
|
let mut mesh = Mesh::with_texture(TextureId::Managed(1));
|
|
|
|
mesh.add_rect_with_uv(rect, uv, Color32::WHITE);
|
|
|
|
shapes.push(Shape::mesh(mesh));
|
|
|
|
|
|
|
|
let mut mesh = Mesh::with_texture(TextureId::Managed(2));
|
|
|
|
mesh.add_rect_with_uv(rect, uv, Color32::WHITE);
|
|
|
|
shapes.push(Shape::mesh(mesh));
|
|
|
|
|
|
|
|
let shape = Shape::Vec(shapes);
|
|
|
|
let clipped_shapes = vec![ClippedShape(rect, shape)];
|
|
|
|
|
2022-05-10 17:31:19 +00:00
|
|
|
let font_tex_size = [1024, 1024]; // unused
|
|
|
|
let prepared_discs = vec![]; // unused
|
|
|
|
|
|
|
|
let primitives = tessellate_shapes(
|
|
|
|
1.0,
|
|
|
|
Default::default(),
|
|
|
|
font_tex_size,
|
|
|
|
prepared_discs,
|
|
|
|
clipped_shapes,
|
|
|
|
);
|
2022-04-03 16:14:27 +00:00
|
|
|
assert_eq!(primitives.len(), 2);
|
|
|
|
}
|