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, Copy, Debug, PartialEq, Eq)]
60#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
61pub enum StrokeKind {
62 Inside,
64
65 Middle,
67
68 Outside,
70}
71
72#[derive(Clone, Debug, PartialEq)]
76#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
77pub struct PathStroke {
78 pub width: f32,
79 pub color: ColorMode,
80 pub kind: StrokeKind,
81}
82
83impl Default for PathStroke {
84 #[inline]
85 fn default() -> Self {
86 Self::NONE
87 }
88}
89
90impl PathStroke {
91 pub const NONE: Self = Self {
93 width: 0.0,
94 color: ColorMode::TRANSPARENT,
95 kind: StrokeKind::Middle,
96 };
97
98 #[inline]
99 pub fn new(width: impl Into<f32>, color: impl Into<Color32>) -> Self {
100 Self {
101 width: width.into(),
102 color: ColorMode::Solid(color.into()),
103 kind: StrokeKind::Middle,
104 }
105 }
106
107 #[inline]
111 pub fn new_uv(
112 width: impl Into<f32>,
113 callback: impl Fn(Rect, Pos2) -> Color32 + Send + Sync + 'static,
114 ) -> Self {
115 Self {
116 width: width.into(),
117 color: ColorMode::UV(Arc::new(callback)),
118 kind: StrokeKind::Middle,
119 }
120 }
121
122 #[inline]
123 pub fn with_kind(self, kind: StrokeKind) -> Self {
124 Self { kind, ..self }
125 }
126
127 #[inline]
129 pub fn middle(self) -> Self {
130 Self {
131 kind: StrokeKind::Middle,
132 ..self
133 }
134 }
135
136 #[inline]
138 pub fn outside(self) -> Self {
139 Self {
140 kind: StrokeKind::Outside,
141 ..self
142 }
143 }
144
145 #[inline]
147 pub fn inside(self) -> Self {
148 Self {
149 kind: StrokeKind::Inside,
150 ..self
151 }
152 }
153
154 #[inline]
156 pub fn is_empty(&self) -> bool {
157 self.width <= 0.0 || self.color == ColorMode::TRANSPARENT
158 }
159}
160
161impl<Color> From<(f32, Color)> for PathStroke
162where
163 Color: Into<Color32>,
164{
165 #[inline(always)]
166 fn from((width, color): (f32, Color)) -> Self {
167 Self::new(width, color)
168 }
169}
170
171impl From<Stroke> for PathStroke {
172 fn from(value: Stroke) -> Self {
173 if value.is_empty() {
174 Self::NONE
176 } else {
177 Self {
178 width: value.width,
179 color: ColorMode::Solid(value.color),
180 kind: StrokeKind::Middle,
181 }
182 }
183 }
184}