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}