p2p_chat/ui/state/
mod.rs

1//! This module defines the central state management for the user interface.
2mod chat;
3mod input;
4mod logs;
5
6use std::collections::VecDeque;
7
8use chrono::{DateTime, Utc};
9use tracing::Level;
10use uuid::Uuid;
11
12use crate::types::Message;
13
14use super::{log_entry::LogEntry, mode::UIMode};
15
16/// Represents the overall state of the user interface.
17///
18/// This struct holds all the data and configuration necessary to render the UI
19/// and respond to user interactions.
20#[derive(Debug)]
21pub struct UIState {
22    /// The current operational mode of the UI (Chat or Logs).
23    pub mode: UIMode,
24    /// Stores the last log mode for easy switching back.
25    pub last_log_mode: Option<UIMode>,
26    /// A vector of chat message entries for display.
27    pub messages: Vec<ChatMessageEntry>,
28    /// A vector of generic chat messages, typically system messages or command output.
29    pub chat_messages: Vec<(DateTime<Utc>, String)>,
30    /// A deque of log entries for the log view.
31    pub logs: VecDeque<LogEntry>,
32    /// The current vertical scroll offset for the chat view.
33    pub scroll_offset: usize,
34    /// The current vertical scroll offset for the log view.
35    pub log_scroll_offset: usize,
36    /// The current horizontal scroll offset for text.
37    pub horizontal_scroll_offset: usize,
38    /// Indicates if the chat view is scrolled to the bottom.
39    pub is_at_bottom_chat: bool,
40    /// Indicates if the log view is scrolled to the bottom.
41    pub is_at_bottom_log: bool,
42    /// The current content of the input buffer.
43    pub input_buffer: String,
44    /// The current cursor position within the input buffer.
45    pub cursor_pos: usize,
46    /// The current size of the terminal (width, height).
47    pub terminal_size: (u16, u16),
48    /// The maximum number of log entries to retain.
49    pub max_log_entries: usize,
50    /// The count of currently connected peers.
51    pub connected_peers_count: usize,
52}
53
54impl UIState {
55    /// Creates a new `UIState` with default values.
56    pub fn new() -> Self {
57        Self {
58            mode: UIMode::default(),
59            last_log_mode: None,
60            messages: Vec::new(),
61            chat_messages: Vec::new(),
62            logs: VecDeque::with_capacity(10000),
63            scroll_offset: 0,
64            log_scroll_offset: 0,
65            horizontal_scroll_offset: 0,
66            is_at_bottom_chat: true,
67            is_at_bottom_log: true,
68            input_buffer: String::new(),
69            cursor_pos: 0,
70            terminal_size: (80, 24),
71            max_log_entries: 10000,
72            connected_peers_count: 0,
73        }
74    }
75
76    /// Toggles the UI mode between chat and logs.
77    ///
78    /// When switching to log mode for the first time or from chat mode,
79    /// it initializes log mode settings if not already defined. 
80    /// Resets scroll offsets and `is_at_bottom` flags upon mode change.
81    pub fn toggle_mode(&mut self) {
82        self.mode = match &self.mode {
83            UIMode::Chat => match &self.last_log_mode {
84                Some(UIMode::Logs { filter, level }) => UIMode::Logs {
85                    filter: filter.clone(),
86                    level: *level,
87                },
88                _ => UIMode::Logs {
89                    filter: None,
90                    level: Level::DEBUG,
91                },
92            },
93            UIMode::Logs { .. } => {
94                self.last_log_mode = Some(self.mode.clone());
95                UIMode::Chat
96            }
97        };
98
99        self.scroll_offset = 0;
100        self.log_scroll_offset = 0;
101        self.is_at_bottom_chat = true;
102        self.is_at_bottom_log = true;
103    }
104
105    /// Replaces the current list of displayed messages with a new set.
106    ///
107    /// Resets scroll offset to the bottom after replacing messages.
108    ///
109    /// # Arguments
110    ///
111    /// * `messages` - A `Vec` of `Message`s to display.
112    pub fn replace_messages(&mut self, messages: Vec<Message>) {
113        self.messages.clear();
114        for message in messages {
115            let arrival = chrono::DateTime::<Utc>::from_timestamp_millis(message.timestamp)
116                .unwrap_or_else(Utc::now);
117            self.messages.push(ChatMessageEntry {
118                message,
119                received_at: arrival,
120            });
121        }
122        self.scroll_offset = 0;
123        self.is_at_bottom_chat = true;
124    }
125
126    /// Checks if a message with the given ID already exists in the UI state.
127    ///
128    /// # Arguments
129    ///
130    /// * `message_id` - The `Uuid` of the message to check.
131    ///
132    /// # Returns
133    ///
134    /// `true` if the message is found, `false` otherwise.
135    fn contains_message(&self, message_id: &Uuid) -> bool {
136        self.messages
137            .iter()
138            .any(|entry| entry.message.id == *message_id)
139    }
140}
141
142/// Represents a chat message along with its reception timestamp.
143#[derive(Debug, Clone)]
144pub struct ChatMessageEntry {
145    /// The actual `Message` content.
146    pub message: Message,
147    /// The `DateTime` when the message was received by the UI.
148    pub received_at: DateTime<Utc>,
149}