//! Time display widget //! //! Shows elapsed time, remaining time, or countdown. use gtk4::glib; use gtk4::prelude::*; use gtk4::subclass::prelude::*; use std::cell::RefCell; mod imp { use super::*; #[derive(Default)] pub struct TimeDisplay { pub label: RefCell>, pub total_secs: RefCell>, pub remaining_secs: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for TimeDisplay { const NAME: &'static str = "ShepherdTimeDisplay"; type Type = super::TimeDisplay; type ParentType = gtk4::Box; } impl ObjectImpl for TimeDisplay { fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); obj.set_orientation(gtk4::Orientation::Horizontal); obj.set_spacing(4); // Time icon let icon = gtk4::Image::from_icon_name("preferences-system-time-symbolic"); icon.set_pixel_size(20); obj.append(&icon); // Time label let label = gtk4::Label::new(Some("--:--")); label.add_css_class("time-display"); obj.append(&label); *self.label.borrow_mut() = Some(label); } } impl WidgetImpl for TimeDisplay {} impl BoxImpl for TimeDisplay {} } glib::wrapper! { pub struct TimeDisplay(ObjectSubclass) @extends gtk4::Box, gtk4::Widget, @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; } impl TimeDisplay { pub fn new() -> Self { glib::Object::builder().build() } /// Set the time limit in seconds pub fn set_time_limit(&self, total_secs: Option) { let imp = self.imp(); *imp.total_secs.borrow_mut() = total_secs; self.update_display(); } /// Set the remaining time in seconds pub fn set_remaining(&self, remaining_secs: Option) { let imp = self.imp(); *imp.remaining_secs.borrow_mut() = remaining_secs; self.update_display(); } /// Update the display based on current state fn update_display(&self) { let imp = self.imp(); if let Some(label) = imp.label.borrow().as_ref() { let remaining = *imp.remaining_secs.borrow(); let text = if let Some(secs) = remaining { format_duration(secs) } else { "--:--".to_string() }; label.set_text(&text); // Update styling based on remaining time label.remove_css_class("time-warning"); label.remove_css_class("time-critical"); if let Some(secs) = remaining { if secs <= 60 { label.add_css_class("time-critical"); } else if secs <= 300 { label.add_css_class("time-warning"); } } } } } impl Default for TimeDisplay { fn default() -> Self { Self::new() } } /// Format a duration in seconds as HH:MM:SS or MM:SS fn format_duration(secs: u64) -> String { let hours = secs / 3600; let minutes = (secs % 3600) / 60; let seconds = secs % 60; if hours > 0 { format!("{:02}:{:02}:{:02}", hours, minutes, seconds) } else { format!("{:02}:{:02}", minutes, seconds) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_format_duration() { assert_eq!(format_duration(0), "00:00"); assert_eq!(format_duration(59), "00:59"); assert_eq!(format_duration(60), "01:00"); assert_eq!(format_duration(3599), "59:59"); assert_eq!(format_duration(3600), "01:00:00"); assert_eq!(format_duration(3661), "01:01:01"); } }