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
|
## 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
@@ -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
@@ -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))),
|
||||||
|
|||||||
Reference in New Issue
Block a user