paiagram/
basic.rs

1use bevy::prelude::*;
2use derive_more::{Add, AddAssign, Sub, SubAssign};
3
4/// Time in seconds from point of origin (e.g. midnight)
5#[derive(
6    Debug,
7    Clone,
8    Copy,
9    PartialEq,
10    Eq,
11    Hash,
12    Ord,
13    PartialOrd,
14    Default,
15    Reflect,
16    Add,
17    Sub,
18    AddAssign,
19    SubAssign,
20)]
21pub struct TimetableTime(pub i32);
22
23impl TimetableTime {
24    #[inline]
25    pub fn from_hms<T: Into<i32>>(h: T, m: T, s: T) -> Self {
26        TimetableTime(h.into() * 3600 + m.into() * 60 + s.into())
27    }
28    #[inline]
29    pub fn from_str(s: &str) -> Option<Self> {
30        let positive = s.contains("+");
31        let parts: Vec<&str> = s.trim().split(|c| c == '+' || c == '-').collect();
32        let time_part = parts.get(0)?;
33        let time_parts: Vec<&str> = time_part.split(':').collect();
34        let base_time = match time_parts.len() {
35            2 => {
36                let h = time_parts[0].parse::<i32>().ok()?;
37                let m = time_parts[1].parse::<i32>().ok()?;
38                Some(TimetableTime::from_hms(h, m, 0))
39            }
40            3 => {
41                let h = time_parts[0].parse::<i32>().ok()?;
42                let m = time_parts[1].parse::<i32>().ok()?;
43                let s = time_parts[2].parse::<i32>().ok()?;
44                Some(TimetableTime::from_hms(h, m, s))
45            }
46            _ => None,
47        };
48        match (base_time, parts.get(1)) {
49            (Some(t), Some(day_str)) => {
50                let day_offset: i32 = day_str.trim().parse().ok()?;
51                Some(if positive {
52                    t + TimetableTime(day_offset * 86400)
53                } else {
54                    t - TimetableTime(day_offset * 86400)
55                })
56            }
57            (Some(t), None) => Some(t),
58            _ => None,
59        }
60    }
61    #[inline]
62    pub fn from_oud2_str(s: &str) -> Option<Self> {
63        match s.len() {
64            3 => {
65                let h = s[0..1].parse::<i32>().ok()?;
66                let m = s[1..3].parse::<i32>().ok()?;
67                Some(TimetableTime::from_hms(h, m, 0))
68            }
69            4 => {
70                let h = s[0..2].parse::<i32>().ok()?;
71                let m = s[2..4].parse::<i32>().ok()?;
72                Some(TimetableTime::from_hms(h, m, 0))
73            }
74            5 => {
75                let h = s[0..1].parse::<i32>().ok()?;
76                let m = s[1..3].parse::<i32>().ok()?;
77                let sec = s[3..5].parse::<i32>().ok()?;
78                Some(TimetableTime::from_hms(h, m, sec))
79            }
80            6 => {
81                let h = s[0..2].parse::<i32>().ok()?;
82                let m = s[2..4].parse::<i32>().ok()?;
83                let sec = s[4..6].parse::<i32>().ok()?;
84                Some(TimetableTime::from_hms(h, m, sec))
85            }
86            _ => None,
87        }
88    }
89    #[inline]
90    pub fn to_hhmm_string_no_colon(self) -> String {
91        let total_seconds = self.0;
92        let hours = total_seconds / 3600;
93        let minutes = (total_seconds % 3600) / 60;
94        if hours < 0 || minutes < 0 {
95            warn!("Negative time encountered in to_hhmm_string_no_colon()");
96        }
97        format!("{:>2}{:02}", hours, minutes)
98    }
99    #[inline]
100    pub fn to_mmss_string(self) -> String {
101        let total_seconds = self.0;
102        let minutes = total_seconds / 60;
103        let seconds = total_seconds % 60;
104        if minutes < 0 || seconds < 0 {
105            warn!("Negative time encountered in to_mmss_string()");
106        }
107        format!("{:02}:{:02}", minutes, seconds)
108    }
109    // #[inline(always)]
110    // pub fn to_hhmmssd_string(self) -> String {
111    //     format!("{}", self)
112    // }
113    #[inline]
114    pub fn to_hhmmss_string(self) -> String {
115        let total_seconds = self.0;
116        let hours = total_seconds / 3600;
117        let minutes = (total_seconds % 3600) / 60;
118        let seconds = total_seconds % 60;
119        if hours < 0 || minutes < 0 || seconds < 0 {
120            warn!("Negative time encountered in to_hhmmss_string()");
121        }
122        format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
123    }
124    #[inline]
125    pub fn to_hmsd_parts(self) -> (i32, i32, i32, i32) {
126        let days = self.0.div_euclid(24 * 3600);
127        let seconds_of_day = self.0.rem_euclid(24 * 3600);
128
129        let hours = seconds_of_day / 3600;
130        let minutes = (seconds_of_day % 3600) / 60;
131        let seconds = seconds_of_day % 60;
132
133        (hours, minutes, seconds, days)
134    }
135    #[inline]
136    pub fn to_ms_parts(self) -> (i32, i32) {
137        let total_seconds = self.0;
138        let minutes = total_seconds / 60;
139        let seconds = total_seconds % 60;
140        (minutes, seconds)
141    }
142}
143
144impl std::fmt::Display for TimetableTime {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        let days = self.0.div_euclid(24 * 3600);
147        let seconds_of_day = self.0.rem_euclid(24 * 3600);
148
149        let hours = seconds_of_day / 3600;
150        let minutes = (seconds_of_day % 3600) / 60;
151        let seconds = seconds_of_day % 60;
152
153        write!(f, "{:02}:{:02}:{:02}", hours, minutes, seconds)?;
154
155        if days != 0 {
156            let sign = if days > 0 { '+' } else { '-' };
157            write!(f, "{}{}", sign, days.abs())?;
158        }
159
160        Ok(())
161    }
162}
163
164/// Distance between two points on a real-world map, in metres.
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Default, Reflect)]
166pub struct TrackDistance(i32);
167
168impl TrackDistance {
169    #[inline(always)]
170    pub fn from_km<T: Into<f64>>(km: T) -> Self {
171        TrackDistance((km.into() * 1000.0).round() as i32)
172    }
173    #[inline(always)]
174    pub fn from_m<T: Into<i32>>(m: T) -> Self {
175        TrackDistance(m.into())
176    }
177    // #[inline(always)]
178    // pub fn from_mi<T: Into<f64>>(mi: T) -> Self {
179    //     TrackDistance((mi.into() * 1609.344).round() as i32)
180    // }
181    #[inline(always)]
182    pub fn as_m(&self) -> i32 {
183        self.0
184    }
185}
186
187impl std::fmt::Display for TrackDistance {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        if self.0 >= 1000 {
190            write!(f, "{:.2} km", self.0 as f64 / 1000.0)
191        } else {
192            write!(f, "{} m", self.0)
193        }
194    }
195}
196
197/// Distance between two points on the canvas, in millimetres.
198#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Reflect)]
199pub struct CanvasDistance(f32);
200impl CanvasDistance {
201    #[inline(always)]
202    pub fn from_mm<T: Into<f32>>(mm: T, factor: T) -> Self {
203        CanvasDistance(mm.into() * factor.into())
204    }
205    // #[inline(always)]
206    // pub fn from_cm<T: Into<f32>>(cm: T, factor: T) -> Self {
207    //     CanvasDistance(cm.into() * 10.0 * factor.into())
208    // }
209    // #[inline(always)]
210    // pub fn from_m<T: Into<f32>>(m: T, factor: T) -> Self {
211    //     CanvasDistance(m.into() * 1000.0 * factor.into())
212    // }
213    #[inline(always)]
214    pub fn as_mm(&self) -> f32 {
215        self.0
216    }
217}
218
219impl From<TrackDistance> for CanvasDistance {
220    fn from(distance: TrackDistance) -> Self {
221        CanvasDistance(distance.0 as f32)
222    }
223}
224
225/// Speed in km/h
226#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Reflect)]
227pub struct Speed(f32);
228impl Speed {
229    // #[inline(always)]
230    // pub fn from_kmh<T: Into<f32>>(kmh: T) -> Self {
231    //     Speed(kmh.into())
232    // }
233    // #[inline(always)]
234    // pub fn from_ms<T: Into<f32>>(ms: T) -> Self {
235    //     Speed(ms.into() * 3.6)
236    // }
237    // #[inline(always)]
238    // pub fn from_mph<T: Into<f32>>(mph: T) -> Self {
239    //     Speed(mph.into() * 1.609344)
240    // }
241}
242
243impl std::fmt::Display for Speed {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        write!(f, "{:.2} km/h", self.0)
246    }
247}
248
249impl std::ops::Div<TimetableTime> for TrackDistance {
250    type Output = Speed;
251
252    fn div(self, rhs: TimetableTime) -> Self::Output {
253        Speed(self.0 as f32 / 1000.0 / rhs.0 as f32 * 3600.0)
254    }
255}
256
257/// A note attached to an entity
258/// The note contains text and a modified timestamp
259#[derive(Component, Debug)]
260pub struct Note {
261    /// The text of the note
262    pub text: String,
263    /// The modified timestamp of the note
264    pub modified_time: i64,
265    /// The created timestamp of the note
266    pub created_time: i64,
267}