use std::sync::Arc;
use crate::{
text::{FontId, Fonts, Galley},
Color32, Mesh, Stroke, TextureId,
};
use emath::*;
pub use crate::{CubicBezierShape, QuadraticBezierShape};
#[must_use = "Add a Shape to a Painter"]
#[derive(Clone, Debug, PartialEq)]
pub enum Shape {
Noop,
Vec(Vec<Shape>),
Circle(CircleShape),
LineSegment {
points: [Pos2; 2],
stroke: Stroke,
},
Path(PathShape),
Rect(RectShape),
Text(TextShape),
Mesh(Mesh),
QuadraticBezier(QuadraticBezierShape),
CubicBezier(CubicBezierShape),
Callback(PaintCallback),
}
#[cfg(test)]
#[test]
fn shape_impl_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Shape>();
}
impl From<Vec<Shape>> for Shape {
#[inline(always)]
fn from(shapes: Vec<Shape>) -> Self {
Self::Vec(shapes)
}
}
impl From<Mesh> for Shape {
#[inline(always)]
fn from(mesh: 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(),
}
}
#[inline]
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
Self::Path(PathShape::line(points, stroke))
}
#[inline]
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> 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);
shapes
}
pub fn dashed_line_many(
points: &[Pos2],
stroke: impl Into<Stroke>,
dash_length: f32,
gap_length: f32,
shapes: &mut Vec<Shape>,
) {
dashes_from_line(points, stroke.into(), dash_length, gap_length, shapes);
}
#[inline]
pub fn convex_polygon(
points: Vec<Pos2>,
fill: impl Into<Color32>,
stroke: impl Into<Stroke>,
) -> 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 rect_filled(
rect: Rect,
rounding: impl Into<Rounding>,
fill_color: impl Into<Color32>,
) -> Self {
Self::Rect(RectShape::filled(rect, rounding, fill_color))
}
#[inline]
pub fn rect_stroke(
rect: Rect,
rounding: impl Into<Rounding>,
stroke: impl Into<Stroke>,
) -> Self {
Self::Rect(RectShape::stroke(rect, rounding, stroke))
}
#[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_rect(Rect::from_min_size(pos, galley.size()));
Self::galley(rect.min, galley)
}
#[inline]
pub fn galley(pos: Pos2, galley: Arc<Galley>) -> Self {
TextShape::new(pos, galley).into()
}
#[inline]
pub fn galley_with_color(pos: Pos2, galley: Arc<Galley>, text_color: Color32) -> Self {
TextShape {
override_text_color: Some(text_color),
..TextShape::new(pos, galley)
}
.into()
}
pub fn mesh(mesh: Mesh) -> Self {
crate::epaint_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);
Shape::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::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) -> super::TextureId {
if let Shape::Mesh(mesh) = self {
mesh.texture_id
} else {
super::TextureId::default()
}
}
pub fn translate(&mut self, delta: Vec2) {
match self {
Shape::Noop => {}
Shape::Vec(shapes) => {
for shape in shapes {
shape.translate(delta);
}
}
Shape::Circle(circle_shape) => {
circle_shape.center += delta;
}
Shape::LineSegment { points, .. } => {
for p in points {
*p += delta;
}
}
Shape::Path(path_shape) => {
for p in &mut path_shape.points {
*p += delta;
}
}
Shape::Rect(rect_shape) => {
rect_shape.rect = rect_shape.rect.translate(delta);
}
Shape::Text(text_shape) => {
text_shape.pos += delta;
}
Shape::Mesh(mesh) => {
mesh.translate(delta);
}
Shape::QuadraticBezier(bezier_shape) => {
bezier_shape.points[0] += delta;
bezier_shape.points[1] += delta;
bezier_shape.points[2] += delta;
}
Shape::CubicBezier(cubie_curve) => {
for p in &mut cubie_curve.points {
*p += delta;
}
}
Shape::Callback(shape) => {
shape.rect = shape.rect.translate(delta);
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct CircleShape {
pub center: Pos2,
pub radius: f32,
pub fill: Color32,
pub stroke: Stroke,
}
impl CircleShape {
#[inline]
pub fn filled(center: Pos2, radius: f32, fill_color: impl Into<Color32>) -> Self {
Self {
center,
radius,
fill: fill_color.into(),
stroke: Default::default(),
}
}
#[inline]
pub fn stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
Self {
center,
radius,
fill: Default::default(),
stroke: stroke.into(),
}
}
pub fn visual_bounding_rect(&self) -> Rect {
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
Rect::NOTHING
} else {
Rect::from_center_size(
self.center,
Vec2::splat(self.radius + self.stroke.width / 2.0),
)
}
}
}
impl From<CircleShape> for Shape {
#[inline(always)]
fn from(shape: CircleShape) -> Self {
Self::Circle(shape)
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct PathShape {
pub points: Vec<Pos2>,
pub closed: bool,
pub fill: Color32,
pub stroke: Stroke,
}
impl PathShape {
#[inline]
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
PathShape {
points,
closed: false,
fill: Default::default(),
stroke: stroke.into(),
}
}
#[inline]
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
PathShape {
points,
closed: true,
fill: Default::default(),
stroke: stroke.into(),
}
}
#[inline]
pub fn convex_polygon(
points: Vec<Pos2>,
fill: impl Into<Color32>,
stroke: impl Into<Stroke>,
) -> Self {
PathShape {
points,
closed: true,
fill: fill.into(),
stroke: stroke.into(),
}
}
#[inline]
pub fn visual_bounding_rect(&self) -> Rect {
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
Rect::NOTHING
} else {
Rect::from_points(&self.points).expand(self.stroke.width / 2.0)
}
}
}
impl From<PathShape> for Shape {
#[inline(always)]
fn from(shape: PathShape) -> Self {
Self::Path(shape)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RectShape {
pub rect: Rect,
pub rounding: Rounding,
pub fill: Color32,
pub stroke: Stroke,
}
impl RectShape {
#[inline]
pub fn filled(
rect: Rect,
rounding: impl Into<Rounding>,
fill_color: impl Into<Color32>,
) -> Self {
Self {
rect,
rounding: rounding.into(),
fill: fill_color.into(),
stroke: Default::default(),
}
}
#[inline]
pub fn stroke(rect: Rect, rounding: impl Into<Rounding>, stroke: impl Into<Stroke>) -> Self {
Self {
rect,
rounding: rounding.into(),
fill: Default::default(),
stroke: stroke.into(),
}
}
#[inline]
pub fn visual_bounding_rect(&self) -> Rect {
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
Rect::NOTHING
} else {
self.rect.expand(self.stroke.width / 2.0)
}
}
}
impl From<RectShape> for Shape {
#[inline(always)]
fn from(shape: RectShape) -> Self {
Self::Rect(shape)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Rounding {
pub nw: f32,
pub ne: f32,
pub sw: f32,
pub se: f32,
}
impl Default for Rounding {
#[inline]
fn default() -> Self {
Self::none()
}
}
impl From<f32> for Rounding {
#[inline]
fn from(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
}
impl Rounding {
#[inline]
pub fn same(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
#[inline]
pub fn none() -> Self {
Self {
nw: 0.0,
ne: 0.0,
sw: 0.0,
se: 0.0,
}
}
#[inline]
pub fn is_same(&self) -> bool {
self.nw == self.ne && self.nw == self.sw && self.nw == self.se
}
#[inline]
pub fn at_least(&self, min: f32) -> Self {
Self {
nw: self.nw.max(min),
ne: self.ne.max(min),
sw: self.sw.max(min),
se: self.se.max(min),
}
}
#[inline]
pub fn at_most(&self, max: f32) -> Self {
Self {
nw: self.nw.min(max),
ne: self.ne.min(max),
sw: self.sw.min(max),
se: self.se.min(max),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct TextShape {
pub pos: Pos2,
pub galley: Arc<Galley>,
pub underline: Stroke,
pub override_text_color: Option<Color32>,
pub angle: f32,
}
impl TextShape {
#[inline]
pub fn new(pos: Pos2, galley: Arc<Galley>) -> Self {
Self {
pos,
galley,
underline: Stroke::none(),
override_text_color: None,
angle: 0.0,
}
}
#[inline]
pub fn visual_bounding_rect(&self) -> Rect {
self.galley.mesh_bounds.translate(self.pos.to_vec2())
}
}
impl From<TextShape> for Shape {
#[inline(always)]
fn from(shape: TextShape) -> Self {
Self::Text(shape)
}
}
fn points_from_line(
path: &[Pos2],
spacing: f32,
radius: f32,
color: Color32,
shapes: &mut Vec<Shape>,
) {
let mut position_on_segment = 0.0;
path.windows(2).for_each(|window| {
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_length: f32,
gap_length: f32,
shapes: &mut Vec<Shape>,
) {
let mut position_on_segment = 0.0;
let mut drawing_dash = false;
path.windows(2).for_each(|window| {
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_length;
} else {
start_point = new_point;
position_on_segment += dash_length;
}
drawing_dash = !drawing_dash;
}
if drawing_dash {
shapes.push(Shape::line_segment([start_point, end], stroke));
}
position_on_segment -= segment_length;
});
}
pub struct PaintCallbackInfo {
pub viewport: Rect,
pub clip_rect: Rect,
pub pixels_per_point: f32,
pub screen_size_px: [u32; 2],
}
pub struct ViewportInPixels {
pub left_px: f32,
pub top_px: f32,
pub from_bottom_px: f32,
pub width_px: f32,
pub height_px: f32,
}
impl PaintCallbackInfo {
fn points_to_pixels(&self, rect: &Rect) -> ViewportInPixels {
ViewportInPixels {
left_px: rect.min.x * self.pixels_per_point,
top_px: rect.min.y * self.pixels_per_point,
from_bottom_px: self.screen_size_px[1] as f32 - rect.max.y * self.pixels_per_point,
width_px: rect.width() * self.pixels_per_point,
height_px: rect.height() * self.pixels_per_point,
}
}
pub fn viewport_in_pixels(&self) -> ViewportInPixels {
self.points_to_pixels(&self.viewport)
}
pub fn clip_rect_in_pixels(&self) -> ViewportInPixels {
self.points_to_pixels(&self.clip_rect)
}
}
#[derive(Clone)]
pub struct PaintCallback {
pub rect: Rect,
pub callback: Arc<dyn Fn(&PaintCallbackInfo, &dyn std::any::Any) + Send + Sync>,
}
impl PaintCallback {
#[inline]
pub fn call(&self, info: &PaintCallbackInfo, render_ctx: &dyn std::any::Any) {
(self.callback)(info, render_ctx);
}
}
impl std::fmt::Debug for PaintCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CustomShape")
.field("rect", &self.rect)
.finish_non_exhaustive()
}
}
impl std::cmp::PartialEq for PaintCallback {
fn eq(&self, other: &Self) -> bool {
#[allow(clippy::vtable_address_comparisons)]
{
self.rect.eq(&other.rect) && Arc::ptr_eq(&self.callback, &other.callback)
}
}
}
impl From<PaintCallback> for Shape {
#[inline(always)]
fn from(shape: PaintCallback) -> Self {
Self::Callback(shape)
}
}