diff --git a/Cargo.lock b/Cargo.lock index c9f84c6..39f7fb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,9 +159,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" dependencies = [ "rustversion", ] @@ -209,24 +209,28 @@ dependencies = [ ] [[package]] -name = "color-to-tui" -version = "0.3.0" +name = "compact_str" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb7fe3c2fe4e88669ff503b2502dffc2f69125f276ae8ecb79439c3dd22f292" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" dependencies = [ - "ratatui", - "serde", + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", ] [[package]] name = "compact_str" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" dependencies = [ "castaway", "cfg-if", "itoa", + "rustversion", "ryu", "static_assertions", ] @@ -276,15 +280,15 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.27.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.5.0", "crossterm_winapi", - "libc", - "mio", + "mio 1.0.2", "parking_lot", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -928,6 +932,16 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instability" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +dependencies = [ + "quote", + "syn 2.0.66", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -1094,11 +1108,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "log", "wasi", "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -1149,7 +1175,6 @@ dependencies = [ "arboard", "base64", "chrono", - "color-to-tui", "crossterm", "directories", "dirs", @@ -1159,7 +1184,7 @@ dependencies = [ "lexopt", "nix", "open", - "ratatui", + "ratatui 0.28.0", "ratatui-image", "reqwest", "rss", @@ -1619,8 +1644,7 @@ checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" dependencies = [ "bitflags 2.5.0", "cassowary", - "compact_str", - "crossterm", + "compact_str 0.7.1", "itertools 0.13.0", "lru", "paste", @@ -1632,6 +1656,27 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "ratatui" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba6a365afbe5615999275bea2446b970b10a41102500e27ce7678d50d978303" +dependencies = [ + "bitflags 2.5.0", + "cassowary", + "compact_str 0.8.0", + "crossterm", + "instability", + "itertools 0.13.0", + "lru", + "paste", + "strum", + "strum_macros", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + [[package]] name = "ratatui-image" version = "1.0.1" @@ -1643,7 +1688,7 @@ dependencies = [ "icy_sixel", "image", "rand", - "ratatui", + "ratatui 0.27.0", ] [[package]] @@ -1947,12 +1992,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 1.0.2", "signal-hook", ] @@ -2225,7 +2270,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 0.8.11", "num_cpus", "pin-project-lite", "socket2", diff --git a/Cargo.toml b/Cargo.toml index 479eb94..9485157 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,9 @@ lto = true reqwest = { version = "0.12.5", features = ["cookies", "gzip"], default-features = false } tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] } urlencoding = "2.1.3" -ratatui = { version = "0.27.0", default-features = false, features = ["crossterm"] } +ratatui = { version = "0.28.0", default-features = false, features = ["crossterm"] } textwrap = { version = "0.16.1", default-features = false } -crossterm = { version = "0.27.0", default-features = false } +crossterm = { version = "0.28.1", default-features = false } unicode-width = "0.1.13" toml = "0.8.14" directories = "5.0.1" @@ -46,7 +46,6 @@ base64 = { version = "0.22.1", default-features = false, features = ["alloc"] } lexopt = "0.3.0" ratatui-image = { version = "1.0.1", optional = true , default-features = false } image = { version = "0.25.1", optional = true, features = ["png"], default-features = false } -color-to-tui = { version = "0.3.0", default-features = false } [lib] name = "nyaa" diff --git a/src/app.rs b/src/app.rs index 1cbb784..4be5a3a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -9,7 +9,7 @@ use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; use indexmap::IndexMap; use ratatui::{ backend::Backend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Position}, Frame, Terminal, }; use reqwest::cookie::Jar; @@ -431,7 +431,7 @@ impl App { ctx.deltatime = last_time.map(|l| (now - l).as_secs_f64()).unwrap_or(0.0); last_time = Some(now); - if self.widgets.notification.update(ctx.deltatime, size) { + if self.widgets.notification.update(ctx.deltatime, (Position::ORIGIN, size).into()) { break; } } else { @@ -524,7 +524,7 @@ impl App { Direction::Vertical, [Constraint::Length(3), Constraint::Min(1)], ) - .split(f.size()); + .split(f.area()); self.widgets.search.draw(f, ctx, layout_vertical[0]); // Dont draw batch pane if empty @@ -543,7 +543,7 @@ impl App { self.widgets.batch.draw(f, ctx, layout_horizontal[1]); } self.widgets.draw_popups(ctx, f); - self.widgets.notification.draw(f, ctx, f.size()); + self.widgets.notification.draw(f, ctx, f.area()); } fn on( diff --git a/src/macros.rs b/src/macros.rs index cdefd76..c442156 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -38,7 +38,7 @@ macro_rules! widgets { match ctx.mode { $( $(#[$docs])* - $($pmode => self.$pwidget.draw(f, ctx, f.size()),)? + $($pmode => self.$pwidget.draw(f, ctx, f.area()),)? )+ _ => {} } diff --git a/src/source/nyaa_html.rs b/src/source/nyaa_html.rs index 978600d..7bb2766 100644 --- a/src/source/nyaa_html.rs +++ b/src/source/nyaa_html.rs @@ -18,6 +18,7 @@ use crate::{ sync::SearchQuery, theme::Theme, util::{ + colors::color_to_tui, conv::{shorten_number, to_bytes}, html::{as_type, attr, inner}, }, diff --git a/src/source/sukebei_nyaa.rs b/src/source/sukebei_nyaa.rs index d7df9b0..602d2e1 100644 --- a/src/source/sukebei_nyaa.rs +++ b/src/source/sukebei_nyaa.rs @@ -15,6 +15,7 @@ use crate::{ sync::SearchQuery, theme::Theme, util::{ + colors::color_to_tui, conv::to_bytes, html::{attr, inner}, }, diff --git a/src/source/torrent_galaxy.rs b/src/source/torrent_galaxy.rs index a9d0555..08b300b 100644 --- a/src/source/torrent_galaxy.rs +++ b/src/source/torrent_galaxy.rs @@ -22,6 +22,7 @@ use crate::{ sync::SearchQuery, theme::Theme, util::{ + colors::color_to_tui, conv::{shorten_number, to_bytes}, html::{as_type, attr, inner}, }, diff --git a/src/theme.rs b/src/theme.rs index 215defd..728fea3 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -8,7 +8,7 @@ use indexmap::IndexMap; use ratatui::{prelude::Color, widgets::BorderType}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::{app::Context, collection, config, source::SourceTheme}; +use crate::{app::Context, collection, config, source::SourceTheme, util::colors::color_to_tui}; pub static THEMES_PATH: &str = "themes"; diff --git a/src/util.rs b/src/util.rs index 9d78eb0..fcfd875 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,5 @@ pub mod cmd; +pub mod colors; pub mod conv; pub mod html; pub mod strings; diff --git a/src/util/colors.rs b/src/util/colors.rs new file mode 100644 index 0000000..52bd80d --- /dev/null +++ b/src/util/colors.rs @@ -0,0 +1,111 @@ +// Source: https://docs.rs/color-to-tui/0.3.0/src/color_to_tui +pub mod color_to_tui { + use ratatui::style::Color; + use serde::{Deserialize as _, Deserializer, Serializer}; + + pub fn serialize(color: &Color, serializer: S) -> Result { + serializer.serialize_str(&match color { + Color::Reset => "Reset".to_string(), + Color::Red => "Red".to_string(), + Color::Green => "Green".to_string(), + Color::Black => "Black".to_string(), + Color::Yellow => "Yellow".to_string(), + Color::Blue => "Blue".to_string(), + Color::Magenta => "Magenta".to_string(), + Color::Cyan => "Cyan".to_string(), + Color::Gray => "Gray".to_string(), + Color::White => "White".to_string(), + + Color::DarkGray => "DarkGray".to_string(), + Color::LightBlue => "LightBlue".to_string(), + Color::LightCyan => "LightCyan".to_string(), + Color::LightGreen => "LightGreen".to_string(), + Color::LightMagenta => "LightMagenta".to_string(), + Color::LightRed => "LightRed".to_string(), + Color::LightYellow => "LightYellow".to_string(), + Color::Indexed(index) => format!("{:03}", index), + Color::Rgb(r, g, b) => format!("#{:02X}{:02X}{:02X}", r, g, b), + }) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + use serde::de::{Error, Unexpected}; + + let color_string = String::deserialize(deserializer)?; + Ok(match color_string.to_lowercase().as_str() { + "reset" => Color::Reset, + "red" => Color::Red, + "green" => Color::Green, + "black" => Color::Black, + "yellow" => Color::Yellow, + "blue" => Color::Blue, + "magenta" => Color::Magenta, + "cyan" => Color::Cyan, + "gray" => Color::Gray, + "white" => Color::White, + + "darkgray" => Color::DarkGray, + "lightblue" => Color::LightBlue, + "lightcyan" => Color::LightCyan, + "lightgreen" => Color::LightGreen, + "lightmagenta" => Color::LightMagenta, + "lightred" => Color::LightRed, + "lightyellow" => Color::LightYellow, + _ => match color_string.len() { + 3 => { + let index = color_string.parse::(); + if let Ok(index) = index { + Color::Indexed(index) + } else { + return Err(Error::invalid_type( + Unexpected::Bytes(color_string.as_bytes()), + &"u8 index color", + )); + } + } + 4 | 7 => { + if !color_string.starts_with('#') { + return Err(Error::invalid_value( + Unexpected::Char(color_string.chars().next().unwrap()), + &"# at the start", + )); + } + + let color_string = color_string.trim_start_matches('#'); + + let (r, g, b); + + match color_string.len() { + 6 => { + r = u8::from_str_radix(&color_string[0..2], 16); + g = u8::from_str_radix(&color_string[2..4], 16); + b = u8::from_str_radix(&color_string[4..6], 16); + } + 3 => { + r = u8::from_str_radix(&color_string[0..1], 16).map(|r| r * 17); + g = u8::from_str_radix(&color_string[1..2], 16).map(|g| g * 17); + b = u8::from_str_radix(&color_string[2..3], 16).map(|b| b * 17); + } + _ => unreachable!("Can't be reached since already checked"), + } + + match (r, g, b) { + (Ok(r), Ok(g), Ok(b)) => Color::Rgb(r, g, b), + (_, _, _) => { + return Err(Error::invalid_value( + Unexpected::Bytes(color_string.as_bytes()), + &"hex color string", + )); + } + } + } + _ => { + return Err(serde::de::Error::invalid_length( + color_string.len(), + &"color string with length 4 or 7", + )) + } + }, + }) + } +} diff --git a/src/util/term.rs b/src/util/term.rs index 387bbab..b5bfd44 100644 --- a/src/util/term.rs +++ b/src/util/term.rs @@ -37,7 +37,7 @@ pub fn reset_terminal() -> io::Result<()> { pub fn suspend_self(terminal: &mut Terminal) -> Result<(), Box> { // Make sure cursor is drawn - terminal.draw(|f| f.set_cursor(0, 0))?; + terminal.draw(|f| f.set_cursor_position((0, 0)))?; reset_terminal()?; diff --git a/src/widget.rs b/src/widget.rs index 5c793d3..a7d5515 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -98,18 +98,19 @@ pub fn scroll_padding( pub fn dim_buffer(area: Rect, buf: &mut Buffer, amt: f32) { for r in area.top()..area.bottom() { for c in area.left()..area.right() { - let cell = buf.get_mut(c, r); - if let Color::Rgb(r, g, b) = cell.fg { - let r = (r as f32 * amt) as u8; - let g = (g as f32 * amt) as u8; - let b = (b as f32 * amt) as u8; - cell.fg = Color::Rgb(r, g, b); - } - if let Color::Rgb(r, g, b) = cell.bg { - let r = (r as f32 * amt) as u8; - let g = (g as f32 * amt) as u8; - let b = (b as f32 * amt) as u8; - cell.bg = Color::Rgb(r, g, b); + if let Some(cell) = buf.cell_mut((c, r)) { + if let Color::Rgb(r, g, b) = cell.fg { + let r = (r as f32 * amt) as u8; + let g = (g as f32 * amt) as u8; + let b = (b as f32 * amt) as u8; + cell.fg = Color::Rgb(r, g, b); + } + if let Color::Rgb(r, g, b) = cell.bg { + let r = (r as f32 * amt) as u8; + let g = (g as f32 * amt) as u8; + let b = (b as f32 * amt) as u8; + cell.bg = Color::Rgb(r, g, b); + } } } } @@ -170,9 +171,10 @@ pub fn clear(area: Rect, buf: &mut Buffer, fill: Color) { // Deal with wide chars which might extend too far if area.left() > 0 && buf.area.contains((area.left() - 1, area.top()).into()) { for i in area.top()..area.bottom() { - let c = buf.get_mut(area.left() - 1, i); - if c.symbol().width() > 1 { - c.set_char(' '); + if let Some(c) = buf.cell_mut((area.left() - 1, i)) { + if c.symbol().width() > 1 { + c.set_char(' '); + } } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index ae576af..8e8b8be 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -12,6 +12,7 @@ use nyaa::{ use ratatui::{ backend::{Backend as _, TestBackend}, buffer::Buffer, + layout::Position, style::Style, Terminal, }; @@ -140,7 +141,7 @@ pub async fn run_app( pub fn reset_buffer(terminal: &Terminal) -> Buffer { let area = terminal.size().unwrap(); let mut buf = terminal.backend().buffer().clone(); - buf.set_style(area, Style::reset()); + buf.set_style((Position::ORIGIN, area).into(), Style::reset()); buf }