diff --git a/src/config.rs b/src/config.rs index dca8216..1dc090b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -79,6 +79,7 @@ impl Config { // Load user-defined themes if let Some((i, _, theme)) = ctx.themes.get_full(&self.theme) { w.theme.selected = i; + w.theme.table.select(i); ctx.theme = theme.clone(); } diff --git a/src/source.rs b/src/source.rs index cb9513a..dc72851 100644 --- a/src/source.rs +++ b/src/source.rs @@ -42,13 +42,22 @@ pub struct SourceInfo { } impl SourceInfo { - pub fn entry_from_cfg(self, s: &str) -> CatEntry { + pub fn get_major_minor(&self, id: usize) -> (usize, usize) { + for (major, cat) in self.cats.iter().enumerate() { + if let Some((minor, _)) = cat.entries.iter().enumerate().find(|(_, ent)| ent.id == id) { + return (major, minor); + } + } + (0, 0) + } + pub fn entry_from_cfg(&self, s: &str) -> CatEntry { for cat in self.cats.iter() { if let Some(ent) = cat.entries.iter().find(|ent| ent.cfg == s) { return ent.clone(); } } self.cats[0].entries[0].clone() + // self.cats[0].entries[0].clone() } pub fn entry_from_str(self, s: &str) -> CatEntry { @@ -215,13 +224,16 @@ impl Sources { pub fn apply(self, ctx: &mut Context, w: &mut Widgets) { ctx.src_info = self.info(); w.category.selected = self.default_category(&ctx.config.sources); - w.category.major = 0; - w.category.minor = 0; - w.category.table.select(1); + let (major, minor) = ctx.src_info.get_major_minor(w.category.selected); + w.category.table.select(major + minor + 1); + w.category.major = major; + w.category.minor = minor; w.sort.selected.sort = self.default_sort(&ctx.config.sources); + w.sort.table.select(w.sort.selected.sort); w.filter.selected = self.default_filter(&ctx.config.sources); + w.filter.table.select(w.filter.selected); // Go back to first page when changing source ctx.page = 1; diff --git a/src/source/nyaa_html.rs b/src/source/nyaa_html.rs index a908899..79bbdd9 100644 --- a/src/source/nyaa_html.rs +++ b/src/source/nyaa_html.rs @@ -1,4 +1,4 @@ -use std::{cmp::max, error::Error}; +use std::{cmp::max, error::Error, time::Duration}; use chrono::{DateTime, Local, NaiveDateTime, TimeZone}; use ratatui::{ @@ -34,6 +34,7 @@ pub struct NyaaConfig { pub default_category: String, pub default_search: String, pub rss: bool, + pub timeout: Option, pub columns: Option, } @@ -71,6 +72,7 @@ impl Default for NyaaConfig { default_category: "AllCategories".to_owned(), default_search: Default::default(), rss: false, + timeout: None, columns: None, } } @@ -211,7 +213,11 @@ impl Source for NyaaHtmlSource { ))); // let client = super::request_client(ctx)?; - let response = client.get(url_query.to_owned()).send().await?; + let mut request = client.get(url_query.to_owned()); + if let Some(timeout) = nyaa.timeout { + request = request.timeout(Duration::from_secs(timeout)); + } + let response = request.send().await?; if response.status() != StatusCode::OK { // Throw error if response code is not OK let code = response.status().as_u16(); diff --git a/src/source/nyaa_rss.rs b/src/source/nyaa_rss.rs index 2782012..ba2f27e 100644 --- a/src/source/nyaa_rss.rs +++ b/src/source/nyaa_rss.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, collections::BTreeMap, error::Error, str::FromStr}; +use std::{cmp::Ordering, collections::BTreeMap, error::Error, str::FromStr, time::Duration}; use chrono::{DateTime, Local}; use reqwest::{StatusCode, Url}; @@ -81,7 +81,11 @@ pub async fn search_rss( // let client = super::request_client(ctx)?; - let response = client.get(url.to_owned()).send().await?; + let mut request = client.get(url.to_owned()); + if let Some(timeout) = nyaa.timeout { + request = request.timeout(Duration::from_secs(timeout)); + } + let response = request.send().await?; let code = response.status().as_u16(); if code != StatusCode::OK { // Throw error if response code is not OK diff --git a/src/source/sukebei_nyaa.rs b/src/source/sukebei_nyaa.rs index a1c03b3..ca0f182 100644 --- a/src/source/sukebei_nyaa.rs +++ b/src/source/sukebei_nyaa.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use std::{error::Error, time::Duration}; use chrono::{DateTime, Local, NaiveDateTime, TimeZone}; use reqwest::{StatusCode, Url}; @@ -33,6 +33,7 @@ pub struct SukebeiNyaaConfig { pub default_filter: NyaaFilter, pub default_category: String, pub default_search: String, + pub timeout: Option, pub columns: Option, } @@ -44,6 +45,7 @@ impl Default for SukebeiNyaaConfig { default_filter: NyaaFilter::NoFilter, default_category: "AllCategories".to_owned(), default_search: Default::default(), + timeout: None, columns: None, } } @@ -102,8 +104,11 @@ impl Source for SubekiHtmlSource { query, high, low, filter, page, sort, dir, user ))); - // let client = super::request_client(ctx)?; - let response = client.get(url_query.to_owned()).send().await?; + let mut request = client.get(url_query.to_owned()); + if let Some(timeout) = sukebei.timeout { + request = request.timeout(Duration::from_secs(timeout)); + } + let response = request.send().await?; if response.status() != StatusCode::OK { // Throw error if response code is not OK let code = response.status().as_u16(); diff --git a/src/source/torrent_galaxy.rs b/src/source/torrent_galaxy.rs index 163fb7b..8888bac 100644 --- a/src/source/torrent_galaxy.rs +++ b/src/source/torrent_galaxy.rs @@ -1,4 +1,4 @@ -use std::{cmp::max, collections::HashMap, error::Error}; +use std::{cmp::max, collections::HashMap, error::Error, time::Duration}; use ratatui::{ layout::{Alignment, Constraint}, @@ -32,6 +32,7 @@ pub struct TgxConfig { pub default_filter: TgxFilter, pub default_category: String, pub default_search: String, + pub timeout: Option, pub columns: Option, } @@ -43,6 +44,7 @@ impl Default for TgxConfig { default_filter: TgxFilter::NoFilter, default_category: "AllCategories".to_owned(), default_search: Default::default(), + timeout: None, columns: None, } } @@ -210,7 +212,11 @@ impl Source for TorrentGalaxyHtmlSource { let mut url = base_url.clone(); url.set_query(Some(&q)); - let response = client.get(url.to_owned()).send().await?; + let mut request = client.get(url.to_owned()); + if let Some(timeout) = tgx.timeout { + request = request.timeout(Duration::from_secs(timeout)); + } + let response = request.send().await?; if response.status() != StatusCode::OK { // Throw error if response code is not OK let code = response.status().as_u16(); diff --git a/src/widget/batch.rs b/src/widget/batch.rs index e557471..2ed2b8a 100644 --- a/src/widget/batch.rs +++ b/src/widget/batch.rs @@ -136,6 +136,9 @@ impl super::Widget for BatchWidget { (Char('a'), &KeyModifiers::CONTROL) => { ctx.mode = Mode::Loading(LoadType::Batching); } + (Char('x'), &KeyModifiers::CONTROL) => { + ctx.batch.clear(); + } _ => {} }; } @@ -145,6 +148,7 @@ impl super::Widget for BatchWidget { Some(vec![ ("Enter", "Download single torrent"), ("Ctrl-A", "Download all torrents"), + ("Ctrl-X", "Clear batch"), ("Esc/Tab/Shift-Tab", "Back to results"), ("q", "Exit app"), ("g/G", "Goto Top/Bottom"), diff --git a/src/widget/category.rs b/src/widget/category.rs index dc70889..7f9a5de 100644 --- a/src/widget/category.rs +++ b/src/widget/category.rs @@ -121,7 +121,6 @@ impl Widget for CategoryPopup { tbl.splice(self.major + 1..self.major + 1, cat_rows); let center = super::centered_rect(33, 14, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); super::clear(center, f.buffer_mut(), ctx.theme.bg); let table = Table::new(tbl, [Constraint::Percentage(100)]) .block(border_block(&ctx.theme, true).title(title!("Category"))) diff --git a/src/widget/clients.rs b/src/widget/clients.rs index 9252031..5177dcc 100644 --- a/src/widget/clients.rs +++ b/src/widget/clients.rs @@ -29,7 +29,6 @@ impl Widget for ClientsPopup { fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { let buf = f.buffer_mut(); let center = super::centered_rect(30, self.table.items.len() as u16 + 2, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); let items = self.table.items.iter().map(|item| { Row::new(vec![match item == &ctx.client { true => format!("  {}", item), diff --git a/src/widget/filter.rs b/src/widget/filter.rs index 0904dec..988ec4d 100644 --- a/src/widget/filter.rs +++ b/src/widget/filter.rs @@ -29,7 +29,6 @@ impl Default for FilterPopup { impl Widget for FilterPopup { fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { let center = super::centered_rect(30, ctx.src_info.filters.len() as u16 + 2, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); let items = ctx.src_info .filters diff --git a/src/widget/help.rs b/src/widget/help.rs index c90db1a..52e42f4 100644 --- a/src/widget/help.rs +++ b/src/widget/help.rs @@ -51,7 +51,6 @@ impl Widget for HelpPopup { let height = min(max_size, self.table.items.len() + 3) as u16; let center = super::centered_rect(key_min + map_min + 6, height, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); let items = self.table.items.iter().map(|(key, map)| { Row::new([ Line::from(key.to_string()).alignment(Alignment::Right), diff --git a/src/widget/notifications.rs b/src/widget/notifications.rs index e902e42..5d49b1c 100644 --- a/src/widget/notifications.rs +++ b/src/widget/notifications.rs @@ -86,7 +86,7 @@ impl Widget for NotificationWidget { for (offset, height) in res.iter() { self.notifs.iter_mut().for_each(|n| { if n.is_error() && n.offset() > *offset { - n.sub_offset(*height); + n.add_offset(-(*height as i32)); } }) } diff --git a/src/widget/notify_box.rs b/src/widget/notify_box.rs index 8a6b428..ca49d30 100644 --- a/src/widget/notify_box.rs +++ b/src/widget/notify_box.rs @@ -74,7 +74,45 @@ impl AnimateState { } } - fn linear( + // fn linear( + // &mut self, + // start_pos: (i32, i32), + // stop_pos: (i32, i32), + // rate: f64, + // deltatime: f64, + // ) -> (i32, i32) { + // if self.time >= 1.0 { + // self.done = true; + // } + // let pos = ( + // ((self.time * (stop_pos.0 - start_pos.0) as f64) + start_pos.0 as f64).round() as i32, + // ((self.time * (stop_pos.1 - start_pos.1) as f64) + start_pos.1 as f64).round() as i32, + // ); + // self.time = 1.0_f64.min(self.time + rate * deltatime); + // pos + // } + + pub fn ease_out( + &mut self, + start_pos: (i32, i32), + stop_pos: (i32, i32), + rate: f64, + deltatime: f64, + ) -> (i32, i32) { + if self.time >= 1.0 { + self.done = true; + } + let pos = ( + ((Self::_ease_out(self.time) * (stop_pos.0 - start_pos.0) as f64) + start_pos.0 as f64) + .round() as i32, + ((Self::_ease_out(self.time) * (stop_pos.1 - start_pos.1) as f64) + start_pos.1 as f64) + .round() as i32, + ); + self.time = 1.0_f64.min(self.time + rate * deltatime); + pos + } + + pub fn ease_in( &mut self, start_pos: (i32, i32), stop_pos: (i32, i32), @@ -85,13 +123,23 @@ impl AnimateState { self.done = true; } let pos = ( - ((self.time * (stop_pos.0 - start_pos.0) as f64) + start_pos.0 as f64).round() as i32, - ((self.time * (stop_pos.1 - start_pos.1) as f64) + start_pos.1 as f64).round() as i32, + ((Self::_ease_in(self.time) * (stop_pos.0 - start_pos.0) as f64) + start_pos.0 as f64) + .round() as i32, + ((Self::_ease_in(self.time) * (stop_pos.1 - start_pos.1) as f64) + start_pos.1 as f64) + .round() as i32, ); self.time = 1.0_f64.min(self.time + rate * deltatime); pos } + fn _ease_out(x: f64) -> f64 { + 1.0 - (1.0 - x).powi(3) + } + + fn _ease_in(x: f64) -> f64 { + x.powi(3) + } + fn is_done(self) -> bool { self.done } @@ -166,19 +214,30 @@ impl NotifyBox { self.error } - pub fn add_offset(&mut self, offset: u16) { + pub fn add_offset + Copy>(&mut self, offset: I) { self.enter_state.reset(); self.start_offset = self.stop_offset + self.height; - self.stop_offset += offset; + self.stop_offset = (self.stop_offset as i32 + Into::::into(offset)).max(0) as u16; } - pub fn sub_offset(&mut self, offset: u16) { - self.stop_offset = (self.stop_offset as i32 - offset as i32).max(0) as u16; - self.start_offset = (self.start_offset as i32 - offset as i32).max(0) as u16; - } + // pub fn sub_offset(&mut self, offset: u16) { + // self.start_offset = self.stop_offset + self.height; + // self.stop_offset = (self.stop_offset as i32 - offset as i32).max(0) as u16; + // + // self.enter_state.reset(); + // } pub fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { + let max_width = match self.error { + true => (area.width / 3).max(MAX_WIDTH), + false => area.width.min(MAX_WIDTH), + } as usize; + let lines = textwrap::wrap(&self.raw_content, max_width); + self.width = lines.iter().fold(0, |acc, x| acc.max(x.len())) as u16 + 2; + self.height = lines.len() as u16 + 2; + self.content = lines.join("\n"); + let pos = self.pos.unwrap_or(self.next_pos(ctx.deltatime, area)); let offset = Offset { x: self.width as i32, @@ -232,13 +291,6 @@ impl NotifyBox { } }; - // let clear = Rect::new( - // (pos.0 - 1).max(0) as u16, - // pos.1.max(0) as u16, - // self.width + 2, - // self.height, - // ) - // .intersection(area); super::clear(rect, f.buffer_mut(), ctx.theme.bg); Paragraph::new(self.content.clone()) .block(block) @@ -257,23 +309,14 @@ impl NotifyBox { match self.time >= 1.0 { false => self .enter_state - .linear(start_pos, stop_pos, ANIM_SPEED, deltatime), + .ease_out(start_pos, stop_pos, ANIM_SPEED, deltatime), true => self .leave_state - .linear(stop_pos, leave_pos, ANIM_SPEED, deltatime), + .ease_in(stop_pos, leave_pos, ANIM_SPEED / 2.0, deltatime), } } pub fn update(&mut self, deltatime: f64, area: Rect) -> bool { - let max_width = match self.error { - true => (area.width / 3).max(MAX_WIDTH), - false => area.width.min(MAX_WIDTH), - } as usize; - let lines = textwrap::wrap(&self.raw_content, max_width); - self.width = lines.iter().fold(0, |acc, x| acc.max(x.len())) as u16 + 2; - self.height = lines.len() as u16 + 2; - self.content = lines.join("\n"); - let last_pos = self.pos; self.pos = Some(self.next_pos(deltatime, area)); diff --git a/src/widget/page.rs b/src/widget/page.rs index 9a2f10c..95755d0 100644 --- a/src/widget/page.rs +++ b/src/widget/page.rs @@ -33,7 +33,6 @@ impl Widget for PagePopup { fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { let buf = f.buffer_mut(); let center = super::centered_rect(13, 3, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); let page_p = Paragraph::new(self.input.input.clone()); let indicator = Paragraph::new(">").block(border_block(&ctx.theme, true).title(title!("Goto Page"))); diff --git a/src/widget/sort.rs b/src/widget/sort.rs index 95c53d3..8c4957b 100644 --- a/src/widget/sort.rs +++ b/src/widget/sort.rs @@ -61,7 +61,6 @@ impl Widget for SortPopup { fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { let buf = f.buffer_mut(); let center = super::centered_rect(30, ctx.src_info.sorts.len() as u16 + 2, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); let items = ctx.src_info.sorts.iter().enumerate().map(|(i, item)| { Row::new([match i == self.selected.sort { true => format!("  {}", item), diff --git a/src/widget/sources.rs b/src/widget/sources.rs index b937c27..28c6e91 100644 --- a/src/widget/sources.rs +++ b/src/widget/sources.rs @@ -29,7 +29,6 @@ impl Widget for SourcesPopup { fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { let buf = f.buffer_mut(); let center = super::centered_rect(30, self.table.items.len() as u16 + 2, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); let items = self.table.items.iter().map(|item| { Row::new(vec![match item == &ctx.src { true => format!("  {}", item), diff --git a/src/widget/themes.rs b/src/widget/themes.rs index 1b3c8e1..4b79bc3 100644 --- a/src/widget/themes.rs +++ b/src/widget/themes.rs @@ -33,7 +33,6 @@ impl Widget for ThemePopup { let buf = f.buffer_mut(); let height = min(min(ctx.themes.len() as u16 + 2, 10), area.height); let center = super::centered_rect(30, height, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); let items = ctx.themes.keys().enumerate().map(|(i, item)| { Row::new(vec![ match i == self.selected { diff --git a/src/widget/user.rs b/src/widget/user.rs index 84b527e..d4c159f 100644 --- a/src/widget/user.rs +++ b/src/widget/user.rs @@ -31,7 +31,6 @@ impl Widget for UserPopup { fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { let buf = f.buffer_mut(); let center = super::centered_rect(30, 3, area); - // let clear = super::centered_rect(center.width + 2, center.height, area); let page_p = Paragraph::new(self.input.input.clone()); let indicator = Paragraph::new(">") .block(border_block(&ctx.theme, true).title(title!("Posts by User")));