Skip to main content

egui_charts/ui/timeframe_toolbar/
state.rs

1//! State management for bottom panel
2//!
3//! Manages the state of the time scale controls and range selection.
4
5use super::DateRange;
6use crate::model::Timeframe;
7
8/// Session type for Regular Trading Hours / Extended Hours
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum SessionType {
11    /// Regular Trading Hours only
12    #[default]
13    RTH,
14    /// Extended Trading Hours (pre-market + after-hours)
15    ETH,
16    /// 24/7 (for crypto, forex)
17    Full24x7,
18}
19
20impl SessionType {
21    /// Get display label
22    pub fn label(&self) -> &'static str {
23        match self {
24            SessionType::RTH => "RTH",
25            SessionType::ETH => "ETH",
26            SessionType::Full24x7 => "24/7",
27        }
28    }
29
30    /// Get tooltip desc
31    pub fn tooltip(&self) -> &'static str {
32        match self {
33            SessionType::RTH => "Regular trading hours: on",
34            SessionType::ETH => "Regular trading hours: off (extended hours)",
35            SessionType::Full24x7 => "Regular trading hours: off (24/7 trading)",
36        }
37    }
38}
39
40/// Bottom panel state
41#[derive(Clone, Debug)]
42pub struct TimeframeToolbarState {
43    /// Current timeframe
44    pub timeframe: Timeframe,
45    /// Selected date range preset
46    pub sel_range: DateRange,
47    /// Whether date picker is open
48    pub date_picker_open: bool,
49    /// Whether date range dropdown is collapsed (mobile mode)
50    pub range_collapsed: bool,
51    /// Selected timezone display string (e.g., "UTC", "America/New_York")
52    pub timezone: String,
53    /// Whether timezone menu is open
54    pub timezone_menu_open: bool,
55    /// Current session type (RTH/ETH/24x7)
56    pub session_type: SessionType,
57    /// Whether session menu is open
58    pub session_menu_open: bool,
59    /// Whether to adjust data for dividends
60    pub adjust_for_dividends: bool,
61    /// Current display time (for timezone display)
62    pub curr_time: Option<chrono::DateTime<chrono::Utc>>,
63}
64
65impl Default for TimeframeToolbarState {
66    fn default() -> Self {
67        Self {
68            timeframe: Timeframe::Min1,
69            sel_range: DateRange::Month1,
70            date_picker_open: false,
71            range_collapsed: false,
72            timezone: "UTC".to_string(),
73            timezone_menu_open: false,
74            session_type: SessionType::RTH,
75            session_menu_open: false,
76            adjust_for_dividends: false,
77            curr_time: None,
78        }
79    }
80}
81
82impl TimeframeToolbarState {
83    /// Create new bottom panel state
84    pub fn new() -> Self {
85        Self::default()
86    }
87
88    /// Set timeframe
89    pub fn set_timeframe(&mut self, timeframe: Timeframe) {
90        self.timeframe = timeframe;
91    }
92
93    /// Set selected date range
94    pub fn set_date_range(&mut self, range: DateRange) {
95        self.sel_range = range;
96    }
97
98    /// Toggle date picker
99    pub fn toggle_date_picker(&mut self) {
100        self.date_picker_open = !self.date_picker_open;
101    }
102
103    /// Toggle range collapsed mode
104    pub fn toggle_range_collapsed(&mut self) {
105        self.range_collapsed = !self.range_collapsed;
106    }
107
108    /// Set timezone
109    pub fn set_timezone(&mut self, timezone: String) {
110        self.timezone = timezone;
111    }
112
113    /// Toggle timezone menu
114    pub fn toggle_timezone_menu(&mut self) {
115        self.timezone_menu_open = !self.timezone_menu_open;
116    }
117
118    /// Set session type
119    pub fn set_session_type(&mut self, session: SessionType) {
120        self.session_type = session;
121    }
122
123    /// Toggle session menu
124    pub fn toggle_session_menu(&mut self) {
125        self.session_menu_open = !self.session_menu_open;
126    }
127
128    /// Toggle adjust for dividends
129    pub fn toggle_adjust_for_dividends(&mut self) {
130        self.adjust_for_dividends = !self.adjust_for_dividends;
131    }
132
133    /// Update current time
134    pub fn update_time(&mut self) {
135        self.curr_time = Some(chrono::Utc::now());
136    }
137
138    /// Get formatted time string for display
139    pub fn formatted_time(&self) -> String {
140        let zone = self
141            .timezone
142            .parse::<crate::model::TimezoneId>()
143            .unwrap_or(crate::model::TimezoneId::utc());
144        match &self.curr_time {
145            Some(time) => format!("{} {zone}", time.with_timezone(&zone).format("%H:%M:%S")),
146            None => format!("--:--:-- {zone}"),
147        }
148    }
149
150    /// Sync from application state.
151    pub fn sync_from_app_state(&mut self, app_state: &dyn crate::ui::app_state::ChartAppState) {
152        self.timeframe = *app_state.active_timeframe();
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn clock_converts_the_named_zone_and_falls_back_to_utc() {
162        for (instant, zone, expected) in [
163            ("2026-01-15T13:00:00Z", "UTC", "13:00:00 UTC"),
164            ("2026-01-15T13:00:00Z", "+05:45", "18:45:00 +05:45"),
165            #[cfg(feature = "timezones")]
166            (
167                "2026-01-15T13:00:00Z",
168                "America/New_York",
169                "08:00:00 America/New_York",
170            ),
171            #[cfg(feature = "timezones")]
172            (
173                "2026-07-15T13:00:00Z",
174                "America/New_York",
175                "09:00:00 America/New_York",
176            ),
177            ("2026-01-15T13:00:00Z", "unknown", "13:00:00 UTC"),
178        ] {
179            let state = TimeframeToolbarState {
180                curr_time: Some(instant.parse().unwrap()),
181                timezone: zone.to_owned(),
182                ..Default::default()
183            };
184            assert_eq!(state.formatted_time(), expected);
185        }
186        let state = TimeframeToolbarState {
187            timezone: "unknown".to_owned(),
188            ..Default::default()
189        };
190        assert_eq!(state.formatted_time(), "--:--:-- UTC");
191    }
192}