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