Skip to main content

tusistor_core/
update.rs

1use std::str::FromStr;
2
3use rusistor::{Color, Resistor};
4
5use crate::model::{ColorCodesToSpecsModel, InputFocus, SpecsToColorModel};
6
7pub enum ColorCodesMsg {
8    ThreeBands,
9    FourBands,
10    FiveBands,
11    SixBands,
12    NextBand,
13    PrevBand,
14    NextColor,
15    PrevColor,
16}
17
18pub enum SpecsMsg {
19    Determine,
20    NextSpecInput,
21    PrevSpecInput,
22    PrevHistory,
23    NextHistory,
24    Reset,
25}
26
27pub fn update_on_colorcodemsg(model: &mut ColorCodesToSpecsModel, msg: ColorCodesMsg) {
28    match msg {
29        ColorCodesMsg::ThreeBands => {
30            model.resistor = Resistor::ThreeBand {
31                band1: rusistor::Color::Brown,
32                band2: rusistor::Color::Black,
33                band3: rusistor::Color::Black,
34            };
35            model.selected_band = model.selected_band.min(2)
36        }
37        ColorCodesMsg::FourBands => {
38            model.resistor = Resistor::FourBand {
39                band1: rusistor::Color::Brown,
40                band2: rusistor::Color::Black,
41                band3: rusistor::Color::Black,
42                band4: rusistor::Color::Brown,
43            };
44            model.selected_band = model.selected_band.min(3)
45        }
46        ColorCodesMsg::FiveBands => {
47            model.resistor = Resistor::FiveBand {
48                band1: rusistor::Color::Brown,
49                band2: rusistor::Color::Black,
50                band3: rusistor::Color::Black,
51                band4: rusistor::Color::Black,
52                band5: rusistor::Color::Brown,
53            };
54            model.selected_band = model.selected_band.min(4)
55        }
56        ColorCodesMsg::SixBands => {
57            model.resistor = Resistor::SixBand {
58                band1: rusistor::Color::Brown,
59                band2: rusistor::Color::Black,
60                band3: rusistor::Color::Black,
61                band4: rusistor::Color::Black,
62                band5: rusistor::Color::Brown,
63                band6: rusistor::Color::Black,
64            };
65            model.selected_band = model.selected_band.min(5)
66        }
67        ColorCodesMsg::NextBand => {
68            model.selected_band = (model.selected_band + 1) % model.resistor.bands().len()
69        }
70        ColorCodesMsg::PrevBand => {
71            let bands_count = model.resistor.bands().len();
72            model.selected_band = (model.selected_band + (bands_count - 1)) % bands_count
73        }
74        ColorCodesMsg::NextColor => {
75            let current_idx: usize = *model.resistor.bands()[model.selected_band] as usize;
76            let mut i: usize = 0;
77            let mut resistor = Err("".to_string());
78            while resistor.is_err() {
79                i += 1;
80                let next_color = Color::from((current_idx + i) % 13);
81                resistor = model.resistor.with_color(next_color, model.selected_band);
82            }
83            model.resistor = resistor.unwrap();
84        }
85        ColorCodesMsg::PrevColor => {
86            let current_idx = *model.resistor.bands()[model.selected_band] as usize;
87            let mut i: usize = 13;
88            let mut resistor = Err("".to_string());
89            while resistor.is_err() {
90                i -= 1;
91                let next_color = Color::from((current_idx + i) % 13);
92                resistor = model.resistor.with_color(next_color, model.selected_band);
93            }
94            model.resistor = resistor.unwrap();
95        }
96    }
97}
98pub fn update_on_specsmsg(model: &mut SpecsToColorModel, msg: SpecsMsg) {
99    match msg {
100        SpecsMsg::Determine => {
101            match try_determine_resistor(
102                &model.resistance_input_state.value,
103                &model.tolerance_input_state.value,
104                &model.tcr_input_state.value,
105            ) {
106                Ok(resistor) => {
107                    model.resistor = Some(resistor);
108                    model.error = None;
109                    model.history.add((
110                        model.resistance_input_state.value.to_string(),
111                        model.tolerance_input_state.value.to_string(),
112                        model.tcr_input_state.value.to_string(),
113                    ));
114                    model.add_specs_to_history();
115                    model.history.clear_idx();
116                }
117                Err(e) => {
118                    model.resistor = None;
119                    model.error = Some(e);
120                }
121            }
122        }
123        SpecsMsg::NextSpecInput | SpecsMsg::PrevSpecInput => {
124            model.error = match model.focus {
125                InputFocus::Resistance => {
126                    let value = &model.resistance_input_state.value;
127                    if value.trim().is_empty() {
128                        None
129                    } else {
130                        try_parse_resistance(value).err().map(|err| err.to_string())
131                    }
132                }
133                InputFocus::Tolerance => {
134                    let value = &model.tolerance_input_state.value;
135                    if value.trim().is_empty() {
136                        None
137                    } else {
138                        value.parse::<f64>().err().map(|err| err.to_string())
139                    }
140                }
141                InputFocus::Tcr => {
142                    let value = &model.tcr_input_state.value;
143                    if value.trim().is_empty() {
144                        None
145                    } else {
146                        value.parse::<u32>().err().map(|err| err.to_string())
147                    }
148                }
149            };
150            if model.error.is_none() {
151                model.focus = match msg {
152                    SpecsMsg::NextSpecInput => model.focus.next(),
153                    _ => model.focus.prev(),
154                };
155            } else {
156                model.resistor = None;
157            }
158        }
159        SpecsMsg::PrevHistory => {
160            model.history.prev();
161            model.set_specs_from_history();
162        }
163        SpecsMsg::NextHistory => {
164            model.history.next();
165            model.set_specs_from_history();
166        }
167        SpecsMsg::Reset => *model = SpecsToColorModel::default(),
168    }
169}
170
171pub fn try_parse_resistance(input: &str) -> Result<f64, String> {
172    match input.parse::<f64>() {
173        Ok(t) => Ok(t),
174        Err(e) => match engineering_repr::EngineeringQuantity::<i64>::from_str(input) {
175            Ok(t) => {
176                let r: i64 = t.into();
177                let r = r as f64;
178                Ok(r)
179            }
180            Err(_) => Err(format!("invalid input for resistance: {}", e)),
181        },
182    }
183}
184
185pub fn try_determine_resistor(
186    resistance_input: &str,
187    tolerance_input: &str,
188    tcr_input: &str,
189) -> Result<Resistor, String> {
190    let resistance = try_parse_resistance(resistance_input);
191    let tolerance = if tolerance_input.is_empty() {
192        Ok(None)
193    } else {
194        match tolerance_input.parse::<f64>() {
195            Ok(t) => Ok(Some(t)),
196            Err(e) => Err(format!("invalid input for tolerance: {}", e)),
197        }
198    };
199
200    let tcr = if tcr_input.is_empty() {
201        Ok(None)
202    } else {
203        match tcr_input.parse::<u32>() {
204            Ok(t) => Ok(Some(t)),
205            Err(e) => Err(format!("invalid input for tcr: {}", e)),
206        }
207    };
208
209    match (resistance, tolerance, tcr) {
210        (Ok(resistance), Ok(tolerance), Ok(tcr)) => {
211            match Resistor::determine(resistance, tolerance, tcr) {
212                Ok(resistor) => Ok(resistor),
213                Err(e) => Err(format!(
214                    "could not determine a resistor for these inputs: {}",
215                    e
216                )),
217            }
218        }
219        (res, tol, tcr) => {
220            let mut error_msg: String = String::from("");
221            if let Err(res_error) = res {
222                error_msg.push_str(res_error.to_string().as_str());
223            }
224            if let Err(tol_error) = tol {
225                error_msg.push('\n');
226                error_msg.push_str(tol_error.to_string().as_str());
227            }
228            if let Err(tcr_error) = tcr {
229                error_msg.push('\n');
230                error_msg.push_str(tcr_error.to_string().as_str());
231            }
232            if error_msg.is_empty() {
233                panic!("unknown error");
234            } else {
235                Err(error_msg)
236            }
237        }
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::ColorCodesMsg;
244    use crate::{
245        model::{ColorCodesToSpecsModel, InputState, SpecsToColorModel},
246        update::{SpecsMsg, update_on_colorcodemsg, update_on_specsmsg},
247    };
248
249    #[test]
250    fn test_nbands_msg() {
251        let mut model = ColorCodesToSpecsModel::default();
252        assert_eq!(model.resistor.bands().len(), 6);
253
254        update_on_colorcodemsg(&mut model, ColorCodesMsg::ThreeBands);
255        assert_eq!(model.resistor.bands().len(), 3);
256
257        update_on_colorcodemsg(&mut model, ColorCodesMsg::FourBands);
258        assert_eq!(model.resistor.bands().len(), 4);
259
260        update_on_colorcodemsg(&mut model, ColorCodesMsg::FiveBands);
261        assert_eq!(model.resistor.bands().len(), 5);
262
263        update_on_colorcodemsg(&mut model, ColorCodesMsg::SixBands);
264        assert_eq!(model.resistor.bands().len(), 6);
265    }
266
267    #[test]
268    fn test_reset_msg() {
269        let mut model = SpecsToColorModel {
270            resistance_input_state: InputState {
271                value: String::from("z"),
272                cursor: 1,
273            },
274            tolerance_input_state: InputState {
275                value: String::from("z"),
276                cursor: 1,
277            },
278            tcr_input_state: InputState {
279                value: String::from("z"),
280                cursor: 1,
281            },
282            ..Default::default()
283        };
284        update_on_specsmsg(&mut model, SpecsMsg::Reset);
285        assert_eq!(model.resistance_input_state.value, "");
286        assert_eq!(model.tolerance_input_state.value, "");
287        assert_eq!(model.tcr_input_state.value, "");
288    }
289
290    #[test]
291    fn test_history() {
292        let mut model = SpecsToColorModel {
293            resistance_input_state: InputState {
294                value: String::from("1"),
295                cursor: 1,
296            },
297            tolerance_input_state: InputState {
298                value: String::from("2"),
299                cursor: 1,
300            },
301            tcr_input_state: InputState {
302                value: String::from("5"),
303                cursor: 1,
304            },
305            ..Default::default()
306        };
307        update_on_specsmsg(&mut model, SpecsMsg::Determine);
308
309        model.resistance_input_state = InputState {
310            value: String::from("2"),
311            cursor: 1,
312        };
313        model.tolerance_input_state = InputState {
314            value: String::from("5"),
315            cursor: 1,
316        };
317
318        model.tcr_input_state = InputState {
319            value: String::from("1"),
320            cursor: 1,
321        };
322        update_on_specsmsg(&mut model, SpecsMsg::Determine);
323        update_on_specsmsg(&mut model, SpecsMsg::PrevHistory);
324        update_on_specsmsg(&mut model, SpecsMsg::PrevHistory);
325        assert_eq!(model.resistance_input_state.value, "1");
326        assert_eq!(model.tolerance_input_state.value, "2");
327        assert_eq!(model.tcr_input_state.value, "5");
328    }
329}