aethermap_gui/widgets/
curve_graph.rs1use iced::widget::canvas::{self, event, Frame, Geometry, Path, Program, Stroke};
8use iced::{Color, Point, Rectangle};
9use iced::mouse;
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()
93 .with_color(Color::WHITE)
94 .with_width(2.0),
95 );
96 frame.stroke(
97 &y_axis,
98 Stroke::default()
99 .with_color(Color::WHITE)
100 .with_width(2.0),
101 );
102
103 let num_points = 51;
105 let points: Vec<Point> = (0..num_points)
106 .map(|i| {
107 let input = i as f32 / (num_points - 1) as f32; let output = Self::apply_curve(input, &self.curve);
109
110 Point::new(
112 origin.x + input * graph_width,
113 origin.y - output * graph_height,
114 )
115 })
116 .collect();
117
118 for window in points.windows(2) {
120 let segment = Path::line(window[0], window[1]);
121 frame.stroke(
122 &segment,
123 Stroke::default()
124 .with_color(Color::from_rgb(0.3, 0.8, 0.3))
125 .with_width(2.0),
126 );
127 }
128
129 if self.multiplier > 1.0 {
131 let clamped_y = origin.y - graph_height;
133 let indicator = Path::line(
134 Point::new(bounds.width - margin - 30.0, clamped_y + 5.0),
135 Point::new(bounds.width - margin, clamped_y + 5.0),
136 );
137 frame.stroke(
138 &indicator,
139 Stroke::default()
140 .with_color(Color::from_rgb(0.8, 0.5, 0.0))
141 .with_width(3.0),
142 );
143 }
144
145 vec![frame.into_geometry()]
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_apply_curve_linear() {
155 assert!((CurveGraph::apply_curve(0.0, &SensitivityCurve::Linear) - 0.0).abs() < f32::EPSILON);
157 assert!((CurveGraph::apply_curve(0.5, &SensitivityCurve::Linear) - 0.5).abs() < f32::EPSILON);
158 assert!((CurveGraph::apply_curve(1.0, &SensitivityCurve::Linear) - 1.0).abs() < f32::EPSILON);
159 }
160
161 #[test]
162 fn test_apply_curve_quadratic() {
163 let result = CurveGraph::apply_curve(0.5, &SensitivityCurve::Quadratic);
165 assert!((result - 0.25).abs() < 0.001);
166 }
167
168 #[test]
169 fn test_apply_curve_exponential() {
170 let result = CurveGraph::apply_curve(0.5, &SensitivityCurve::Exponential);
172 assert!((result - 0.25).abs() < 0.001); }
174
175 #[test]
176 fn test_apply_curve_with_multiplier() {
177 let graph = CurveGraph::new(SensitivityCurve::Linear, 2.0);
180 assert_eq!(graph.multiplier, 2.0);
181 }
182
183 #[test]
184 fn test_apply_curve_negative_input_quadratic() {
185 let result = CurveGraph::apply_curve(-0.5, &SensitivityCurve::Quadratic);
188 assert!((result - 0.25).abs() < 0.001); }
190
191 #[test]
192 fn test_apply_curve_negative_input_exponential() {
193 let result = CurveGraph::apply_curve(-0.5, &SensitivityCurve::Exponential);
195 assert!((result - (-0.25)).abs() < 0.001); }
197
198 #[test]
199 fn test_curve_graph_new() {
200 let graph = CurveGraph::new(SensitivityCurve::Quadratic, 1.5);
201 assert_eq!(graph.multiplier, 1.5);
202 }
204
205 #[test]
206 fn test_apply_curve_zero() {
207 assert!((CurveGraph::apply_curve(0.0, &SensitivityCurve::Linear) - 0.0).abs() < f32::EPSILON);
209 assert!((CurveGraph::apply_curve(0.0, &SensitivityCurve::Quadratic) - 0.0).abs() < f32::EPSILON);
210 assert!((CurveGraph::apply_curve(0.0, &SensitivityCurve::Exponential) - 0.0).abs() < f32::EPSILON);
211 }
212
213 #[test]
214 fn test_apply_curve_full_deflection() {
215 assert_eq!(CurveGraph::apply_curve(1.0, &SensitivityCurve::Linear), 1.0);
217 assert_eq!(CurveGraph::apply_curve(1.0, &SensitivityCurve::Quadratic), 1.0);
218 assert_eq!(CurveGraph::apply_curve(1.0, &SensitivityCurve::Exponential), 1.0);
219 }
220}