mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-02 08:01:47 -04:00
Implement fullscreen functionality
This commit is contained in:
+1
-1
@@ -49,7 +49,7 @@ pub async fn run_conversion_loop(
|
|||||||
let Some((page_info, new_iter)) = (idx_start..page)
|
let Some((page_info, new_iter)) = (idx_start..page)
|
||||||
.interleave(page..idx_end)
|
.interleave(page..idx_end)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.skip(*iteration)
|
// .skip(*iteration)
|
||||||
.find_map(|(i_idx, p_idx)| images[p_idx].take().map(|p| (p, i_idx)))
|
.find_map(|(i_idx, p_idx)| images[p_idx].take().map(|p| (p, i_idx)))
|
||||||
else {
|
else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
|||||||
+9
-5
@@ -45,6 +45,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
optional -r,--r-to-l r_to_l: bool
|
optional -r,--r-to-l r_to_l: bool
|
||||||
/// The maximum number of pages to display together, horizontally, at a time
|
/// The maximum number of pages to display together, horizontally, at a time
|
||||||
optional -m,--max-wide max_wide: NonZeroUsize
|
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
|
/// PDF file to read
|
||||||
required file: PathBuf
|
required file: PathBuf
|
||||||
};
|
};
|
||||||
@@ -167,8 +169,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
)?;
|
)?;
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
|
||||||
let mut main_area = Tui::main_layout(&term.get_frame());
|
let mut fullscreen = flags.fullscreen.unwrap_or_default();
|
||||||
tui_tx.send(RenderNotif::Area(main_area[1]))?;
|
let mut main_area = Tui::main_layout(&term.get_frame(), fullscreen);
|
||||||
|
tui_tx.send(RenderNotif::Area(main_area.page_area))?;
|
||||||
|
|
||||||
let mut tui_rx = tui_rx.into_stream();
|
let mut tui_rx = tui_rx.into_stream();
|
||||||
let mut from_converter = from_converter.into_stream();
|
let mut from_converter = from_converter.into_stream();
|
||||||
@@ -191,7 +194,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
to_converter.send(ConverterMsg::GoToPage(page))?;
|
to_converter.send(ConverterMsg::GoToPage(page))?;
|
||||||
},
|
},
|
||||||
InputAction::Search(term) => tui_tx.send(RenderNotif::Search(term))?,
|
InputAction::Search(term) => tui_tx.send(RenderNotif::Search(term))?,
|
||||||
InputAction::Invert => tui_tx.send(RenderNotif::Invert)?
|
InputAction::Invert => tui_tx.send(RenderNotif::Invert)?,
|
||||||
|
InputAction::Fullscreen => fullscreen = !fullscreen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -219,10 +223,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_area = Tui::main_layout(&term.get_frame());
|
let new_area = Tui::main_layout(&term.get_frame(), fullscreen);
|
||||||
if new_area != main_area {
|
if new_area != main_area {
|
||||||
main_area = new_area;
|
main_area = new_area;
|
||||||
tui_tx.send(RenderNotif::Area(main_area[1]))?;
|
tui_tx.send(RenderNotif::Area(main_area.page_area))?;
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-25
@@ -136,26 +136,26 @@ pub fn start_rendering(
|
|||||||
fill_default::<PrevRender>(&mut rendered, n_pages);
|
fill_default::<PrevRender>(&mut rendered, n_pages);
|
||||||
let mut start_point = 0;
|
let mut start_point = 0;
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is kinda a weird way of doing this, but if we get a notification that the area
|
// This is kinda a weird way of doing this, but if we get a notification that the area
|
||||||
// changed, we want to start re-rending all of the pages, but we don't want to reload the
|
// changed, we want to start re-rending all of the pages, but we don't want to reload the
|
||||||
// document. If there was a mechanism to say 'start this for-loop over' then I would do
|
// 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
|
// that, but I don't think such a thing exists, so this is our attempt
|
||||||
'render_pages: loop {
|
'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
|
// 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
|
// rendering the list of pages or we're all done
|
||||||
macro_rules! handle_notif {
|
macro_rules! handle_notif {
|
||||||
@@ -170,17 +170,9 @@ pub fn start_rendering(
|
|||||||
continue 'render_pages;
|
continue 'render_pages;
|
||||||
}
|
}
|
||||||
RenderNotif::Area(new_area) => {
|
RenderNotif::Area(new_area) => {
|
||||||
let bigger =
|
|
||||||
new_area.width > area.width || new_area.height > area.height;
|
|
||||||
preserved_area = Some(new_area);
|
preserved_area = Some(new_area);
|
||||||
// we only want to re-render pages if the new area is greater than the old
|
fill_default(&mut rendered, n_pages);
|
||||||
// one, 'cause then we might need sharper images to make it all look good.
|
continue 'render_pages;
|
||||||
// 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) => {
|
RenderNotif::JumpToPage(page) => {
|
||||||
start_point = page;
|
start_point = page;
|
||||||
|
|||||||
+124
-102
@@ -1,4 +1,4 @@
|
|||||||
use std::{borrow::Cow, io::stdout, num::NonZeroUsize, rc::Rc};
|
use std::{borrow::Cow, io::stdout, num::NonZeroUsize};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{Event, KeyCode, KeyModifiers, MouseEventKind},
|
event::{Event, KeyCode, KeyModifiers, MouseEventKind},
|
||||||
@@ -79,6 +79,12 @@ struct RenderedInfo {
|
|||||||
num_results: Option<usize>
|
num_results: Option<usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub struct RenderLayout {
|
||||||
|
pub page_area: Rect,
|
||||||
|
pub top_and_bottom: Option<(Rect, Rect)>
|
||||||
|
}
|
||||||
|
|
||||||
impl Tui {
|
impl Tui {
|
||||||
pub fn new(name: String, max_wide: Option<NonZeroUsize>, r_to_l: bool) -> Tui {
|
pub fn new(name: String, max_wide: Option<NonZeroUsize>, r_to_l: bool) -> Tui {
|
||||||
Self {
|
Self {
|
||||||
@@ -93,119 +99,133 @@ impl Tui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_layout(frame: &Frame<'_>) -> Rc<[Rect]> {
|
pub fn main_layout(frame: &Frame<'_>, fullscreened: bool) -> RenderLayout {
|
||||||
Layout::default()
|
if fullscreened {
|
||||||
.constraints([
|
RenderLayout {
|
||||||
Constraint::Length(3),
|
page_area: frame.area(),
|
||||||
Constraint::Fill(1),
|
top_and_bottom: None
|
||||||
Constraint::Length(3)
|
}
|
||||||
])
|
} else {
|
||||||
.horizontal_margin(2)
|
let layout = Layout::default()
|
||||||
.vertical_margin(1)
|
.constraints([
|
||||||
.split(frame.area())
|
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]))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make a way to fill the width of the screen with one page and scroll down to view it
|
// 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<'_>, main_area: &[Rect]) {
|
pub fn render(&mut self, frame: &mut Frame<'_>, full_layout: &RenderLayout) {
|
||||||
if self.showing_help_msg {
|
if self.showing_help_msg {
|
||||||
self.render_help_msg(frame);
|
self.render_help_msg(frame);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let top_block = Block::new()
|
if let Some((top_area, bottom_area)) = full_layout.top_and_bottom {
|
||||||
.padding(Padding {
|
let top_block = Block::new()
|
||||||
right: 2,
|
.padding(Padding {
|
||||||
left: 2,
|
right: 2,
|
||||||
..Padding::default()
|
left: 2,
|
||||||
})
|
..Padding::default()
|
||||||
.borders(Borders::BOTTOM);
|
})
|
||||||
|
.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([
|
let top_layout = Layout::horizontal([
|
||||||
Constraint::Fill(1),
|
Constraint::Fill(1),
|
||||||
Constraint::Length(page_nums_text.len() as u16)
|
Constraint::Length(page_nums_text.len() as u16)
|
||||||
])
|
])
|
||||||
.split(top_area);
|
.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(top_block, top_area);
|
||||||
frame.render_widget(title, top_layout[0]);
|
frame.render_widget(title, top_layout[0]);
|
||||||
frame.render_widget(page_nums, top_layout[1]);
|
frame.render_widget(page_nums, top_layout[1]);
|
||||||
|
|
||||||
let bottom_block = Block::new()
|
let bottom_block = Block::new()
|
||||||
.padding(Padding {
|
.padding(Padding {
|
||||||
top: 1,
|
top: 1,
|
||||||
right: 2,
|
right: 2,
|
||||||
left: 2,
|
left: 2,
|
||||||
bottom: 0
|
bottom: 0
|
||||||
})
|
})
|
||||||
.borders(Borders::TOP);
|
.borders(Borders::TOP);
|
||||||
let bottom_area = bottom_block.inner(main_area[2]);
|
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() {
|
let rendered_str = if !self.rendered.is_empty() {
|
||||||
format!(
|
format!(
|
||||||
"Rendered: {}%",
|
"Rendered: {}%",
|
||||||
(self.rendered.iter().filter(|i| i.img.is_some()).count() * 100)
|
(self.rendered.iter().filter(|i| i.img.is_some()).count() * 100)
|
||||||
/ self.rendered.len()
|
/ self.rendered.len()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
let bottom_layout = Layout::horizontal([
|
let bottom_layout = Layout::horizontal([
|
||||||
Constraint::Fill(1),
|
Constraint::Fill(1),
|
||||||
Constraint::Length(rendered_str.len() as u16)
|
Constraint::Length(rendered_str.len() as u16)
|
||||||
])
|
])
|
||||||
.split(bottom_area);
|
.split(bottom_inside_block);
|
||||||
|
|
||||||
let rendered_span = Span::styled(&rendered_str, Style::new().fg(Color::Cyan));
|
let rendered_span = Span::styled(&rendered_str, Style::new().fg(Color::Cyan));
|
||||||
frame.render_widget(rendered_span, bottom_layout[1]);
|
frame.render_widget(rendered_span, bottom_layout[1]);
|
||||||
|
|
||||||
let (msg_str, color): (Cow<'_, str>, _) = match self.bottom_msg {
|
let (msg_str, color): (Cow<'_, str>, _) = match self.bottom_msg {
|
||||||
BottomMessage::Help => ("?: Show help page".into(), Color::Blue),
|
BottomMessage::Help => ("?: Show help page".into(), Color::Blue),
|
||||||
BottomMessage::Error(ref e) => (e.as_str().into(), Color::Red),
|
BottomMessage::Error(ref e) => (e.as_str().into(), Color::Red),
|
||||||
BottomMessage::Input(ref input_state) => (
|
BottomMessage::Input(ref input_state) => (
|
||||||
match input_state {
|
match input_state {
|
||||||
InputCommand::GoToPage(page) => format!("Go to: {page}"),
|
InputCommand::GoToPage(page) => format!("Go to: {page}"),
|
||||||
InputCommand::Search(s) => format!("Search: {s}")
|
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(),
|
.into(),
|
||||||
Color::Blue
|
Color::Blue
|
||||||
)
|
),
|
||||||
}
|
BottomMessage::SearchResults(ref term) => {
|
||||||
BottomMessage::Reloaded => ("Document was reloaded!".into(), Color::Blue)
|
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)
|
||||||
|
};
|
||||||
|
|
||||||
let span = Span::styled(msg_str, Style::new().fg(color));
|
let span = Span::styled(msg_str, Style::new().fg(color));
|
||||||
frame.render_widget(span, bottom_layout[0]);
|
frame.render_widget(span, bottom_layout[0]);
|
||||||
|
}
|
||||||
|
|
||||||
let mut img_area = main_area[1];
|
let mut img_area = full_layout.page_area;
|
||||||
|
|
||||||
let size = frame.area();
|
let size = frame.area();
|
||||||
if size == self.last_render.rect {
|
if size == self.last_render.rect {
|
||||||
@@ -424,6 +444,7 @@ impl Tui {
|
|||||||
self.showing_help_msg = true;
|
self.showing_help_msg = true;
|
||||||
Some(InputAction::Redraw)
|
Some(InputAction::Redraw)
|
||||||
}
|
}
|
||||||
|
'f' => Some(InputAction::Fullscreen),
|
||||||
'n' if self.page < self.rendered.len() - 1 => {
|
'n' if self.page < self.rendered.len() - 1 => {
|
||||||
// TODO: If we can't find one, then maybe like block until we've verified
|
// TODO: If we can't find one, then maybe like block until we've verified
|
||||||
// all the pages have been checked?
|
// all the pages have been checked?
|
||||||
@@ -661,25 +682,25 @@ impl Tui {
|
|||||||
|
|
||||||
static HELP_PAGE: &str = "\
|
static HELP_PAGE: &str = "\
|
||||||
l, h, left, right:
|
l, h, left, right:
|
||||||
Go forward/backwards a single page
|
Go forward/backwards a single page
|
||||||
j, k, down, up:
|
j, k, down, up:
|
||||||
Go forwards/backwards a screen's worth of pages
|
Go forwards/backwards a screen's worth of pages
|
||||||
q, esc:
|
q, esc:
|
||||||
Quit
|
Quit
|
||||||
g:
|
g:
|
||||||
Go to specific page (type numbers after 'g')
|
Go to specific page (type numbers after 'g')
|
||||||
/:
|
/:
|
||||||
Search
|
Search
|
||||||
n, N:
|
n, N:
|
||||||
Next/Previous search result
|
Next/Previous search result
|
||||||
i:
|
i:
|
||||||
Invert colors
|
Invert colors
|
||||||
f:
|
f:
|
||||||
Remove borders/fullscreen
|
Remove borders/fullscreen
|
||||||
?:
|
?:
|
||||||
Show this page
|
Show this page
|
||||||
ctrl+z:
|
ctrl+z:
|
||||||
Suspend & background tdf \
|
Suspend & background tdf \
|
||||||
";
|
";
|
||||||
|
|
||||||
pub enum InputAction {
|
pub enum InputAction {
|
||||||
@@ -687,7 +708,8 @@ pub enum InputAction {
|
|||||||
JumpingToPage(usize),
|
JumpingToPage(usize),
|
||||||
Search(String),
|
Search(String),
|
||||||
QuitApp,
|
QuitApp,
|
||||||
Invert
|
Invert,
|
||||||
|
Fullscreen
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
|||||||
Reference in New Issue
Block a user