multiverse/widgets/room_view/
input.rs

1use clap::{Parser, Subcommand};
2use crossterm::event::KeyEvent;
3use matrix_sdk::ruma::OwnedUserId;
4use matrix_sdk_ui::room_list_service::Room;
5use ratatui::{prelude::*, widgets::*};
6use style::palette::tailwind;
7use tui_textarea::TextArea;
8
9#[derive(Debug, Parser)]
10#[command(name = "multiverse", disable_help_flag = true, disable_help_subcommand = true)]
11struct Cli {
12    #[command(subcommand)]
13    command: Command,
14}
15
16#[derive(Debug, Subcommand)]
17pub enum Command {
18    Invite { user_id: OwnedUserId },
19}
20
21pub enum MessageOrCommand {
22    Message(String),
23    Command(Command),
24}
25
26/// A widget representing a text input to send messages to a room.
27#[derive(Default)]
28pub struct Input {
29    /// The text area that will keep track of what the user has input.
30    textarea: TextArea<'static>,
31}
32
33impl Input {
34    /// Create a new empty [`Input`] widget.
35    pub fn new() -> Self {
36        let textarea = TextArea::default();
37
38        Self { textarea }
39    }
40
41    /// Receive a key press event and handle it.
42    pub fn handle_key_press(&mut self, event: KeyEvent) {
43        self.textarea.input(event);
44    }
45
46    /// Get the currently input text.
47    pub fn get_input(&self) -> Result<MessageOrCommand, clap::Error> {
48        let input = self.textarea.lines().join("\n");
49
50        if let Some(input) = input.strip_prefix("/") {
51            let arguments = input.split_whitespace();
52
53            // Clap expects the first argument to be the binary name, like when a command is
54            // invoked on the command line. Since we aren't a command line, but
55            // still find clap a neat command parser, let's give it what it
56            // expects so we can parse our commands.
57            Cli::try_parse_from(std::iter::once("multiverse").chain(arguments))
58                .map(|cli| MessageOrCommand::Command(cli.command))
59        } else {
60            Ok(MessageOrCommand::Message(input))
61        }
62    }
63
64    /// Is the input area empty, returns false if the user hasn't input anything
65    /// yet.
66    pub fn is_empty(&self) -> bool {
67        self.textarea.is_empty()
68    }
69
70    /// Clear the text from the input area.
71    pub fn clear(&mut self) {
72        self.textarea = TextArea::default();
73    }
74}
75
76impl<'a> StatefulWidget for &'a mut Input {
77    type State = Option<&'a Room>;
78
79    fn render(self, area: Rect, buf: &mut Buffer, room: &mut Self::State)
80    where
81        Self: Sized,
82    {
83        // Set the placeholder text depending on the encryption state of the room.
84        //
85        // We assume that the encryption state is synced because the RoomListService
86        // sets it as required state.
87        if let Some(room) = room.as_deref() {
88            let is_encrypted = room.encryption_state().is_encrypted();
89
90            if is_encrypted {
91                self.textarea.set_placeholder_text("(Send an encrypted message)");
92            } else {
93                self.textarea.set_placeholder_text("(Send an unencrypted message)");
94            }
95        } else {
96            self.textarea.set_placeholder_text("(No room selected)");
97        }
98
99        // Let's first create a block to set the background color.
100        let input_block = Block::new().borders(Borders::NONE).bg(tailwind::BLUE.c400);
101
102        // Now we set the block and we render the textarea.
103        self.textarea.set_block(input_block);
104        self.textarea.render(area, buf);
105    }
106}