use std::{any::Any, sync::Arc};
use crate::{
stroke::PathStroke,
text::{FontId, Fonts, Galley},
Color32, Mesh, Stroke, TextureId,
};
use emath::{pos2, Align2, Pos2, Rangef, Rect, TSTransform, Vec2};
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),
Ellipse(EllipseShape),
LineSegment {
points: [Pos2; 2],
stroke: PathStroke,
},
Path(PathShape),
Rect(RectShape),
Text(TextShape),
Mesh(Mesh),
QuadraticBezier(QuadraticBezierShape),
CubicBezier(CubicBezierShape),
Callback(PaintCallback),
}
#[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)
}
}
impl Shape {
#[inline]
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<PathStroke>) -> Self {
Self::LineSegment {
points,
stroke: stroke.into(),
}
}
pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<PathStroke>) -> Self {
let x = x.into();
Self::LineSegment {
points: [pos2(x.min, y), pos2(x.max, y)],
stroke: stroke.into(),
}
}
pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<PathStroke>) -> Self {
let y = y.into();
Self::LineSegment {
points: [pos2(x, y.min), pos2(x, y.max)],
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,
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_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: Mesh) -> Self {
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) -> super::TextureId {
if let Self::Mesh(mesh) = self {
mesh.texture_id
} else if let Self::Rect(rect_shape) = self {
rect_shape.fill_texture_id
} else {
super::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.stroke.width *= transform.scaling;
rect_shape.rounding *= 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) => {
mesh.transform(transform);
}
Self::QuadraticBezier(bezier_shape) => {
bezier_shape.points[0] = transform * bezier_shape.points[0];
bezier_shape.points[1] = transform * bezier_shape.points[1];
bezier_shape.points[2] = transform * bezier_shape.points[2];
bezier_shape.stroke.width *= transform.scaling;
}
Self::CubicBezier(cubic_curve) => {
for p in &mut cubic_curve.points {
*p = transform * *p;
}
cubic_curve.stroke.width *= transform.scaling;
}
Self::Callback(shape) => {
shape.rect = transform * shape.rect;
}
}
}
}
#[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 * 2.0 + self.stroke.width),
)
}
}
}
impl From<CircleShape> for Shape {
#[inline(always)]
fn from(shape: CircleShape) -> Self {
Self::Circle(shape)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct EllipseShape {
pub center: Pos2,
pub radius: Vec2,
pub fill: Color32,
pub stroke: Stroke,
}
impl EllipseShape {
#[inline]
pub fn filled(center: Pos2, radius: Vec2, fill_color: impl Into<Color32>) -> Self {
Self {
center,
radius,
fill: fill_color.into(),
stroke: Default::default(),
}
}
#[inline]
pub fn stroke(center: Pos2, radius: Vec2, 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,
self.radius * 2.0 + Vec2::splat(self.stroke.width),
)
}
}
}
impl From<EllipseShape> for Shape {
#[inline(always)]
fn from(shape: EllipseShape) -> Self {
Self::Ellipse(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: PathStroke,
}
impl PathShape {
#[inline]
pub fn line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
Self {
points,
closed: false,
fill: Default::default(),
stroke: stroke.into(),
}
}
#[inline]
pub fn closed_line(points: Vec<Pos2>, stroke: impl Into<PathStroke>) -> Self {
Self {
points,
closed: true,
fill: Default::default(),
stroke: stroke.into(),
}
}
#[inline]
pub fn convex_polygon(
points: Vec<Pos2>,
fill: impl Into<Color32>,
stroke: impl Into<PathStroke>,
) -> Self {
Self {
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,
pub blur_width: f32,
pub fill_texture_id: TextureId,
pub uv: Rect,
}
impl RectShape {
#[inline]
pub fn new(
rect: Rect,
rounding: impl Into<Rounding>,
fill_color: impl Into<Color32>,
stroke: impl Into<Stroke>,
) -> Self {
Self {
rect,
rounding: rounding.into(),
fill: fill_color.into(),
stroke: stroke.into(),
blur_width: 0.0,
fill_texture_id: Default::default(),
uv: Rect::ZERO,
}
}
#[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(),
blur_width: 0.0,
fill_texture_id: Default::default(),
uv: Rect::ZERO,
}
}
#[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(),
blur_width: 0.0,
fill_texture_id: Default::default(),
uv: Rect::ZERO,
}
}
#[inline]
pub fn with_blur_width(mut self, blur_width: f32) -> Self {
self.blur_width = blur_width;
self
}
#[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 + self.blur_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::ZERO
}
}
impl From<f32> for Rounding {
#[inline]
fn from(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
}
impl Rounding {
pub const ZERO: Self = Self {
nw: 0.0,
ne: 0.0,
sw: 0.0,
se: 0.0,
};
#[inline]
pub const fn same(radius: f32) -> Self {
Self {
nw: radius,
ne: radius,
sw: radius,
se: radius,
}
}
#[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),
}
}
}
impl std::ops::Add for Rounding {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self {
nw: self.nw + rhs.nw,
ne: self.ne + rhs.ne,
sw: self.sw + rhs.sw,
se: self.se + rhs.se,
}
}
}
impl std::ops::AddAssign for Rounding {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = Self {
nw: self.nw + rhs.nw,
ne: self.ne + rhs.ne,
sw: self.sw + rhs.sw,
se: self.se + rhs.se,
};
}
}
impl std::ops::AddAssign<f32> for Rounding {
#[inline]
fn add_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw + rhs,
ne: self.ne + rhs,
sw: self.sw + rhs,
se: self.se + rhs,
};
}
}
impl std::ops::Sub for Rounding {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self {
nw: self.nw - rhs.nw,
ne: self.ne - rhs.ne,
sw: self.sw - rhs.sw,
se: self.se - rhs.se,
}
}
}
impl std::ops::SubAssign for Rounding {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = Self {
nw: self.nw - rhs.nw,
ne: self.ne - rhs.ne,
sw: self.sw - rhs.sw,
se: self.se - rhs.se,
};
}
}
impl std::ops::SubAssign<f32> for Rounding {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw - rhs,
ne: self.ne - rhs,
sw: self.sw - rhs,
se: self.se - rhs,
};
}
}
impl std::ops::Div<f32> for Rounding {
type Output = Self;
#[inline]
fn div(self, rhs: f32) -> Self {
Self {
nw: self.nw / rhs,
ne: self.ne / rhs,
sw: self.sw / rhs,
se: self.se / rhs,
}
}
}
impl std::ops::DivAssign<f32> for Rounding {
#[inline]
fn div_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw / rhs,
ne: self.ne / rhs,
sw: self.sw / rhs,
se: self.se / rhs,
};
}
}
impl std::ops::Mul<f32> for Rounding {
type Output = Self;
#[inline]
fn mul(self, rhs: f32) -> Self {
Self {
nw: self.nw * rhs,
ne: self.ne * rhs,
sw: self.sw * rhs,
se: self.se * rhs,
}
}
}
impl std::ops::MulAssign<f32> for Rounding {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
*self = Self {
nw: self.nw * rhs,
ne: self.ne * rhs,
sw: self.sw * rhs,
se: self.se * rhs,
};
}
}
#[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 fallback_color: Color32,
pub override_text_color: Option<Color32>,
pub opacity_factor: f32,
pub angle: f32,
}
impl TextShape {
#[inline]
pub fn new(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
Self {
pos,
galley,
underline: Stroke::NONE,
fallback_color,
override_text_color: None,
opacity_factor: 1.0,
angle: 0.0,
}
}
#[inline]
pub fn visual_bounding_rect(&self) -> Rect {
self.galley.mesh_bounds.translate(self.pos.to_vec2())
}
#[inline]
pub fn with_underline(mut self, underline: Stroke) -> Self {
self.underline = underline;
self
}
#[inline]
pub fn with_override_text_color(mut self, override_text_color: Color32) -> Self {
self.override_text_color = Some(override_text_color);
self
}
#[inline]
pub fn with_angle(mut self, angle: f32) -> Self {
self.angle = angle;
self
}
#[inline]
pub fn with_opacity_factor(mut self, opacity_factor: f32) -> Self {
self.opacity_factor = opacity_factor;
self
}
}
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_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();
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_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;
});
}
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: i32,
pub top_px: i32,
pub from_bottom_px: i32,
pub width_px: i32,
pub height_px: i32,
}
impl ViewportInPixels {
fn from_points(rect: &Rect, pixels_per_point: f32, screen_size_px: [u32; 2]) -> Self {
let left_px = (pixels_per_point * rect.min.x).round() as i32; let top_px = (pixels_per_point * rect.min.y).round() as i32; let right_px = (pixels_per_point * rect.max.x).round() as i32; let bottom_px = (pixels_per_point * rect.max.y).round() as i32;
let screen_width = screen_size_px[0] as i32;
let screen_height = screen_size_px[1] as i32;
let left_px = left_px.clamp(0, screen_width);
let right_px = right_px.clamp(left_px, screen_width);
let top_px = top_px.clamp(0, screen_height);
let bottom_px = bottom_px.clamp(top_px, screen_height);
let width_px = right_px - left_px;
let height_px = bottom_px - top_px;
Self {
left_px,
top_px,
from_bottom_px: screen_height - height_px - top_px,
width_px,
height_px,
}
}
}
#[test]
fn test_viewport_rounding() {
for i in 0..=10_000 {
let x = i as f32 / 97.0;
let left = Rect::from_min_max(pos2(0.0, 0.0), pos2(100.0, 100.0)).with_max_x(x);
let right = Rect::from_min_max(pos2(0.0, 0.0), pos2(100.0, 100.0)).with_min_x(x);
for pixels_per_point in [0.618, 1.0, std::f32::consts::PI] {
let left = ViewportInPixels::from_points(&left, pixels_per_point, [100, 100]);
let right = ViewportInPixels::from_points(&right, pixels_per_point, [100, 100]);
assert_eq!(left.left_px + left.width_px, right.left_px);
}
}
}
impl PaintCallbackInfo {
pub fn viewport_in_pixels(&self) -> ViewportInPixels {
ViewportInPixels::from_points(&self.viewport, self.pixels_per_point, self.screen_size_px)
}
pub fn clip_rect_in_pixels(&self) -> ViewportInPixels {
ViewportInPixels::from_points(&self.clip_rect, self.pixels_per_point, self.screen_size_px)
}
}
#[derive(Clone)]
pub struct PaintCallback {
pub rect: Rect,
pub callback: Arc<dyn Any + Send + Sync>,
}
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 {
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)
}
}