use std::sync::Arc;
use emath::{pos2, Align2, Pos2, Rangef, Rect, TSTransform, Vec2};
use crate::{
stroke::PathStroke,
text::{FontId, Fonts, Galley},
Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId,
};
use super::{
CircleShape, CubicBezierShape, EllipseShape, PaintCallback, PathShape, QuadraticBezierShape,
RectShape, TextShape,
};
#[must_use = "Add a Shape to a Painter"]
#[derive(Clone, Debug, PartialEq)]
pub enum Shape {
Noop,
Vec(Vec<Shape>),
Circle(CircleShape),
Ellipse(EllipseShape),
LineSegment { points: [Pos2; 2], stroke: Stroke },
Path(PathShape),
Rect(RectShape),
Text(TextShape),
Mesh(Arc<Mesh>),
QuadraticBezier(QuadraticBezierShape),
CubicBezier(CubicBezierShape),
Callback(PaintCallback),
}
#[test]
fn shape_size() {
assert_eq!(
std::mem::size_of::<Shape>(), 64,
"Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
);
assert!(
std::mem::size_of::<Shape>() <= 64,
"Shape is getting way too big!"
);
}
#[test]
fn shape_impl_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Shape>();
}
impl From<Vec<Self>> for Shape {
#[inline(always)]
fn from(shapes: Vec<Self>) -> Self {
Self::Vec(shapes)
}
}
impl From<Mesh> for Shape {
#[inline(always)]
fn from(mesh: Mesh) -> Self {
Self::Mesh(mesh.into())
}
}
impl From<Arc<Mesh>> for Shape {
#[inline(always)]
fn from(mesh: Arc<Mesh>) -> Self {
Self::Mesh(mesh)
}
}
impl Shape {
#[inline]
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
Self::LineSegment {
points,
stroke: stroke.into(),
}
}
pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> Self {
let x = x.into();
Self::LineSegment {
points: [pos2(x.min, y), pos2(x.max, y)],
stroke: stroke.into(),
}
}
pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> Self {
let y = y.into();
Self::LineSegment {
points: [pos2(x, y.min), pos2(x, y.max)],
stroke: stroke.into(),
}
}
#[inline]
pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
Self::Path(PathShape::line(points, stroke))
}
#[inline]
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
Self::Path(PathShape::closed_line(points, stroke))
}
pub fn dotted_line(
path: &[Pos2],
color: impl Into<Color32>,
spacing: f32,
radius: f32,
) -> Vec<Self> {
let mut shapes = Vec::new();
points_from_line(path, spacing, radius, color.into(), &mut shapes);
shapes
}
pub fn dashed_line(
path: &[Pos2],
stroke: impl Into<Stroke>,
dash_length: f32,
gap_length: f32,
) -> Vec<Self> {
let mut shapes = Vec::new();
dashes_from_line(
path,
stroke.into(),
&[dash_length],
&[gap_length],
&mut shapes,
0.,
);
shapes
}
pub fn dashed_line_with_offset(
path: &[Pos2],
stroke: impl Into<Stroke>,
dash_lengths: &[f32],
gap_lengths: &[f32],
dash_offset: f32,
) -> Vec<Self> {
let mut shapes = Vec::new();
dashes_from_line(
path,
stroke.into(),
dash_lengths,
gap_lengths,
&mut shapes,
dash_offset,
);
shapes
}
pub fn dashed_line_many(
points: &[Pos2],
stroke: impl Into<Stroke>,
dash_length: f32,
gap_length: f32,
shapes: &mut Vec<Self>,
) {
dashes_from_line(
points,
stroke.into(),
&[dash_length],
&[gap_length],
shapes,
0.,
);
}
pub fn dashed_line_many_with_offset(
points: &[Pos2],
stroke: impl Into<Stroke>,
dash_lengths: &[f32],
gap_lengths: &[f32],
dash_offset: f32,
shapes: &mut Vec<Self>,
) {
dashes_from_line(
points,
stroke.into(),
dash_lengths,
gap_lengths,
shapes,
dash_offset,
);
}
#[inline]
pub fn convex_polygon(
points: Vec<Pos2>,
fill: impl Into<Color32>,
stroke: impl Into<PathStroke>,
) -> Self {
Self::Path(PathShape::convex_polygon(points, fill, stroke))
}
#[inline]
pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
Self::Circle(CircleShape::filled(center, radius, fill_color))
}
#[inline]
pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
Self::Circle(CircleShape::stroke(center, radius, stroke))
}
#[inline]
pub fn ellipse_filled(center: Pos2, radius: Vec2, fill_color: impl Into<Color32>) -> Self {
Self::Ellipse(EllipseShape::filled(center, radius, fill_color))
}
#[inline]
pub fn ellipse_stroke(center: Pos2, radius: Vec2, stroke: impl Into<Stroke>) -> Self {
Self::Ellipse(EllipseShape::stroke(center, radius, stroke))
}
#[inline]
pub fn rect_filled(
rect: Rect,
corner_radius: impl Into<CornerRadius>,
fill_color: impl Into<Color32>,
) -> Self {
Self::Rect(RectShape::filled(rect, corner_radius, fill_color))
}
#[inline]
pub fn rect_stroke(
rect: Rect,
corner_radius: impl Into<CornerRadius>,
stroke: impl Into<Stroke>,
stroke_kind: StrokeKind,
) -> Self {
Self::Rect(RectShape::stroke(rect, corner_radius, stroke, stroke_kind))
}
#[allow(clippy::needless_pass_by_value)]
pub fn text(
fonts: &Fonts,
pos: Pos2,
anchor: Align2,
text: impl ToString,
font_id: FontId,
color: Color32,
) -> Self {
let galley = fonts.layout_no_wrap(text.to_string(), font_id, color);
let rect = anchor.anchor_size(pos, galley.size());
Self::galley(rect.min, galley, color)
}
#[inline]
pub fn galley(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
TextShape::new(pos, galley, fallback_color).into()
}
#[inline]
pub fn galley_with_override_text_color(
pos: Pos2,
galley: Arc<Galley>,
text_color: Color32,
) -> Self {
TextShape::new(pos, galley, text_color)
.with_override_text_color(text_color)
.into()
}
#[inline]
#[deprecated = "Use `Shape::galley` or `Shape::galley_with_override_text_color` instead"]
pub fn galley_with_color(pos: Pos2, galley: Arc<Galley>, text_color: Color32) -> Self {
Self::galley_with_override_text_color(pos, galley, text_color)
}
#[inline]
pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
let mesh = mesh.into();
debug_assert!(mesh.is_valid());
Self::Mesh(mesh)
}
pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
let mut mesh = Mesh::with_texture(texture_id);
mesh.add_rect_with_uv(rect, uv, tint);
Self::mesh(mesh)
}
pub fn visual_bounding_rect(&self) -> Rect {
match self {
Self::Noop => Rect::NOTHING,
Self::Vec(shapes) => {
let mut rect = Rect::NOTHING;
for shape in shapes {
rect = rect.union(shape.visual_bounding_rect());
}
rect
}
Self::Circle(circle_shape) => circle_shape.visual_bounding_rect(),
Self::Ellipse(ellipse_shape) => ellipse_shape.visual_bounding_rect(),
Self::LineSegment { points, stroke } => {
if stroke.is_empty() {
Rect::NOTHING
} else {
Rect::from_two_pos(points[0], points[1]).expand(stroke.width / 2.0)
}
}
Self::Path(path_shape) => path_shape.visual_bounding_rect(),
Self::Rect(rect_shape) => rect_shape.visual_bounding_rect(),
Self::Text(text_shape) => text_shape.visual_bounding_rect(),
Self::Mesh(mesh) => mesh.calc_bounds(),
Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(),
Self::CubicBezier(bezier) => bezier.visual_bounding_rect(),
Self::Callback(custom) => custom.rect,
}
}
}
impl Shape {
#[inline(always)]
pub fn texture_id(&self) -> crate::TextureId {
if let Self::Mesh(mesh) = self {
mesh.texture_id
} else if let Self::Rect(rect_shape) = self {
rect_shape.fill_texture_id()
} else {
crate::TextureId::default()
}
}
#[inline(always)]
pub fn scale(&mut self, factor: f32) {
self.transform(TSTransform::from_scaling(factor));
}
#[inline(always)]
pub fn translate(&mut self, delta: Vec2) {
self.transform(TSTransform::from_translation(delta));
}
pub fn transform(&mut self, transform: TSTransform) {
match self {
Self::Noop => {}
Self::Vec(shapes) => {
for shape in shapes {
shape.transform(transform);
}
}
Self::Circle(circle_shape) => {
circle_shape.center = transform * circle_shape.center;
circle_shape.radius *= transform.scaling;
circle_shape.stroke.width *= transform.scaling;
}
Self::Ellipse(ellipse_shape) => {
ellipse_shape.center = transform * ellipse_shape.center;
ellipse_shape.radius *= transform.scaling;
ellipse_shape.stroke.width *= transform.scaling;
}
Self::LineSegment { points, stroke } => {
for p in points {
*p = transform * *p;
}
stroke.width *= transform.scaling;
}
Self::Path(path_shape) => {
for p in &mut path_shape.points {
*p = transform * *p;
}
path_shape.stroke.width *= transform.scaling;
}
Self::Rect(rect_shape) => {
rect_shape.rect = transform * rect_shape.rect;
rect_shape.corner_radius *= transform.scaling;
rect_shape.stroke.width *= transform.scaling;
rect_shape.blur_width *= transform.scaling;
}
Self::Text(text_shape) => {
text_shape.pos = transform * text_shape.pos;
let galley = Arc::make_mut(&mut text_shape.galley);
for row in &mut galley.rows {
row.visuals.mesh_bounds = transform.scaling * row.visuals.mesh_bounds;
for v in &mut row.visuals.mesh.vertices {
v.pos = Pos2::new(transform.scaling * v.pos.x, transform.scaling * v.pos.y);
}
}
galley.mesh_bounds = transform.scaling * galley.mesh_bounds;
galley.rect = transform.scaling * galley.rect;
}
Self::Mesh(mesh) => {
Arc::make_mut(mesh).transform(transform);
}
Self::QuadraticBezier(bezier) => {
for p in &mut bezier.points {
*p = transform * *p;
}
bezier.stroke.width *= transform.scaling;
}
Self::CubicBezier(bezier) => {
for p in &mut bezier.points {
*p = transform * *p;
}
bezier.stroke.width *= transform.scaling;
}
Self::Callback(shape) => {
shape.rect = transform * shape.rect;
}
}
}
}
fn points_from_line(
path: &[Pos2],
spacing: f32,
radius: f32,
color: Color32,
shapes: &mut Vec<Shape>,
) {
let mut position_on_segment = 0.0;
for window in path.windows(2) {
let (start, end) = (window[0], window[1]);
let vector = end - start;
let segment_length = vector.length();
while position_on_segment < segment_length {
let new_point = start + vector * (position_on_segment / segment_length);
shapes.push(Shape::circle_filled(new_point, radius, color));
position_on_segment += spacing;
}
position_on_segment -= segment_length;
}
}
fn dashes_from_line(
path: &[Pos2],
stroke: Stroke,
dash_lengths: &[f32],
gap_lengths: &[f32],
shapes: &mut Vec<Shape>,
dash_offset: f32,
) {
assert_eq!(dash_lengths.len(), gap_lengths.len());
let mut position_on_segment = dash_offset;
let mut drawing_dash = false;
let mut step = 0;
let steps = dash_lengths.len();
for window in path.windows(2) {
let (start, end) = (window[0], window[1]);
let vector = end - start;
let segment_length = vector.length();
let mut start_point = start;
while position_on_segment < segment_length {
let new_point = start + vector * (position_on_segment / segment_length);
if drawing_dash {
shapes.push(Shape::line_segment([start_point, new_point], stroke));
position_on_segment += gap_lengths[step];
step += 1;
if step >= steps {
step = 0;
}
} else {
start_point = new_point;
position_on_segment += dash_lengths[step];
}
drawing_dash = !drawing_dash;
}
if drawing_dash {
shapes.push(Shape::line_segment([start_point, end], stroke));
}
position_on_segment -= segment_length;
}
}