snapr/drawing/geometry/
point.rs1use std::fmt;
4
5use geo::MapCoords;
6use tiny_skia::{FillRule, Paint, Path, PathBuilder, Pixmap, Shader, Stroke, Transform};
7
8use crate::drawing::{
9 style::{ColorOptions, Effect, Styleable, Styled},
10 Context, Drawable,
11};
12
13use super::macros::impl_styled_geo;
14
15#[derive(Clone, Debug, PartialEq)]
17pub enum Shape {
18 Circle { radius: f32 },
19}
20
21impl Shape {
22 pub fn to_path(&self, x: f32, y: f32) -> Result<Path, crate::Error> {
24 let mut path_builder = PathBuilder::new();
25
26 match self {
27 Self::Circle { radius } => {
28 path_builder.push_circle(x, y, *radius);
29 }
30 }
31
32 path_builder.finish().ok_or(crate::Error::PathConstruction)
33 }
34}
35
36impl Default for Shape {
37 fn default() -> Self {
38 Self::Circle { radius: 4.0 }
39 }
40}
41
42#[derive(Clone, Debug, PartialEq)]
44pub enum Representation {
45 Shape(Shape),
46
47 #[cfg(feature = "svg")]
48 Svg(crate::drawing::svg::Svg),
49}
50
51impl Default for Representation {
52 fn default() -> Self {
53 Self::Shape(Shape::default())
54 }
55}
56
57#[derive(Clone, Default)]
59pub struct PointStyle<'a> {
60 pub color_options: ColorOptions,
61 pub representation: Representation,
62 pub effect: Option<Effect<'a, geo::Point<f64>, Self>>,
63
64 #[cfg(feature = "svg")]
65 pub label: Option<crate::drawing::svg::Label>,
66}
67
68impl<'a> fmt::Debug for PointStyle<'a> {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 f.debug_struct("PointStyle")
71 .field("color_options", &self.color_options)
72 .field("representation", &self.representation)
73 .field("label", &self.label)
74 .finish()
75 }
76}
77
78impl_styled_geo!(
79 Point,
80 PointStyle<'_>,
81 fn draw(&self, pixmap: &mut Pixmap, context: &Context) -> Result<(), crate::Error> {
82 let style = match &self.style.effect {
83 Some(effect) => {
84 &(effect
85 .clone()
86 .apply(self.style.clone(), self.inner, context))
87 }
88
89 None => &self.style,
90 };
91
92 let point = self
93 .inner
94 .map_coords(|coord| context.epsg_4326_to_pixel(&coord));
95
96 let shape = match &style.representation {
97 Representation::Shape(shape) => shape,
98
99 #[cfg(feature = "svg")]
100 Representation::Svg(svg) => {
101 let svg = svg.try_as_svg((point.x(), point.y()))?;
102 svg.draw(pixmap, context)?;
103
104 return Ok(());
105 }
106 };
107
108 #[cfg(feature = "tracing")]
109 {
110 tracing::trace!(position = ?point, "rendering `Point` to `pixmap`");
111 }
112
113 let shape = shape.to_path(point.x() as f32, point.y() as f32)?;
114
115 pixmap.fill_path(
116 &shape,
117 &Paint {
118 shader: Shader::SolidColor(style.color_options.foreground),
119 anti_alias: style.color_options.anti_alias,
120 ..Paint::default()
121 },
122 FillRule::default(),
123 Transform::default(),
124 None,
125 );
126
127 if let Some(border) = style.color_options.border {
128 pixmap.stroke_path(
129 &shape,
130 &Paint {
131 shader: Shader::SolidColor(style.color_options.background),
132 anti_alias: style.color_options.anti_alias,
133 ..Paint::default()
134 },
135 &Stroke {
136 width: border,
137 ..Stroke::default()
138 },
139 Transform::default(),
140 None,
141 );
142 }
143
144 #[cfg(feature = "svg")]
145 if let Some(label) = &style.label {
146 let svg = label.try_as_svg((point.x(), point.y()))?;
147 svg.draw(pixmap, context)?;
148 }
149
150 Ok(())
151 }
152);
153
154impl_styled_geo!(
155 MultiPoint,
156 PointStyle<'_>,
157 fn draw(&self, pixmap: &mut Pixmap, context: &Context) -> Result<(), crate::Error> {
158 self.inner
159 .iter()
160 .map(|point| point.as_styled(self.style.clone()))
161 .try_for_each(|point| point.draw(pixmap, context))
162 }
163);