egui_arbor/
event_log.rs

1//! Event logging system for tracking outliner interactions.
2//!
3//! This module provides types for logging and tracking user interactions with the outliner,
4//! including selections, visibility changes, lock toggles, drag-drop operations, and renames.
5//!
6//! # Examples
7//!
8//! ```
9//! use egui_arbor::event_log::{EventLog, EventType};
10//!
11//! let mut log = EventLog::<u64>::new(10); // Keep last 10 events
12//!
13//! log.log("Selected node 5", EventType::Selection, Some(5));
14//! log.log("Renamed node 3", EventType::Rename, Some(3));
15//!
16//! for entry in log.entries() {
17//!     println!("{}: {}", entry.event_type_str(), entry.message);
18//! }
19//! ```
20
21use std::collections::VecDeque;
22use std::time::SystemTime;
23
24/// Type of event that occurred in the outliner.
25///
26/// This enum categorizes different types of user interactions for logging
27/// and filtering purposes.
28#[derive(Clone, Debug, PartialEq, Eq, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub enum EventType {
31    /// Node selection or deselection event.
32    Selection,
33    
34    /// Visibility toggle event (show/hide).
35    Visibility,
36    
37    /// Lock state toggle event.
38    Lock,
39    
40    /// Drag-and-drop operation event.
41    DragDrop,
42    
43    /// Node rename event.
44    Rename,
45    
46    /// Custom event type with a string identifier.
47    Custom(String),
48}
49
50impl EventType {
51    /// Returns a string representation of the event type.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use egui_arbor::event_log::EventType;
57    ///
58    /// assert_eq!(EventType::Selection.as_str(), "Selection");
59    /// assert_eq!(EventType::Custom("MyEvent".into()).as_str(), "MyEvent");
60    /// ```
61    pub fn as_str(&self) -> &str {
62        match self {
63            EventType::Selection => "Selection",
64            EventType::Visibility => "Visibility",
65            EventType::Lock => "Lock",
66            EventType::DragDrop => "DragDrop",
67            EventType::Rename => "Rename",
68            EventType::Custom(s) => s.as_str(),
69        }
70    }
71}
72
73/// A single log entry recording an event.
74///
75/// Each entry contains:
76/// - A timestamp of when the event occurred
77/// - A human-readable message describing the event
78/// - The type of event
79/// - An optional node ID associated with the event
80#[derive(Clone, Debug)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub struct LogEntry<Id> {
83    /// When the event occurred.
84    pub timestamp: SystemTime,
85    
86    /// Human-readable description of the event.
87    pub message: String,
88    
89    /// The type of event that occurred.
90    pub event_type: EventType,
91    
92    /// The ID of the node involved in the event, if applicable.
93    pub node_id: Option<Id>,
94}
95
96impl<Id> LogEntry<Id> {
97    /// Creates a new log entry.
98    ///
99    /// # Arguments
100    ///
101    /// * `message` - Human-readable description of the event
102    /// * `event_type` - The type of event
103    /// * `node_id` - Optional ID of the node involved
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// use egui_arbor::event_log::{LogEntry, EventType};
109    ///
110    /// let entry = LogEntry::new(
111    ///     "Selected node".to_string(),
112    ///     EventType::Selection,
113    ///     Some(42u64),
114    /// );
115    /// ```
116    pub fn new(message: String, event_type: EventType, node_id: Option<Id>) -> Self {
117        Self {
118            timestamp: SystemTime::now(),
119            message,
120            event_type,
121            node_id,
122        }
123    }
124
125    /// Returns the event type as a string.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use egui_arbor::event_log::{LogEntry, EventType};
131    ///
132    /// let entry = LogEntry::<u64>::new("test".into(), EventType::Selection, None);
133    /// assert_eq!(entry.event_type_str(), "Selection");
134    /// ```
135    pub fn event_type_str(&self) -> &str {
136        self.event_type.as_str()
137    }
138
139    /// Returns the elapsed time since this event occurred.
140    ///
141    /// # Returns
142    ///
143    /// `Ok(Duration)` if the elapsed time could be calculated, or an error if
144    /// the system time has moved backwards.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use egui_arbor::event_log::{LogEntry, EventType};
150    ///
151    /// let entry = LogEntry::<u64>::new("test".into(), EventType::Selection, None);
152    /// if let Ok(elapsed) = entry.elapsed() {
153    ///     println!("Event occurred {} seconds ago", elapsed.as_secs());
154    /// }
155    /// ```
156    pub fn elapsed(&self) -> Result<std::time::Duration, std::time::SystemTimeError> {
157        self.timestamp.elapsed()
158    }
159
160    /// Formats the elapsed time as a human-readable string.
161    ///
162    /// Returns strings like "5s ago", "2m ago", "1h ago", etc.
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// use egui_arbor::event_log::{LogEntry, EventType};
168    ///
169    /// let entry = LogEntry::<u64>::new("test".into(), EventType::Selection, None);
170    /// println!("{}", entry.format_elapsed());
171    /// ```
172    pub fn format_elapsed(&self) -> String {
173        match self.elapsed() {
174            Ok(duration) => {
175                let secs = duration.as_secs();
176                if secs < 60 {
177                    format!("{}s ago", secs)
178                } else if secs < 3600 {
179                    format!("{}m ago", secs / 60)
180                } else if secs < 86400 {
181                    format!("{}h ago", secs / 3600)
182                } else {
183                    format!("{}d ago", secs / 86400)
184                }
185            }
186            Err(_) => "unknown".to_string(),
187        }
188    }
189}
190
191/// Event log for tracking outliner interactions.
192///
193/// This structure maintains a circular buffer of recent events, automatically
194/// discarding old events when the maximum capacity is reached.
195///
196/// # Examples
197///
198/// ```
199/// use egui_arbor::event_log::{EventLog, EventType};
200///
201/// let mut log = EventLog::<u64>::new(100);
202///
203/// log.log("Node 5 selected", EventType::Selection, Some(5));
204/// log.log("Node 3 renamed to 'New Name'", EventType::Rename, Some(3));
205///
206/// assert_eq!(log.len(), 2);
207///
208/// for entry in log.entries() {
209///     println!("{}: {}", entry.event_type_str(), entry.message);
210/// }
211/// ```
212#[derive(Clone, Debug)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214pub struct EventLog<Id> {
215    /// The log entries, with most recent first.
216    entries: VecDeque<LogEntry<Id>>,
217    
218    /// Maximum number of entries to keep.
219    max_entries: usize,
220}
221
222impl<Id> EventLog<Id> {
223    /// Creates a new event log with the specified maximum capacity.
224    ///
225    /// # Arguments
226    ///
227    /// * `max_entries` - Maximum number of entries to keep. When this limit is
228    ///   reached, the oldest entries are discarded.
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// use egui_arbor::event_log::EventLog;
234    ///
235    /// let log = EventLog::<u64>::new(50);
236    /// ```
237    pub fn new(max_entries: usize) -> Self {
238        Self {
239            entries: VecDeque::with_capacity(max_entries),
240            max_entries,
241        }
242    }
243
244    /// Logs a new event.
245    ///
246    /// The event is added to the front of the log (most recent). If the log
247    /// is at capacity, the oldest event is removed.
248    ///
249    /// # Arguments
250    ///
251    /// * `message` - Human-readable description of the event
252    /// * `event_type` - The type of event
253    /// * `node_id` - Optional ID of the node involved
254    ///
255    /// # Examples
256    ///
257    /// ```
258    /// use egui_arbor::event_log::{EventLog, EventType};
259    ///
260    /// let mut log = EventLog::new(10);
261    /// log.log("Selected node 5", EventType::Selection, Some(5u64));
262    /// ```
263    pub fn log(&mut self, message: impl Into<String>, event_type: EventType, node_id: Option<Id>) {
264        self.entries.push_front(LogEntry::new(
265            message.into(),
266            event_type,
267            node_id,
268        ));
269
270        if self.entries.len() > self.max_entries {
271            self.entries.pop_back();
272        }
273    }
274
275    /// Returns a slice of all log entries, with most recent first.
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use egui_arbor::event_log::{EventLog, EventType};
281    ///
282    /// let mut log = EventLog::new(10);
283    /// log.log("Event 1", EventType::Selection, Some(1u64));
284    /// log.log("Event 2", EventType::Rename, Some(2u64));
285    ///
286    /// assert_eq!(log.entries().count(), 2);
287    /// let entries: Vec<_> = log.entries().collect();
288    /// assert_eq!(entries[0].message, "Event 2"); // Most recent first
289    /// ```
290    pub fn entries(&self) -> impl Iterator<Item = &LogEntry<Id>> {
291        self.entries.iter()
292    }
293
294    /// Returns the number of entries in the log.
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// use egui_arbor::event_log::{EventLog, EventType};
300    ///
301    /// let mut log = EventLog::<u64>::new(10);
302    /// assert_eq!(log.len(), 0);
303    ///
304    /// log.log("Event", EventType::Selection, None);
305    /// assert_eq!(log.len(), 1);
306    /// ```
307    pub fn len(&self) -> usize {
308        self.entries.len()
309    }
310
311    /// Returns `true` if the log contains no entries.
312    ///
313    /// # Examples
314    ///
315    /// ```
316    /// use egui_arbor::event_log::EventLog;
317    ///
318    /// let log = EventLog::<u64>::new(10);
319    /// assert!(log.is_empty());
320    /// ```
321    pub fn is_empty(&self) -> bool {
322        self.entries.is_empty()
323    }
324
325    /// Clears all entries from the log.
326    ///
327    /// # Examples
328    ///
329    /// ```
330    /// use egui_arbor::event_log::{EventLog, EventType};
331    ///
332    /// let mut log = EventLog::<u64>::new(10);
333    /// log.log("Event", EventType::Selection, None);
334    /// assert!(!log.is_empty());
335    ///
336    /// log.clear();
337    /// assert!(log.is_empty());
338    /// ```
339    pub fn clear(&mut self) {
340        self.entries.clear();
341    }
342
343    /// Returns the maximum number of entries this log can hold.
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// use egui_arbor::event_log::EventLog;
349    ///
350    /// let log = EventLog::<u64>::new(50);
351    /// assert_eq!(log.max_entries(), 50);
352    /// ```
353    pub fn max_entries(&self) -> usize {
354        self.max_entries
355    }
356
357    /// Sets the maximum number of entries this log can hold.
358    ///
359    /// If the new maximum is less than the current number of entries,
360    /// the oldest entries are removed to fit the new limit.
361    ///
362    /// # Examples
363    ///
364    /// ```
365    /// use egui_arbor::event_log::{EventLog, EventType};
366    ///
367    /// let mut log = EventLog::new(10);
368    /// for i in 0..10 {
369    ///     log.log(format!("Event {}", i), EventType::Selection, Some(i));
370    /// }
371    ///
372    /// log.set_max_entries(5);
373    /// assert_eq!(log.len(), 5);
374    /// ```
375    pub fn set_max_entries(&mut self, max_entries: usize) {
376        self.max_entries = max_entries;
377        while self.entries.len() > max_entries {
378            self.entries.pop_back();
379        }
380    }
381
382    /// Filters entries by event type.
383    ///
384    /// Returns an iterator over entries matching the specified event type.
385    ///
386    /// # Examples
387    ///
388    /// ```
389    /// use egui_arbor::event_log::{EventLog, EventType};
390    ///
391    /// let mut log = EventLog::new(10);
392    /// log.log("Selected", EventType::Selection, Some(1u64));
393    /// log.log("Renamed", EventType::Rename, Some(2u64));
394    /// log.log("Selected again", EventType::Selection, Some(3u64));
395    ///
396    /// let selections: Vec<_> = log.filter_by_type(&EventType::Selection).collect();
397    /// assert_eq!(selections.len(), 2);
398    /// ```
399    pub fn filter_by_type(&self, event_type: &EventType) -> impl Iterator<Item = &LogEntry<Id>> {
400        self.entries.iter().filter(move |entry| &entry.event_type == event_type)
401    }
402}
403
404impl<Id> Default for EventLog<Id> {
405    /// Creates a new event log with a default capacity of 100 entries.
406    fn default() -> Self {
407        Self::new(100)
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414
415    #[test]
416    fn test_event_type_as_str() {
417        assert_eq!(EventType::Selection.as_str(), "Selection");
418        assert_eq!(EventType::Visibility.as_str(), "Visibility");
419        assert_eq!(EventType::Lock.as_str(), "Lock");
420        assert_eq!(EventType::DragDrop.as_str(), "DragDrop");
421        assert_eq!(EventType::Rename.as_str(), "Rename");
422        assert_eq!(EventType::Custom("Test".into()).as_str(), "Test");
423    }
424
425    #[test]
426    fn test_log_entry_creation() {
427        let entry = LogEntry::new(
428            "Test message".to_string(),
429            EventType::Selection,
430            Some(42u64),
431        );
432
433        assert_eq!(entry.message, "Test message");
434        assert_eq!(entry.event_type, EventType::Selection);
435        assert_eq!(entry.node_id, Some(42));
436    }
437
438    #[test]
439    fn test_log_entry_event_type_str() {
440        let entry = LogEntry::<u64>::new(
441            "Test".into(),
442            EventType::Rename,
443            None,
444        );
445
446        assert_eq!(entry.event_type_str(), "Rename");
447    }
448
449    #[test]
450    fn test_event_log_new() {
451        let log = EventLog::<u64>::new(10);
452        assert_eq!(log.max_entries(), 10);
453        assert_eq!(log.len(), 0);
454        assert!(log.is_empty());
455    }
456
457    #[test]
458    fn test_event_log_log() {
459        let mut log = EventLog::new(10);
460        
461        log.log("Event 1", EventType::Selection, Some(1u64));
462        assert_eq!(log.len(), 1);
463        
464        log.log("Event 2", EventType::Rename, Some(2u64));
465        assert_eq!(log.len(), 2);
466    }
467
468    #[test]
469    fn test_event_log_max_capacity() {
470        let mut log = EventLog::new(3);
471        
472        log.log("Event 1", EventType::Selection, Some(1u64));
473        log.log("Event 2", EventType::Selection, Some(2u64));
474        log.log("Event 3", EventType::Selection, Some(3u64));
475        log.log("Event 4", EventType::Selection, Some(4u64));
476        
477        assert_eq!(log.len(), 3);
478        
479        // Most recent entries should be kept
480        let entries: Vec<_> = log.entries().collect();
481        assert_eq!(entries[0].message, "Event 4");
482        assert_eq!(entries[1].message, "Event 3");
483        assert_eq!(entries[2].message, "Event 2");
484    }
485
486    #[test]
487    fn test_event_log_clear() {
488        let mut log = EventLog::<u64>::new(10);
489        
490        log.log("Event 1", EventType::Selection, None);
491        log.log("Event 2", EventType::Selection, None);
492        assert_eq!(log.len(), 2);
493        
494        log.clear();
495        assert_eq!(log.len(), 0);
496        assert!(log.is_empty());
497    }
498
499    #[test]
500    fn test_event_log_set_max_entries() {
501        let mut log = EventLog::new(10);
502        
503        for i in 0..10 {
504            log.log(format!("Event {}", i), EventType::Selection, Some(i));
505        }
506        assert_eq!(log.len(), 10);
507        
508        log.set_max_entries(5);
509        assert_eq!(log.len(), 5);
510        assert_eq!(log.max_entries(), 5);
511        
512        // Most recent entries should be kept
513        let entries: Vec<_> = log.entries().collect();
514        assert_eq!(entries[0].node_id, Some(9));
515        assert_eq!(entries[4].node_id, Some(5));
516    }
517
518    #[test]
519    fn test_event_log_filter_by_type() {
520        let mut log = EventLog::new(10);
521        
522        log.log("Select 1", EventType::Selection, Some(1u64));
523        log.log("Rename 1", EventType::Rename, Some(2u64));
524        log.log("Select 2", EventType::Selection, Some(3u64));
525        log.log("Lock 1", EventType::Lock, Some(4u64));
526        log.log("Select 3", EventType::Selection, Some(5u64));
527        
528        let selections: Vec<_> = log.filter_by_type(&EventType::Selection).collect();
529        assert_eq!(selections.len(), 3);
530        
531        let renames: Vec<_> = log.filter_by_type(&EventType::Rename).collect();
532        assert_eq!(renames.len(), 1);
533        
534        let visibility: Vec<_> = log.filter_by_type(&EventType::Visibility).collect();
535        assert_eq!(visibility.len(), 0);
536    }
537
538    #[test]
539    fn test_event_log_default() {
540        let log = EventLog::<u64>::default();
541        assert_eq!(log.max_entries(), 100);
542        assert!(log.is_empty());
543    }
544
545    #[test]
546    fn test_log_entry_format_elapsed() {
547        let entry = LogEntry::<u64>::new(
548            "Test".into(),
549            EventType::Selection,
550            None,
551        );
552        
553        let formatted = entry.format_elapsed();
554        assert!(formatted.ends_with("ago"));
555    }
556}