Skip to main content

envision/component/timeline/
types.rs

1//! Types for the Timeline component.
2//!
3//! Contains [`TimelineEvent`], [`TimelineSpan`], [`SelectedType`],
4//! [`TimelineMessage`], and [`TimelineOutput`].
5
6use ratatui::prelude::*;
7
8/// A point event on the timeline.
9///
10/// Represents a single moment in time, rendered as a marker on the timeline.
11///
12/// # Example
13///
14/// ```rust
15/// use envision::component::TimelineEvent;
16/// use ratatui::style::Color;
17///
18/// let event = TimelineEvent::new("e1", 100.0, "Deploy")
19///     .with_color(Color::Green);
20/// assert_eq!(event.id, "e1");
21/// assert_eq!(event.timestamp, 100.0);
22/// assert_eq!(event.label, "Deploy");
23/// assert_eq!(event.color, Color::Green);
24/// ```
25#[derive(Clone, Debug, PartialEq)]
26#[cfg_attr(
27    feature = "serialization",
28    derive(serde::Serialize, serde::Deserialize)
29)]
30pub struct TimelineEvent {
31    /// Unique identifier.
32    pub id: String,
33    /// Timestamp in milliseconds from epoch (or any consistent unit).
34    pub timestamp: f64,
35    /// Display label.
36    pub label: String,
37    /// Color for this event marker.
38    pub color: Color,
39}
40
41impl TimelineEvent {
42    /// Creates a new timeline event with default color.
43    ///
44    /// # Example
45    ///
46    /// ```rust
47    /// use envision::component::TimelineEvent;
48    /// use ratatui::style::Color;
49    ///
50    /// let event = TimelineEvent::new("deploy-1", 500.0, "Deployed v2.0");
51    /// assert_eq!(event.id, "deploy-1");
52    /// assert_eq!(event.timestamp, 500.0);
53    /// assert_eq!(event.label, "Deployed v2.0");
54    /// assert_eq!(event.color, Color::Yellow);
55    /// ```
56    pub fn new(id: impl Into<String>, timestamp: f64, label: impl Into<String>) -> Self {
57        Self {
58            id: id.into(),
59            timestamp,
60            label: label.into(),
61            color: Color::Yellow,
62        }
63    }
64
65    /// Sets the color (builder pattern).
66    ///
67    /// # Example
68    ///
69    /// ```rust
70    /// use envision::component::TimelineEvent;
71    /// use ratatui::style::Color;
72    ///
73    /// let event = TimelineEvent::new("e1", 0.0, "Start")
74    ///     .with_color(Color::Red);
75    /// assert_eq!(event.color, Color::Red);
76    /// ```
77    pub fn with_color(mut self, color: Color) -> Self {
78        self.color = color;
79        self
80    }
81
82    /// Sets the color.
83    ///
84    /// # Example
85    ///
86    /// ```rust
87    /// use envision::component::TimelineEvent;
88    /// use ratatui::style::Color;
89    ///
90    /// let mut event = TimelineEvent::new("e1", 0.0, "Start");
91    /// event.set_color(Color::Red);
92    /// assert_eq!(event.color, Color::Red);
93    /// ```
94    pub fn set_color(&mut self, color: Color) {
95        self.color = color;
96    }
97
98    /// Returns the color for this event marker.
99    ///
100    /// # Example
101    ///
102    /// ```rust
103    /// use envision::component::TimelineEvent;
104    /// use ratatui::style::Color;
105    ///
106    /// let event = TimelineEvent::new("e1", 0.0, "Start").with_color(Color::Red);
107    /// assert_eq!(event.color(), Color::Red);
108    /// ```
109    pub fn color(&self) -> Color {
110        self.color
111    }
112}
113
114/// A span (duration) on the timeline.
115///
116/// Represents a range of time, rendered as a horizontal bar.
117///
118/// # Example
119///
120/// ```rust
121/// use envision::component::TimelineSpan;
122/// use ratatui::style::Color;
123///
124/// let span = TimelineSpan::new("s1", 100.0, 500.0, "HTTP Request")
125///     .with_color(Color::Cyan)
126///     .with_lane(1);
127/// assert_eq!(span.id, "s1");
128/// assert_eq!(span.start, 100.0);
129/// assert_eq!(span.end, 500.0);
130/// assert_eq!(span.duration(), 400.0);
131/// assert_eq!(span.lane, 1);
132/// ```
133#[derive(Clone, Debug, PartialEq)]
134#[cfg_attr(
135    feature = "serialization",
136    derive(serde::Serialize, serde::Deserialize)
137)]
138pub struct TimelineSpan {
139    /// Unique identifier.
140    pub id: String,
141    /// Start timestamp.
142    pub start: f64,
143    /// End timestamp.
144    pub end: f64,
145    /// Display label.
146    pub label: String,
147    /// Color for this span bar.
148    pub color: Color,
149    /// Optional row/lane index for vertical positioning.
150    pub lane: usize,
151}
152
153impl TimelineSpan {
154    /// Creates a new timeline span with default color and lane 0.
155    ///
156    /// # Example
157    ///
158    /// ```rust
159    /// use envision::component::TimelineSpan;
160    /// use ratatui::style::Color;
161    ///
162    /// let span = TimelineSpan::new("s1", 200.0, 800.0, "request-1");
163    /// assert_eq!(span.id, "s1");
164    /// assert_eq!(span.start, 200.0);
165    /// assert_eq!(span.end, 800.0);
166    /// assert_eq!(span.label, "request-1");
167    /// assert_eq!(span.color, Color::Cyan);
168    /// assert_eq!(span.lane, 0);
169    /// ```
170    pub fn new(id: impl Into<String>, start: f64, end: f64, label: impl Into<String>) -> Self {
171        Self {
172            id: id.into(),
173            start,
174            end,
175            label: label.into(),
176            color: Color::Cyan,
177            lane: 0,
178        }
179    }
180
181    /// Sets the color (builder pattern).
182    ///
183    /// # Example
184    ///
185    /// ```rust
186    /// use envision::component::TimelineSpan;
187    /// use ratatui::style::Color;
188    ///
189    /// let span = TimelineSpan::new("s1", 0.0, 100.0, "task")
190    ///     .with_color(Color::Red);
191    /// assert_eq!(span.color, Color::Red);
192    /// ```
193    pub fn with_color(mut self, color: Color) -> Self {
194        self.color = color;
195        self
196    }
197
198    /// Sets the color.
199    ///
200    /// # Example
201    ///
202    /// ```rust
203    /// use envision::component::TimelineSpan;
204    /// use ratatui::style::Color;
205    ///
206    /// let mut span = TimelineSpan::new("s1", 0.0, 100.0, "task");
207    /// span.set_color(Color::Red);
208    /// assert_eq!(span.color, Color::Red);
209    /// ```
210    pub fn set_color(&mut self, color: Color) {
211        self.color = color;
212    }
213
214    /// Returns the color for this span bar.
215    ///
216    /// # Example
217    ///
218    /// ```rust
219    /// use envision::component::TimelineSpan;
220    /// use ratatui::style::Color;
221    ///
222    /// let span = TimelineSpan::new("s1", 0.0, 100.0, "task").with_color(Color::Red);
223    /// assert_eq!(span.color(), Color::Red);
224    /// ```
225    pub fn color(&self) -> Color {
226        self.color
227    }
228
229    /// Sets the lane (builder pattern).
230    ///
231    /// # Example
232    ///
233    /// ```rust
234    /// use envision::component::TimelineSpan;
235    ///
236    /// let span = TimelineSpan::new("s1", 0.0, 100.0, "task")
237    ///     .with_lane(2);
238    /// assert_eq!(span.lane, 2);
239    /// ```
240    pub fn with_lane(mut self, lane: usize) -> Self {
241        self.lane = lane;
242        self
243    }
244
245    /// Returns the duration of this span.
246    ///
247    /// # Example
248    ///
249    /// ```rust
250    /// use envision::component::TimelineSpan;
251    ///
252    /// let span = TimelineSpan::new("s1", 100.0, 400.0, "task");
253    /// assert_eq!(span.duration(), 300.0);
254    /// ```
255    pub fn duration(&self) -> f64 {
256        self.end - self.start
257    }
258}
259
260/// Distinguishes whether the selected item is an event or a span.
261///
262/// # Example
263///
264/// ```rust
265/// use envision::component::SelectedType;
266///
267/// let default = SelectedType::default();
268/// assert_eq!(default, SelectedType::Event);
269/// ```
270#[derive(Clone, Debug, Default, PartialEq, Eq)]
271#[cfg_attr(
272    feature = "serialization",
273    derive(serde::Serialize, serde::Deserialize)
274)]
275pub enum SelectedType {
276    /// A point event is selected.
277    #[default]
278    Event,
279    /// A span is selected.
280    Span,
281}
282
283/// Messages that can be sent to a Timeline.
284///
285/// # Example
286///
287/// ```rust
288/// use envision::component::{
289///     Component, Timeline, TimelineState, TimelineMessage, TimelineEvent,
290/// };
291///
292/// let mut state = TimelineState::new();
293/// let event = TimelineEvent::new("e1", 100.0, "Start");
294/// state.update(TimelineMessage::AddEvent(event));
295/// assert_eq!(state.events().len(), 1);
296/// ```
297#[derive(Clone, Debug, PartialEq)]
298#[cfg_attr(
299    feature = "serialization",
300    derive(serde::Serialize, serde::Deserialize)
301)]
302pub enum TimelineMessage {
303    /// Add a point event.
304    AddEvent(TimelineEvent),
305    /// Add a duration span.
306    AddSpan(TimelineSpan),
307    /// Replace all events.
308    SetEvents(Vec<TimelineEvent>),
309    /// Replace all spans.
310    SetSpans(Vec<TimelineSpan>),
311    /// Clear everything.
312    Clear,
313    /// Narrow the visible window (zoom in).
314    ZoomIn,
315    /// Widen the visible window (zoom out).
316    ZoomOut,
317    /// Shift visible window left.
318    PanLeft,
319    /// Shift visible window right.
320    PanRight,
321    /// Adjust view to show all events/spans.
322    FitAll,
323    /// Select next event/span.
324    SelectNext,
325    /// Select previous event/span.
326    SelectPrev,
327}
328
329/// Output messages from a Timeline.
330///
331/// # Example
332///
333/// ```rust
334/// use envision::component::TimelineOutput;
335///
336/// let output = TimelineOutput::EventSelected("e1".into());
337/// assert_eq!(output, TimelineOutput::EventSelected("e1".into()));
338/// ```
339#[derive(Clone, Debug, PartialEq)]
340#[cfg_attr(
341    feature = "serialization",
342    derive(serde::Serialize, serde::Deserialize)
343)]
344pub enum TimelineOutput {
345    /// An event was selected (carries event id).
346    EventSelected(String),
347    /// A span was selected (carries span id).
348    SpanSelected(String),
349    /// The visible time window changed.
350    ViewChanged {
351        /// New view start.
352        start: f64,
353        /// New view end.
354        end: f64,
355    },
356}