multiverse/widgets/room_view/details/
mod.rs1use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
2use ratatui::{prelude::*, widgets::*};
3use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
4use style::palette::tailwind;
5
6use self::{events::EventsView, linked_chunk::LinkedChunkView, read_receipts::ReadReceipts};
7use super::DetailsState;
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 = DetailsState<'a>;
80
81 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
82 where
83 Self: Sized,
84 {
85 match self {
86 SelectedTab::Events => {
87 EventsView::new(state.selected_room).render(area, buf);
88 }
89 SelectedTab::ReadReceipts => {
90 ReadReceipts::new(state).render(area, buf);
91 }
92 SelectedTab::LinkedChunks => {
93 LinkedChunkView::new(state.selected_room).render(area, buf)
94 }
95 }
96 }
97}
98
99#[derive(Default)]
100pub struct RoomDetails {
101 selected_tab: SelectedTab,
102}
103
104impl RoomDetails {
105 pub fn with_events_as_selected() -> Self {
108 Self { selected_tab: SelectedTab::Events }
109 }
110
111 pub fn with_receipts_as_selected() -> Self {
114 Self { selected_tab: SelectedTab::ReadReceipts }
115 }
116
117 pub fn with_chunks_as_selected() -> Self {
120 Self { selected_tab: SelectedTab::LinkedChunks }
121 }
122
123 pub fn handle_key_press(&mut self, event: KeyEvent) -> ShouldExit {
124 use KeyCode::*;
125
126 if event.kind != KeyEventKind::Press {
127 return ShouldExit::No;
128 }
129
130 match event.code {
131 Char('l') | Right => {
132 self.next_tab();
133 ShouldExit::No
134 }
135
136 Tab => {
137 self.cycle_next_tab();
138 ShouldExit::No
139 }
140
141 BackTab => {
142 self.cycle_prev_tab();
143 ShouldExit::No
144 }
145
146 Char('h') | Left => {
147 self.previous_tab();
148 ShouldExit::No
149 }
150
151 Char('q') | Esc => ShouldExit::Yes,
152
153 _ => ShouldExit::No,
154 }
155 }
156
157 fn cycle_next_tab(&mut self) {
158 self.selected_tab = self.selected_tab.cycle_next();
159 }
160
161 fn cycle_prev_tab(&mut self) {
162 self.selected_tab = self.selected_tab.cycle_prev();
163 }
164
165 fn next_tab(&mut self) {
166 self.selected_tab = self.selected_tab.next();
167 }
168
169 fn previous_tab(&mut self) {
170 self.selected_tab = self.selected_tab.previous();
171 }
172}
173
174impl<'a> StatefulWidget for &'a mut RoomDetails {
175 type State = DetailsState<'a>;
176
177 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
178 where
179 Self: Sized,
180 {
181 use Constraint::{Length, Min};
182 let vertical = Layout::vertical([Length(1), Min(0), Length(1)]);
183 let [header_area, inner_area, footer_area] = vertical.areas(area);
184
185 let horizontal = Layout::horizontal([Min(0), Length(20)]);
186 let [tab_title_area, title_area] = horizontal.areas(header_area);
187
188 "Room details".bold().render(title_area, buf);
189
190 Block::bordered()
191 .border_set(symbols::border::PROPORTIONAL_TALL)
192 .padding(Padding::horizontal(1))
193 .border_style(tailwind::BLUE.c700)
194 .render(inner_area, buf);
195
196 let titles = SelectedTab::iter().map(SelectedTab::title);
197 let highlight_style = (Color::default(), self.selected_tab.palette().c700);
198 let selected_tab_index = self.selected_tab as usize;
199
200 let tabs_area = inner_area.inner(Margin::new(1, 1));
201
202 Tabs::new(titles)
203 .highlight_style(highlight_style)
204 .select(selected_tab_index)
205 .padding("", "")
206 .divider(" ")
207 .render(tab_title_area, buf);
208
209 self.selected_tab.render(tabs_area, buf, state);
210
211 Line::raw("◄ ► to change tab | Press q to exit the details screen")
212 .centered()
213 .render(footer_area, buf);
214 }
215}