egui_charts/ui/
connection_status.rs1use crate::icons::{Icon, icons};
7use crate::localization as l10n;
8use crate::styles::icons as icon_sizes;
9use crate::theme::ThemeExt;
10use crate::ui::model::ConnectionState;
11use egui::{Color32, Response, RichText, Ui, Vec2, Widget};
12
13pub struct ConnectionStatusIndicator<'a> {
15 state: &'a ConnectionState,
17 show_label: bool,
19}
20
21impl<'a> ConnectionStatusIndicator<'a> {
22 pub fn new(state: &'a ConnectionState) -> Self {
24 Self {
25 state,
26 show_label: false,
27 }
28 }
29
30 #[must_use]
32 pub fn with_label(mut self) -> Self {
33 self.show_label = true;
34 self
35 }
36
37 fn state_color(&self, ui: &Ui) -> Color32 {
39 match self.state {
40 ConnectionState::Connected => ui.active_theme().ui.success,
41 ConnectionState::Connecting => ui.visuals().warn_fg_color,
42 ConnectionState::Reconnecting { .. } => ui.visuals().warn_fg_color,
43 ConnectionState::Disconnected => ui.visuals().weak_text_color(),
44 ConnectionState::Failed(_) => ui.visuals().error_fg_color,
45 }
46 }
47
48 fn state_icon(&self) -> &'static Icon {
50 match self.state {
51 ConnectionState::Connected => &icons::STATUS_CONNECTED,
52 ConnectionState::Connecting | ConnectionState::Reconnecting { .. } => {
53 &icons::STATUS_CONNECTING
54 }
55 ConnectionState::Disconnected => &icons::STATUS_DISCONNECTED,
56 ConnectionState::Failed(_) => &icons::STATUS_ERROR,
57 }
58 }
59
60 fn state_label(&self) -> &'static str {
62 match self.state {
63 ConnectionState::Connected => "Live",
64 ConnectionState::Connecting => "Connecting...",
65 ConnectionState::Reconnecting { attempt: _ } => {
66 "Reconnecting..."
68 }
69 ConnectionState::Disconnected => "Offline",
70 ConnectionState::Failed(_) => "Error",
71 }
72 }
73}
74
75impl<'a> Widget for ConnectionStatusIndicator<'a> {
76 fn ui(self, ui: &mut Ui) -> Response {
77 let color = self.state_color(ui);
78 let icon = self.state_icon();
79
80 ui.horizontal(|ui| {
81 ui.spacing_mut().item_spacing.x = ui.active_theme().spacing.xs;
82
83 ui.add(icon.as_image_tinted(Vec2::splat(icon_sizes::XS), color));
85
86 if self.show_label {
88 ui.label(
89 RichText::new(l10n::text(ui.ctx(), self.state_label()))
90 .color(color)
91 .size(11.0),
92 );
93 }
94 })
95 .response
96 }
97}
98
99pub fn show_connection_status(ui: &mut Ui, state: &ConnectionState) -> Response {
103 ConnectionStatusIndicator::new(state).ui(ui)
104}
105
106pub fn show_connection_status_with_label(ui: &mut Ui, state: &ConnectionState) -> Response {
108 ConnectionStatusIndicator::new(state).with_label().ui(ui)
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_state_icons() {
117 let ctx = egui::Context::default();
118 for (_, theme) in crate::theme::presets() {
119 crate::theme::apply_to_egui(&ctx, &theme);
120 ctx.run_ui(Default::default(), |ui| {
121 for (state, color) in [
122 (ConnectionState::Connected, theme.ui.success),
123 (ConnectionState::Connecting, theme.ui.warning),
124 (
125 ConnectionState::Reconnecting { attempt: 1 },
126 theme.ui.warning,
127 ),
128 (ConnectionState::Failed("failed".into()), theme.ui.error),
129 ] {
130 assert_eq!(
131 ConnectionStatusIndicator::new(&state).state_color(ui),
132 color
133 );
134 }
135 })
136 .drop_without_applying_deltas();
137 }
138 let connected = ConnectionStatusIndicator::new(&ConnectionState::Connected);
139 assert_eq!(connected.state_icon(), &icons::STATUS_CONNECTED);
140
141 let disconnected = ConnectionStatusIndicator::new(&ConnectionState::Disconnected);
142 assert_eq!(disconnected.state_icon(), &icons::STATUS_DISCONNECTED);
143
144 let failed_state = ConnectionState::Failed("error".to_string());
145 let failed = ConnectionStatusIndicator::new(&failed_state);
146 assert_eq!(failed.state_icon(), &icons::STATUS_ERROR);
147 }
148}