1#![allow(clippy::derived_hash_with_manual_eq)] use std::{fmt::Debug, sync::Arc};
4
5use super::{emath, Color32, ColorMode, Pos2, Rect};
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
39impl<Color> From<(f32, Color)> for Stroke
40where
41 Color: Into<Color32>,
42{
43 #[inline(always)]
44 fn from((width, color): (f32, Color)) -> Self {
45 Self::new(width, color)
46 }
47}
48
49impl std::hash::Hash for Stroke {
50 #[inline(always)]
51 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
52 let Self { width, color } = *self;
53 emath::OrderedFloat(width).hash(state);
54 color.hash(state);
55 }
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
60#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
61pub enum StrokeKind {
62 Outside,
64
65 Inside,
67
68 Middle,
70}
71
72impl Default for StrokeKind {
73 fn default() -> Self {
74 Self::Middle
75 }
76}
77
78#[derive(Clone, Debug, Default, PartialEq)]
82#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
83pub struct PathStroke {
84 pub width: f32,
85 pub color: ColorMode,
86 pub kind: StrokeKind,
87}
88
89impl PathStroke {
90 pub const NONE: Self = Self {
92 width: 0.0,
93 color: ColorMode::TRANSPARENT,
94 kind: StrokeKind::Middle,
95 };
96
97 #[inline]
98 pub fn new(width: impl Into<f32>, color: impl Into<Color32>) -> Self {
99 Self {
100 width: width.into(),
101 color: ColorMode::Solid(color.into()),
102 kind: StrokeKind::default(),
103 }
104 }
105
106 #[inline]
110 pub fn new_uv(
111 width: impl Into<f32>,
112 callback: impl Fn(Rect, Pos2) -> Color32 + Send + Sync + 'static,
113 ) -> Self {
114 Self {
115 width: width.into(),
116 color: ColorMode::UV(Arc::new(callback)),
117 kind: StrokeKind::default(),
118 }
119 }
120
121 pub fn middle(self) -> Self {
123 Self {
124 kind: StrokeKind::Middle,
125 ..self
126 }
127 }
128
129 pub fn outside(self) -> Self {
131 Self {
132 kind: StrokeKind::Outside,
133 ..self
134 }
135 }
136
137 pub fn inside(self) -> Self {
139 Self {
140 kind: StrokeKind::Inside,
141 ..self
142 }
143 }
144
145 #[inline]
147 pub fn is_empty(&self) -> bool {
148 self.width <= 0.0 || self.color == ColorMode::TRANSPARENT
149 }
150}
151
152impl<Color> From<(f32, Color)> for PathStroke
153where
154 Color: Into<Color32>,
155{
156 #[inline(always)]
157 fn from((width, color): (f32, Color)) -> Self {
158 Self::new(width, color)
159 }
160}
161
162impl From<Stroke> for PathStroke {
163 fn from(value: Stroke) -> Self {
164 if value.is_empty() {
165 Self::NONE
167 } else {
168 Self {
169 width: value.width,
170 color: ColorMode::Solid(value.color),
171 kind: StrokeKind::default(),
172 }
173 }
174 }
175}