mod exchange; mod clock; use exchange::{get_exchanges, Exchange}; use iced::widget::{column, container, row, text}; use iced::{Element, Length, Color, Theme, Subscription}; use chrono::{DateTime, Utc}; use std::time::Duration; use std::path::Path; pub fn main() -> iced::Result { // Chargement propre de l'icône avec la crate `image` let icon = if Path::new("clock.png").exists() { match image::open("clock.png") { Ok(img) => { let rgba = img.to_rgba8(); let (width, height) = rgba.dimensions(); iced::window::icon::from_rgba(rgba.into_raw(), width, height).ok() } Err(_) => None, } } else { None }; iced::application( "Horloges Boursières Mondiales", WorldClocksApp::update, WorldClocksApp::view, ) .subscription(WorldClocksApp::subscription) .window(iced::window::Settings { size: iced::Size { width: 1700.0, height: 320.0 }, icon, ..Default::default() }) .run_with(WorldClocksApp::new) } struct WorldClocksApp { exchanges: Vec, now: DateTime, } #[derive(Debug, Clone)] pub enum Message { Tick(DateTime), } impl WorldClocksApp { fn new() -> (Self, iced::Task) { ( Self { exchanges: get_exchanges(), now: Utc::now(), }, iced::Task::none(), ) } fn update(&mut self, message: Message) { match message { Message::Tick(time) => { self.now = time; } } } fn subscription(&self) -> Subscription { iced::time::every(Duration::from_secs(1)).map(|_| Message::Tick(Utc::now())) } fn view(&self) -> Element<'_, Message> { let mut row_layout = row![].spacing(10).padding(15); for (i, exchange) in self.exchanges.iter().enumerate() { let is_utc = i == 0; let local_time = exchange.local_time(&self.now); let time_str = local_time.format("%H:%M:%S").to_string(); // En-tête normalisé sur une hauteur fixe pour éviter tout décalage let mut col = column![].spacing(4).align_x(iced::Alignment::Center); if is_utc { col = col.push( text("UTC") .size(13) .style(|_theme: &Theme| text::Style { color: Some(Color::from_rgb(0.0, 0.8, 0.8)) }) ); // Espace vide invisible pour garder exactement la même hauteur que les lignes "ville + statut" des autres bourses col = col.push(text(" ").size(11)); } else { col = col.push(text(exchange.city.clone()).size(13)); let is_open = exchange.is_open(&self.now); let (status_text, status_color) = if is_open { ("ouvert", Color::from_rgb(0.0, 0.8, 0.2)) } else { ("fermé", Color::from_rgb(0.9, 0.2, 0.2)) }; col = col.push( text(status_text) .size(11) .style(move |_theme: &Theme| text::Style { color: Some(status_color) }) ); } // Horloge analogique graphique col = col.push(clock::view_clock(local_time)); // Horloge numérique let digital_color = if is_utc { Color::from_rgb(0.0, 0.8, 0.8) } else { Color::WHITE }; col = col.push( text(time_str) .size(13) .style(move |_theme: &Theme| text::Style { color: Some(digital_color) }) ); // Conteneur strict aux dimensions fixes absolues let boxed_clock = container(col) .width(Length::Fixed(155.0)) .height(Length::Fixed(230.0)) .padding(6) .style(|_theme: &Theme| container::Style { background: Some(iced::Background::Color(Color::from_rgb(0.12, 0.12, 0.15))), border: iced::Border { color: Color::from_rgb(0.25, 0.25, 0.3), width: 1.0, radius: 4.0.into(), }, ..Default::default() }); row_layout = row_layout.push(boxed_clock); } container(row_layout) .width(Length::Fill) .height(Length::Fill) .center_x(Length::Fill) .center_y(Length::Fill) .style(|_theme: &Theme| container::Style { background: Some(iced::Background::Color(Color::from_rgb(0.08, 0.08, 0.1))), ..Default::default() }) .into() } }