Skip to main content

egui_charts/ui/
connection_status.rs

1//! Connection status icon with an optional text label.
2//!
3//! Pass the host's [`ConnectionState`] to [`ConnectionStatusIndicator`] to display
4//! connectivity independently of the transport used.
5
6use 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
13/// Connection status indicator widget
14pub struct ConnectionStatusIndicator<'a> {
15    /// Current connection state
16    state: &'a ConnectionState,
17    /// Whether to show text label
18    show_label: bool,
19}
20
21impl<'a> ConnectionStatusIndicator<'a> {
22    /// Create a new connection status indicator
23    pub fn new(state: &'a ConnectionState) -> Self {
24        Self {
25            state,
26            show_label: false,
27        }
28    }
29
30    /// Show text label alongside the indicator
31    #[must_use]
32    pub fn with_label(mut self) -> Self {
33        self.show_label = true;
34        self
35    }
36
37    /// Get color for the current state
38    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    /// Get icon for the current state
49    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    /// Get label text for the current state
61    fn state_label(&self) -> &'static str {
62        match self.state {
63            ConnectionState::Connected => "Live",
64            ConnectionState::Connecting => "Connecting...",
65            ConnectionState::Reconnecting { attempt: _ } => {
66                // Can't include attempt in static str, so just show generic
67                "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            // Icon - use SVG with tint color
84            ui.add(icon.as_image_tinted(Vec2::splat(icon_sizes::XS), color));
85
86            // Optional label
87            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
99/// Show a connection status indicator in the UI
100///
101/// This is a convenience function for showing the indicator inline.
102pub fn show_connection_status(ui: &mut Ui, state: &ConnectionState) -> Response {
103    ConnectionStatusIndicator::new(state).ui(ui)
104}
105
106/// Show a connection status indicator with label
107pub 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}