horloges petites
This commit is contained in:
Generated
+4377
-1
File diff suppressed because it is too large
Load Diff
+6
-2
@@ -1,6 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "horloge-bourses-mondiales"
|
name = "horloge-bourses-mondiales-gui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
iced = { version = "0.13", features = ["canvas", "tokio"] }
|
||||||
|
tokio = { version = "1.0", features = ["time"] }
|
||||||
|
chrono = "0.4"
|
||||||
|
chrono-tz = "0.10"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Horloge Bourses Mondiales (TUI)
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
## Marchés Affichés
|
||||||
|
|
||||||
|
0. **UTC** (Cyan)
|
||||||
|
1. **New York Stock Exchange (NYSE)**
|
||||||
|
2. **Shanghai Stock Exchange**
|
||||||
|
3. **Euronext Paris**
|
||||||
|
4. **Japan Exchange Group (Tokyo)**
|
||||||
|
5. **Shenzhen Stock Exchange**
|
||||||
|
6. **Hong Kong Exchanges and Clearing**
|
||||||
|
7. **TMX Group (Toronto)**
|
||||||
|
8. **London Stock Exchange**
|
||||||
|
9. **National Stock Exchange of India (NSE)**
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
- Rust (édition stable actuelle)
|
||||||
|
- Cargo
|
||||||
|
|
||||||
|
## Installation & Compilation
|
||||||
|
|
||||||
|
Clonez ou placez-vous dans le répertoire du projet, puis compilez en mode release :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
pub struct AnalogClock {
|
||||||
|
pub time: DateTime<Tz>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> Program<Message> for AnalogClock {
|
||||||
|
type State = ();
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_state: &Self::State,
|
||||||
|
renderer: &Renderer,
|
||||||
|
_theme: &Theme,
|
||||||
|
bounds: Rectangle,
|
||||||
|
_cursor: 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()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
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)
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
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)
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
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));
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
+155
@@ -0,0 +1,155 @@
|
|||||||
|
use chrono::{DateTime, Datelike, NaiveTime, Timelike, Utc};
|
||||||
|
use chrono_tz::Tz;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TradingSession {
|
||||||
|
pub open: NaiveTime,
|
||||||
|
pub close: NaiveTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Exchange {
|
||||||
|
pub name: String,
|
||||||
|
pub city: String,
|
||||||
|
pub timezone: Tz,
|
||||||
|
pub sessions: Vec<TradingSession>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Exchange {
|
||||||
|
pub fn local_time(&self, utc_now: &DateTime<Utc>) -> DateTime<Tz> {
|
||||||
|
utc_now.with_timezone(&self.timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_open(&self, utc_now: &DateTime<Utc>) -> bool {
|
||||||
|
let local = self.local_time(utc_now);
|
||||||
|
let weekday = local.weekday().number_from_monday();
|
||||||
|
if weekday > 5 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let time = local.time();
|
||||||
|
self.sessions.iter().any(|session| {
|
||||||
|
if session.open <= session.close {
|
||||||
|
time >= session.open && time <= session.close
|
||||||
|
} else {
|
||||||
|
time >= session.open || time <= session.close
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_exchanges() -> Vec<Exchange> {
|
||||||
|
vec![
|
||||||
|
Exchange {
|
||||||
|
name: "UTC".to_string(),
|
||||||
|
city: "UTC".to_string(),
|
||||||
|
timezone: chrono_tz::UTC,
|
||||||
|
sessions: vec![],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "NEW YORK STOCK EXCHANGE".to_string(),
|
||||||
|
city: "NEW YORK".to_string(),
|
||||||
|
timezone: chrono_tz::America::New_York,
|
||||||
|
sessions: vec![TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "SHANGHAI STOCK EXCHANGE".to_string(),
|
||||||
|
city: "SHANGHAI".to_string(),
|
||||||
|
timezone: chrono_tz::Asia::Shanghai,
|
||||||
|
sessions: vec![
|
||||||
|
TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(11, 30, 0).unwrap(),
|
||||||
|
},
|
||||||
|
TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(13, 0, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "EURONEXT PARIS".to_string(),
|
||||||
|
city: "PARIS".to_string(),
|
||||||
|
timezone: chrono_tz::Europe::Paris,
|
||||||
|
sessions: vec![TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(17, 30, 0).unwrap(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "JAPAN EXCHANGE GROUP".to_string(),
|
||||||
|
city: "TOKYO".to_string(),
|
||||||
|
timezone: chrono_tz::Asia::Tokyo,
|
||||||
|
sessions: vec![
|
||||||
|
TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(11, 30, 0).unwrap(),
|
||||||
|
},
|
||||||
|
TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(12, 30, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(15, 30, 0).unwrap(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "SHENZHEN STOCK EXCHANGE".to_string(),
|
||||||
|
city: "SHENZHEN".to_string(),
|
||||||
|
timezone: chrono_tz::Asia::Shanghai,
|
||||||
|
sessions: vec![
|
||||||
|
TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(11, 30, 0).unwrap(),
|
||||||
|
},
|
||||||
|
TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(13, 0, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(15, 0, 0).unwrap(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "HONG KONG EXCHANGES".to_string(),
|
||||||
|
city: "HONG KONG".to_string(),
|
||||||
|
timezone: chrono_tz::Asia::Hong_Kong,
|
||||||
|
sessions: vec![
|
||||||
|
TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
|
||||||
|
},
|
||||||
|
TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(13, 0, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "TMX GROUP".to_string(),
|
||||||
|
city: "TORONTO".to_string(),
|
||||||
|
timezone: chrono_tz::America::Toronto,
|
||||||
|
sessions: vec![TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "LONDON STOCK EXCHANGE".to_string(),
|
||||||
|
city: "LONDON".to_string(),
|
||||||
|
timezone: chrono_tz::Europe::London,
|
||||||
|
sessions: vec![TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(8, 0, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(16, 30, 0).unwrap(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
Exchange {
|
||||||
|
name: "NATIONAL STOCK EXCHANGE OF INDIA".to_string(),
|
||||||
|
city: "INDIA".to_string(),
|
||||||
|
timezone: chrono_tz::Asia::Kolkata,
|
||||||
|
sessions: vec![TradingSession {
|
||||||
|
open: NaiveTime::from_hms_opt(9, 15, 0).unwrap(),
|
||||||
|
close: NaiveTime::from_hms_opt(15, 30, 0).unwrap(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
+126
-2
@@ -1,3 +1,127 @@
|
|||||||
fn main() {
|
mod exchange;
|
||||||
println!("Hello, world!");
|
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;
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
iced::application(
|
||||||
|
"Horloges Boursières Mondiales",
|
||||||
|
WorldClocksApp::update,
|
||||||
|
WorldClocksApp::view,
|
||||||
|
)
|
||||||
|
.subscription(WorldClocksApp::subscription)
|
||||||
|
.window(iced::window::Settings {
|
||||||
|
size: iced::Size { width: 1400.0, height: 320.0 },
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.run_with(WorldClocksApp::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WorldClocksApp {
|
||||||
|
exchanges: Vec<Exchange>,
|
||||||
|
now: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
Tick(DateTime<Utc>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorldClocksApp {
|
||||||
|
fn new() -> (Self, iced::Task<Message>) {
|
||||||
|
(
|
||||||
|
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<Message> {
|
||||||
|
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();
|
||||||
|
|
||||||
|
let mut col = column![].spacing(5).align_x(iced::Alignment::Center);
|
||||||
|
|
||||||
|
if is_utc {
|
||||||
|
col = col.push(
|
||||||
|
text("UTC")
|
||||||
|
.size(14)
|
||||||
|
.style(|_theme: &Theme| text::Style { color: Some(Color::from_rgb(0.0, 0.8, 0.8)) })
|
||||||
|
);
|
||||||
|
col = col.push(text("").size(12));
|
||||||
|
} 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(12)
|
||||||
|
.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) })
|
||||||
|
);
|
||||||
|
|
||||||
|
let boxed_clock = container(col)
|
||||||
|
.width(Length::Fixed(125.0))
|
||||||
|
.padding(8)
|
||||||
|
.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user