Horloges grandes

This commit is contained in:
2026-08-30 11:35:07 +02:00
parent 39620a8a66
commit 3861dd7fb6
3 changed files with 113 additions and 63 deletions
+14 -11
View File
@@ -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 ## Fonctionnalités
- **Bandeau horizontal complet** affichant 10 horloges synchronisées. - **Interface graphique fluide et élégante** organisée en panneaux synchronisés.
- **Gestion dynamique des fuseaux horaires IANA** (incluant les changements d'heure été/hiver). - **Gestion dynamique des fuseaux horaires IANA** (incluant les règles d'été/hiver via `chrono-tz`).
- **Statut d'ouverture en temps réel** (Calcul automatique des week-ends et des pauses déjeuner des marchés). - **Statut d'ouverture en direct** (Calcul automatique des jours de fermeture, week-ends et horaires d'ouverture spécifiques).
- **Horloge analogique et numérique** intégrées avec précision. - **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. - **Actualisation automatique** chaque seconde sans surcoût CPU.
- **Interface fluide et réactive** basée sur `ratatui` et `crossterm`.
## Marchés Affichés ## Marchés Affichés
0. **UTC** (Cyan) 0. **UTC** (Référence Cyan)
1. **New York Stock Exchange (NYSE)** 1. **New York Stock Exchange (NYSE)**
2. **Shanghai Stock Exchange** 2. **Shanghai Stock Exchange**
3. **Euronext Paris** 3. **Euronext Paris**
@@ -26,13 +25,17 @@ Application terminal moderne (TUI) écrite en Rust permettant de suivre en temps
## Prérequis ## Prérequis
- Rust (édition stable actuelle) - Rust (édition stable actuelle, 2021+)
- Cargo - Cargo
## Installation & Compilation ## 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 ```bash
# Lancement direct
cargo run
# Compilation pour une version de production performante
cargo build --release cargo build --release
``` ```
+96 -49
View File
@@ -1,14 +1,25 @@
use iced::mouse; use chrono::{DateTime, TimeZone, Timelike};
use iced::widget::canvas::{Canvas, Frame, Geometry, Path, Program, Stroke}; use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Program, Stroke};
use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme}; use iced::{Element, Length, Point, Renderer, Theme};
use chrono::{DateTime, Timelike};
use chrono_tz::Tz;
pub struct AnalogClock { pub fn view_clock<Tz: TimeZone>(time: DateTime<Tz>) -> Element<'static, super::Message> {
pub time: DateTime<Tz>, 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<Message> Program<Message> for AnalogClock { struct Clock {
hour: f32,
minute: f32,
second: f32,
}
impl<Message> Program<Message> for Clock {
type State = (); type State = ();
fn draw( fn draw(
@@ -16,64 +27,100 @@ impl<Message> Program<Message> for AnalogClock {
_state: &Self::State, _state: &Self::State,
renderer: &Renderer, renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: iced::Rectangle,
_cursor: mouse::Cursor, _cursor: iced::mouse::Cursor,
) -> Vec<Geometry> { ) -> Vec<Geometry> {
let mut frame = Frame::new(renderer, bounds.size()); 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 { let center = frame.center();
return vec![frame.into_geometry()]; 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 // 3. Aiguille des heures
let dial = Path::circle(center, radius); let hour_angle = (self.hour + self.minute / 60.0) * (std::f32::consts::PI / 6.0) - std::f32::consts::FRAC_PI_2;
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)
let hour_len = radius * 0.5; let hour_len = radius * 0.5;
let hour_end = Point::new( let hour_end = Point::new(
center.x + hour_angle.cos() * hour_len, center.x + hour_len * hour_angle.cos(),
center.y + hour_angle.sin() * hour_len, 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_len = radius * 0.75;
let min_end = Point::new( let min_end = Point::new(
center.x + minute_angle.cos() * min_len, center.x + min_len * min_angle.cos(),
center.y + minute_angle.sin() * min_len, 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_len = radius * 0.85;
let sec_end = Point::new( let sec_end = Point::new(
center.x + second_angle.cos() * sec_len, center.x + sec_len * sec_angle.cos(),
center.y + second_angle.sin() * sec_len, center.y + sec_len * sec_angle.sin(),
); );
let sec_line = Path::line(center, sec_end); frame.stroke(
frame.stroke(&sec_line, Stroke::default().with_color(Color::from_rgb(1.0, 0.2, 0.2)).with_width(1.0)); &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()] vec![frame.into_geometry()]
} }
} }
pub fn view_clock(time: DateTime<Tz>) -> Element<'static, crate::Message> {
Canvas::new(AnalogClock { time })
.width(Length::Fixed(90.0))
.height(Length::Fixed(90.0))
.into()
}
+3 -3
View File
@@ -15,7 +15,7 @@ pub fn main() -> iced::Result {
) )
.subscription(WorldClocksApp::subscription) .subscription(WorldClocksApp::subscription)
.window(iced::window::Settings { .window(iced::window::Settings {
size: iced::Size { width: 1400.0, height: 320.0 }, size: iced::Size { width: 1600.0, height: 320.0 },
..Default::default() ..Default::default()
}) })
.run_with(WorldClocksApp::new) .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)); col = col.push(clock::view_clock(local_time));
// Horloge numérique // Horloge numérique
@@ -98,7 +98,7 @@ impl WorldClocksApp {
); );
let boxed_clock = container(col) let boxed_clock = container(col)
.width(Length::Fixed(125.0)) .width(Length::Fixed(160.0))
.padding(8) .padding(8)
.style(|_theme: &Theme| container::Style { .style(|_theme: &Theme| container::Style {
background: Some(iced::Background::Color(Color::from_rgb(0.12, 0.12, 0.15))), background: Some(iced::Background::Color(Color::from_rgb(0.12, 0.12, 0.15))),