Skip to main content

epaint/
stroke.rs

1use core::fmt::Debug;
2use std::sync::Arc;
3
4use emath::GuiRounding as _;
5
6use super::{Color32, ColorMode, Pos2, Rect, emath};
7
8/// Describes the width and color of a line.
9///
10/// The default stroke is the same as [`Stroke::NONE`].
11#[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    /// Same as [`Stroke::default`].
20    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    /// True if width is zero or color is transparent
34    #[inline]
35    pub fn is_empty(&self) -> bool {
36        self.width <= 0.0 || self.color == Color32::TRANSPARENT
37    }
38
39    /// For vertical or horizontal lines:
40    /// round the stroke center to produce a sharp, pixel-aligned line.
41    pub fn round_center_to_pixel(&self, pixels_per_point: f32, coord: &mut f32) {
42        // If the stroke is an odd number of pixels wide,
43        // we want to round the center of it to the center of a pixel.
44        //
45        // If however it is an even number of pixels wide,
46        // we want to round the center to be between two pixels.
47        //
48        // We also want to treat strokes that are _almost_ odd as it it was odd,
49        // to make it symmetric. Same for strokes that are _almost_ even.
50        //
51        // For strokes less than a pixel wide we also round to the center,
52        // because it will rendered as a single row of pixels by the tessellator.
53
54        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        // We put odd-width strokes in the center of pixels.
65        // To understand why, see `fn round_center_to_pixel`.
66
67        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/// Describes how the stroke of a shape should be painted.
100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
102pub enum StrokeKind {
103    /// The stroke should be painted entirely inside of the shape
104    Inside,
105
106    /// The stroke should be painted right on the edge of the shape, half inside and half outside.
107    Middle,
108
109    /// The stroke should be painted entirely outside of the shape
110    Outside,
111}
112
113/// Describes the width and color of paths. The color can either be solid or provided by a callback. For more information, see [`ColorMode`]
114///
115/// The default stroke is the same as [`Stroke::NONE`].
116#[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    /// Same as [`PathStroke::default`].
133    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    /// Create a new `PathStroke` with a UV function
149    ///
150    /// The bounding box passed to the callback will have a margin of [`TessellationOptions::feathering_size_in_pixels`](`crate::tessellator::TessellationOptions::feathering_size_in_pixels`)
151    #[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    /// Set the stroke to be painted right on the edge of the shape, half inside and half outside.
169    #[inline]
170    pub fn middle(self) -> Self {
171        Self {
172            kind: StrokeKind::Middle,
173            ..self
174        }
175    }
176
177    /// Set the stroke to be painted entirely outside of the shape
178    #[inline]
179    pub fn outside(self) -> Self {
180        Self {
181            kind: StrokeKind::Outside,
182            ..self
183        }
184    }
185
186    /// Set the stroke to be painted entirely inside of the shape
187    #[inline]
188    pub fn inside(self) -> Self {
189        Self {
190            kind: StrokeKind::Inside,
191            ..self
192        }
193    }
194
195    /// True if width is zero or color is solid and transparent
196    #[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            // Important, since we use the stroke color when doing feathering of the fill!
216            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
227/// Returns true if the nearest integer is odd.
228fn 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}