Horloges grandes
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||
+96
-49
@@ -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<Tz>,
|
||||
pub fn view_clock<Tz: TimeZone>(time: DateTime<Tz>) -> 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<Message> Program<Message> for AnalogClock {
|
||||
struct Clock {
|
||||
hour: f32,
|
||||
minute: f32,
|
||||
second: f32,
|
||||
}
|
||||
|
||||
impl<Message> Program<Message> for Clock {
|
||||
type State = ();
|
||||
|
||||
fn draw(
|
||||
@@ -16,64 +27,100 @@ impl<Message> Program<Message> for AnalogClock {
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
bounds: iced::Rectangle,
|
||||
_cursor: iced::mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
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<Tz>) -> Element<'static, crate::Message> {
|
||||
Canvas::new(AnalogClock { time })
|
||||
.width(Length::Fixed(90.0))
|
||||
.height(Length::Fixed(90.0))
|
||||
.into()
|
||||
}
|
||||
+3
-3
@@ -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))),
|
||||
|
||||
Reference in New Issue
Block a user