Skip to main content

signalk_multidisplay/
app.rs

1use std::sync::mpsc::{channel, Receiver, Sender};
2use std::time::{Duration, Instant};
3
4use eframe::egui;
5
6use crate::communication::SignalKCommunicator;
7use crate::layouts::LayoutComponent;
8
9#[derive(serde::Deserialize, serde::Serialize)]
10#[serde(default)]
11pub struct DisplayApplication {
12    server: String,
13    view_config: bool,
14    #[serde(skip)]
15    communicator: Option<SignalKCommunicator>,
16    #[serde(skip)]
17    layouts: Vec<crate::layouts::Layout>,
18    #[serde(skip)]
19    current_layout: usize,
20    #[serde(skip)]
21    last_layout_change: Instant,
22    #[serde(skip)]
23    server_changed_tx: Option<Sender<String>>,
24    #[serde(skip)]
25    server_changed_rx: Option<Receiver<String>>,
26}
27
28impl Default for DisplayApplication {
29    fn default() -> Self {
30        Self {
31            server: "https://demo.signalk.org/signalk".to_owned(),
32            view_config: false,
33            communicator: None,
34            layouts: vec![
35                crate::layouts::Layout::DualValues(crate::layouts::DualValuesLayout::new(
36                    4,
37                    crate::datatypes::DataValues::SpeedOverGround(
38                        crate::datatypes::SpeedOverGround::default(),
39                    ),
40                    crate::datatypes::DataValues::CourseOverGround(
41                        crate::datatypes::CourseOverGround::default(),
42                    ),
43                )),
44                crate::layouts::Layout::SingleValue(crate::layouts::SingleValueLayout::new(
45                    0,
46                    crate::datatypes::DataValues::SpeedThroughWater(
47                        crate::datatypes::SpeedThroughWater::default(),
48                    ),
49                )),
50                crate::layouts::Layout::SingleValue(crate::layouts::SingleValueLayout::new(
51                    3,
52                    crate::datatypes::DataValues::WaterTemperature(
53                        crate::datatypes::WaterTemperature::default(),
54                    ),
55                )),
56            ],
57            current_layout: 0,
58            last_layout_change: Instant::now(),
59            server_changed_tx: None,
60            server_changed_rx: None,
61        }
62    }
63}
64
65impl DisplayApplication {
66    /// Called once before the first frame.
67    pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
68        let mut app = if let Some(storage) = cc.storage {
69            let restored_app: DisplayApplication =
70                eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default();
71            log::debug!("Restore object with server {}", restored_app.server);
72            restored_app
73        } else {
74            log::debug!("Creating new instance.");
75            Self::default()
76        };
77        let mut communicator = SignalKCommunicator::default();
78        communicator.set_up_server_connections(app.server.to_string());
79        let (server_changed_tx, server_changed_rx): (Sender<String>, Receiver<String>) = channel();
80        app.server_changed_tx = Some(server_changed_tx);
81        app.server_changed_rx = Some(server_changed_rx);
82
83        app.communicator = Some(communicator);
84        app
85    }
86    pub fn server_changed(&mut self) {
87        log::warn!("Server changed to {} IGNORED!!!", self.server);
88    }
89}
90
91impl eframe::App for DisplayApplication {
92    /// Called each time the UI needs repainting, which may be many times per second.
93    /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
94    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
95        // log::debug!("TemplateApp::update() - Enter");
96        ctx.request_repaint();
97        if let Some(ref mut sk_com) = self.communicator {
98            // log::debug!("Handle sk_com.handle_data()");
99            sk_com.handle_data(ctx);
100        }
101        if let Some(ref mut server_changed_rx) = self.server_changed_rx {
102            // log::debug!("Server changed..");
103            if server_changed_rx.try_recv().is_ok() {
104                if let Some(ref mut communicator) = self.communicator {
105                    communicator.disconnect_server();
106                    communicator.set_up_server_connections(self.server.to_string());
107                } else {
108                    let mut communicator = SignalKCommunicator::default();
109                    communicator.set_up_server_connections(self.server.to_string());
110                    self.communicator = Some(communicator);
111                }
112            }
113        }
114        // log::debug!("Draw UI..");
115
116        let Self {
117            server,
118            view_config,
119            layouts,
120            current_layout,
121            server_changed_tx,
122            last_layout_change,
123            ..
124        } = self;
125
126        if last_layout_change.elapsed() > Duration::from_secs(3) {
127            log::info!("Update current layout {}", *current_layout);
128            *last_layout_change = Instant::now();
129            *current_layout = (*current_layout + 1) % layouts.len();
130            log::info!("New current layout {}", *current_layout);
131        }
132        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
133            // The top panel is often a good place for a menu bar:
134            egui::menu::bar(ui, |ui| {
135                ui.menu_button("File", |ui| {
136                    if ui.button("Config").clicked() {
137                        *view_config = !*view_config;
138                    }
139                    #[cfg(not(target_arch = "wasm32"))] // no File->Quit on web pages!
140                    if ui.button("Quit").clicked() {
141                        frame.close();
142                    }
143                });
144                if !frame.is_web() {
145                    ui.menu_button("View", |ui| {
146                        egui::gui_zoom::zoom_menu_buttons(ui, frame.info().native_pixels_per_point);
147                    });
148                }
149            });
150        });
151
152        // Side panel for config? Maybe a different view?
153        if *view_config {
154            egui::SidePanel::left("side_panel").show(ctx, |ui| {
155                ui.heading("Configuration");
156
157                ui.vertical(|ui| {
158                    ui.label("Server Address: ");
159                    let response = ui.text_edit_singleline(server);
160                    if response.lost_focus() {
161                        if let Some(tx_channel) = server_changed_tx {
162                            if let Err(err) = tx_channel.send(server.to_string()) {
163                                log::error!("Can't send server changed message {:?}", err);
164                            };
165                        }
166                    }
167                });
168
169                ui.add_space(6.);
170
171                for layout in layouts.iter_mut() {
172                    ui.group(|ui| {
173                        layout.add_config(ui);
174                    });
175                }
176
177                ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
178                    ui.horizontal(|ui| {
179                        egui::warn_if_debug_build(ui);
180                    });
181                });
182            });
183        }
184        egui::CentralPanel::default().show(ctx, |ui| {
185            if let Some(ref comm) = self.communicator {
186                layouts[*current_layout].draw_ui(ui, comm);
187            }
188        });
189        // log::debug!("TemplateApp::update() - Exit");
190    }
191
192    /// Called by the frame work to save state before shutdown.
193    fn save(&mut self, storage: &mut dyn eframe::Storage) {
194        eframe::set_value(storage, eframe::APP_KEY, self);
195    }
196}