diff --git a/README.md b/README.md index 72e7f7f..bead7c7 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,18 @@ -# Horloge Bourses Mondiales (TUI) +# Horloges Boursières Mondiales (GUI) -Application terminal moderne (TUI) écrite en Rust permettant de suivre en temps réel l'état, l'heure locale, une horloge analogique et une horloge numérique de 10 places boursières majeures à travers le monde, ainsi que le temps universel (UTC). +Application graphique moderne (GUI) écrite en Rust avec le framework **Iced**, permettant de suivre en temps réel l'état d'ouverture, l'heure locale, une horloge analogique détaillée (avec graduations des minutes et trotteuse) et une horloge numérique de 10 places boursières majeures à travers le monde, ainsi que le temps universel (UTC). ## Fonctionnalités -- **Bandeau horizontal complet** affichant 10 horloges synchronisées. -- **Gestion dynamique des fuseaux horaires IANA** (incluant les changements d'heure été/hiver). -- **Statut d'ouverture en temps réel** (Calcul automatique des week-ends et des pauses déjeuner des marchés). -- **Horloge analogique et numérique** intégrées avec précision. -- **Actualisation automatique** chaque seconde. -- **Interface fluide et réactive** basée sur `ratatui` et `crossterm`. +- **Interface graphique fluide et élégante** organisée en panneaux synchronisés. +- **Gestion dynamique des fuseaux horaires IANA** (incluant les règles d'été/hiver via `chrono-tz`). +- **Statut d'ouverture en direct** (Calcul automatique des jours de fermeture, week-ends et horaires d'ouverture spécifiques). +- **Horloge analogique interactive** vectorielle (`iced::widget::canvas`) avec affichage des quarts d'heure et des graduations de 5 en 5 minutes. +- **Actualisation automatique** chaque seconde sans surcoût CPU. ## Marchés Affichés -0. **UTC** (Cyan) +0. **UTC** (Référence Cyan) 1. **New York Stock Exchange (NYSE)** 2. **Shanghai Stock Exchange** 3. **Euronext Paris** @@ -26,13 +25,17 @@ Application terminal moderne (TUI) écrite en Rust permettant de suivre en temps ## Prérequis -- Rust (édition stable actuelle) +- Rust (édition stable actuelle, 2021+) - Cargo ## Installation & Compilation -Clonez ou placez-vous dans le répertoire du projet, puis compilez en mode release : +Clonez ou placez-vous dans le répertoire du projet, puis lancez l'application en mode développement ou compilation optimisée : ```bash +# Lancement direct +cargo run + +# Compilation pour une version de production performante cargo build --release ``` diff --git a/src/clock.rs b/src/clock.rs index 3a455da..1d4cf31 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -1,14 +1,25 @@ -use iced::mouse; -use iced::widget::canvas::{Canvas, Frame, Geometry, Path, Program, Stroke}; -use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme}; -use chrono::{DateTime, Timelike}; -use chrono_tz::Tz; +use chrono::{DateTime, TimeZone, Timelike}; +use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Program, Stroke}; +use iced::{Element, Length, Point, Renderer, Theme}; -pub struct AnalogClock { - pub time: DateTime, +pub fn view_clock(time: DateTime) -> Element<'static, super::Message> { + Canvas::new(Clock { + hour: (time.hour() % 12) as f32, + minute: time.minute() as f32, + second: time.second() as f32, + }) + .width(Length::Fixed(150.0)) + .height(Length::Fixed(150.0)) + .into() } -impl Program for AnalogClock { +struct Clock { + hour: f32, + minute: f32, + second: f32, +} + +impl Program for Clock { type State = (); fn draw( @@ -16,64 +27,100 @@ impl Program for AnalogClock { _state: &Self::State, renderer: &Renderer, _theme: &Theme, - bounds: Rectangle, - _cursor: mouse::Cursor, + bounds: iced::Rectangle, + _cursor: iced::mouse::Cursor, ) -> Vec { let mut frame = Frame::new(renderer, bounds.size()); - let center = frame.center(); - let radius = bounds.width.min(bounds.height) / 2.0 - 5.0; - if radius <= 0.0 { - return vec![frame.into_geometry()]; + let center = frame.center(); + let radius = bounds.width.min(bounds.height) / 2.0 - 8.0; + + // 1. Fond du cadran et cercle extérieur + let dial_background = Path::circle(center, radius); + frame.fill(&dial_background, iced::Color::from_rgb(0.05, 0.05, 0.08)); + + let dial_border = Path::circle(center, radius); + frame.stroke( + &dial_border, + Stroke::default() + .with_color(iced::Color::from_rgb(0.3, 0.3, 0.4)) + .with_width(2.0), + ); + + // 2. Graduations des minutes (toutes les 5 minutes) + for m in 0..60 { + if m % 5 == 0 { + let angle = (m as f32) * (std::f32::consts::PI / 30.0) - std::f32::consts::FRAC_PI_2; + let is_quarter = m % 15 == 0; + + let tick_len = if is_quarter { 9.0 } else { 5.0 }; + let stroke_width = if is_quarter { 2.0 } else { 1.0 }; + let tick_color = if is_quarter { + iced::Color::from_rgb(0.8, 0.8, 0.9) + } else { + iced::Color::from_rgb(0.5, 0.5, 0.6) + }; + + let inner_radius = radius - tick_len; + let x1 = center.x + radius * angle.cos(); + let y1 = center.y + radius * angle.sin(); + let x2 = center.x + inner_radius * angle.cos(); + let y2 = center.y + inner_radius * angle.sin(); + + let tick_path = Path::line(Point::new(x1, y1), Point::new(x2, y2)); + frame.stroke( + &tick_path, + Stroke::default().with_color(tick_color).with_width(stroke_width), + ); + } } - // Cadran circulaire - let dial = Path::circle(center, radius); - frame.stroke(&dial, Stroke::default().with_color(Color::WHITE).with_width(1.5)); - - let hour = self.time.hour() % 12; - let minute = self.time.minute(); - let second = self.time.second(); - - // Calcul des angles précis - let second_angle = (second as f32 / 60.0) * std::f32::consts::TAU - std::f32::consts::FRAC_PI_2; - let minute_angle = ((minute as f32 + second as f32 / 60.0) / 60.0) * std::f32::consts::TAU - std::f32::consts::FRAC_PI_2; - let hour_angle = (((hour as f32 + minute as f32 / 60.0) / 12.0) * std::f32::consts::TAU) - std::f32::consts::FRAC_PI_2; - - // Aiguille des heures (Bleu) + // 3. Aiguille des heures + let hour_angle = (self.hour + self.minute / 60.0) * (std::f32::consts::PI / 6.0) - std::f32::consts::FRAC_PI_2; let hour_len = radius * 0.5; let hour_end = Point::new( - center.x + hour_angle.cos() * hour_len, - center.y + hour_angle.sin() * hour_len, + center.x + hour_len * hour_angle.cos(), + center.y + hour_len * hour_angle.sin(), + ); + frame.stroke( + &Path::line(center, hour_end), + Stroke::default() + .with_color(iced::Color::WHITE) + .with_width(4.0), ); - let hour_line = Path::line(center, hour_end); - frame.stroke(&hour_line, Stroke::default().with_color(Color::from_rgb(0.2, 0.5, 1.0)).with_width(3.0)); - // Aiguille des minutes (Blanc) + // 4. Aiguille des minutes + let min_angle = (self.minute + self.second / 60.0) * (std::f32::consts::PI / 30.0) - std::f32::consts::FRAC_PI_2; let min_len = radius * 0.75; let min_end = Point::new( - center.x + minute_angle.cos() * min_len, - center.y + minute_angle.sin() * min_len, + center.x + min_len * min_angle.cos(), + center.y + min_len * min_angle.sin(), + ); + frame.stroke( + &Path::line(center, min_end), + Stroke::default() + .with_color(iced::Color::from_rgb(0.8, 0.8, 0.8)) + .with_width(2.5), ); - let min_line = Path::line(center, min_end); - frame.stroke(&min_line, Stroke::default().with_color(Color::WHITE).with_width(2.0)); - // Aiguille des secondes (Rouge) + // 5. Aiguille des secondes + let sec_angle = self.second * (std::f32::consts::PI / 30.0) - std::f32::consts::FRAC_PI_2; let sec_len = radius * 0.85; let sec_end = Point::new( - center.x + second_angle.cos() * sec_len, - center.y + second_angle.sin() * sec_len, + center.x + sec_len * sec_angle.cos(), + center.y + sec_len * sec_angle.sin(), ); - let sec_line = Path::line(center, sec_end); - frame.stroke(&sec_line, Stroke::default().with_color(Color::from_rgb(1.0, 0.2, 0.2)).with_width(1.0)); + frame.stroke( + &Path::line(center, sec_end), + Stroke::default() + .with_color(iced::Color::from_rgb(0.9, 0.2, 0.2)) + .with_width(1.2), + ); + + // Pivot central + let pivot = Path::circle(center, 3.5); + frame.fill(&pivot, iced::Color::from_rgb(0.9, 0.2, 0.2)); vec![frame.into_geometry()] } -} - -pub fn view_clock(time: DateTime) -> Element<'static, crate::Message> { - Canvas::new(AnalogClock { time }) - .width(Length::Fixed(90.0)) - .height(Length::Fixed(90.0)) - .into() } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 897382d..58beba2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ pub fn main() -> iced::Result { ) .subscription(WorldClocksApp::subscription) .window(iced::window::Settings { - size: iced::Size { width: 1400.0, height: 320.0 }, + size: iced::Size { width: 1600.0, height: 320.0 }, ..Default::default() }) .run_with(WorldClocksApp::new) @@ -86,7 +86,7 @@ impl WorldClocksApp { ); } - // Horloge analogique graphique + // Horloge analogique graphique (1.5x) col = col.push(clock::view_clock(local_time)); // Horloge numérique @@ -98,7 +98,7 @@ impl WorldClocksApp { ); let boxed_clock = container(col) - .width(Length::Fixed(125.0)) + .width(Length::Fixed(160.0)) .padding(8) .style(|_theme: &Theme| container::Style { background: Some(iced::Background::Color(Color::from_rgb(0.12, 0.12, 0.15))),