aethermap_gui/widgets/
curve_graph.rs1use iced::mouse;
8use iced::widget::canvas::{self, event, Frame, Geometry, Path, Program, Stroke};
9use iced::{Color, Point, Rectangle};
10
11use crate::gui::SensitivityCurve;
12
13pub struct CurveGraph {
18 pub curve: SensitivityCurve,
20 pub multiplier: f32,
22}
23
24impl CurveGraph {
25 pub fn new(curve: SensitivityCurve, multiplier: f32) -> Self {
27 Self { curve, multiplier }
28 }
29
30 pub fn apply_curve(input: f32, curve: &SensitivityCurve) -> f32 {
35 match curve {
36 SensitivityCurve::Linear => input,
37 SensitivityCurve::Quadratic => input * input,
38 SensitivityCurve::Exponential => {
40 if input >= 0.0 {
41 input.powf(2.0)
42 } else {
43 -(-input).powf(2.0)
44 }
45 }
46 }
47 }
48}
49
50impl<Message> Program<Message> for CurveGraph {
51 type State = ();
52
53 fn update(
54 &self,
55 _state: &mut Self::State,
56 _event: canvas::Event,
57 _bounds: Rectangle,
58 _cursor: mouse::Cursor,
59 ) -> (event::Status, Option<Message>) {
60 (event::Status::Ignored, None)
61 }
62
63 fn draw(
64 &self,
65 _state: &Self::State,
66 renderer: &iced::Renderer,
67 _theme: &iced::Theme,
68 bounds: Rectangle,
69 _cursor: mouse::Cursor,
70 ) -> Vec<Geometry> {
71 let mut frame = Frame::new(renderer, bounds.size());
72
73 let margin = 20.0;
75 let graph_width = bounds.width - 2.0 * margin;
76 let graph_height = bounds.height - 2.0 * margin;
77
78 let origin = Point::new(margin, bounds.height - margin);
80
81 let x_axis = Path::line(
83 Point::new(margin, bounds.height - margin),
84 Point::new(bounds.width - margin, bounds.height - margin),
85 );
86 let y_axis = Path::line(
87 Point::new(margin, margin),
88 Point::new(margin, bounds.height - margin),
89 );
90 frame.stroke(
91 &x_axis,
92 Stroke::default().with_color(Color::WHITE).with_width(2.0),
93 );
94 frame.stroke(
95 &y_axis,
96 Stroke::default().with_color(Color::WHITE).with_width(2.0),
97 );
98
99 let num_points = 51;
101 let points: Vec<Point> = (0..num_points)
102 .map(|i| {
103 let input = i as f32 / (num_points - 1) as f32; let output = Self::apply_curve(input, &self.curve);
105
106 Point::new(
108 origin.x + input * graph_width,
109 origin.y - output * graph_height,
110 )
111 })
112 .collect();
113
114 for window in points.windows(2) {
116 let segment = Path::line(window[0], window[1]);
117 frame.stroke(
118 &segment,
119 Stroke::default()
120 .with_color(Color::from_rgb(0.3, 0.8, 0.3))
121 .with_width(2.0),
122 );
123 }
124
125 if self.multiplier > 1.0 {
127 let clamped_y = origin.y - graph_height;
129 let indicator = Path::line(
130 Point::new(bounds.width - margin - 30.0, clamped_y + 5.0),
131 Point::new(bounds.width - margin, clamped_y + 5.0),
132 );
133 frame.stroke(
134 &indicator,
135 Stroke::default()
136 .with_color(Color::from_rgb(0.8, 0.5, 0.0))
137 .with_width(3.0),
138 );
139 }
140
141 vec![frame.into_geometry()]
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_apply_curve_linear() {
151 assert!(
153 (CurveGraph::apply_curve(0.0, &SensitivityCurve::Linear) - 0.0).abs() < f32::EPSILON
154 );
155 assert!(
156 (CurveGraph::apply_curve(0.5, &SensitivityCurve::Linear) - 0.5).abs() < f32::EPSILON
157 );
158 assert!(
159 (CurveGraph::apply_curve(1.0, &SensitivityCurve::Linear) - 1.0).abs() < f32::EPSILON
160 );
161 }
162
163 #[test]
164 fn test_apply_curve_quadratic() {
165 let result = CurveGraph::apply_curve(0.5, &SensitivityCurve::Quadratic);
167 assert!((result - 0.25).abs() < 0.001);
168 }
169
170 #[test]
171 fn test_apply_curve_exponential() {
172 let result = CurveGraph::apply_curve(0.5, &SensitivityCurve::Exponential);
174 assert!((result - 0.25).abs() < 0.001); }
176
177 #[test]
178 fn test_apply_curve_with_multiplier() {
179 let graph = CurveGraph::new(SensitivityCurve::Linear, 2.0);
182 assert_eq!(graph.multiplier, 2.0);
183 }
184
185 #[test]
186 fn test_apply_curve_negative_input_quadratic() {
187 let result = CurveGraph::apply_curve(-0.5, &SensitivityCurve::Quadratic);
190 assert!((result - 0.25).abs() < 0.001); }
192
193 #[test]
194 fn test_apply_curve_negative_input_exponential() {
195 let result = CurveGraph::apply_curve(-0.5, &SensitivityCurve::Exponential);
197 assert!((result - (-0.25)).abs() < 0.001); }
199
200 #[test]
201 fn test_curve_graph_new() {
202 let graph = CurveGraph::new(SensitivityCurve::Quadratic, 1.5);
203 assert_eq!(graph.multiplier, 1.5);
204 }
206
207 #[test]
208 fn test_apply_curve_zero() {
209 assert!(
211 (CurveGraph::apply_curve(0.0, &SensitivityCurve::Linear) - 0.0).abs() < f32::EPSILON
212 );
213 assert!(
214 (CurveGraph::apply_curve(0.0, &SensitivityCurve::Quadratic) - 0.0).abs() < f32::EPSILON
215 );
216 assert!(
217 (CurveGraph::apply_curve(0.0, &SensitivityCurve::Exponential) - 0.0).abs()
218 < f32::EPSILON
219 );
220 }
221
222 #[test]
223 fn test_apply_curve_full_deflection() {
224 assert_eq!(CurveGraph::apply_curve(1.0, &SensitivityCurve::Linear), 1.0);
226 assert_eq!(
227 CurveGraph::apply_curve(1.0, &SensitivityCurve::Quadratic),
228 1.0
229 );
230 assert_eq!(
231 CurveGraph::apply_curve(1.0, &SensitivityCurve::Exponential),
232 1.0
233 );
234 }
235}