mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-01 23:51:46 -04:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 134ba601fa | |||
| 3a264a0ddb | |||
| 85857890ed | |||
| c1c410ebe6 | |||
| 34047ca106 | |||
| 3452294f59 | |||
| d22aa4596d | |||
| 6e5bb0bdc5 | |||
| 7b68fe6b33 | |||
| a44dba20a7 | |||
| d6102de3c6 |
+1
-4
@@ -1,11 +1,8 @@
|
||||
# v0.3.0
|
||||
# Unreleased
|
||||
|
||||
- Update ratatui(-image) dependencies
|
||||
- Enable Ctrl+Z/Suspend functionality
|
||||
- Rewrite with mupdf as the backend for much better performance and rendering quality
|
||||
- Support easy inversion of colors via `i` keypress
|
||||
- Support for filling all available space with `f` keypress
|
||||
- Change help text at bottom into full help page
|
||||
|
||||
# v0.2.0
|
||||
|
||||
|
||||
Generated
+41
-71
@@ -52,9 +52,9 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.96"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -149,9 +149,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "avif-serialize"
|
||||
version = "0.8.3"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e"
|
||||
checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
@@ -349,9 +349,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.16"
|
||||
version = "1.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -429,18 +429,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.31"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
|
||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.31"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
|
||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@@ -834,9 +834,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.14.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
@@ -889,15 +889,6 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.3"
|
||||
@@ -935,9 +926,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.0"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
||||
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@@ -1353,7 +1344,6 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"num-traits",
|
||||
"png",
|
||||
"ravif",
|
||||
"rayon",
|
||||
"zune-core",
|
||||
@@ -1542,9 +1532,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.170"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -1614,9 +1604,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.26"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "loop9"
|
||||
@@ -1724,12 +1714,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.5"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1747,26 +1736,26 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "mupdf"
|
||||
version = "0.4.4"
|
||||
source = "git+https://github.com/itsjunetime/mupdf-rs?branch=june%2Fmupdf_1_25#9c4f2379d205a78f967bb230f0e72ec18fad23f7"
|
||||
source = "git+https://github.com/itsjunetime/mupdf-rs?branch=remove_debug_print#10f1b1629540e7d62354842198317bcf5e7d619c"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"font-kit",
|
||||
"mupdf-sys",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"zerocopy 0.8.21",
|
||||
"zerocopy 0.8.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mupdf-sys"
|
||||
version = "0.4.4"
|
||||
source = "git+https://github.com/itsjunetime/mupdf-rs?branch=june%2Fmupdf_1_25#9c4f2379d205a78f967bb230f0e72ec18fad23f7"
|
||||
source = "git+https://github.com/itsjunetime/mupdf-rs?branch=remove_debug_print#10f1b1629540e7d62354842198317bcf5e7d619c"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"regex",
|
||||
"zerocopy 0.8.21",
|
||||
"zerocopy 0.8.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2192,19 +2181,6 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -2492,9 +2468,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.9"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
|
||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
]
|
||||
@@ -2647,18 +2623,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.218"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2667,9 +2643,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.139"
|
||||
version = "1.0.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -2742,12 +2718,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "simd_helpers"
|
||||
version = "0.1.0"
|
||||
@@ -3332,9 +3302,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.15.1"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
|
||||
checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"getrandom 0.3.1",
|
||||
@@ -3801,9 +3771,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
|
||||
checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -3864,11 +3834,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.21"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478"
|
||||
checksum = "8207f485579465f62ae51a983e42c906736a17efd2de48b021e64f1bbd8e98c7"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.21",
|
||||
"zerocopy-derive 0.8.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3884,9 +3854,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.21"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2"
|
||||
checksum = "7dbe1304a711c6eb4cf1ed333aa0d9b344685e71f6f00c3b176072213bd3783e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
+3
-4
@@ -2,7 +2,7 @@
|
||||
name = "tdf-viewer"
|
||||
version = "0.2.0"
|
||||
authors = ["June Welker <junewelker@gmail.com>"]
|
||||
edition = "2024"
|
||||
edition = "2021"
|
||||
description = "A terminal viewer for PDFs"
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/itsjunetime/tdf"
|
||||
@@ -11,7 +11,6 @@ license = "AGPL-3.0-only"
|
||||
keywords = ["pdf", "tui", "cli", "terminal"]
|
||||
categories = ["command-line-utilities", "text-processing", "visualization"]
|
||||
default-run = "tdf"
|
||||
rust-version = "1.85"
|
||||
|
||||
[[bin]]
|
||||
name = "tdf"
|
||||
@@ -29,7 +28,7 @@ ratatui = { git = "https://github.com/itsjunetime/ratatui.git" }
|
||||
ratatui-image = { git = "https://github.com/itsjunetime/ratatui-image.git", branch = "vb64_on_personal", default-features = false }
|
||||
# ratatui-image = { path = "./ratatui-image", features = ["vb64"], default-features = false }
|
||||
crossterm = { version = "0.28.1", features = ["event-stream"] }
|
||||
image = { version = "0.25.1", features = ["pnm", "rayon", "png"], default-features = false }
|
||||
image = { version = "0.25.1", features = ["pnm", "rayon"], default-features = false }
|
||||
notify = { version = "8.0.0", features = ["crossbeam-channel"] }
|
||||
tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] }
|
||||
futures-util = { version = "0.3.30", default-features = false }
|
||||
@@ -38,7 +37,7 @@ flume = { version = "0.11.0", default-features = false, features = ["async"] }
|
||||
xflags = "0.4.0-pre.2"
|
||||
mimalloc = "0.1.43"
|
||||
nix = { version = "0.29.0", features = ["signal"] }
|
||||
mupdf = { git = "https://github.com/itsjunetime/mupdf-rs", branch = "june/mupdf_1_25", default-features = false, features = ["svg", "system-fonts", "img"] }
|
||||
mupdf = { git = "https://github.com/itsjunetime/mupdf-rs", branch = "remove_debug_print", default-features = false, features = ["svg", "system-fonts", "img"] }
|
||||
rayon = { version = "*", default-features = false }
|
||||
|
||||
# for tracing with tokio-console
|
||||
|
||||
@@ -6,15 +6,15 @@ use std::{
|
||||
time::{SystemTime, UNIX_EPOCH}
|
||||
};
|
||||
|
||||
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main, profiler::Profiler};
|
||||
use criterion::{criterion_group, criterion_main, profiler::Profiler, BenchmarkId, Criterion};
|
||||
use futures_util::StreamExt;
|
||||
use tdf::{
|
||||
converter::{ConvertedPage, ConverterMsg},
|
||||
renderer::{PageInfo, RenderInfo, fill_default}
|
||||
renderer::{fill_default, PageInfo, RenderInfo}
|
||||
};
|
||||
use utils::{
|
||||
RenderState, handle_converter_msg, handle_renderer_msg, render_doc, start_all_rendering,
|
||||
start_converting_loop, start_rendering_loop
|
||||
handle_converter_msg, handle_renderer_msg, render_doc, start_all_rendering,
|
||||
start_converting_loop, start_rendering_loop, RenderState
|
||||
};
|
||||
|
||||
const FILES: [&str; 3] = [
|
||||
|
||||
+7
-7
@@ -1,13 +1,13 @@
|
||||
use std::{hint::black_box, path::Path};
|
||||
|
||||
use crossterm::terminal::WindowSize;
|
||||
use flume::{Sender, r#async::RecvStream, unbounded};
|
||||
use flume::{r#async::RecvStream, unbounded, Sender};
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui_image::picker::{Picker, ProtocolType};
|
||||
use tdf::{
|
||||
converter::{ConvertedPage, ConverterMsg, run_conversion_loop},
|
||||
renderer::{RenderError, RenderInfo, RenderNotif, fill_default, start_rendering}
|
||||
converter::{run_conversion_loop, ConvertedPage, ConverterMsg},
|
||||
renderer::{fill_default, start_rendering, RenderError, RenderInfo, RenderNotif}
|
||||
};
|
||||
|
||||
pub fn handle_renderer_msg(
|
||||
@@ -37,13 +37,13 @@ pub fn handle_converter_msg(
|
||||
|
||||
pages[num] = Some(page);
|
||||
|
||||
let first_none = pages.iter().position(Option::is_none);
|
||||
let num_got = pages.iter().filter(|p| p.is_some()).count();
|
||||
|
||||
// we have to tell it to jump to a certain page so that it will actually render it (since
|
||||
// it only renders fanning out from the page that we currently have selected)
|
||||
if let Some(first) = first_none {
|
||||
to_converter_tx.send(ConverterMsg::GoToPage(first)).unwrap();
|
||||
}
|
||||
to_converter_tx
|
||||
.send(ConverterMsg::GoToPage(num_got))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub struct RenderState {
|
||||
|
||||
+3
-3
@@ -2,10 +2,10 @@ use flume::{Receiver, SendError, Sender, TryRecvError};
|
||||
use futures_util::stream::StreamExt;
|
||||
use image::DynamicImage;
|
||||
use itertools::Itertools;
|
||||
use ratatui_image::{Resize, picker::Picker, protocol::Protocol};
|
||||
use ratatui_image::{picker::Picker, protocol::Protocol, Resize};
|
||||
use rayon::iter::ParallelIterator;
|
||||
|
||||
use crate::renderer::{PageInfo, RenderError, fill_default};
|
||||
use crate::renderer::{fill_default, PageInfo, RenderError};
|
||||
|
||||
pub struct ConvertedPage {
|
||||
pub page: Protocol,
|
||||
@@ -49,7 +49,7 @@ pub async fn run_conversion_loop(
|
||||
let Some((page_info, new_iter)) = (idx_start..page)
|
||||
.interleave(page..idx_end)
|
||||
.enumerate()
|
||||
// .skip(*iteration)
|
||||
.skip(*iteration)
|
||||
.find_map(|(i_idx, p_idx)| images[p_idx].take().map(|p| (p, i_idx)))
|
||||
else {
|
||||
return Ok(None);
|
||||
|
||||
+10
-15
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
io::{Read, Write, stdout},
|
||||
io::{stdout, Read, Write},
|
||||
num::NonZeroUsize,
|
||||
path::PathBuf
|
||||
};
|
||||
@@ -8,16 +8,16 @@ use std::{
|
||||
use crossterm::{
|
||||
execute,
|
||||
terminal::{
|
||||
EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode,
|
||||
enable_raw_mode, window_size
|
||||
disable_raw_mode, enable_raw_mode, window_size, EndSynchronizedUpdate,
|
||||
EnterAlternateScreen, LeaveAlternateScreen
|
||||
}
|
||||
};
|
||||
use futures_util::{FutureExt, stream::StreamExt};
|
||||
use futures_util::{stream::StreamExt, FutureExt};
|
||||
use notify::{Event, EventKind, RecursiveMode, Watcher};
|
||||
use ratatui::{Terminal, backend::CrosstermBackend};
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
use ratatui_image::picker::Picker;
|
||||
use tdf::{
|
||||
converter::{ConvertedPage, ConverterMsg, run_conversion_loop},
|
||||
converter::{run_conversion_loop, ConvertedPage, ConverterMsg},
|
||||
renderer::{self, RenderError, RenderInfo, RenderNotif},
|
||||
tui::{BottomMessage, InputAction, MessageSetting, Tui}
|
||||
};
|
||||
@@ -45,8 +45,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
optional -r,--r-to-l r_to_l: bool
|
||||
/// The maximum number of pages to display together, horizontally, at a time
|
||||
optional -m,--max-wide max_wide: NonZeroUsize
|
||||
/// Fullscreen the pdf (hide document name, page count, etc)
|
||||
optional -f,--fullscreen fullscreen: bool
|
||||
/// PDF file to read
|
||||
required file: PathBuf
|
||||
};
|
||||
@@ -169,9 +167,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
)?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
let mut fullscreen = flags.fullscreen.unwrap_or_default();
|
||||
let mut main_area = Tui::main_layout(&term.get_frame(), fullscreen);
|
||||
tui_tx.send(RenderNotif::Area(main_area.page_area))?;
|
||||
let mut main_area = Tui::main_layout(&term.get_frame());
|
||||
tui_tx.send(RenderNotif::Area(main_area[1]))?;
|
||||
|
||||
let mut tui_rx = tui_rx.into_stream();
|
||||
let mut from_converter = from_converter.into_stream();
|
||||
@@ -194,8 +191,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
to_converter.send(ConverterMsg::GoToPage(page))?;
|
||||
},
|
||||
InputAction::Search(term) => tui_tx.send(RenderNotif::Search(term))?,
|
||||
InputAction::Invert => tui_tx.send(RenderNotif::Invert)?,
|
||||
InputAction::Fullscreen => fullscreen = !fullscreen,
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -223,10 +218,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
},
|
||||
};
|
||||
|
||||
let new_area = Tui::main_layout(&term.get_frame(), fullscreen);
|
||||
let new_area = Tui::main_layout(&term.get_frame());
|
||||
if new_area != main_area {
|
||||
main_area = new_area;
|
||||
tui_tx.send(RenderNotif::Area(main_area.page_area))?;
|
||||
tui_tx.send(RenderNotif::Area(main_area[1]))?;
|
||||
needs_redraw = true;
|
||||
}
|
||||
|
||||
|
||||
+28
-40
@@ -10,8 +10,7 @@ pub enum RenderNotif {
|
||||
Area(Rect),
|
||||
JumpToPage(usize),
|
||||
Search(String),
|
||||
Reload,
|
||||
Invert
|
||||
Reload
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -46,10 +45,12 @@ struct PrevRender {
|
||||
contained_term: Option<bool>
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fill_default<T: Default>(vec: &mut Vec<T>, size: usize) {
|
||||
vec.clear();
|
||||
vec.resize_with(size, T::default);
|
||||
vec.reserve(size.saturating_sub(vec.len()));
|
||||
for _ in 0..size {
|
||||
vec.push(T::default());
|
||||
}
|
||||
}
|
||||
|
||||
// this function has to be sync (non-async) because the mupdf::Document needs to be held during
|
||||
@@ -71,6 +72,14 @@ pub fn start_rendering(
|
||||
receiver: Receiver<RenderNotif>,
|
||||
size: WindowSize
|
||||
) -> Result<(), SendError<Result<RenderInfo, RenderError>>> {
|
||||
// first, wait 'til we get told what the current starting area is so that we can set it to
|
||||
// know what to render to
|
||||
let mut area = loop {
|
||||
if let RenderNotif::Area(r) = receiver.recv().unwrap() {
|
||||
break r;
|
||||
}
|
||||
};
|
||||
|
||||
// We want this outside of 'reload so that if the doc reloads, the search term that somebody
|
||||
// set will still get highlighted in the reloaded doc
|
||||
let mut search_term = None;
|
||||
@@ -81,8 +90,6 @@ pub fn start_rendering(
|
||||
let col_h = size.height / size.rows;
|
||||
|
||||
let mut stored_doc = None;
|
||||
let mut invert = false;
|
||||
let mut preserved_area = None;
|
||||
|
||||
'reload: loop {
|
||||
let doc = match Document::open(path) {
|
||||
@@ -132,7 +139,7 @@ pub fn start_rendering(
|
||||
// `split_at_mut` at 0 initially (which bascially makes `right == rendered && left == []`),
|
||||
// doing basically nothing, but if we get a notification that something has been jumped to,
|
||||
// then we can split at that page and render at both sides of it
|
||||
let mut rendered = Vec::new();
|
||||
let mut rendered = vec![];
|
||||
fill_default::<PrevRender>(&mut rendered, n_pages);
|
||||
let mut start_point = 0;
|
||||
|
||||
@@ -141,38 +148,24 @@ pub fn start_rendering(
|
||||
// document. If there was a mechanism to say 'start this for-loop over' then I would do
|
||||
// that, but I don't think such a thing exists, so this is our attempt
|
||||
'render_pages: loop {
|
||||
// next, we gotta wait 'til we get told what the current starting area is so that we can
|
||||
// set it to know what to render to
|
||||
let area = match preserved_area {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
let new_area = loop {
|
||||
if let RenderNotif::Area(r) = receiver.recv().unwrap() {
|
||||
break r;
|
||||
}
|
||||
};
|
||||
preserved_area = Some(new_area);
|
||||
new_area
|
||||
}
|
||||
};
|
||||
|
||||
// what we do with a notif is the same regardless of if we're in the middle of
|
||||
// rendering the list of pages or we're all done
|
||||
macro_rules! handle_notif {
|
||||
($notif:ident) => {
|
||||
match $notif {
|
||||
RenderNotif::Reload => continue 'reload,
|
||||
RenderNotif::Invert => {
|
||||
invert = !invert;
|
||||
for page in &mut rendered {
|
||||
page.successful = false;
|
||||
}
|
||||
continue 'render_pages;
|
||||
}
|
||||
RenderNotif::Area(new_area) => {
|
||||
preserved_area = Some(new_area);
|
||||
fill_default(&mut rendered, n_pages);
|
||||
continue 'render_pages;
|
||||
let bigger =
|
||||
new_area.width > area.width || new_area.height > area.height;
|
||||
area = new_area;
|
||||
// we only want to re-render pages if the new area is greater than the old
|
||||
// one, 'cause then we might need sharper images to make it all look good.
|
||||
// If the new area is smaller, then the same high-quality-rendered images
|
||||
// will still look fine, so it's ok to leave it.
|
||||
if bigger {
|
||||
fill_default(&mut rendered, n_pages);
|
||||
continue 'render_pages;
|
||||
}
|
||||
}
|
||||
RenderNotif::JumpToPage(page) => {
|
||||
start_point = page;
|
||||
@@ -259,7 +252,6 @@ pub fn start_rendering(
|
||||
&page,
|
||||
search_term.as_deref(),
|
||||
rendered_with_no_results,
|
||||
invert,
|
||||
(area_w, area_h)
|
||||
) {
|
||||
// If we've already rendered it just fine and we don't need to render it again,
|
||||
@@ -274,9 +266,9 @@ pub fn start_rendering(
|
||||
rendered.contained_term = Some(ctx.result_rects.is_empty());
|
||||
rendered.successful = true;
|
||||
|
||||
let w = ctx.pixmap.width();
|
||||
let h = ctx.pixmap.height();
|
||||
let cap = (w * h * u32::from(ctx.pixmap.n())) as usize + 16;
|
||||
let cap = (ctx.pixmap.width()
|
||||
* ctx.pixmap.height() * u32::from(ctx.pixmap.n()))
|
||||
as usize;
|
||||
let mut pixels = Vec::with_capacity(cap);
|
||||
if let Err(e) = ctx.pixmap.write_to(&mut pixels, mupdf::ImageFormat::PNM) {
|
||||
sender.send(Err(RenderError::Doc(e)))?;
|
||||
@@ -327,7 +319,6 @@ fn render_single_page_to_ctx(
|
||||
page: &Page,
|
||||
search_term: Option<&str>,
|
||||
already_rendered_no_results: bool,
|
||||
invert: bool,
|
||||
(area_w, area_h): (f32, f32)
|
||||
) -> Result<Option<RenderedContext>, mupdf::error::Error> {
|
||||
let mut max_hits = 10;
|
||||
@@ -387,9 +378,6 @@ fn render_single_page_to_ctx(
|
||||
let matrix = Matrix::new_scale(scale_factor, scale_factor);
|
||||
|
||||
let mut pixmap = page.to_pixmap(&matrix, &colorspace, 0.0, false)?;
|
||||
if invert {
|
||||
pixmap.invert()?;
|
||||
}
|
||||
|
||||
let (x_res, y_res) = pixmap.resolution();
|
||||
let new_x = (x_res as f32 * scale_factor) as i32;
|
||||
|
||||
+108
-213
@@ -1,26 +1,25 @@
|
||||
use std::{borrow::Cow, io::stdout, num::NonZeroUsize};
|
||||
use std::{borrow::Cow, io::stdout, num::NonZeroUsize, rc::Rc};
|
||||
|
||||
use crossterm::{
|
||||
event::{Event, KeyCode, KeyModifiers, MouseEventKind},
|
||||
execute,
|
||||
terminal::{
|
||||
BeginSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode,
|
||||
enable_raw_mode
|
||||
disable_raw_mode, enable_raw_mode, BeginSynchronizedUpdate, EnterAlternateScreen,
|
||||
LeaveAlternateScreen
|
||||
}
|
||||
};
|
||||
use nix::{
|
||||
sys::signal::{Signal::SIGSTOP, kill},
|
||||
sys::signal::{kill, Signal::SIGSTOP},
|
||||
unistd::Pid
|
||||
};
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
symbols::border,
|
||||
text::{Span, Text},
|
||||
widgets::{Block, Borders, Clear, Padding}
|
||||
text::Span,
|
||||
widgets::{Block, Borders, Padding},
|
||||
Frame
|
||||
};
|
||||
use ratatui_image::{Image, protocol::Protocol};
|
||||
use ratatui_image::{protocol::Protocol, Image};
|
||||
|
||||
use crate::{renderer::RenderError, skip::Skip};
|
||||
|
||||
@@ -33,8 +32,7 @@ pub struct Tui {
|
||||
// jumping to a specific page
|
||||
prev_msg: Option<BottomMessage>,
|
||||
rendered: Vec<RenderedInfo>,
|
||||
page_constraints: PageConstraints,
|
||||
showing_help_msg: bool
|
||||
page_constraints: PageConstraints
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -79,12 +77,6 @@ struct RenderedInfo {
|
||||
num_results: Option<usize>
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct RenderLayout {
|
||||
pub page_area: Rect,
|
||||
pub top_and_bottom: Option<(Rect, Rect)>
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
pub fn new(name: String, max_wide: Option<NonZeroUsize>, r_to_l: bool) -> Tui {
|
||||
Self {
|
||||
@@ -94,138 +86,121 @@ impl Tui {
|
||||
bottom_msg: BottomMessage::Help,
|
||||
last_render: LastRender::default(),
|
||||
rendered: vec![],
|
||||
page_constraints: PageConstraints { max_wide, r_to_l },
|
||||
showing_help_msg: false
|
||||
page_constraints: PageConstraints { max_wide, r_to_l }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_layout(frame: &Frame<'_>, fullscreened: bool) -> RenderLayout {
|
||||
if fullscreened {
|
||||
RenderLayout {
|
||||
page_area: frame.area(),
|
||||
top_and_bottom: None
|
||||
}
|
||||
} else {
|
||||
let layout = Layout::default()
|
||||
.constraints([
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3)
|
||||
])
|
||||
.horizontal_margin(2)
|
||||
.vertical_margin(1)
|
||||
.split(frame.area());
|
||||
|
||||
RenderLayout {
|
||||
page_area: layout[1],
|
||||
top_and_bottom: Some((layout[0], layout[2]))
|
||||
}
|
||||
}
|
||||
pub fn main_layout(frame: &Frame<'_>) -> Rc<[Rect]> {
|
||||
Layout::default()
|
||||
.constraints([
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3)
|
||||
])
|
||||
.horizontal_margin(2)
|
||||
.vertical_margin(1)
|
||||
.split(frame.area())
|
||||
}
|
||||
|
||||
// TODO: Make a way to fill the width of the screen with one page and scroll down to view it
|
||||
pub fn render(&mut self, frame: &mut Frame<'_>, full_layout: &RenderLayout) {
|
||||
if self.showing_help_msg {
|
||||
self.render_help_msg(frame);
|
||||
return;
|
||||
}
|
||||
pub fn render(&mut self, frame: &mut Frame<'_>, main_area: &[Rect]) {
|
||||
let top_block = Block::new()
|
||||
.padding(Padding {
|
||||
right: 2,
|
||||
left: 2,
|
||||
..Padding::default()
|
||||
})
|
||||
.borders(Borders::BOTTOM);
|
||||
|
||||
if let Some((top_area, bottom_area)) = full_layout.top_and_bottom {
|
||||
let top_block = Block::new()
|
||||
.padding(Padding {
|
||||
right: 2,
|
||||
left: 2,
|
||||
..Padding::default()
|
||||
})
|
||||
.borders(Borders::BOTTOM);
|
||||
let top_area = top_block.inner(main_area[0]);
|
||||
|
||||
let top_area = top_block.inner(top_area);
|
||||
let page_nums_text = format!("{} / {}", self.page + 1, self.rendered.len());
|
||||
|
||||
let page_nums_text = format!("{} / {}", self.page + 1, self.rendered.len());
|
||||
let top_layout = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(page_nums_text.len() as u16)
|
||||
])
|
||||
.split(top_area);
|
||||
|
||||
let top_layout = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(page_nums_text.len() as u16)
|
||||
])
|
||||
.split(top_area);
|
||||
let title = Span::styled(&self.name, Style::new().fg(Color::Cyan));
|
||||
|
||||
let title = Span::styled(&self.name, Style::new().fg(Color::Cyan));
|
||||
let page_nums = Span::styled(&page_nums_text, Style::new().fg(Color::Cyan));
|
||||
|
||||
let page_nums = Span::styled(&page_nums_text, Style::new().fg(Color::Cyan));
|
||||
frame.render_widget(top_block, main_area[0]);
|
||||
frame.render_widget(title, top_layout[0]);
|
||||
frame.render_widget(page_nums, top_layout[1]);
|
||||
|
||||
frame.render_widget(top_block, top_area);
|
||||
frame.render_widget(title, top_layout[0]);
|
||||
frame.render_widget(page_nums, top_layout[1]);
|
||||
let bottom_block = Block::new()
|
||||
.padding(Padding {
|
||||
top: 1,
|
||||
right: 2,
|
||||
left: 2,
|
||||
bottom: 0
|
||||
})
|
||||
.borders(Borders::TOP);
|
||||
let bottom_area = bottom_block.inner(main_area[2]);
|
||||
|
||||
let bottom_block = Block::new()
|
||||
.padding(Padding {
|
||||
top: 1,
|
||||
right: 2,
|
||||
left: 2,
|
||||
bottom: 0
|
||||
})
|
||||
.borders(Borders::TOP);
|
||||
let bottom_inside_block = bottom_block.inner(bottom_area);
|
||||
frame.render_widget(bottom_block, main_area[2]);
|
||||
|
||||
frame.render_widget(bottom_block, bottom_area);
|
||||
let rendered_str = if !self.rendered.is_empty() {
|
||||
format!(
|
||||
"Rendered: {}%",
|
||||
(self.rendered.iter().filter(|i| i.img.is_some()).count() * 100)
|
||||
/ self.rendered.len()
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let bottom_layout = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(rendered_str.len() as u16)
|
||||
])
|
||||
.split(bottom_area);
|
||||
|
||||
let rendered_str = if !self.rendered.is_empty() {
|
||||
format!(
|
||||
"Rendered: {}%",
|
||||
(self.rendered.iter().filter(|i| i.img.is_some()).count() * 100)
|
||||
/ self.rendered.len()
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let bottom_layout = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(rendered_str.len() as u16)
|
||||
])
|
||||
.split(bottom_inside_block);
|
||||
let rendered_span = Span::styled(&rendered_str, Style::new().fg(Color::Cyan));
|
||||
frame.render_widget(rendered_span, bottom_layout[1]);
|
||||
|
||||
let rendered_span = Span::styled(&rendered_str, Style::new().fg(Color::Cyan));
|
||||
frame.render_widget(rendered_span, bottom_layout[1]);
|
||||
|
||||
let (msg_str, color): (Cow<'_, str>, _) = match self.bottom_msg {
|
||||
BottomMessage::Help => ("?: Show help page".into(), Color::Blue),
|
||||
BottomMessage::Error(ref e) => (e.as_str().into(), Color::Red),
|
||||
BottomMessage::Input(ref input_state) => (
|
||||
match input_state {
|
||||
InputCommand::GoToPage(page) => format!("Go to: {page}"),
|
||||
InputCommand::Search(s) => format!("Search: {s}")
|
||||
}
|
||||
let (msg_str, color): (Cow<'_, str>, _) = match self.bottom_msg {
|
||||
BottomMessage::Help => (
|
||||
"/: Search, g: Go To Page, n: Next Search Result, N: Previous Search Result".into(),
|
||||
Color::Blue
|
||||
),
|
||||
BottomMessage::Error(ref e) => (e.as_str().into(), Color::Red),
|
||||
BottomMessage::Input(ref input_state) => (
|
||||
match input_state {
|
||||
InputCommand::GoToPage(page) => format!("Go to: {page}"),
|
||||
InputCommand::Search(s) => format!("Search: {s}")
|
||||
}
|
||||
.into(),
|
||||
Color::Blue
|
||||
),
|
||||
BottomMessage::SearchResults(ref term) => {
|
||||
let num_found = self
|
||||
.rendered
|
||||
.iter()
|
||||
.filter_map(|r| r.num_results)
|
||||
.sum::<usize>();
|
||||
let num_searched = self
|
||||
.rendered
|
||||
.iter()
|
||||
.filter(|r| r.num_results.is_some())
|
||||
.count() * 100;
|
||||
(
|
||||
format!(
|
||||
"Results for '{term}': {num_found} (searched: {}%)",
|
||||
num_searched / self.rendered.len()
|
||||
)
|
||||
.into(),
|
||||
Color::Blue
|
||||
),
|
||||
BottomMessage::SearchResults(ref term) => {
|
||||
let num_found = self
|
||||
.rendered
|
||||
.iter()
|
||||
.filter_map(|r| r.num_results)
|
||||
.sum::<usize>();
|
||||
let num_searched = self
|
||||
.rendered
|
||||
.iter()
|
||||
.filter(|r| r.num_results.is_some())
|
||||
.count() * 100;
|
||||
(
|
||||
format!(
|
||||
"Results for '{term}': {num_found} (searched: {}%)",
|
||||
num_searched / self.rendered.len()
|
||||
)
|
||||
.into(),
|
||||
Color::Blue
|
||||
)
|
||||
}
|
||||
BottomMessage::Reloaded => ("Document was reloaded!".into(), Color::Blue)
|
||||
};
|
||||
)
|
||||
}
|
||||
BottomMessage::Reloaded => ("Document was reloaded!".into(), Color::Blue)
|
||||
};
|
||||
|
||||
let span = Span::styled(msg_str, Style::new().fg(color));
|
||||
frame.render_widget(span, bottom_layout[0]);
|
||||
}
|
||||
let span = Span::styled(msg_str, Style::new().fg(color));
|
||||
frame.render_widget(span, bottom_layout[0]);
|
||||
|
||||
let mut img_area = full_layout.page_area;
|
||||
let mut img_area = main_area[1];
|
||||
|
||||
let size = frame.area();
|
||||
if size == self.last_render.rect {
|
||||
@@ -439,12 +414,6 @@ impl Tui {
|
||||
)));
|
||||
Some(InputAction::Redraw)
|
||||
}
|
||||
'i' => Some(InputAction::Invert),
|
||||
'?' => {
|
||||
self.showing_help_msg = true;
|
||||
Some(InputAction::Redraw)
|
||||
}
|
||||
'f' => Some(InputAction::Fullscreen),
|
||||
'n' if self.page < self.rendered.len() - 1 => {
|
||||
// TODO: If we can't find one, then maybe like block until we've verified
|
||||
// all the pages have been checked?
|
||||
@@ -516,8 +485,8 @@ impl Tui {
|
||||
KeyCode::Down => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
||||
KeyCode::Left => self.change_page(PageChange::Prev, ChangeAmount::Single),
|
||||
KeyCode::Up => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
||||
KeyCode::Esc => match (self.showing_help_msg, &self.bottom_msg) {
|
||||
(false, BottomMessage::Help) => Some(InputAction::QuitApp),
|
||||
KeyCode::Esc => match self.bottom_msg {
|
||||
BottomMessage::Help => Some(InputAction::QuitApp),
|
||||
_ => {
|
||||
// When we hit escape, we just want to pop off the current message and
|
||||
// show the underlying one.
|
||||
@@ -546,9 +515,7 @@ impl Tui {
|
||||
Some(InputAction::JumpingToPage(zero_page))
|
||||
} else {
|
||||
self.set_msg(MessageSetting::Some(BottomMessage::Error(
|
||||
format!(
|
||||
"Cannot jump to page {page}; there are only {rendered_len} pages in the document"
|
||||
)
|
||||
format!("Cannot jump to page {page}; there are only {rendered_len} pages in the document")
|
||||
)));
|
||||
Some(InputAction::Redraw)
|
||||
}
|
||||
@@ -628,88 +595,16 @@ impl Tui {
|
||||
self.prev_msg = None;
|
||||
self.bottom_msg = BottomMessage::default();
|
||||
}
|
||||
MessageSetting::Pop =>
|
||||
if self.showing_help_msg {
|
||||
self.last_render.rect = Rect::default();
|
||||
self.showing_help_msg = false;
|
||||
} else {
|
||||
self.bottom_msg = self.prev_msg.take().unwrap_or_default();
|
||||
},
|
||||
MessageSetting::Pop => self.bottom_msg = self.prev_msg.take().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_help_msg(&self, frame: &mut Frame<'_>) {
|
||||
let frame_area = frame.area();
|
||||
frame.render_widget(Clear, frame_area);
|
||||
|
||||
let block = Block::new()
|
||||
.title("Help")
|
||||
.padding(Padding::proportional(1))
|
||||
.borders(Borders::ALL)
|
||||
.border_set(border::ROUNDED)
|
||||
.border_style(Color::Blue);
|
||||
|
||||
let help_span = Text::raw(HELP_PAGE);
|
||||
|
||||
let max_w: u16 = HELP_PAGE
|
||||
.lines()
|
||||
.map(str::len)
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
.try_into()
|
||||
.expect("Every help text line must be shorter than u16::MAX");
|
||||
|
||||
let layout = Layout::horizontal([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(max_w + 6),
|
||||
Constraint::Fill(1)
|
||||
])
|
||||
.split(frame_area);
|
||||
|
||||
let block_area = Layout::vertical([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(u16::try_from(HELP_PAGE.lines().count()).unwrap() + 4),
|
||||
Constraint::Fill(1)
|
||||
])
|
||||
.split(layout[1]);
|
||||
|
||||
let block_inner = block.inner(block_area[1]);
|
||||
|
||||
frame.render_widget(block, block_area[1]);
|
||||
frame.render_widget(help_span, block_inner);
|
||||
}
|
||||
}
|
||||
|
||||
static HELP_PAGE: &str = "\
|
||||
l, h, left, right:
|
||||
Go forward/backwards a single page
|
||||
j, k, down, up:
|
||||
Go forwards/backwards a screen's worth of pages
|
||||
q, esc:
|
||||
Quit
|
||||
g:
|
||||
Go to specific page (type numbers after 'g')
|
||||
/:
|
||||
Search
|
||||
n, N:
|
||||
Next/Previous search result
|
||||
i:
|
||||
Invert colors
|
||||
f:
|
||||
Remove borders/fullscreen
|
||||
?:
|
||||
Show this page
|
||||
ctrl+z:
|
||||
Suspend & background tdf \
|
||||
";
|
||||
|
||||
pub enum InputAction {
|
||||
Redraw,
|
||||
JumpingToPage(usize),
|
||||
Search(String),
|
||||
QuitApp,
|
||||
Invert,
|
||||
Fullscreen
|
||||
QuitApp
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
||||
Reference in New Issue
Block a user