Implement fullscreen functionality

This commit is contained in:
itsjunetime
2025-03-01 18:27:34 -07:00
parent d2be289e80
commit 1eee193d44
4 changed files with 151 additions and 133 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)]