multiverse/widgets/room_view/details/
mod.rs
1use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
2use matrix_sdk_ui::room_list_service::Room;
3use ratatui::{prelude::*, widgets::*};
4use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
5use style::palette::tailwind;
6
7use self::{events::EventsView, linked_chunk::LinkedChunkView, read_receipts::ReadReceipts};
8use crate::widgets::recovery::ShouldExit;
9
10mod events;
11mod linked_chunk;
12mod read_receipts;
13
14#[derive(Clone, Copy, Default, Display, FromRepr, EnumIter)]
15enum SelectedTab {
16 #[default]
18 Events,
19
20 ReadReceipts,
22
23 LinkedChunks,
25}
26
27impl SelectedTab {
28 fn previous(self) -> Self {
31 let current_index: usize = self as usize;
32 let previous_index = current_index.saturating_sub(1);
33 Self::from_repr(previous_index).unwrap_or(self)
34 }
35
36 fn next(self) -> Self {
38 let current_index = self as usize;
39 let next_index = current_index.saturating_add(1);
40 Self::from_repr(next_index).unwrap_or(self)
41 }
42
43 fn cycle_next(self) -> Self {
46 let current_index = self as usize;
47 let next_index = current_index.saturating_add(1);
48 Self::from_repr(next_index).unwrap_or_default()
49 }
50
51 fn cycle_prev(self) -> Self {
54 let current_index = self as usize;
55
56 if current_index == 0 {
57 Self::iter().next_back().expect("We should always have a last element in our enum")
58 } else {
59 let previous_index = current_index.saturating_sub(1);
60 Self::from_repr(previous_index).unwrap_or(self)
61 }
62 }
63
64 fn title(self) -> Line<'static> {
66 format!(" {self} ").fg(tailwind::SLATE.c200).bg(self.palette().c900).into()
67 }
68
69 const fn palette(&self) -> tailwind::Palette {
70 match self {
71 Self::Events => tailwind::BLUE,
72 Self::ReadReceipts => tailwind::EMERALD,
73 Self::LinkedChunks => tailwind::INDIGO,
74 }
75 }
76}
77
78impl<'a> StatefulWidget for &'a SelectedTab {
79 type State = Option<&'a Room>;
80
81 fn render(self, area: Rect, buf: &mut Buffer, room: &mut Self::State)
82 where
83 Self: Sized,
84 {
85 match self {
86 SelectedTab::Events => {
87 EventsView::new(room.as_deref()).render(area, buf);
88 }
89 SelectedTab::ReadReceipts => {
90 ReadReceipts::new(room.as_deref()).render(area, buf);
91 }
92 SelectedTab::LinkedChunks => LinkedChunkView::new(room.as_deref()).render(area, buf),
93 }
94 }
95}
96
97#[derive(Default)]
98pub struct RoomDetails {
99 selected_tab: SelectedTab,
100}
101
102impl RoomDetails {
103 pub fn with_events_as_selected() -> Self {
106 Self { selected_tab: SelectedTab::Events }
107 }
108
109 pub fn with_receipts_as_selected() -> Self {
112 Self { selected_tab: SelectedTab::ReadReceipts }
113 }
114
115 pub fn with_chunks_as_selected() -> Self {
118 Self { selected_tab: SelectedTab::LinkedChunks }
119 }
120
121 pub fn handle_key_press(&mut self, event: KeyEvent) -> ShouldExit {
122 use KeyCode::*;
123
124 if event.kind != KeyEventKind::Press {
125 return ShouldExit::No;
126 }
127
128 match event.code {
129 Char('l') | Right => {
130 self.next_tab();
131 ShouldExit::No
132 }
133
134 Tab => {
135 self.cycle_next_tab();
136 ShouldExit::No
137 }
138
139 BackTab => {
140 self.cycle_prev_tab();
141 ShouldExit::No
142 }
143
144 Char('h') | Left => {
145 self.previous_tab();
146 ShouldExit::No
147 }
148
149 Char('q') | Esc => ShouldExit::Yes,
150
151 _ => ShouldExit::No,
152 }
153 }
154
155 fn cycle_next_tab(&mut self) {
156 self.selected_tab = self.selected_tab.cycle_next();
157 }
158
159 fn cycle_prev_tab(&mut self) {
160 self.selected_tab = self.selected_tab.cycle_prev();
161 }
162
163 fn next_tab(&mut self) {
164 self.selected_tab = self.selected_tab.next();
165 }
166
167 fn previous_tab(&mut self) {
168 self.selected_tab = self.selected_tab.previous();
169 }
170}
171
172impl<'a> StatefulWidget for &'a mut RoomDetails {
173 type State = Option<&'a Room>;
174
175 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
176 where
177 Self: Sized,
178 {
179 use Constraint::{Length, Min};
180 let vertical = Layout::vertical([Length(1), Min(0), Length(1)]);
181 let [header_area, inner_area, footer_area] = vertical.areas(area);
182
183 let horizontal = Layout::horizontal([Min(0), Length(20)]);
184 let [tab_title_area, title_area] = horizontal.areas(header_area);
185
186 "Room details".bold().render(title_area, buf);
187
188 Block::bordered()
189 .border_set(symbols::border::PROPORTIONAL_TALL)
190 .padding(Padding::horizontal(1))
191 .border_style(tailwind::BLUE.c700)
192 .render(inner_area, buf);
193
194 let titles = SelectedTab::iter().map(SelectedTab::title);
195 let highlight_style = (Color::default(), self.selected_tab.palette().c700);
196 let selected_tab_index = self.selected_tab as usize;
197
198 let tabs_area = inner_area.inner(Margin::new(1, 1));
199
200 Tabs::new(titles)
201 .highlight_style(highlight_style)
202 .select(selected_tab_index)
203 .padding("", "")
204 .divider(" ")
205 .render(tab_title_area, buf);
206
207 self.selected_tab.render(tabs_area, buf, state);
208
209 Line::raw("◄ ► to change tab | Press q to exit the details screen")
210 .centered()
211 .render(footer_area, buf);
212 }
213}