iced_audio/graphics/tick_marks/
radial.rs

1use iced_graphics::widget::canvas::{self, Fill, Frame, LineCap, Path, Stroke};
2use iced_graphics::Primitive;
3use iced_native::{Color, Point, Size, Vector};
4
5use super::PrimitiveCache;
6use crate::core::Normal;
7use crate::native::tick_marks;
8use crate::style::tick_marks::{Appearance, Shape};
9
10#[allow(clippy::too_many_arguments)]
11fn draw_radial_circles(
12    frame: &mut Frame,
13    offset_radius: f32,
14    start_angle: f32,
15    angle_span: f32,
16    tick_marks: &[Normal],
17    color: Color,
18    radius: f32,
19    inverse: bool,
20) {
21    let path = Path::circle(Point::new(0.0, -offset_radius), radius);
22
23    if inverse {
24        for tick_mark in tick_marks {
25            let angle = start_angle + tick_mark.scale_inv(angle_span);
26
27            frame.with_save(|frame| {
28                if !(-0.001..=0.001).contains(&angle) {
29                    frame.rotate(angle);
30                }
31
32                frame.fill(
33                    &path,
34                    Fill {
35                        style: canvas::Style::Solid(color),
36                        ..Fill::default()
37                    },
38                );
39            });
40        }
41    } else {
42        for tick_mark in tick_marks {
43            let angle = start_angle + tick_mark.scale(angle_span);
44
45            frame.with_save(|frame| {
46                if !(-0.001..=0.001).contains(&angle) {
47                    frame.rotate(angle);
48                }
49
50                frame.fill(
51                    &path,
52                    Fill {
53                        style: canvas::Style::Solid(color),
54                        ..Fill::default()
55                    },
56                );
57            });
58        }
59    }
60}
61
62#[allow(clippy::too_many_arguments)]
63fn draw_radial_lines(
64    frame: &mut Frame,
65    offset_radius: f32,
66    start_angle: f32,
67    angle_span: f32,
68    tick_marks: &[Normal],
69    color: Color,
70    width: f32,
71    length: f32,
72    inverse: bool,
73) {
74    let path = Path::line(
75        Point::new(0.0, -offset_radius),
76        Point::new(0.0, -offset_radius - length),
77    );
78
79    if inverse {
80        for tick_mark in tick_marks {
81            let angle = start_angle + tick_mark.scale_inv(angle_span);
82
83            frame.with_save(|frame| {
84                if !(-0.001..=0.001).contains(&angle) {
85                    frame.rotate(angle);
86                }
87
88                frame.stroke(
89                    &path,
90                    Stroke {
91                        width,
92                        style: canvas::Style::Solid(color),
93                        line_cap: LineCap::Butt,
94                        ..Stroke::default()
95                    },
96                );
97            });
98        }
99    } else {
100        for tick_mark in tick_marks {
101            let angle = start_angle + tick_mark.scale(angle_span);
102
103            frame.with_save(|frame| {
104                if !(-0.001..=0.001).contains(&angle) {
105                    frame.rotate(angle);
106                }
107
108                frame.stroke(
109                    &path,
110                    Stroke {
111                        width,
112                        style: canvas::Style::Solid(color),
113                        line_cap: LineCap::Butt,
114                        ..Stroke::default()
115                    },
116                );
117            });
118        }
119    }
120}
121
122#[inline]
123#[allow(clippy::too_many_arguments)]
124fn draw_tier(
125    frame: &mut Frame,
126    offset_radius: f32,
127    start_angle: f32,
128    angle_span: f32,
129    tick_marks: Option<&Vec<Normal>>,
130    shape: &Shape,
131    inside: bool,
132    inverse: bool,
133) {
134    if let Some(tick_marks) = tick_marks {
135        match shape {
136            Shape::None => (),
137            Shape::Line {
138                length,
139                width,
140                color,
141            } => {
142                let length = *length;
143                let width = *width;
144
145                if inside {
146                    draw_radial_lines(
147                        frame,
148                        offset_radius - length,
149                        start_angle,
150                        angle_span,
151                        tick_marks,
152                        *color,
153                        width,
154                        length,
155                        inverse,
156                    );
157                } else {
158                    draw_radial_lines(
159                        frame,
160                        offset_radius,
161                        start_angle,
162                        angle_span,
163                        tick_marks,
164                        *color,
165                        width,
166                        length,
167                        inverse,
168                    );
169                }
170            }
171            Shape::Circle { diameter, color } => {
172                let radius = (*diameter) / 2.0;
173
174                if inside {
175                    draw_radial_circles(
176                        frame,
177                        offset_radius - radius,
178                        start_angle,
179                        angle_span,
180                        tick_marks,
181                        *color,
182                        radius,
183                        inverse,
184                    );
185                } else {
186                    draw_radial_circles(
187                        frame,
188                        offset_radius + radius,
189                        start_angle,
190                        angle_span,
191                        tick_marks,
192                        *color,
193                        radius,
194                        inverse,
195                    );
196                }
197            }
198        }
199    }
200}
201
202fn max_length(style: &Appearance) -> f32 {
203    let length_1 = match style.tier_1 {
204        Shape::None => 0.0,
205        Shape::Line { length, .. } => length,
206        Shape::Circle { diameter, .. } => diameter,
207    };
208
209    let length_2 = match style.tier_1 {
210        Shape::None => 0.0,
211        Shape::Line { length, .. } => length,
212        Shape::Circle { diameter, .. } => diameter,
213    };
214
215    let length_3 = match style.tier_1 {
216        Shape::None => 0.0,
217        Shape::Line { length, .. } => length,
218        Shape::Circle { diameter, .. } => diameter,
219    };
220
221    length_1.max(length_2).max(length_3)
222}
223
224/// Draws tick marks around an arc.
225///
226/// * `center` - The center point of the arc.
227/// * `radius` - The radius of the arc where the tick marks start
228/// * `start_angle` - The starting angle of the arc in radians
229/// * `angle_span` - The span of the angle in radians
230/// * `inside` - Whether to place the tick marks inside the radius (true),
231/// or outside the radius (false).
232/// * `tick_marks` - The group of tick marks.
233/// * `style` - The tick marks style.
234/// * `inverse` - Whether to inverse the positions of the tick marks (true) or
235/// not (false).
236#[allow(clippy::too_many_arguments)]
237pub fn draw_radial_tick_marks(
238    center: Point,
239    radius: f32,
240    start_angle: f32,
241    angle_span: f32,
242    inside: bool,
243    tick_marks: &tick_marks::Group,
244    style: &Appearance,
245    inverse: bool,
246    cache: &PrimitiveCache,
247) -> Primitive {
248    cache.cached_radial(
249        center,
250        radius,
251        start_angle,
252        angle_span,
253        inside,
254        tick_marks,
255        *style,
256        inverse,
257        || {
258            let frame_radius = if inside {
259                radius
260            } else {
261                radius + max_length(style)
262            };
263
264            let frame_size = frame_radius * 2.0;
265
266            let mut frame = Frame::new(Size::new(frame_size, frame_size));
267
268            frame.translate(Vector::new(frame_radius, frame_radius));
269
270            draw_tier(
271                &mut frame,
272                radius,
273                start_angle,
274                angle_span,
275                tick_marks.tier_1(),
276                &style.tier_1,
277                inside,
278                inverse,
279            );
280            draw_tier(
281                &mut frame,
282                radius,
283                start_angle,
284                angle_span,
285                tick_marks.tier_2(),
286                &style.tier_2,
287                inside,
288                inverse,
289            );
290            draw_tier(
291                &mut frame,
292                radius,
293                start_angle,
294                angle_span,
295                tick_marks.tier_3(),
296                &style.tier_3,
297                inside,
298                inverse,
299            );
300
301            Primitive::Translate {
302                translation: Vector::new(
303                    center.x - frame_radius,
304                    center.y - frame_radius,
305                ),
306                content: Box::new(frame.into_geometry().into_primitive()),
307            }
308        },
309    )
310}