signalk_multidisplay/
app.rs1use 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 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 fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
95 ctx.request_repaint();
97 if let Some(ref mut sk_com) = self.communicator {
98 sk_com.handle_data(ctx);
100 }
101 if let Some(ref mut server_changed_rx) = self.server_changed_rx {
102 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 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 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"))] 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 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 }
191
192 fn save(&mut self, storage: &mut dyn eframe::Storage) {
194 eframe::set_value(storage, eframe::APP_KEY, self);
195 }
196}