Reformat code

This commit is contained in:
2025-10-15 21:57:33 +08:00
parent a776e55187
commit d033d8b93f
19 changed files with 206 additions and 194 deletions

View File

@@ -1,16 +1,16 @@
use std::any::{Any};
use crate::event::{AppEvent, EventHandler};
use ratatui::{DefaultTerminal, Frame};
use std::time::Duration;
use color_eyre::Result;
use crossterm::event::{Event, KeyEvent};
use crossterm::event::Event as CrosstermEvent;
use diesel::{Connection, SqliteConnection};
use rat_cursor::HasScreenCursor;
use crate::config::types::ApplicationConfig;
use crate::constants::{APP_CONFIG_DIR, APP_CONIFG_FILE_PATH, APP_DATA_DIR};
use crate::widgets::views::{View};
use crate::event::{AppEvent, EventHandler};
use crate::widgets::views::MainView;
use crate::widgets::views::View;
use color_eyre::Result;
use crossterm::event::Event as CrosstermEvent;
use crossterm::event::{Event, KeyEvent};
use diesel::{Connection, SqliteConnection};
use rat_cursor::HasScreenCursor;
use ratatui::{DefaultTerminal, Frame};
use std::any::Any;
use std::time::Duration;
pub(crate) struct App {
events: EventHandler,
@@ -25,17 +25,21 @@ struct AppState {
impl App {
pub async fn create() -> Self {
let app_conf =
if APP_CONIFG_FILE_PATH.exists() { ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH).unwrap() }
else { ApplicationConfig::new() };
let app_conf = if APP_CONIFG_FILE_PATH.exists() {
ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH).unwrap()
} else {
ApplicationConfig::new()
};
Self::initialize_folders();
let db_conn = Self::establish_db_connection(app_conf.clone());
let state = AppState { view: Some(Box::new(MainView::new(&app_conf))) };
let state = AppState {
view: Some(Box::new(MainView::new(&app_conf))),
};
Self {
events: EventHandler::new(Duration::from_millis(app_conf.basic_config.tick_rate)),
db_connection: db_conn,
app_config: app_conf,
state
state,
}
}
@@ -59,10 +63,11 @@ impl App {
terminal.draw(|frame| self.draw(frame))?;
let event = self.events.next().await?;
self.update(event)?;
if let Some(view) = self.state.view.as_mut() &&
let Some(main_view) = view.downcast_ref::<MainView>() &&
!main_view.is_running() {
break Ok(())
if let Some(view) = self.state.view.as_mut()
&& let Some(main_view) = view.downcast_ref::<MainView>()
&& !main_view.is_running()
{
break Ok(());
}
}
}
@@ -79,7 +84,7 @@ impl App {
}
fn handle_key_event(&mut self, key: &KeyEvent) -> Result<()> {
if let Some(any) = self.state.view .as_mut() {
if let Some(any) = self.state.view.as_mut() {
if let Some(main_view) = any.downcast_mut::<MainView>() {
main_view.handle_key_input(key)?;
}
@@ -99,11 +104,15 @@ impl App {
fn draw(&mut self, frame: &mut Frame) {
if let Some(view) = self.state.view.as_mut() {
if let Some(main_view) = view.downcast_mut::<MainView>() {
frame.render_stateful_widget(MainView::new(&self.app_config), frame.area(), &mut main_view.state);
frame.render_stateful_widget(
MainView::new(&self.app_config),
frame.area(),
&mut main_view.state,
);
if let Some(pos) = main_view.screen_cursor() {
frame.set_cursor_position(pos);
}
}
}
}
}
}

View File

@@ -1,29 +1,25 @@
use std::io::stdout;
use std::path::PathBuf;
use clap::{command, Args, Command, Parser, Subcommand};
use color_eyre::Result;
use crossterm::cursor::{Hide, Show};
use crossterm::execute;
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
use ratatui::crossterm;
use crate::app;
use crate::config::types::ApplicationConfig;
use crate::constants::APP_CONIFG_FILE_PATH;
use clap::{command, Args, Command, Parser, Subcommand};
use color_eyre::Result;
use ratatui::crossterm;
use std::path::PathBuf;
#[derive(Parser, Debug)]
struct FolderAddCommand {
path: String
path: String,
}
#[derive(Parser, Debug)]
enum FolderSubCommand {
Add(FolderAddCommand)
Add(FolderAddCommand),
}
#[derive(Parser, Debug)]
struct FolderCommand {
#[command(subcommand)]
subcommand: FolderSubCommand
subcommand: FolderSubCommand,
}
#[derive(Parser, Debug)]
@@ -35,20 +31,18 @@ enum CliSubCommand {
#[command(version, about)]
pub(crate) struct Cli {
#[command(subcommand)]
subcommand: Option<CliSubCommand>
subcommand: Option<CliSubCommand>,
}
impl Subcommand for Cli {
fn augment_subcommands(cmd: Command) -> Command {
cmd.subcommand(FolderCommand::augment_args(
Command::new("folder"))
).subcommand_required(true)
cmd.subcommand(FolderCommand::augment_args(Command::new("folder")))
.subcommand_required(true)
}
fn augment_subcommands_for_update(cmd: Command) -> Command {
cmd.subcommand(FolderCommand::augment_args
(Command::new("folder"))
).subcommand_required(true)
cmd.subcommand(FolderCommand::augment_args(Command::new("folder")))
.subcommand_required(true)
}
fn has_subcommand(name: &str) -> bool {
@@ -58,15 +52,13 @@ impl Subcommand for Cli {
impl Subcommand for FolderCommand {
fn augment_subcommands(cmd: Command) -> Command {
cmd.subcommand(FolderAddCommand::augment_args(
Command::new("add"))
).subcommand_required(true)
cmd.subcommand(FolderAddCommand::augment_args(Command::new("add")))
.subcommand_required(true)
}
fn augment_subcommands_for_update(cmd: Command) -> Command {
cmd.subcommand(FolderAddCommand::augment_args(
Command::new("add"))
).subcommand_required(true)
cmd.subcommand(FolderAddCommand::augment_args(Command::new("add")))
.subcommand_required(true)
}
fn has_subcommand(name: &str) -> bool {
@@ -77,10 +69,10 @@ impl Subcommand for FolderCommand {
impl Cli {
pub async fn run(&self) -> Result<()> {
if self.subcommand.is_none() {
return self.start_tui().await
return self.start_tui().await;
}
if let Some(sub_command) = &self.subcommand {
return sub_command.handle().await
return sub_command.handle().await;
}
Ok(())
}
@@ -101,7 +93,7 @@ impl Cli {
impl CliSubCommand {
pub async fn handle(&self) -> Result<()> {
match self {
CliSubCommand::Folder(cmd) => cmd.subcommand.handle().await
CliSubCommand::Folder(cmd) => cmd.subcommand.handle().await,
}
}
}
@@ -119,7 +111,10 @@ impl FolderAddCommand {
let mut config = ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH.to_path_buf())?;
let path = PathBuf::from(&self.path);
let abs_path = path.canonicalize()?;
config.path_config.dlsite_paths.push(abs_path.to_str().unwrap().to_string());
config
.path_config
.dlsite_paths
.push(abs_path.to_str().unwrap().to_string());
config.write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf())
}
}
}

View File

@@ -1,8 +1,8 @@
use std::path::{PathBuf};
use ini::Ini;
use color_eyre::Result;
use crate::config::types::{ApplicationConfig, BasicConfig, PathConfig};
use crate::constants::{APP_CONIFG_FILE_PATH, APP_DATA_DIR};
use color_eyre::Result;
use ini::Ini;
use std::path::PathBuf;
pub mod types;
@@ -16,26 +16,37 @@ impl ApplicationConfig {
};
let path_conf_section = conf.section(Some("Path")).unwrap();
let path_conf = PathConfig {
dlsite_paths: path_conf_section.get("DLSite").unwrap()
.split(":").map(|s| s.to_string()).collect(),
dlsite_paths: path_conf_section
.get("DLSite")
.unwrap()
.split(":")
.map(|s| s.to_string())
.collect(),
};
Ok(Self {
basic_config: basic_conf,
path_config: path_conf
path_config: path_conf,
})
}
pub fn new() -> Self {
let conf = Self {
basic_config: BasicConfig {
db_path: APP_DATA_DIR.clone().join("games.db").to_str().unwrap().to_string(),
tick_rate: 250
db_path: APP_DATA_DIR
.clone()
.join("games.db")
.to_str()
.unwrap()
.to_string(),
tick_rate: 250,
},
path_config: PathConfig {
dlsite_paths: vec![]
}
dlsite_paths: vec![],
},
};
conf.clone().write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf()).unwrap();
conf.clone()
.write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf())
.unwrap();
conf
}
@@ -44,10 +55,16 @@ impl ApplicationConfig {
conf.with_section(Some("Basic"))
.set("DBPath", self.basic_config.db_path)
.set("TickRate", self.basic_config.tick_rate.to_string());
conf.with_section(Some("Path"))
.set("DLSite", self.path_config.dlsite_paths.into_iter()
.filter(|x| !x.is_empty()).collect::<Vec<String>>().join(":"));
conf.with_section(Some("Path")).set(
"DLSite",
self.path_config
.dlsite_paths
.into_iter()
.filter(|x| !x.is_empty())
.collect::<Vec<String>>()
.join(":"),
);
conf.write_to_file(path)?;
Ok(())
}
}
}

View File

@@ -1,16 +1,16 @@
#[derive(Clone)]
pub(crate) struct ApplicationConfig {
pub(crate) basic_config: BasicConfig,
pub(crate) path_config: PathConfig
pub(crate) path_config: PathConfig,
}
#[derive(Clone)]
pub(crate) struct BasicConfig {
pub(crate) db_path: String,
pub(crate) tick_rate: u64
pub(crate) tick_rate: u64,
}
#[derive(Clone)]
pub(crate) struct PathConfig {
pub(crate) dlsite_paths: Vec<String>
}
pub(crate) dlsite_paths: Vec<String>,
}

View File

@@ -1,16 +1,13 @@
use std::path::PathBuf;
use directories::BaseDirs;
use lazy_static::lazy_static;
use std::path::PathBuf;
const APP_DIR_NAME: &str = "sus_manager";
lazy_static!(
lazy_static! {
static ref BASE_DIRS: BaseDirs = BaseDirs::new().unwrap();
pub static ref APP_CONFIG_DIR: PathBuf = BASE_DIRS.config_dir().to_path_buf()
.join(APP_DIR_NAME);
pub static ref APP_DATA_DIR: PathBuf = BASE_DIRS.data_dir().to_path_buf()
.join(APP_DIR_NAME);
pub static ref APP_CACHE_PATH: PathBuf = BASE_DIRS.cache_dir().to_path_buf()
.join(APP_DIR_NAME);
pub static ref APP_CONIFG_FILE_PATH: PathBuf = APP_CONFIG_DIR.clone()
.join("config.ini");
);
pub static ref APP_CONFIG_DIR: PathBuf =
BASE_DIRS.config_dir().to_path_buf().join(APP_DIR_NAME);
pub static ref APP_DATA_DIR: PathBuf = BASE_DIRS.data_dir().to_path_buf().join(APP_DIR_NAME);
pub static ref APP_CACHE_PATH: PathBuf = BASE_DIRS.cache_dir().to_path_buf().join(APP_DIR_NAME);
pub static ref APP_CONIFG_FILE_PATH: PathBuf = APP_CONFIG_DIR.clone().join("config.ini");
}

View File

@@ -5,6 +5,4 @@ pub(crate) struct DLSiteCrawler {
crawler: Crawler,
}
impl DLSiteCrawler {
}
impl DLSiteCrawler {}

View File

@@ -1,17 +1,17 @@
mod dlsite;
use reqwest::{Client, Url};
use robotstxt::{DefaultMatcher};
use crate::constants::APP_CACHE_PATH;
use color_eyre::Result;
use reqwest::{Client, Url};
use robotstxt::DefaultMatcher;
use scraper::Html;
use crate::constants::{APP_CACHE_PATH};
#[derive(Clone)]
pub(crate) struct Crawler {
id: String,
base_url: Url,
client: Client,
robots_txt: String
robots_txt: String,
}
impl Crawler {
@@ -23,7 +23,11 @@ impl Crawler {
base_url,
};
let mut matcher = DefaultMatcher::default();
let is_access_allowed = matcher.one_agent_allowed_by_robots(&crawler.robots_txt, "reqwest", crawler.base_url.as_str());
let is_access_allowed = matcher.one_agent_allowed_by_robots(
&crawler.robots_txt,
"reqwest",
crawler.base_url.as_str(),
);
if !is_access_allowed {
panic!("Crawler cannot access site {}", crawler.base_url.as_str());
}
@@ -31,20 +35,22 @@ impl Crawler {
}
async fn get_robots_txt(id: &str, base_url: &Url) -> Result<String> {
let local_robots_path = APP_CACHE_PATH.clone()
.join(id).join("robots.txt");
let local_robots_path = APP_CACHE_PATH.clone().join(id).join("robots.txt");
if !local_robots_path.exists() {
let mut robots_url = base_url.clone();
robots_url.set_path("/robots.txt");
let response = reqwest::get(robots_url).await
.expect(format!("Failed to get robots.txt in `{}/robots.txt`", base_url.as_str()).as_str());
let response = reqwest::get(robots_url).await.expect(
format!(
"Failed to get robots.txt in `{}/robots.txt`",
base_url.as_str()
)
.as_str(),
);
let content = response.text().await?;
tokio::fs::create_dir_all(local_robots_path.parent().unwrap()).await?;
tokio::fs::write(&local_robots_path, &content).await?;
Ok(content)
}
else {
} else {
Ok(tokio::fs::read_to_string(&local_robots_path).await?)
}
}
@@ -55,4 +61,4 @@ impl Crawler {
let html_text = &self.client.get(url).send().await?.text().await?;
Ok(Html::parse_document(html_text))
}
}
}

View File

@@ -1,8 +1,8 @@
use futures::FutureExt;
use color_eyre::eyre::{Result, eyre};
use std::time::Duration;
use color_eyre::eyre::{eyre, Result};
use crossterm::event::EventStream;
use futures::FutureExt;
use futures::StreamExt;
use std::time::Duration;
use tokio::sync::mpsc::UnboundedSender;
use tokio::task::JoinHandle;
@@ -16,7 +16,7 @@ pub(crate) enum AppEvent {
pub(crate) struct EventHandler {
_tx: UnboundedSender<AppEvent>,
rx: tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
pub task: JoinHandle<()>
pub task: JoinHandle<()>,
}
impl EventHandler {
@@ -45,9 +45,7 @@ impl EventHandler {
}
}
});
Self {
_tx, rx, task
}
Self { _tx, rx, task }
}
pub(crate) async fn next(&mut self) -> Result<AppEvent> {

View File

@@ -0,0 +1 @@

View File

@@ -1,18 +1,18 @@
mod app;
mod cli;
mod config;
mod constants;
mod crawler;
mod event;
mod helpers;
mod schema;
mod types;
mod config;
mod helpers;
mod crawler;
mod constants;
mod cli;
mod widgets;
use clap::{Parser};
use crate::cli::Cli;
use clap::Parser;
use color_eyre::Result;
use tokio;
use crate::cli::Cli;
#[tokio::main]
async fn main() -> Result<()> {

View File

@@ -10,5 +10,5 @@ pub(crate) struct GameList<T> {
#[diesel(table_name = crate::schema::dl_games)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub(crate) struct DLSiteGame {
serial_number: String
serial_number: String,
}

View File

@@ -1 +1 @@
pub mod game;
pub mod game;

View File

@@ -1,2 +1,2 @@
mod textarea;
pub use textarea::*;
pub use textarea::*;

View File

@@ -1,60 +1,53 @@
use crossterm::event::{Event};
use color_eyre::Result;
use crossterm::event::Event;
use rat_cursor::HasScreenCursor;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::prelude::StatefulWidget;
use ratatui::style::{Color, Stylize};
use ratatui::text::Text;
use ratatui::widgets::{Block, Borders, Paragraph, Widget};
use tui_input::backend::crossterm::{EventHandler};
use tui_input::{Input};
use color_eyre::Result;
use rat_cursor::HasScreenCursor;
use ratatui::style::{Color, Stylize};
use tui_input::backend::crossterm::EventHandler;
use tui_input::Input;
#[derive(Clone)]
pub struct TextArea {
input: Input,
title: String,
style: TextAreaStyle,
area: Option<Rect>,
input_area: Option<Rect>,
pub active: bool,
}
#[derive(Clone)]
pub enum TextAreaStyle {
Block, SingleLine
Block,
SingleLine,
}
impl StatefulWidget for TextArea {
type State = TextArea;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
where
Self: Sized
Self: Sized,
{
let input_value = state.input.value().to_string();
let title = self.title.clone();
if matches!(self.style, TextAreaStyle::Block) {
let block = Block::default()
.borders(Borders::ALL)
.title(title);
let paragraph = Paragraph::new(Text::from(input_value))
.block(block);
let block = Block::default().borders(Borders::ALL).title(title);
let paragraph = Paragraph::new(Text::from(input_value)).block(block);
state.input_area = Some(area);
paragraph.render(area, buf);
}
else if matches!(self.style, TextAreaStyle::SingleLine) {
} else if matches!(self.style, TextAreaStyle::SingleLine) {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(10),
Constraint::Fill(0),
])
.constraints([Constraint::Percentage(10), Constraint::Fill(0)])
.split(area);
let text = Text::from(self.title);
let line = Paragraph::new(text);
let input_text = Text::from(input_value)
.fg(Color::White);
let paragraph = Paragraph::new(input_text)
.bg(Color::Green);
state.area = Some(chunks[1]);
let input_text = Text::from(input_value).fg(Color::White);
let paragraph = Paragraph::new(input_text).bg(Color::Green);
state.input_area = Some(chunks[1]);
line.render(chunks[0], buf);
paragraph.render(chunks[1], buf);
}
@@ -63,10 +56,10 @@ impl StatefulWidget for TextArea {
impl HasScreenCursor for TextArea {
fn screen_cursor(&self) -> Option<(u16, u16)> {
if self.area.is_none() {
if self.input_area.is_none() {
return None;
}
let area = self.area.unwrap();
let area = self.input_area.unwrap();
let width = area.width.max(3) - 3;
let scroll = self.input.visual_scroll(width as usize);
let x = (self.input.visual_cursor().max(scroll) - scroll) as u16;
@@ -81,12 +74,12 @@ impl TextArea {
title: title.to_string(),
active: false,
style: style.unwrap_or(TextAreaStyle::SingleLine),
area: None,
input_area: None,
}
}
pub fn handle_input(&mut self, event: &Event) -> Result<()> {
let state_result = self.input.handle_event(event);
let _ = self.input.handle_event(event);
Ok(())
}
}
}

View File

@@ -1,3 +1,3 @@
pub mod components;
pub mod popups;
pub mod views;
pub mod views;

View File

@@ -1,21 +1,19 @@
use crate::widgets::components::TextArea;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Direction, Layout, Margin, Rect};
use ratatui::prelude::{StatefulWidget, Widget};
use ratatui::widgets::{Block, Borders};
use crate::widgets::components::TextArea;
#[derive(Clone)]
pub struct AddFolderPopup {
pub textarea: TextArea
pub textarea: TextArea,
}
impl AddFolderPopup {
pub fn new() -> Self {
let mut textarea = TextArea::new("Folder Path", "", None);
textarea.active = true;
Self {
textarea
}
Self { textarea }
}
}
@@ -23,7 +21,7 @@ impl StatefulWidget for AddFolderPopup {
type State = AddFolderPopup;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
where
Self: Sized
Self: Sized,
{
let popup_area = Rect {
x: area.width / 4,
@@ -37,10 +35,8 @@ impl StatefulWidget for AddFolderPopup {
block.render(popup_area, buf);
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![
Constraint::Length(1)
])
.constraints(vec![Constraint::Length(1)])
.split(popup_area.inner(Margin::new(1, 1)));
self.textarea.render(chunks[0], buf, &mut state.textarea);
}
}
}

View File

@@ -1 +1 @@
pub mod folder;
pub mod folder;

View File

@@ -1,16 +1,15 @@
use std::any::Any;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
use crossterm::event::KeyCode::Char;
use rat_cursor::HasScreenCursor;
use ratatui::buffer::Buffer;
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::prelude::{Color, Line, Span, Style, Text, Widget};
use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget};
use crate::config::types::ApplicationConfig;
use crate::constants::APP_CONIFG_FILE_PATH;
use crate::widgets::popups::folder::AddFolderPopup;
use crate::widgets::views::{View};
use crate::widgets::views::View;
use crossterm::event::KeyCode::Char;
use crossterm::event::{Event, KeyCode, KeyEvent, 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::widgets::{Block, Borders, Paragraph, StatefulWidget};
use std::any::Any;
pub struct MainView {
app_config: ApplicationConfig,
@@ -27,7 +26,7 @@ pub struct MainViewState {
enum Status {
Running,
Exiting,
Popup
Popup,
}
impl MainView {
@@ -35,9 +34,9 @@ impl MainView {
Self {
state: MainViewState {
popup: None,
status: Status::Running
status: Status::Running,
},
app_config: app_conf.clone()
app_config: app_conf.clone(),
}
}
@@ -59,8 +58,9 @@ impl MainView {
impl View for MainView {
fn handle_input(&mut self, event: &Event) -> color_eyre::Result<()> {
if let Some(any) = self.state.popup.as_mut() &&
let Some(popup) = any.downcast_mut::<AddFolderPopup>(){
if let Some(any) = self.state.popup.as_mut()
&& let Some(popup) = any.downcast_mut::<AddFolderPopup>()
{
popup.textarea.handle_input(event)?;
}
Ok(())
@@ -90,7 +90,7 @@ impl StatefulWidget for MainView {
type State = MainViewState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
where
Self: Sized
Self: Sized,
{
let chunks = Layout::default()
.direction(Direction::Vertical)
@@ -101,12 +101,13 @@ impl StatefulWidget for MainView {
])
.split(area);
Self::render_header(state,chunks[0], buf);
Self::render_game_list(state,chunks[1], buf);
Self::render_footer(state,chunks[2], buf);
Self::render_header(chunks[0], buf);
Self::render_game_list(chunks[1], buf);
Self::render_footer(state, chunks[2], buf);
if let Some(boxed) = state.popup.as_mut() &&
let Some(popup) = boxed.downcast_mut::<AddFolderPopup>() {
if let Some(boxed) = state.popup.as_mut()
&& let Some(popup) = boxed.downcast_mut::<AddFolderPopup>()
{
popup.clone().render(area, buf, popup);
}
}
@@ -114,16 +115,17 @@ impl StatefulWidget for MainView {
impl HasScreenCursor for MainView {
fn screen_cursor(&self) -> Option<(u16, u16)> {
if let Some(popup) = &self.state.popup &&
let Some(add_folder) = popup.downcast_ref::<AddFolderPopup>() {
return add_folder.textarea.screen_cursor()
if let Some(popup) = &self.state.popup
&& let Some(add_folder) = popup.downcast_ref::<AddFolderPopup>()
{
return add_folder.textarea.screen_cursor();
}
None
}
}
impl MainView {
fn render_game_list(state: &mut MainViewState, area: Rect, buf: &mut Buffer) {
fn render_game_list(area: Rect, buf: &mut Buffer) {
let game_list = Block::new()
.title(Line::raw("Games"))
.borders(Borders::ALL)
@@ -131,20 +133,19 @@ impl MainView {
game_list.render(area, buf);
}
fn render_header(state: &mut MainViewState, area: Rect, buf: &mut Buffer) {
let title = Paragraph::new(
Text::styled(
"SuS Manager",
Style::default().fg(Color::Green),
)
);
fn render_header(area: Rect, buf: &mut Buffer) {
let title = Paragraph::new(Text::styled(
"SuS Manager",
Style::default().fg(Color::Green),
));
title.render(area, buf);
}
fn render_footer(state: &mut MainViewState, area: Rect, buf: &mut Buffer) {
let mut navigation_text = vec![
Span::styled("(q) quit / (a) add folders", Style::default().fg(Color::Green)),
];
let mut navigation_text = vec![Span::styled(
"(q) quit / (a) add folders",
Style::default().fg(Color::Green),
)];
if matches!(state.status, Status::Popup) {
navigation_text[0] = Span::styled("(Esc) close", Style::default().fg(Color::Green));
}
@@ -152,4 +153,4 @@ impl MainView {
let footer = Paragraph::new(line);
footer.render(area, buf);
}
}
}

View File

@@ -2,9 +2,10 @@ mod main_view;
use crossterm::event::{Event, KeyEvent};
pub use main_view::MainView;
use std::any::Any;
pub trait View {
pub trait View: Any + 'static {
fn handle_input(&mut self, event: &Event) -> color_eyre::Result<()>;
fn handle_key_input(&mut self, key: &KeyEvent) -> color_eyre::Result<()>;
fn is_running(&self) -> bool;
}
}