Add color mode feature, rendering e.g. in dark mode
This commit is contained in:
parent
c8795588d8
commit
b5239e14b6
30
src/cache.rs
30
src/cache.rs
@ -1,4 +1,4 @@
|
|||||||
use crate::draw;
|
use crate::{color_mode::{self, ColorMode}, draw};
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use glib::timeout_future;
|
use glib::timeout_future;
|
||||||
use gtk::{gdk::Texture, prelude::TextureExt};
|
use gtk::{gdk::Texture, prelude::TextureExt};
|
||||||
@ -19,15 +19,17 @@ pub struct PageCache {
|
|||||||
max_num_stored_pages: usize,
|
max_num_stored_pages: usize,
|
||||||
pages: BTreeMap<usize, Rc<MyPageType>>,
|
pages: BTreeMap<usize, Rc<MyPageType>>,
|
||||||
last_requested_page_number: PageNumber,
|
last_requested_page_number: PageNumber,
|
||||||
|
color_mode: ColorMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageCache {
|
impl PageCache {
|
||||||
pub fn new(document: Document, max_num_stored_pages: usize) -> Self {
|
pub fn new(document: Document, max_num_stored_pages: usize, color_mode : ColorMode) -> Self {
|
||||||
PageCache {
|
PageCache {
|
||||||
document,
|
document,
|
||||||
max_num_stored_pages,
|
max_num_stored_pages,
|
||||||
pages: BTreeMap::new(),
|
pages: BTreeMap::new(),
|
||||||
last_requested_page_number: 0,
|
last_requested_page_number: 0,
|
||||||
|
color_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +72,7 @@ impl PageCache {
|
|||||||
|
|
||||||
if let Some(page) = self.document.page(page_number as i32) {
|
if let Some(page) = self.document.page(page_number as i32) {
|
||||||
let pages = vec![Rc::new(page)];
|
let pages = vec![Rc::new(page)];
|
||||||
let texture = draw::draw_pages_to_texture(&pages, height);
|
let texture = draw::draw_pages_to_texture(&pages, height, &self.color_mode);
|
||||||
let page = Rc::new(texture);
|
let page = Rc::new(texture);
|
||||||
|
|
||||||
// Overwrite page with lower resolution if exists
|
// Overwrite page with lower resolution if exists
|
||||||
@ -162,6 +164,9 @@ pub struct CachePageCommand {
|
|||||||
height: i32,
|
height: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CacheKillSignal;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RetrievePagesCommand {
|
pub enum RetrievePagesCommand {
|
||||||
GetCurrentTwoPages { page_left_number: PageNumber },
|
GetCurrentTwoPages { page_left_number: PageNumber },
|
||||||
@ -186,6 +191,7 @@ pub struct SyncCacheCommandChannel {
|
|||||||
retrieve_commands: Vec<RetrievePagesCommand>,
|
retrieve_commands: Vec<RetrievePagesCommand>,
|
||||||
cache_commands: VecDeque<CachePageCommand>,
|
cache_commands: VecDeque<CachePageCommand>,
|
||||||
priority_cache_commands: Vec<CachePageCommand>,
|
priority_cache_commands: Vec<CachePageCommand>,
|
||||||
|
kill_signals : Vec<CacheKillSignal>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SyncCacheCommandSender {
|
pub struct SyncCacheCommandSender {
|
||||||
@ -193,6 +199,7 @@ pub struct SyncCacheCommandSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct SyncCacheCommandReceiver {
|
pub struct SyncCacheCommandReceiver {
|
||||||
|
cache_closed : bool,
|
||||||
channel: Rc<RefCell<SyncCacheCommandChannel>>,
|
channel: Rc<RefCell<SyncCacheCommandChannel>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,13 +209,14 @@ impl SyncCacheCommandChannel {
|
|||||||
retrieve_commands: Vec::new(),
|
retrieve_commands: Vec::new(),
|
||||||
cache_commands: VecDeque::new(),
|
cache_commands: VecDeque::new(),
|
||||||
priority_cache_commands: Vec::new(),
|
priority_cache_commands: Vec::new(),
|
||||||
|
kill_signals: Vec::new(),
|
||||||
};
|
};
|
||||||
let channel = Rc::new(RefCell::new(channel));
|
let channel = Rc::new(RefCell::new(channel));
|
||||||
|
|
||||||
let sender = SyncCacheCommandSender {
|
let sender = SyncCacheCommandSender {
|
||||||
channel: Rc::clone(&channel),
|
channel: Rc::clone(&channel),
|
||||||
};
|
};
|
||||||
let receiver = SyncCacheCommandReceiver { channel };
|
let receiver = SyncCacheCommandReceiver { cache_closed: false, channel };
|
||||||
(sender, receiver)
|
(sender, receiver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,6 +254,10 @@ impl SyncCacheCommandSender {
|
|||||||
.push_back(CachePageCommand { page, height });
|
.push_back(CachePageCommand { page, height });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_kill_signal(&self) {
|
||||||
|
self.channel.borrow_mut().kill_signals.push(CacheKillSignal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncCacheCommandReceiver {
|
impl SyncCacheCommandReceiver {
|
||||||
@ -255,6 +267,10 @@ impl SyncCacheCommandReceiver {
|
|||||||
|
|
||||||
pub fn receive_most_important_command(&self) -> Option<CacheCommand> {
|
pub fn receive_most_important_command(&self) -> Option<CacheCommand> {
|
||||||
let mut channel = self.channel.borrow_mut();
|
let mut channel = self.channel.borrow_mut();
|
||||||
|
if let Some(command) = channel.kill_signals.pop() {
|
||||||
|
// self.cache_closed = true;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
if let Some(command) = channel.priority_cache_commands.pop() {
|
if let Some(command) = channel.priority_cache_commands.pop() {
|
||||||
return Some(CacheCommand::Cache(command));
|
return Some(CacheCommand::Cache(command));
|
||||||
} else if let Some(command) = channel.retrieve_commands.pop() {
|
} else if let Some(command) = channel.retrieve_commands.pop() {
|
||||||
@ -266,17 +282,17 @@ impl SyncCacheCommandReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_sync_cache<F>(document: Document, receiver: F) -> SyncCacheCommandSender
|
pub fn spawn_sync_cache<F>(document: Document, color_mode:ColorMode,receiver: F) -> SyncCacheCommandSender
|
||||||
where
|
where
|
||||||
F: Fn(CacheResponse) + 'static,
|
F: Fn(CacheResponse) + 'static,
|
||||||
{
|
{
|
||||||
let (command_sender, command_receiver) = SyncCacheCommandChannel::open();
|
let (command_sender, command_receiver) = SyncCacheCommandChannel::open();
|
||||||
|
|
||||||
let mut cache = PageCache::new(document, 30);
|
let mut cache = PageCache::new(document, 30, color_mode);
|
||||||
|
|
||||||
// Besides the name, it is not in another thread
|
// Besides the name, it is not in another thread
|
||||||
glib::spawn_future_local(async move {
|
glib::spawn_future_local(async move {
|
||||||
while command_receiver.is_channel_open() {
|
while command_receiver.is_channel_open() && !command_receiver.cache_closed {
|
||||||
// Add delay to tell gtk to give rendering priority
|
// Add delay to tell gtk to give rendering priority
|
||||||
timeout_future(Duration::from_millis(1)).await;
|
timeout_future(Duration::from_millis(1)).await;
|
||||||
|
|
||||||
|
131
src/color_mode.rs
Normal file
131
src/color_mode.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use cairo::ImageSurface;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ColorMode {
|
||||||
|
Normal,
|
||||||
|
Dark,
|
||||||
|
Sepia,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorMode {
|
||||||
|
fn transform_color(&self, r: u8, g: u8, b: u8) -> (u8, u8, u8) {
|
||||||
|
// l of black is 0.13 (approx 0x22)
|
||||||
|
match self {
|
||||||
|
ColorMode::Normal => (r, g, b),
|
||||||
|
ColorMode::Dark => {
|
||||||
|
let (mut h, mut s, mut l) = rgb_to_hsl(r, g, b);
|
||||||
|
l = (1.0 - l) * (1.0 - 0.13) + 0.13;
|
||||||
|
hsl_to_rgb(h, s, l)
|
||||||
|
}
|
||||||
|
ColorMode::Sepia => {
|
||||||
|
let (mut h, mut s, mut l) = rgb_to_hsl(r, g, b);
|
||||||
|
h = h + 30.0;
|
||||||
|
if h < 0.0 {
|
||||||
|
h += 360.0;
|
||||||
|
}
|
||||||
|
s += 0.2;
|
||||||
|
if s > 1.0 {
|
||||||
|
s = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
l = l * (1.0 - 0.13);
|
||||||
|
hsl_to_rgb(h, s, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply color mode transformation to an RGB24 surface.
|
||||||
|
pub fn apply_to_rgb24_surface(&self, surface: &mut ImageSurface) {
|
||||||
|
let width = surface.width();
|
||||||
|
let height = surface.height();
|
||||||
|
let stride = surface.stride();
|
||||||
|
|
||||||
|
let mut data = surface.data().expect("Failed to get source surface data");
|
||||||
|
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
let offset = (y * stride + x * 4) as usize; // Rgb24: 3 bytes per pixel (R, G, B)
|
||||||
|
|
||||||
|
// Read source pixel
|
||||||
|
let r = data[offset + 2];
|
||||||
|
let g = data[offset + 1];
|
||||||
|
let b = data[offset];
|
||||||
|
|
||||||
|
// Apply color transformation
|
||||||
|
let (tr, tg, tb) = self.transform_color(r, g, b);
|
||||||
|
|
||||||
|
// Write transformed pixel to destination surface
|
||||||
|
data[offset + 2] = tr;
|
||||||
|
data[offset + 1] = tg;
|
||||||
|
data[offset] = tb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// h from 0 to 360
|
||||||
|
// s from 0 to 1
|
||||||
|
// l from 0 to 1
|
||||||
|
fn rgb_to_hsl(r: u8, g: u8, b: u8) -> (f64, f64, f64) {
|
||||||
|
let r = r as f64 / 255.0;
|
||||||
|
let g = g as f64 / 255.0;
|
||||||
|
let b = b as f64 / 255.0;
|
||||||
|
|
||||||
|
let max = r.max(g).max(b);
|
||||||
|
let min = r.min(g).min(b);
|
||||||
|
let delta = max - min;
|
||||||
|
|
||||||
|
// Calculate Lightness
|
||||||
|
let l = (max + min) / 2.0;
|
||||||
|
|
||||||
|
// Calculate Saturation
|
||||||
|
let s = if delta == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else if l < 0.5 {
|
||||||
|
delta / (max + min)
|
||||||
|
} else {
|
||||||
|
delta / (2.0 - max - min)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate Hue
|
||||||
|
let h = if delta == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else if max == r {
|
||||||
|
60.0 * (((g - b) / delta) % 6.0)
|
||||||
|
} else if max == g {
|
||||||
|
60.0 * (((b - r) / delta) + 2.0)
|
||||||
|
} else {
|
||||||
|
60.0 * (((r - g) / delta) + 4.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
let h = if h < 0.0 { h + 360.0 } else { h };
|
||||||
|
|
||||||
|
(h, s, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hsl_to_rgb(h: f64, s: f64, l: f64) -> (u8, u8, u8) {
|
||||||
|
let c = (1.0 - (2.0 * l - 1.0).abs()) * s; // Chroma
|
||||||
|
let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
|
||||||
|
let m = l - c / 2.0;
|
||||||
|
|
||||||
|
let (r, g, b) = if (0.0..60.0).contains(&h) {
|
||||||
|
(c, x, 0.0)
|
||||||
|
} else if (60.0..120.0).contains(&h) {
|
||||||
|
(x, c, 0.0)
|
||||||
|
} else if (120.0..180.0).contains(&h) {
|
||||||
|
(0.0, c, x)
|
||||||
|
} else if (180.0..240.0).contains(&h) {
|
||||||
|
(0.0, x, c)
|
||||||
|
} else if (240.0..300.0).contains(&h) {
|
||||||
|
(x, 0.0, c)
|
||||||
|
} else {
|
||||||
|
(c, 0.0, x)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert back to [0, 255]
|
||||||
|
(
|
||||||
|
((r + m) * 255.0).round() as u8,
|
||||||
|
((g + m) * 255.0).round() as u8,
|
||||||
|
((b + m) * 255.0).round() as u8,
|
||||||
|
)
|
||||||
|
}
|
20
src/draw.rs
20
src/draw.rs
@ -1,12 +1,18 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use cairo::{Context, ImageSurface};
|
use cairo::{Context, ImageSurface, ImageSurfaceData};
|
||||||
use glib::Bytes;
|
use glib::Bytes;
|
||||||
use gtk::gdk::Texture;
|
use gtk::gdk::Texture;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use poppler::Page;
|
use poppler::Page;
|
||||||
|
|
||||||
pub fn draw_pages_to_texture(pages: &[Rc<Page>], area_height: i32) -> Texture {
|
use crate::color_mode::ColorMode;
|
||||||
|
|
||||||
|
pub fn draw_pages_to_texture(
|
||||||
|
pages: &[Rc<Page>],
|
||||||
|
area_height: i32,
|
||||||
|
color_mode: &ColorMode,
|
||||||
|
) -> Texture {
|
||||||
let area_height = i32::max(100, area_height);
|
let area_height = i32::max(100, area_height);
|
||||||
let total_width_normalized: f64 = pages
|
let total_width_normalized: f64 = pages
|
||||||
.iter()
|
.iter()
|
||||||
@ -15,9 +21,13 @@ pub fn draw_pages_to_texture(pages: &[Rc<Page>], area_height: i32) -> Texture {
|
|||||||
.sum();
|
.sum();
|
||||||
let area_width = (total_width_normalized * area_height as f64 + 0.5) as i32;
|
let area_width = (total_width_normalized * area_height as f64 + 0.5) as i32;
|
||||||
|
|
||||||
let surface = ImageSurface::create(cairo::Format::Rgb24, area_width, area_height).unwrap();
|
let mut surface = ImageSurface::create(cairo::Format::Rgb24, area_width, area_height).unwrap();
|
||||||
let context = Context::new(&surface).unwrap();
|
{
|
||||||
draw_pages(pages, &context, area_width, area_height);
|
let context = Context::new(&surface).unwrap();
|
||||||
|
draw_pages(pages, &context, area_width, area_height);
|
||||||
|
} // Assure the context gets dropped and the data of the surface can be accessed again
|
||||||
|
|
||||||
|
color_mode.apply_to_rgb24_surface(&mut surface);
|
||||||
|
|
||||||
let mut stream: Vec<u8> = Vec::new();
|
let mut stream: Vec<u8> = Vec::new();
|
||||||
surface.write_to_png(&mut stream).unwrap();
|
surface.write_to_png(&mut stream).unwrap();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
mod cache;
|
mod cache;
|
||||||
|
mod color_mode;
|
||||||
mod draw;
|
mod draw;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ fn main() {
|
|||||||
app.connect_activate(move |app| {
|
app.connect_activate(move |app| {
|
||||||
let ui = build_ui(app);
|
let ui = build_ui(app);
|
||||||
if let Some(file) = cli.file.as_ref() {
|
if let Some(file) = cli.file.as_ref() {
|
||||||
ui::load_document(file, Rc::clone(&ui));
|
ui::load_document(file, Rc::clone(&ui), 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
97
src/ui.rs
97
src/ui.rs
@ -7,11 +7,14 @@ use std::{
|
|||||||
|
|
||||||
use gtk::{
|
use gtk::{
|
||||||
glib, Application, ApplicationWindow, Box, Button, FileChooserAction, FileChooserDialog,
|
glib, Application, ApplicationWindow, Box, Button, FileChooserAction, FileChooserDialog,
|
||||||
HeaderBar, Label, Overlay, Picture, ResponseType,
|
HeaderBar, Label, Overlay, Picture, ResponseType, ToggleButton,
|
||||||
};
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
use crate::cache::{self, PageNumber, SyncCacheCommandSender};
|
use crate::{
|
||||||
|
cache::{self, PageNumber, SyncCacheCommandSender},
|
||||||
|
color_mode::{self, ColorMode},
|
||||||
|
};
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
@ -20,6 +23,7 @@ pub struct Ui {
|
|||||||
bottom_bar: gtk::Box,
|
bottom_bar: gtk::Box,
|
||||||
header_bar: gtk::HeaderBar,
|
header_bar: gtk::HeaderBar,
|
||||||
page_indicator: gtk::Label,
|
page_indicator: gtk::Label,
|
||||||
|
pub color_mode: ColorMode,
|
||||||
pub app_wrapper: Overlay,
|
pub app_wrapper: Overlay,
|
||||||
pub image_container: Box,
|
pub image_container: Box,
|
||||||
pub image_left: Picture,
|
pub image_left: Picture,
|
||||||
@ -29,14 +33,16 @@ pub struct Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct DocumentCanvas {
|
pub struct DocumentCanvas {
|
||||||
|
pub document_path: PathBuf,
|
||||||
pub current_page_number: usize,
|
pub current_page_number: usize,
|
||||||
pub num_pages: Option<usize>,
|
pub num_pages: Option<usize>,
|
||||||
page_cache_sender: SyncCacheCommandSender,
|
page_cache_sender: SyncCacheCommandSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentCanvas {
|
impl DocumentCanvas {
|
||||||
pub fn new(page_cache_sender: SyncCacheCommandSender) -> Self {
|
pub fn new(document_path: PathBuf, page_cache_sender: SyncCacheCommandSender) -> Self {
|
||||||
DocumentCanvas {
|
DocumentCanvas {
|
||||||
|
document_path,
|
||||||
current_page_number: 0,
|
current_page_number: 0,
|
||||||
num_pages: None,
|
num_pages: None,
|
||||||
page_cache_sender,
|
page_cache_sender,
|
||||||
@ -66,6 +72,10 @@ impl DocumentCanvas {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn purge_cache(&self) {
|
||||||
|
self.page_cache_sender.send_kill_signal();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cache_surrounding_pages(&self, area_height: i32) {
|
pub fn cache_surrounding_pages(&self, area_height: i32) {
|
||||||
self.page_cache_sender.send_cache_commands(
|
self.page_cache_sender.send_cache_commands(
|
||||||
&[
|
&[
|
||||||
@ -206,6 +216,31 @@ impl Ui {
|
|||||||
pub fn build(app: &Application) -> Rc<RefCell<Ui>> {
|
pub fn build(app: &Application) -> Rc<RefCell<Ui>> {
|
||||||
debug!("building ui");
|
debug!("building ui");
|
||||||
let open_file_button = Button::from_icon_name("document-open");
|
let open_file_button = Button::from_icon_name("document-open");
|
||||||
|
let normal_color_mode_button = ToggleButton::builder().label("Std").active(true).build();
|
||||||
|
let dark_color_mode_button = ToggleButton::builder()
|
||||||
|
.label("Dark")
|
||||||
|
.group(&normal_color_mode_button)
|
||||||
|
.build();
|
||||||
|
let sepia_color_mode_button = ToggleButton::builder()
|
||||||
|
.label("Sepia")
|
||||||
|
.group(&normal_color_mode_button)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let button_container = Box::builder()
|
||||||
|
.spacing(5)
|
||||||
|
.hexpand(true)
|
||||||
|
.orientation(gtk::Orientation::Horizontal)
|
||||||
|
.build();
|
||||||
|
let color_mode_button_container = Box::builder()
|
||||||
|
.spacing(0)
|
||||||
|
.hexpand(true)
|
||||||
|
.orientation(gtk::Orientation::Horizontal)
|
||||||
|
.build();
|
||||||
|
color_mode_button_container.append(&normal_color_mode_button);
|
||||||
|
color_mode_button_container.append(&dark_color_mode_button);
|
||||||
|
color_mode_button_container.append(&sepia_color_mode_button);
|
||||||
|
button_container.append(&open_file_button);
|
||||||
|
button_container.append(&color_mode_button_container);
|
||||||
|
|
||||||
let image_container = Box::builder()
|
let image_container = Box::builder()
|
||||||
.spacing(0)
|
.spacing(0)
|
||||||
@ -260,10 +295,11 @@ impl Ui {
|
|||||||
image_right,
|
image_right,
|
||||||
document_canvas: None,
|
document_canvas: None,
|
||||||
last_touch_time: None,
|
last_touch_time: None,
|
||||||
|
color_mode: ColorMode::Normal,
|
||||||
};
|
};
|
||||||
let ui = Rc::new(RefCell::new(ui));
|
let ui = Rc::new(RefCell::new(ui));
|
||||||
|
|
||||||
ui.borrow().header_bar.pack_start(&open_file_button);
|
ui.borrow().header_bar.pack_start(&button_container);
|
||||||
ui.borrow().app_wrapper.add_overlay(&ui.borrow().bottom_bar);
|
ui.borrow().app_wrapper.add_overlay(&ui.borrow().bottom_bar);
|
||||||
ui.borrow().bottom_bar.append(&ui.borrow().page_indicator);
|
ui.borrow().bottom_bar.append(&ui.borrow().page_indicator);
|
||||||
|
|
||||||
@ -291,12 +327,53 @@ impl Ui {
|
|||||||
choose_file(Rc::clone(&ui), &ui.borrow().window);
|
choose_file(Rc::clone(&ui), &ui.borrow().window);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
normal_color_mode_button.connect_clicked(
|
||||||
|
glib::clone!(@strong ui => @default-panic, move |_button| {
|
||||||
|
switch_color_mode(Rc::clone(&ui), ColorMode::Normal);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
dark_color_mode_button.connect_clicked(
|
||||||
|
glib::clone!(@strong ui => @default-panic, move |_button| {
|
||||||
|
switch_color_mode(Rc::clone(&ui), ColorMode::Dark);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
sepia_color_mode_button.connect_clicked(
|
||||||
|
glib::clone!(@strong ui => @default-panic, move |_button| {
|
||||||
|
switch_color_mode(Rc::clone(&ui), ColorMode::Sepia);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
ui.borrow().window.present();
|
ui.borrow().window.present();
|
||||||
ui
|
ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn switch_color_mode(ui: Rc<RefCell<Ui>>, color_mode: ColorMode) {
|
||||||
|
ui.borrow_mut().color_mode = color_mode;
|
||||||
|
|
||||||
|
if ui.borrow().document_canvas.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.borrow().document_canvas.as_ref().unwrap().purge_cache();
|
||||||
|
|
||||||
|
let path = ui
|
||||||
|
.borrow()
|
||||||
|
.document_canvas
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.document_path
|
||||||
|
.clone();
|
||||||
|
let current_page_number = ui
|
||||||
|
.borrow()
|
||||||
|
.document_canvas
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.current_page_number;
|
||||||
|
|
||||||
|
load_document(path, Rc::clone(&ui), current_page_number);
|
||||||
|
}
|
||||||
|
|
||||||
fn choose_file(ui: Rc<RefCell<Ui>>, window: &ApplicationWindow) {
|
fn choose_file(ui: Rc<RefCell<Ui>>, window: &ApplicationWindow) {
|
||||||
let filechooser = FileChooserDialog::builder()
|
let filechooser = FileChooserDialog::builder()
|
||||||
.title("Choose a PDF...")
|
.title("Choose a PDF...")
|
||||||
@ -309,23 +386,28 @@ fn choose_file(ui: Rc<RefCell<Ui>>, window: &ApplicationWindow) {
|
|||||||
filechooser.connect_response(move |d, response| {
|
filechooser.connect_response(move |d, response| {
|
||||||
if response == ResponseType::Accept {
|
if response == ResponseType::Accept {
|
||||||
let path = d.file().unwrap().path().unwrap();
|
let path = d.file().unwrap().path().unwrap();
|
||||||
load_document(path, Rc::clone(&ui));
|
|
||||||
|
ui.borrow().document_canvas.as_ref().unwrap().purge_cache();
|
||||||
|
|
||||||
|
load_document(path, Rc::clone(&ui), 0);
|
||||||
}
|
}
|
||||||
d.destroy();
|
d.destroy();
|
||||||
});
|
});
|
||||||
filechooser.show()
|
filechooser.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_document(file: impl AsRef<Path>, ui: Rc<RefCell<Ui>>) {
|
pub fn load_document(file: impl AsRef<Path>, ui: Rc<RefCell<Ui>>, initial_page_number: usize) {
|
||||||
debug!("Loading file...");
|
debug!("Loading file...");
|
||||||
// TODO: catch errors, maybe show error dialog
|
// TODO: catch errors, maybe show error dialog
|
||||||
let path: PathBuf = file.as_ref().to_path_buf();
|
let path: PathBuf = file.as_ref().to_path_buf();
|
||||||
let uri = format!("file://{}", path.to_str().unwrap());
|
let uri = format!("file://{}", path.to_str().unwrap());
|
||||||
let document = poppler::Document::from_file(&uri, None).unwrap();
|
let document = poppler::Document::from_file(&uri, None).unwrap();
|
||||||
let num_pages = document.n_pages() as usize;
|
let num_pages = document.n_pages() as usize;
|
||||||
|
let color_mode = ui.borrow().color_mode.clone();
|
||||||
|
|
||||||
let sender = cache::spawn_sync_cache(
|
let sender = cache::spawn_sync_cache(
|
||||||
document,
|
document,
|
||||||
|
color_mode,
|
||||||
clone!(@weak ui => move |cache_response| match cache_response {
|
clone!(@weak ui => move |cache_response| match cache_response {
|
||||||
cache::CacheResponse::SinglePageRetrieved { page } => {
|
cache::CacheResponse::SinglePageRetrieved { page } => {
|
||||||
ui.borrow_mut().image_left.set_paintable(Some(page.as_ref()));
|
ui.borrow_mut().image_left.set_paintable(Some(page.as_ref()));
|
||||||
@ -360,8 +442,9 @@ pub fn load_document(file: impl AsRef<Path>, ui: Rc<RefCell<Ui>>) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut document_canvas = DocumentCanvas::new(sender);
|
let mut document_canvas = DocumentCanvas::new(path, sender);
|
||||||
document_canvas.num_pages = Some(num_pages);
|
document_canvas.num_pages = Some(num_pages);
|
||||||
|
document_canvas.current_page_number = initial_page_number;
|
||||||
document_canvas.cache_initial_pages(ui.borrow().image_container.height());
|
document_canvas.cache_initial_pages(ui.borrow().image_container.height());
|
||||||
|
|
||||||
ui.borrow_mut().document_canvas = Some(document_canvas);
|
ui.borrow_mut().document_canvas = Some(document_canvas);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user