1use std::{
2 sync::{
3 mpsc::{self, Receiver},
4 Arc,
5 },
6 time::Duration,
7};
89use matrix_sdk_common::locks::Mutex;
10use ratatui::{
11 prelude::{Buffer, Rect, *},
12 widgets::Paragraph,
13};
14use tokio::{
15 spawn,
16 task::{spawn_blocking, JoinHandle},
17 time::sleep,
18};
1920use crate::{AppState, GlobalMode};
2122const MESSAGE_DURATION: Duration = Duration::from_secs(4);
2324pub struct Status {
25/// Content of the latest status message, if set.
26last_status_message: Arc<Mutex<Option<String>>>,
2728/// An [mpsc::Sender] that other widgets can use to change the status
29 /// message.
30message_sender: mpsc::Sender<String>,
3132/// The task listening for status messages to be received over the
33 /// [mpsc::Receiver].
34_receiver_task: JoinHandle<()>,
35}
3637impl Default for Status {
38fn default() -> Self {
39Self::new()
40 }
41}
4243/// A handle to the [`Status`] widget, this handle can be moved to different
44/// threads where it can be used to set the status message.
45#[derive(Clone)]
46pub struct StatusHandle {
47 message_sender: mpsc::Sender<String>,
48}
4950impl StatusHandle {
51/// Set the current status message (displayed at the bottom), for a few
52 /// seconds.
53pub fn set_message(&self, status: String) {
54self.message_sender.send(status).expect(
55"We should be able to send the status message since the receiver is alive \
56 as long as we are alive",
57 );
58 }
59}
6061impl Status {
62/// Create a new empty [`Status`] widget.
63pub fn new() -> Self {
64let (message_sender, receiver) = mpsc::channel();
65let last_status_message = Arc::new(Mutex::new(None));
6667let receiver_task = spawn_blocking({
68let last_status_message = last_status_message.clone();
69move || Self::receiving_task(receiver, last_status_message)
70 });
7172Self { last_status_message, _receiver_task: receiver_task, message_sender }
73 }
7475fn receiving_task(receiver: Receiver<String>, status_message: Arc<Mutex<Option<String>>>) {
76let mut clear_message_task: Option<JoinHandle<()>> = None;
7778while let Ok(message) = receiver.recv() {
79if let Some(task) = clear_message_task.take() {
80 task.abort();
81 }
8283 {
84let mut status_message = status_message.lock();
85*status_message = Some(message);
86 }
8788 clear_message_task = Some(spawn({
89let status_message = status_message.clone();
9091async move {
92// Clear the status message in 4 seconds.
93sleep(MESSAGE_DURATION).await;
94 status_message.lock().take();
95 }
96 }));
97 }
98 }
99100/// Set the current status message (displayed at the bottom), for a few
101 /// seconds.
102pub fn set_message(&self, status: String) {
103self.message_sender.send(status).expect(
104"We should be able to send the status message since the receiver is alive \
105 as long as we are alive",
106 );
107 }
108109/// Get a handle to the [`Status`] widget, this can be used to set the
110 /// status message from a separate thread.
111pub fn handle(&self) -> StatusHandle {
112 StatusHandle { message_sender: self.message_sender.clone() }
113 }
114}
115116impl StatefulWidget for &mut Status {
117type State = AppState;
118119/// Render the bottom part of the screen, with a status message if one is
120 /// set, or a default help message otherwise.
121fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
122let status_message = self.last_status_message.lock();
123124let content = if let Some(status_message) = status_message.as_deref() {
125 status_message
126 } else {
127let AppState { global_mode, throbber_state: _ } = state;
128129match global_mode {
130 GlobalMode::Help => "Press q to exit the help screen",
131 GlobalMode::Settings { .. } => "Press ESC to exit the settings screen",
132 GlobalMode::Default => "Press F1 to show the help screen",
133 GlobalMode::Exiting { .. } => "",
134 }
135 };
136137 Paragraph::new(content).centered().render(area, buf);
138 }
139}