Add info boxes contents
This commit is contained in:
82
ui/src/widgets/components/game_info_box.rs
Executable file
82
ui/src/widgets/components/game_info_box.rs
Executable file
@@ -0,0 +1,82 @@
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Line;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget, Widget};
|
||||
use db::RocksDBFactory;
|
||||
use models::config::ApplicationConfig;
|
||||
use models::db::RocksReferences;
|
||||
use models::dlsite::{DLSiteGenre, DLSiteManiax, DLSiteTranslation};
|
||||
use crate::widgets::components::Component;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GameInfoBox {
|
||||
pub state: GameInfoBoxState
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GameInfoBoxState {
|
||||
game_title: String,
|
||||
genres: Vec<String>,
|
||||
active: bool
|
||||
}
|
||||
|
||||
impl Component for GameInfoBoxState {
|
||||
fn set_active(&mut self, active: bool) {
|
||||
self.active = active;
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DLSiteManiax> for GameInfoBoxState {
|
||||
type Error = color_eyre::Report;
|
||||
|
||||
fn try_from(value: DLSiteManiax) -> Result<Self, Self::Error> {
|
||||
let db = RocksDBFactory::default().get_current_context()?;
|
||||
let locale = ApplicationConfig::get_config()?.basic_config.locale;
|
||||
let game_title = value.name.get_translation(locale.clone())?;
|
||||
let game_genres =
|
||||
db.get_reference_values::<DLSiteGenre, DLSiteManiax>(&value.get_reference_ids())?
|
||||
.iter()
|
||||
.map(|v| v.name.get_translation(locale.clone()))
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Self { game_title, genres: game_genres, active: false })
|
||||
}
|
||||
}
|
||||
|
||||
impl GameInfoBox {
|
||||
pub fn new(maniax: DLSiteManiax) -> color_eyre::Result<Self> {
|
||||
Ok(
|
||||
Self {
|
||||
state: GameInfoBoxState::try_from(maniax)?
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_info(&mut self, maniax: &DLSiteManiax) -> color_eyre::Result<()> {
|
||||
self.state = GameInfoBoxState::try_from(maniax.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidget for GameInfoBox {
|
||||
type State = GameInfoBoxState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let block = Block::new()
|
||||
.title(Line::raw("Info"))
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default());
|
||||
let title_text = Line::styled(
|
||||
state.game_title.clone(),
|
||||
Style::default().fg(ratatui::style::Color::Yellow)
|
||||
);
|
||||
let text = Paragraph::new(
|
||||
vec![
|
||||
title_text,
|
||||
Line::raw(state.genres.join(", "))
|
||||
]
|
||||
).block(block.clone());
|
||||
text.render(area, buf);;
|
||||
}
|
||||
}
|
||||
108
ui/src/widgets/components/game_list.rs
Executable file
108
ui/src/widgets/components/game_list.rs
Executable file
@@ -0,0 +1,108 @@
|
||||
use std::marker::PhantomData;
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::palette::tailwind::SLATE;
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line};
|
||||
use ratatui::widgets::{Block, Borders, HighlightSpacing, List, ListItem, ListState, StatefulWidget};
|
||||
use serde::de::DeserializeOwned;
|
||||
use models::db::RocksColumn;
|
||||
use crate::widgets::components::Component;
|
||||
|
||||
const SELECTED_STYLE: Style = Style::new()
|
||||
.bg(SLATE.c800)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GameList<'a, T> where T: Into<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone {
|
||||
pub state: GameListState<'a, T>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GameListState<'a, T> where T: Into<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone {
|
||||
games: Vec<T>,
|
||||
list_state: ListState,
|
||||
list_page_size: usize,
|
||||
active: bool,
|
||||
_phantom: PhantomData<&'a ()>
|
||||
}
|
||||
|
||||
impl<'a, T> Component for GameList<'a, T> where T: Into<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone {
|
||||
fn set_active(&mut self, active: bool) {
|
||||
self.state.active = active;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Default for GameList<'a, T> where T: Into<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone {
|
||||
fn default() -> Self {
|
||||
Self::new(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> StatefulWidget for GameList<'a, T>
|
||||
where
|
||||
T: Into<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone
|
||||
{
|
||||
type State = GameListState<'a, T>;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let list_block = Block::new()
|
||||
.title(Line::raw("Games"))
|
||||
.borders(Borders::ALL);
|
||||
let list_block =
|
||||
if state.active { list_block.style(Style::default().fg(Color::Yellow)) }
|
||||
else { list_block.style(Style::default()) };
|
||||
let game_list = List::new(state.games.clone())
|
||||
.block(list_block)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.highlight_style(SELECTED_STYLE)
|
||||
.highlight_symbol(">")
|
||||
.highlight_spacing(HighlightSpacing::WhenSelected);
|
||||
state.list_page_size = (area.height - 2) as usize;
|
||||
game_list.render(area, buf, &mut state.list_state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> GameList<'a, T>
|
||||
where
|
||||
T: Into<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone
|
||||
{
|
||||
pub fn new(games: Vec<T>) -> Self {
|
||||
let mut state = ListState::default();
|
||||
state.select_first();
|
||||
let game_list = GameList {
|
||||
state: GameListState::<T> {
|
||||
games,
|
||||
list_state: state,
|
||||
_phantom: PhantomData,
|
||||
list_page_size: 0,
|
||||
active: false
|
||||
},
|
||||
};
|
||||
game_list
|
||||
}
|
||||
|
||||
pub fn handle_game_list_key(&mut self, event: &KeyEvent) -> Result<()> {
|
||||
let mut game_list_state = self.state.list_state.clone();
|
||||
match event.code {
|
||||
KeyCode::Down => game_list_state.select_next(),
|
||||
KeyCode::Up => game_list_state.select_previous(),
|
||||
KeyCode::PageUp =>game_list_state.scroll_up_by(self.state.list_page_size as u16),
|
||||
KeyCode::PageDown => game_list_state.scroll_down_by(self.state.list_page_size as u16),
|
||||
KeyCode::Home => game_list_state.select_first(),
|
||||
KeyCode::End => game_list_state.select_last(),
|
||||
_ => {}
|
||||
}
|
||||
self.state.list_state = game_list_state;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_selected_value(&self) -> Option<&T> {
|
||||
let Some(index) = self.state.list_state.selected() else {
|
||||
return None;
|
||||
};
|
||||
self.state.games.get(index.clamp(0, self.state.games.len() - 1))
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,11 @@
|
||||
mod textarea;
|
||||
mod game_list;
|
||||
mod game_info_box;
|
||||
|
||||
pub use textarea::*;
|
||||
pub use game_list::*;
|
||||
pub use game_info_box::*;
|
||||
|
||||
pub trait Component {
|
||||
fn set_active(&mut self, active: bool);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ impl AddFolderPopup {
|
||||
let mut textarea = TextArea::new(
|
||||
"Folder Path",
|
||||
"",
|
||||
|x| {
|
||||
let path = Path::new(x);
|
||||
|v| {
|
||||
let path = Path::new(v);
|
||||
path.exists() && path.is_dir()
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use crate::widgets::popups::folder::AddFolderPopup;
|
||||
use crossterm::event::KeyCode::Char;
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
||||
use rat_cursor::HasScreenCursor;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use ratatui::prelude::{Color, Line, Span, Style, Text, Widget};
|
||||
use ratatui::style::Modifier;
|
||||
use ratatui::style::palette::tailwind::SLATE;
|
||||
use ratatui::widgets::{Block, Borders, HighlightSpacing, List, Paragraph, StatefulWidget};
|
||||
use db::RocksDBFactory;
|
||||
use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget};
|
||||
use db::{RocksDBFactory};
|
||||
use models::config::ApplicationConfig;
|
||||
use models::dlsite::DLSiteManiax;
|
||||
use crate::models::GameList;
|
||||
use models::dlsite::{DLSiteManiax};
|
||||
use crate::widgets::components::{Component, GameInfoBox, GameList};
|
||||
use crate::widgets::popups::AppPopup;
|
||||
use crate::widgets::views::View;
|
||||
|
||||
@@ -24,8 +22,8 @@ pub struct MainView {
|
||||
#[derive(Clone)]
|
||||
pub struct MainViewState {
|
||||
status: Status,
|
||||
dl_game_list: GameList<DLSiteManiax>,
|
||||
list_page_size: usize,
|
||||
dl_game_list: GameList<'static, DLSiteManiax>,
|
||||
game_info_box: GameInfoBox
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -37,8 +35,11 @@ enum Status {
|
||||
|
||||
impl MainView {
|
||||
pub fn new(mut db_factory: RocksDBFactory) -> color_eyre::Result<Self> {
|
||||
let db = db_factory.get_current_context()?;
|
||||
let mut games = db.get_all_values::<DLSiteManiax>()?;
|
||||
let mut games = {
|
||||
let db = db_factory.get_current_context()?;
|
||||
let values = db.get_all_values::<DLSiteManiax>()?;
|
||||
values
|
||||
};
|
||||
games.sort_by(|a, b| {
|
||||
let left = a.rj_num
|
||||
.chars().skip(2)
|
||||
@@ -52,12 +53,14 @@ impl MainView {
|
||||
.unwrap();
|
||||
left.cmp(&right)
|
||||
});
|
||||
let dl_game_list = GameList::new(games)?;
|
||||
let first_game = games[0].clone();
|
||||
let mut dl_game_list = GameList::new(games);
|
||||
dl_game_list.set_active(true);
|
||||
let view = Self {
|
||||
state: MainViewState {
|
||||
status: Status::Running,
|
||||
list_page_size: 0,
|
||||
dl_game_list,
|
||||
game_info_box: GameInfoBox::new(first_game)?
|
||||
},
|
||||
db_factory
|
||||
};
|
||||
@@ -99,29 +102,6 @@ impl MainViewState {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_game_list_key(&mut self, event: &KeyEvent) -> color_eyre::Result<()> {
|
||||
let game_list_state = &mut self.dl_game_list.state;
|
||||
let game_list_len = self.dl_game_list.games.len();
|
||||
let selected_value = game_list_state.selected().unwrap_or(0);
|
||||
match event.code {
|
||||
KeyCode::Down => game_list_state.select_next(),
|
||||
KeyCode::Up => game_list_state.select_previous(),
|
||||
KeyCode::PageUp => {
|
||||
let selected_index =
|
||||
if selected_value < self.list_page_size { 0 }
|
||||
else { selected_value - self.list_page_size };
|
||||
game_list_state.select(Some(selected_index))
|
||||
},
|
||||
KeyCode::PageDown => {
|
||||
game_list_state.select(Some((selected_value + self.list_page_size).clamp(0, game_list_len)))
|
||||
},
|
||||
KeyCode::Home => game_list_state.select_first(),
|
||||
KeyCode::End => game_list_state.select_last(),
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl View for MainView {
|
||||
@@ -143,7 +123,13 @@ impl View for MainView {
|
||||
Char('a') => state.folder_popup(),
|
||||
_ => {}
|
||||
}
|
||||
state.handle_game_list_key(key_event)?;
|
||||
state.dl_game_list.handle_game_list_key(key_event)?;
|
||||
match state.dl_game_list.get_selected_value() {
|
||||
Some(value) => {
|
||||
state.game_info_box.set_info(value)?;
|
||||
}
|
||||
None => println!("No game selected")
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -169,7 +155,6 @@ impl StatefulWidget for MainView {
|
||||
])
|
||||
.split(area);
|
||||
|
||||
state.list_page_size = chunks[1].height as usize;
|
||||
Self::render_header(chunks[0], buf);
|
||||
Self::render_game_info(chunks[1], buf, state);
|
||||
Self::render_footer(state, chunks[2], buf);
|
||||
@@ -199,9 +184,6 @@ impl HasScreenCursor for MainView {
|
||||
}
|
||||
|
||||
impl MainView {
|
||||
const SELECTED_STYLE: Style = Style::new()
|
||||
.bg(SLATE.c800)
|
||||
.add_modifier(Modifier::BOLD);
|
||||
fn render_game_info(area: Rect, buf: &mut Buffer, state: &mut MainViewState) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
@@ -210,29 +192,8 @@ impl MainView {
|
||||
Constraint::Fill(0),
|
||||
])
|
||||
.split(area);
|
||||
Self::render_game_list(chunks[0], buf, state);
|
||||
Self::render_game_box(chunks[1], buf, state);
|
||||
}
|
||||
|
||||
fn render_game_list(area: Rect, buf: &mut Buffer, state: &mut MainViewState) {
|
||||
let list_block = Block::new()
|
||||
.title(Line::raw("Games"))
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default());
|
||||
let game_list = List::new(&state.dl_game_list.games)
|
||||
.block(list_block)
|
||||
.highlight_style(Self::SELECTED_STYLE)
|
||||
.highlight_symbol(">")
|
||||
.highlight_spacing(HighlightSpacing::WhenSelected);
|
||||
StatefulWidget::render(game_list, area, buf, &mut state.dl_game_list.state);
|
||||
}
|
||||
|
||||
fn render_game_box(area: Rect, buf: &mut Buffer, state: &mut MainViewState) {
|
||||
let block = Block::new()
|
||||
.title(Line::raw("Info"))
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default());
|
||||
block.render(area, buf);
|
||||
state.dl_game_list.clone().render(chunks[0], buf, &mut state.dl_game_list.state);
|
||||
state.game_info_box.clone().render(chunks[1], buf, &mut state.game_info_box.state);
|
||||
}
|
||||
|
||||
fn render_header(area: Rect, buf: &mut Buffer) {
|
||||
|
||||
Reference in New Issue
Block a user