1use core::fmt::Debug;
2use std::sync::Arc;
3
4use emath::GuiRounding as _;
5
6use super::{Color32, ColorMode, Pos2, Rect, emath};
7
8#[derive(Clone, Copy, Debug, Default, PartialEq)]
12#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
13pub struct Stroke {
14 pub width: f32,
15 pub color: Color32,
16}
17
18impl Stroke {
19 pub const NONE: Self = Self {
21 width: 0.0,
22 color: Color32::TRANSPARENT,
23 };
24
25 #[inline]
26 pub fn new(width: f32, color: impl Into<Color32>) -> Self {
27 Self {
28 width,
29 color: color.into(),
30 }
31 }
32
33 #[inline]
35 pub fn is_empty(&self) -> bool {
36 self.width <= 0.0 || self.color == Color32::TRANSPARENT
37 }
38
39 pub fn round_center_to_pixel(&self, pixels_per_point: f32, coord: &mut f32) {
42 let pixel_size = 1.0 / pixels_per_point;
55
56 if self.width <= pixel_size || is_nearest_integer_odd(pixels_per_point * self.width) {
57 *coord = coord.round_to_pixel_center(pixels_per_point);
58 } else {
59 *coord = coord.round_to_pixels(pixels_per_point);
60 }
61 }
62
63 pub(crate) fn round_rect_to_pixel(&self, pixels_per_point: f32, rect: &mut Rect) {
64 let pixel_size = 1.0 / pixels_per_point;
68
69 let width = self.width;
70 if width <= 0.0 {
71 *rect = rect.round_to_pixels(pixels_per_point);
72 } else if width <= pixel_size || is_nearest_integer_odd(pixels_per_point * width) {
73 *rect = rect.round_to_pixel_center(pixels_per_point);
74 } else {
75 *rect = rect.round_to_pixels(pixels_per_point);
76 }
77 }
78}
79
80impl<Color> From<(f32, Color)> for Stroke
81where
82 Color: Into<Color32>,
83{
84 #[inline(always)]
85 fn from((width, color): (f32, Color)) -> Self {
86 Self::new(width, color)
87 }
88}
89
90impl core::hash::Hash for Stroke {
91 #[inline(always)]
92 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
93 let Self { width, color } = *self;
94 emath::OrderedFloat(width).hash(state);
95 color.hash(state);
96 }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
102pub enum StrokeKind {
103 Inside,
105
106 Middle,
108
109 Outside,
111}
112
113#[derive(Clone, Debug, PartialEq)]
117#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
118pub struct PathStroke {
119 pub width: f32,
120 pub color: ColorMode,
121 pub kind: StrokeKind,
122}
123
124impl Default for PathStroke {
125 #[inline]
126 fn default() -> Self {
127 Self::NONE
128 }
129}
130
131impl PathStroke {
132 pub const NONE: Self = Self {
134 width: 0.0,
135 color: ColorMode::TRANSPARENT,
136 kind: StrokeKind::Middle,
137 };
138
139 #[inline]
140 pub fn new(width: f32, color: impl Into<Color32>) -> Self {
141 Self {
142 width,
143 color: ColorMode::Solid(color.into()),
144 kind: StrokeKind::Middle,
145 }
146 }
147
148 #[inline]
152 pub fn new_uv(
153 width: f32,
154 callback: impl Fn(Rect, Pos2) -> Color32 + Send + Sync + 'static,
155 ) -> Self {
156 Self {
157 width,
158 color: ColorMode::UV(Arc::new(callback)),
159 kind: StrokeKind::Middle,
160 }
161 }
162
163 #[inline]
164 pub fn with_kind(self, kind: StrokeKind) -> Self {
165 Self { kind, ..self }
166 }
167
168 #[inline]
170 pub fn middle(self) -> Self {
171 Self {
172 kind: StrokeKind::Middle,
173 ..self
174 }
175 }
176
177 #[inline]
179 pub fn outside(self) -> Self {
180 Self {
181 kind: StrokeKind::Outside,
182 ..self
183 }
184 }
185
186 #[inline]
188 pub fn inside(self) -> Self {
189 Self {
190 kind: StrokeKind::Inside,
191 ..self
192 }
193 }
194
195 #[inline]
197 pub fn is_empty(&self) -> bool {
198 self.width <= 0.0 || self.color == ColorMode::TRANSPARENT
199 }
200}
201
202impl<Color> From<(f32, Color)> for PathStroke
203where
204 Color: Into<Color32>,
205{
206 #[inline(always)]
207 fn from((width, color): (f32, Color)) -> Self {
208 Self::new(width, color)
209 }
210}
211
212impl From<Stroke> for PathStroke {
213 fn from(value: Stroke) -> Self {
214 if value.is_empty() {
215 Self::NONE
217 } else {
218 Self {
219 width: value.width,
220 color: ColorMode::Solid(value.color),
221 kind: StrokeKind::Middle,
222 }
223 }
224 }
225}
226
227fn is_nearest_integer_odd(x: f32) -> bool {
229 (x * 0.5 + 0.25).fract() > 0.5
230}
231
232#[test]
233fn test_is_nearest_integer_odd() {
234 assert!(is_nearest_integer_odd(0.6));
235 assert!(is_nearest_integer_odd(1.0));
236 assert!(is_nearest_integer_odd(1.4));
237 assert!(!is_nearest_integer_odd(1.6));
238 assert!(!is_nearest_integer_odd(2.0));
239 assert!(!is_nearest_integer_odd(2.4));
240 assert!(is_nearest_integer_odd(2.6));
241 assert!(is_nearest_integer_odd(3.0));
242 assert!(is_nearest_integer_odd(3.4));
243}