caliphui/
app.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2License, v. 2.0. If a copy of the MPL was not distributed with this
3file, You can obtain one at https://mozilla.org/MPL/2.0/.
4Copyright 2021 Peter Dunne */
5
6use eframe::{
7    egui::{self, Widget},
8    epi,
9};
10use libcaliph::routines;
11
12#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
13#[cfg_attr(feature = "persistence", serde(default))]
14pub struct TemplateApp {
15    ph4: f64,
16    ph10: f64,
17    temperature: f64,
18    slope: f64,
19    offset: f64,
20    ph_measured: f64,
21    calibrated_ph: f64,
22}
23
24impl Default for TemplateApp {
25    fn default() -> Self {
26        Self {
27            // Example stuff:
28            ph4: 4.01,
29            ph10: 10.01,
30            temperature: 25.0,
31            slope: 1.0,
32            offset: 0.0,
33            ph_measured: 7.0,
34            calibrated_ph: 7.0,
35        }
36    }
37}
38
39impl epi::App for TemplateApp {
40    fn name(&self) -> &str {
41        "Caliphui"
42    }
43
44    /// Called once before the first frame.
45    fn setup(
46        &mut self,
47        _ctx: &egui::CtxRef,
48        _frame: &mut epi::Frame<'_>,
49        _storage: Option<&dyn epi::Storage>,
50    ) {
51        // let mut spacing_mut = egui::style::Spacing::default();
52        // //
53        // spacing_mut.item_spacing = egui::Vec2::new(8.0, 12.0);
54        // spacing_mut.interact_size = egui::Vec2::new(40.0, 18.0);
55
56        let _spacing_mut = egui::style::Spacing {
57            item_spacing: egui::Vec2::new(8.0, 12.0),
58            window_padding: egui::Vec2::splat(6.0),
59            button_padding: egui::Vec2::new(4.0, 1.0),
60            indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing`
61            interact_size: egui::Vec2::new(40.0, 18.0),
62            slider_width: 100.0,
63            text_edit_width: 280.0,
64            icon_width: 14.0,
65            icon_spacing: 0.0,
66            tooltip_width: 600.0,
67            combo_height: 200.0,
68            scroll_bar_width: 8.0,
69            indent_ends_with_horizontal_line: false,
70        };
71
72        let mut fonts = egui::FontDefinitions::default();
73
74        fonts.family_and_size.insert(
75            egui::TextStyle::Heading,
76            (egui::FontFamily::Proportional, 26.0),
77        );
78        fonts.family_and_size.insert(
79            egui::TextStyle::Body,
80            (egui::FontFamily::Proportional, 22.0),
81        );
82
83        fonts.family_and_size.insert(
84            egui::TextStyle::Button,
85            (egui::FontFamily::Proportional, 22.0),
86        );
87        fonts.family_and_size.insert(
88            egui::TextStyle::Monospace,
89            (egui::FontFamily::Monospace, 22.0),
90        );
91
92        _ctx.set_fonts(fonts);
93        // Load previous app state (if any).
94        // Note that you must enable the `persistence` feature for this to work.
95        #[cfg(feature = "persistence")]
96        if let Some(storage) = _storage {
97            *self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default()
98        }
99
100        self.calibrated_ph =
101            update_conversion(&self.ph_measured, &mut self.slope, &mut self.offset);
102    }
103
104    /// Called by the frame work to save state before shutdown.
105    #[cfg(feature = "persistence")]
106    fn save(&mut self, storage: &mut dyn epi::Storage) {
107        epi::set_value(storage, epi::APP_KEY, self);
108    }
109
110    /// Called each time the UI needs repainting, which may be many times per second.
111    fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
112        let Self {
113            ph4,
114            ph10,
115            temperature,
116            slope,
117            offset,
118            ph_measured,
119            calibrated_ph,
120        } = self;
121
122        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
123            egui::widgets::global_dark_light_mode_switch(ui);
124        });
125
126        egui::CentralPanel::default().show(ctx, |ui| {
127            ui.heading("Calibrate");
128            let ph4_slider = egui::Slider::new(ph4, 0.0..=14.0)
129                .text("pH 4")
130                .fixed_decimals(2);
131            let ph4_response = ph4_slider.ui(ui);
132            if ph4_response.changed() {
133                update_calibration(ph4, ph10, temperature, slope, offset);
134                *calibrated_ph = update_conversion(ph_measured, slope, offset);
135            }
136
137            let ph10_slider = egui::Slider::new(ph10, 0.0..=14.0)
138                .text("pH 10")
139                .fixed_decimals(2);
140            let ph10_response = ph10_slider.ui(ui);
141            if ph10_response.changed() {
142                update_calibration(ph4, ph10, temperature, slope, offset);
143                *calibrated_ph = update_conversion(ph_measured, slope, offset);
144            }
145
146            let temperature_slider = egui::Slider::new(temperature, 0.0..=100.0)
147                .suffix(" ˚C")
148                .text("T")
149                .fixed_decimals(1);
150            let temperature_response = temperature_slider.ui(ui);
151            if temperature_response.changed() {
152                update_calibration(ph4, ph10, temperature, slope, offset);
153                *calibrated_ph = update_conversion(ph_measured, slope, offset);
154            }
155
156            ui.add_space(5.0);
157
158            // ================
159            ui.heading("Convert");
160            let ph_measured_slider = egui::Slider::new(ph_measured, 0.0..=14.0)
161                .text("Input pH")
162                .fixed_decimals(2);
163            let ph_measured_response = ph_measured_slider.ui(ui);
164            if ph_measured_response.changed() {
165                *calibrated_ph = update_conversion(ph_measured, slope, offset);
166            }
167
168            ui.add_space(10.0);
169            ui.heading("Calibrated pH:");
170            ui.add(egui::DragValue::new(calibrated_ph).speed(1.0));
171
172            egui::warn_if_debug_build(ui);
173        });
174
175        if false {
176            egui::Window::new("Window").show(ctx, |ui| {
177                ui.label("Windows can be moved by dragging them.");
178                ui.label("They are automatically sized based on contents.");
179                ui.label("You can turn on resizing and scrolling if you like.");
180                ui.label("You would normally chose either panels OR windows.");
181            });
182        }
183    }
184}
185
186pub fn update_calibration(
187    ph4: &f64,
188    ph10: &f64,
189    temperature: &f64,
190    slope: &mut f64,
191    offset: &mut f64,
192) {
193    let calibration = routines::ph_calibration(&[*ph4, *ph10], temperature);
194    *slope = calibration.slope;
195    *offset = calibration.offset;
196}
197
198pub fn update_conversion(ph_measured: &f64, slope: &mut f64, offset: &mut f64) -> f64 {
199    routines::ph_convert(ph_measured, &[*slope, *offset])
200}