use crossterm::event::{KeyCode, KeyEvent};
use indexmap::IndexMap;
use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
use ratatui::symbols::border;
use ratatui::{prelude::*, widgets::*};
use ratatui_macros::line;
use crate::app::app_event::AppEvent;
use crate::app::app_main::App;
use crate::app::models::SyncRoom;
use crate::server::types::RoomId;
use crate::ui::theme::Theme;
use crate::ui::utils::{
BlockDefault, BlockExt, CollapsedBorder, CombinedWidgetState, ScrollbarStateExt, Shortcut,
StringExt,
};
type SyncRooms = IndexMap<RoomId, SyncRoom>;
#[derive(Default)]
pub struct RoomListWidgetState {
pub area: Rect, pub focus: FocusFlag,
pub list_state: ListState,
pub scrollbar_state: ScrollbarState,
pub rooms: SyncRooms,
}
impl RoomListWidgetState {
pub fn get_selected_index(&self) -> Option<usize> {
self.list_state.selected()
}
pub fn get_selected_id(&self) -> Option<&RoomId> {
if let Some(i) = self.get_selected_index() {
let keys: Vec<&RoomId> = self.rooms.keys().collect();
Some(keys[i])
} else {
None
}
}
pub fn get_selected(&self) -> Option<&SyncRoom> {
if let Some(i) = self.get_selected_index() {
let keys: Vec<&RoomId> = self.rooms.keys().collect();
self.rooms.get(keys[i])
} else {
None
}
}
}
impl HasFocus for RoomListWidgetState {
fn area(&self) -> Rect {
self.area
}
fn build(&self, builder: &mut FocusBuilder) {
builder.leaf_widget(self);
}
fn focus(&self) -> FocusFlag {
self.focus.clone()
}
}
impl CombinedWidgetState for RoomListWidgetState {
fn get_shortcuts(&self) -> Vec<Shortcut> {
vec![
Shortcut {
description: "First".to_string(),
button: "g".to_string(),
},
Shortcut {
description: "Last".to_string(),
button: "G".to_string(),
},
Shortcut {
description: "None".to_string(),
button: "h".to_string(),
},
Shortcut {
description: "Down".to_string(),
button: "j".to_string(),
},
Shortcut {
description: "Up".to_string(),
button: "k".to_string(),
},
]
}
fn handle_key_events(&mut self, key_event: &KeyEvent) -> color_eyre::Result<AppEvent> {
let result: AppEvent = AppEvent::None;
if key_event.is_release() {
match key_event.code {
KeyCode::Char('g') | KeyCode::Home => {
self.list_state.select_first();
self.scrollbar_state.match_list_state(&self.list_state);
}
KeyCode::Char('G') | KeyCode::End => {
self.list_state.select_last();
self.scrollbar_state.match_list_state(&self.list_state);
}
KeyCode::Char('h') | KeyCode::Left => {
self.list_state.select(None);
}
KeyCode::Char('j') | KeyCode::Down => {
self.list_state.select_next();
self.scrollbar_state.match_list_state(&self.list_state);
}
KeyCode::Char('k') | KeyCode::Up => {
self.list_state.select_previous();
self.scrollbar_state.match_list_state(&self.list_state);
}
_ => {}
}
}
Ok(result)
}
}
struct RoomListWidget<'a> {
theme: &'a Theme,
title: Option<String>,
borders: Borders,
border_set: symbols::border::Set,
}
impl<'a> RoomListWidget<'a> {
fn new(
theme: &'a Theme,
title: Option<String>,
borders: Borders,
border_set: symbols::border::Set,
) -> Self {
Self {
theme,
title,
borders,
border_set,
}
}
}
impl<'a> StatefulWidget for RoomListWidget<'a> {
type State = RoomListWidgetState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
state.area = area;
let mut block = BlockDefault::plain(self.theme)
.borders(self.borders)
.border_set(self.border_set);
if let Some(widget_title) = &self.title {
block = block.title(widget_title.spaced());
}
if state.is_focused() {
block = BlockDefault::focus_style_block(&block);
}
let items: Vec<ListItem> = state
.rooms
.iter()
.map(|(room_id, _room)| ListItem::from(line!(room_id.clone())))
.collect();
let list = List::new(items)
.highlight_symbol("> ")
.highlight_spacing(HighlightSpacing::Always);
let inner = block.inner_with_margin(area, 0, 1);
block.render(area, buf); state
.scrollbar_state
.render_list(list, &mut state.list_state, inner, buf);
}
}
pub fn rooms_widget(app: &mut App, area: Rect, buf: &mut Buffer, builder: &mut FocusBuilder) {
let room_list = RoomListWidget::new(
&app.theme,
Some("List of rooms".to_string()),
CollapsedBorder::all(),
border::PLAIN,
);
room_list.render(area, buf, &mut app.room_list_widget_state);
app.room_list_widget_state.build(builder);
}