diff --git a/.gitignore b/.gitignore index 5440084b..6636aff5 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ /plato-*.zip /thirdparty/*/* !/thirdparty/*/*kobo* +/.idea diff --git a/Cargo.lock b/Cargo.lock index 03005613..9816be29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "adler" version = "1.0.2" @@ -306,6 +308,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + [[package]] name = "globset" version = "0.4.6" @@ -707,6 +720,7 @@ dependencies = [ "paragraph-breaker", "percent-encoding", "png", + "rand", "rand_core", "rand_xoshiro", "regex", @@ -737,6 +751,12 @@ dependencies = [ "miniz_oxide 0.3.7", ] +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -767,11 +787,45 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] [[package]] name = "rand_xoshiro" diff --git a/Cargo.toml b/Cargo.toml index fd23ad29..9fd5ee85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,11 @@ name = "article_fetcher" path = "src/fetcher.rs" required-features = ["fetcher"] +[[bin]] +name = "xkcd" +path = "src/xkcd.rs" +required-features = ["xkcd"] + [dependencies] bitflags = "1.2.1" downcast-rs = "1.2.0" @@ -55,6 +60,7 @@ fxhash = "0.2.1" rand_core = "0.6.2" rand_xoshiro = "0.6.0" percent-encoding = "2.1.0" +rand = "0.8.3" [dependencies.getopts] version = "0.2.21" @@ -82,3 +88,4 @@ optional = true importer = ["getopts"] emulator = ["sdl2"] fetcher = ["reqwest", "signal-hook"] +xkcd = ["reqwest", "signal-hook"] diff --git a/src/app.rs b/src/app.rs index d8c9ad51..5778a015 100644 --- a/src/app.rs +++ b/src/app.rs @@ -32,7 +32,7 @@ use crate::input::{DeviceEvent, PowerSource, ButtonCode, ButtonStatus, VAL_RELEA use crate::input::{raw_events, device_events, usb_events, display_rotate_event, button_scheme_event}; use crate::gesture::{GestureEvent, gesture_events}; use crate::helpers::{load_json, load_toml, save_toml, IsHidden}; -use crate::settings::{ButtonScheme, Settings, SETTINGS_PATH, RotationLock, IntermKind}; +use crate::settings::{ButtonScheme, Settings, SETTINGS_PATH, RotationLock}; use crate::frontlight::{Frontlight, StandardFrontlight, NaturalFrontlight, PremixedFrontlight}; use crate::lightsensor::{LightSensor, KoboLightSensor}; use crate::battery::{Battery, KoboBattery}; @@ -40,12 +40,13 @@ use crate::geom::{Rectangle, DiagDir, Region}; use crate::view::home::Home; use crate::view::reader::Reader; use crate::view::dialog::Dialog; -use crate::view::intermission::Intermission; +use crate::view::intermission::{Intermission, IntermKind}; use crate::view::notification::Notification; use crate::device::{CURRENT_DEVICE, Orientation, FrontlightKind}; use crate::library::Library; use crate::font::Fonts; use crate::rtc::Rtc; +use crate::metadata::Info; pub const APP_NAME: &str = "Plato"; const FB_DEVICE: &str = "/dev/fb0"; @@ -83,21 +84,38 @@ pub struct Context { pub covered: bool, pub shared: bool, pub online: bool, + pub last_read: Box, } impl Context { pub fn new(fb: Box, rtc: Option, library: Library, settings: Settings, fonts: Fonts, battery: Box, - frontlight: Box, lightsensor: Box) -> Context { + frontlight: Box, lightsensor: Box, last_read: Box) -> Context { let dims = fb.dims(); let rotation = CURRENT_DEVICE.transformed_rotation(fb.rotation()); let rng = Xoroshiro128Plus::seed_from_u64(Local::now().timestamp_nanos() as u64); - Context { fb, rtc, display: Display { dims, rotation }, - library, settings, fonts, dictionaries: BTreeMap::new(), - keyboard_layouts: BTreeMap::new(), input_history: FxHashMap::default(), - battery, frontlight, lightsensor, notification_index: 0, - kb_rect: Rectangle::default(), rng, plugged: false, covered: false, - shared: false, online: false } + Context { + fb, + rtc, + display: Display { dims, rotation }, + library, + settings, + fonts, + dictionaries: BTreeMap::new(), + keyboard_layouts: BTreeMap::new(), + input_history: FxHashMap::default(), + battery, + frontlight, + lightsensor, + notification_index: 0, + kb_rect: Rectangle::default(), + rng, + plugged: false, + covered: false, + shared: false, + online: false, + last_read + } } pub fn batch_import(&mut self) { @@ -116,7 +134,7 @@ impl Context { pub fn load_keyboard_layouts(&mut self) { let glob = Glob::new("**/*.json").unwrap().compile_matcher(); for entry in WalkDir::new(Path::new(KEYBOARD_LAYOUTS_DIRNAME)).min_depth(1) - .into_iter().filter_entry(|e| !e.is_hidden()) { + .into_iter().filter_entry(|e| !e.is_hidden()) { if entry.is_err() { continue; } @@ -134,7 +152,7 @@ impl Context { pub fn load_dictionaries(&mut self) { let glob = Glob::new("**/*.index").unwrap().compile_matcher(); for entry in WalkDir::new(Path::new(DICTIONARIES_DIRNAME)).min_depth(1) - .into_iter().filter_entry(|e| !e.is_hidden()) { + .into_iter().filter_entry(|e| !e.is_hidden()) { if entry.is_err() { continue; } @@ -165,7 +183,7 @@ impl Context { } let history = self.input_history.entry(id) - .or_insert_with(VecDeque::new); + .or_insert_with(VecDeque::new); if history.front().map(String::as_str) != Some(text) { history.push_front(text.to_string()); @@ -218,12 +236,12 @@ struct HistoryItem { fn build_context(fb: Box) -> Result { let rtc = Rtc::new(RTC_DEVICE) - .map_err(|e| eprintln!("Can't open RTC device: {:#}.", e)) - .ok(); + .map_err(|e| eprintln!("Can't open RTC device: {:#}.", e)) + .ok(); let path = Path::new(SETTINGS_PATH); let mut settings = load_toml::(path) - .map_err(|e| eprintln!("Can't load settings: {:#}.", e)) - .unwrap_or_default(); + .map_err(|e| eprintln!("Can't load settings: {:#}.", e)) + .unwrap_or_default(); if settings.libraries.is_empty() { return Err(format_err!("no libraries found")); @@ -249,15 +267,17 @@ fn build_context(fb: Box) -> Result { let levels = settings.frontlight_levels; let frontlight = match CURRENT_DEVICE.frontlight_kind() { FrontlightKind::Standard => Box::new(StandardFrontlight::new(levels.intensity) - .context("can't create standard frontlight")?) as Box, + .context("can't create standard frontlight")?) as Box, FrontlightKind::Natural => Box::new(NaturalFrontlight::new(levels.intensity, levels.warmth) - .context("can't create natural frontlight")?) as Box, + .context("can't create natural frontlight")?) as Box, FrontlightKind::Premixed => Box::new(PremixedFrontlight::new(levels.intensity, levels.warmth) - .context("can't create premixed frontlight")?) as Box, + .context("can't create premixed frontlight")?) as Box, }; + let last_read = Box::new(Info::default()); + Ok(Context::new(fb, rtc, library, settings, - fonts, battery, frontlight, lightsensor)) + fonts, battery, frontlight, lightsensor, last_read)) } fn schedule_task(id: TaskId, event: Event, delay: Duration, hub: &Sender, tasks: &mut Vec) { @@ -282,8 +302,8 @@ fn resume(id: TaskId, tasks: &mut Vec, view: &mut dyn View, hub: &Sender Result<(), Error> { let current_dir = env::current_dir()?; println!("{} is running on a Kobo {}.", APP_NAME, - CURRENT_DEVICE.model); + CURRENT_DEVICE.model); println!("The framebuffer resolution is {} by {}.", context.fb.rect().width(), - context.fb.rect().height()); + context.fb.rect().height()); let mut bus = VecDeque::with_capacity(4); @@ -446,33 +466,31 @@ pub fn run() -> Result<(), Error> { } else if tasks.iter().any(|task| task.id == TaskId::Suspend) { resume(TaskId::Suspend, &mut tasks, view.as_mut(), &tx, &mut rq, &mut context); } else { - view.handle_event(&Event::Suspend, &tx, &mut bus, &mut rq, &mut context); let interm = Intermission::new(context.fb.rect(), IntermKind::Suspend, &context); rq.add(RenderData::new(interm.id(), *interm.rect(), UpdateMode::Full)); schedule_task(TaskId::PrepareSuspend, Event::PrepareSuspend, PREPARE_SUSPEND_WAIT_DELAY, &tx, &mut tasks); view.children_mut().push(Box::new(interm) as Box); } - }, + } DeviceEvent::Button { code: ButtonCode::Light, status: ButtonStatus::Pressed, .. } => { tx.send(Event::ToggleFrontlight).ok(); - }, + } DeviceEvent::CoverOn => { context.covered = true; if !context.settings.sleep_cover || context.shared || - tasks.iter().any(|task| task.id == TaskId::PrepareSuspend || - task.id == TaskId::Suspend) { + tasks.iter().any(|task| task.id == TaskId::PrepareSuspend || + task.id == TaskId::Suspend) { continue; } - view.handle_event(&Event::Suspend, &tx, &mut bus, &mut rq, &mut context); let interm = Intermission::new(context.fb.rect(), IntermKind::Suspend, &context); rq.add(RenderData::new(interm.id(), *interm.rect(), UpdateMode::Full)); schedule_task(TaskId::PrepareSuspend, Event::PrepareSuspend, PREPARE_SUSPEND_WAIT_DELAY, &tx, &mut tasks); view.children_mut().push(Box::new(interm) as Box); - }, + } DeviceEvent::CoverOff => { context.covered = false; @@ -494,18 +512,18 @@ pub fn run() -> Result<(), Error> { } else if tasks.iter().any(|task| task.id == TaskId::Suspend) { resume(TaskId::Suspend, &mut tasks, view.as_mut(), &tx, &mut rq, &mut context); } - }, + } DeviceEvent::NetUp => { if tasks.iter().any(|task| task.id == TaskId::PrepareSuspend || - task.id == TaskId::Suspend) { + task.id == TaskId::Suspend) { continue; } let ip = Command::new("scripts/ip.sh").output() - .map(|o| String::from_utf8_lossy(&o.stdout).trim_end().to_string()) - .unwrap_or_default(); + .map(|o| String::from_utf8_lossy(&o.stdout).trim_end().to_string()) + .unwrap_or_default(); let essid = Command::new("scripts/essid.sh").output() - .map(|o| String::from_utf8_lossy(&o.stdout).trim_end().to_string()) - .unwrap_or_default(); + .map(|o| String::from_utf8_lossy(&o.stdout).trim_end().to_string()) + .unwrap_or_default(); let notif = Notification::new(format!("Network is up ({}, {}).", ip, essid), &tx, &mut rq, &mut context); context.online = true; @@ -516,7 +534,7 @@ pub fn run() -> Result<(), Error> { let (tx, _rx) = mpsc::channel(); entry.view.handle_event(&evt, &tx, &mut VecDeque::new(), &mut RenderQueue::new(), &mut context); } - }, + } DeviceEvent::Plug(power_source) => { if context.plugged { continue; @@ -538,7 +556,7 @@ pub fn run() -> Result<(), Error> { SUSPEND_WAIT_DELAY, &tx, &mut tasks); continue; } - }, + } PowerSource::Host => { if tasks.iter().any(|task| task.id == TaskId::PrepareSuspend) { resume(TaskId::PrepareSuspend, &mut tasks, view.as_mut(), &tx, &mut rq, &mut context); @@ -558,11 +576,11 @@ pub fn run() -> Result<(), Error> { } inactive_since = Instant::now(); - }, + } } tx.send(Event::BatteryTick).ok(); - }, + } DeviceEvent::Unplug(..) => { if !context.plugged { continue; @@ -576,13 +594,13 @@ pub fn run() -> Result<(), Error> { .ok(); let path = Path::new(SETTINGS_PATH); if let Ok(settings) = load_toml::(path) - .map_err(|e| eprintln!("Can't load settings: {:#}.", e)) { + .map_err(|e| eprintln!("Can't load settings: {:#}.", e)) { context.settings = settings; } if context.settings.wifi { Command::new("scripts/wifi-enable.sh") - .status() - .ok(); + .status() + .ok(); } if context.settings.frontlight { let levels = context.settings.frontlight_levels; @@ -618,37 +636,37 @@ pub fn run() -> Result<(), Error> { tx.send(Event::BatteryTick).ok(); } } - }, + } DeviceEvent::RotateScreen(n) => { if context.shared || tasks.iter().any(|task| task.id == TaskId::PrepareSuspend || - task.id == TaskId::Suspend) { + task.id == TaskId::Suspend) { continue; } if let Some(rotation_lock) = context.settings.rotation_lock { let orientation = CURRENT_DEVICE.orientation(n); if rotation_lock == RotationLock::Current || - (rotation_lock == RotationLock::Portrait && orientation == Orientation::Landscape) || - (rotation_lock == RotationLock::Landscape && orientation == Orientation::Portrait) { + (rotation_lock == RotationLock::Portrait && orientation == Orientation::Landscape) || + (rotation_lock == RotationLock::Landscape && orientation == Orientation::Portrait) { continue; } } tx.send(Event::Select(EntryId::Rotate(n))).ok(); - }, + } DeviceEvent::UserActivity if context.settings.auto_suspend > 0 => { inactive_since = Instant::now(); - }, + } _ => { handle_event(view.as_mut(), &evt, &tx, &mut bus, &mut rq, &mut context); } } - }, + } Event::CheckBattery => { schedule_task(TaskId::CheckBattery, Event::CheckBattery, BATTERY_REFRESH_INTERVAL, &tx, &mut tasks); if tasks.iter().any(|task| task.id == TaskId::PrepareSuspend || - task.id == TaskId::Suspend) { + task.id == TaskId::Suspend) { continue; } if let Ok(v) = context.battery.capacity() { @@ -662,7 +680,7 @@ pub fn run() -> Result<(), Error> { view.children_mut().push(Box::new(notif) as Box); } } - }, + } Event::PrepareSuspend => { tasks.retain(|task| task.id != TaskId::PrepareSuspend); updating.retain(|tok, _| context.fb.wait(*tok).is_err()); @@ -677,41 +695,41 @@ pub fn run() -> Result<(), Error> { } if context.settings.wifi { Command::new("scripts/wifi-disable.sh") - .status() - .ok(); + .status() + .ok(); context.online = false; } // https://github.com/koreader/koreader/commit/71afe36 schedule_task(TaskId::Suspend, Event::Suspend, SUSPEND_WAIT_DELAY, &tx, &mut tasks); - }, + } Event::Suspend => { if context.settings.auto_power_off > 0 { context.rtc.iter().for_each(|rtc| { rtc.set_alarm(context.settings.auto_power_off) - .map_err(|e| eprintln!("Can't set alarm: {:#}.", e)) - .ok(); + .map_err(|e| eprintln!("Can't set alarm: {:#}.", e)) + .ok(); }); } println!("{}", Local::now().format("Went to sleep on %B %-d, %Y at %H:%M.")); Command::new("scripts/suspend.sh") - .status() - .ok(); + .status() + .ok(); println!("{}", Local::now().format("Woke up on %B %-d, %Y at %H:%M.")); Command::new("scripts/resume.sh") - .status() - .ok(); + .status() + .ok(); inactive_since = Instant::now(); if context.settings.auto_power_off > 0 { if let Some(enabled) = context.rtc.as_ref() - .and_then(|rtc| rtc.is_alarm_enabled() - .map_err(|e| eprintln!("Can't get alarm: {:#}", e)) - .ok()) { + .and_then(|rtc| rtc.is_alarm_enabled() + .map_err(|e| eprintln!("Can't get alarm: {:#}", e)) + .ok()) { if enabled { context.rtc.iter().for_each(|rtc| { rtc.disable_alarm() - .map_err(|e| eprintln!("Can't disable alarm: {:#}.", e)) - .ok(); + .map_err(|e| eprintln!("Can't disable alarm: {:#}.", e)) + .ok(); }); } else { power_off(view.as_mut(), &mut history, &mut updating, &mut context); @@ -720,7 +738,7 @@ pub fn run() -> Result<(), Error> { } } } - }, + } Event::PrepareShare => { if context.shared { continue; @@ -742,7 +760,7 @@ pub fn run() -> Result<(), Error> { } let path = Path::new(SETTINGS_PATH); save_toml(&context.settings, path) - .map_err(|e| eprintln!("Can't save settings: {:#}.", e)).ok(); + .map_err(|e| eprintln!("Can't save settings: {:#}.", e)).ok(); context.library.flush(); if context.settings.frontlight { @@ -752,16 +770,15 @@ pub fn run() -> Result<(), Error> { } if context.settings.wifi { Command::new("scripts/wifi-disable.sh") - .status() - .ok(); + .status() + .ok(); context.online = false; } - let interm = Intermission::new(context.fb.rect(), IntermKind::Share, &context); rq.add(RenderData::new(interm.id(), *interm.rect(), UpdateMode::Full)); view.children_mut().push(Box::new(interm) as Box); tx.send(Event::Share).ok(); - }, + } Event::Share => { if context.shared { continue; @@ -769,14 +786,14 @@ pub fn run() -> Result<(), Error> { context.shared = true; Command::new("scripts/usb-enable.sh").status().ok(); - }, + } Event::Gesture(ge) => { match ge { GestureEvent::HoldButtonLong(ButtonCode::Power) => { power_off(view.as_mut(), &mut history, &mut updating, &mut context); exit_status = ExitStatus::PowerOff; break; - }, + } GestureEvent::MultiTap(mut points) => { if points[0].x > points[1].x { points.swap(0, 1); @@ -791,22 +808,22 @@ pub fn run() -> Result<(), Error> { match (r1, r2) { (Region::Corner(DiagDir::SouthWest), Region::Corner(DiagDir::NorthEast)) => { rq.add(RenderData::new(view.id(), context.fb.rect(), UpdateMode::Full)); - }, + } (Region::Corner(DiagDir::NorthWest), Region::Corner(DiagDir::SouthEast)) => { tx.send(Event::Select(EntryId::TakeScreenshot)).ok(); - }, + } _ => (), } - }, + } _ => { handle_event(view.as_mut(), &evt, &tx, &mut bus, &mut rq, &mut context); - }, + } } - }, + } Event::ToggleFrontlight => { context.set_frontlight(!context.settings.frontlight); view.handle_event(&Event::ToggleFrontlight, &tx, &mut bus, &mut rq, &mut context); - }, + } Event::Open(info) => { let rotation = context.display.rotation; let dithered = context.fb.dithered(); @@ -847,7 +864,7 @@ pub fn run() -> Result<(), Error> { context.fb.set_dithered(dithered); handle_event(view.as_mut(), &Event::Invalid(path), &tx, &mut bus, &mut rq, &mut context); } - }, + } Event::Select(EntryId::About) => { let dialog = Dialog::new(ViewId::AboutDialog, None, @@ -855,7 +872,7 @@ pub fn run() -> Result<(), Error> { &mut context); rq.add(RenderData::new(dialog.id(), *dialog.rect(), UpdateMode::Gui)); view.children_mut().push(Box::new(dialog) as Box); - }, + } Event::Select(EntryId::SystemInfo) => { view.children_mut().retain(|child| !child.is::()); let html = sys_info_as_html(); @@ -869,7 +886,7 @@ pub fn run() -> Result<(), Error> { dithered: context.fb.dithered(), }); view = next_view; - }, + } Event::OpenHtml(ref html, ref link_uri) => { view.children_mut().retain(|child| !child.is::()); let r = Reader::from_html(context.fb.rect(), html, link_uri.as_deref(), &tx, &mut context); @@ -882,7 +899,7 @@ pub fn run() -> Result<(), Error> { dithered: context.fb.dithered(), }); view = next_view; - }, + } Event::Select(EntryId::Launch(app_cmd)) => { view.children_mut().retain(|child| !child.is::()); let monochrome = context.fb.monochrome(); @@ -890,16 +907,16 @@ pub fn run() -> Result<(), Error> { AppCmd::Sketch => { context.fb.set_monochrome(true); Box::new(Sketch::new(context.fb.rect(), &mut rq, &mut context)) - }, + } AppCmd::Calculator => Box::new(Calculator::new(context.fb.rect(), &tx, &mut rq, &mut context)?), AppCmd::Dictionary { ref query, ref language } => Box::new(DictionaryApp::new(context.fb.rect(), query, language, &tx, &mut rq, &mut context)), AppCmd::TouchEvents => { Box::new(TouchEvents::new(context.fb.rect(), &mut rq, &mut context)) - }, + } AppCmd::RotationValues => { Box::new(RotationValues::new(context.fb.rect(), &mut rq, &mut context)) - }, + } }; transfer_notifications(view.as_mut(), next_view.as_mut(), &mut rq, &mut context); history.push(HistoryItem { @@ -909,7 +926,7 @@ pub fn run() -> Result<(), Error> { dithered: context.fb.dithered(), }); view = next_view; - }, + } Event::Back => { if let Some(item) = history.pop() { view = item.view; @@ -931,7 +948,7 @@ pub fn run() -> Result<(), Error> { } else if !view.is::() { break; } - }, + } Event::TogglePresetMenu(rect, index) => { if let Some(index) = locate_by_id(view.as_ref(), ViewId::PresetMenu) { let rect = *view.child(index).rect(); @@ -945,7 +962,7 @@ pub fn run() -> Result<(), Error> { rq.add(RenderData::new(preset_menu.id(), *preset_menu.rect(), UpdateMode::Gui)); view.children_mut().push(Box::new(preset_menu) as Box); } - }, + } Event::Show(ViewId::Frontlight) => { if !context.settings.frontlight { context.set_frontlight(true); @@ -954,35 +971,44 @@ pub fn run() -> Result<(), Error> { let flw = FrontlightWindow::new(&mut context); rq.add(RenderData::new(flw.id(), *flw.rect(), UpdateMode::Gui)); view.children_mut().push(Box::new(flw) as Box); - }, + } Event::ToggleInputHistoryMenu(id, rect) => { toggle_input_history_menu(view.as_mut(), id, rect, None, &mut rq, &mut context); - }, + } Event::ToggleNear(ViewId::KeyboardLayoutMenu, rect) => { toggle_keyboard_layout_menu(view.as_mut(), rect, None, &mut rq, &mut context); - }, + } Event::Close(ViewId::Frontlight) => { if let Some(index) = locate::(view.as_ref()) { let rect = *view.child(index).rect(); view.children_mut().remove(index); rq.add(RenderData::expose(rect, UpdateMode::Gui)); } - }, + } Event::Close(id) => { if let Some(index) = locate_by_id(view.as_ref(), id) { let rect = overlapping_rectangle(view.child(index)); rq.add(RenderData::expose(rect, UpdateMode::Gui)); view.children_mut().remove(index); } - }, + } Event::Select(EntryId::ToggleInverted) => { context.fb.toggle_inverted(); rq.add(RenderData::new(view.id(), context.fb.rect(), UpdateMode::Gui)); - }, + } Event::Select(EntryId::ToggleDithered) => { context.fb.toggle_dithered(); rq.add(RenderData::new(view.id(), context.fb.rect(), UpdateMode::Full)); }, + Event::Select(EntryId::ToggleIntermissionImage(ref kind, ref path)) => { + let full_path = context.library.home.join(path); + let key = kind.key(); + if context.settings.intermission_images.get(key) == Some(&full_path) { + context.settings.intermission_images.remove(key); + } else { + context.settings.intermission_images.insert(key.to_string(), full_path); + } + }, Event::Select(EntryId::Rotate(n)) if n != context.display.rotation && view.might_rotate() => { updating.retain(|tok, _| context.fb.wait(*tok).is_err()); if let Ok(dims) = context.fb.set_rotation(n) { @@ -996,11 +1022,10 @@ pub fn run() -> Result<(), Error> { rq.add(RenderData::new(view.id(), context.fb.rect(), UpdateMode::Full)); } } - }, + } Event::Select(EntryId::SetRotationLock(rotation_lock)) => { context.settings.rotation_lock = rotation_lock; - - }, + } Event::Select(EntryId::SetButtonScheme(button_scheme)) => { context.settings.button_scheme = button_scheme; @@ -1008,18 +1033,18 @@ pub fn run() -> Result<(), Error> { match button_scheme { ButtonScheme::Natural => { raw_sender.send(button_scheme_event(VAL_RELEASE)).ok(); - }, + } ButtonScheme::Inverted => { raw_sender.send(button_scheme_event(VAL_PRESS)).ok(); } } - }, + } Event::SetWifi(enable) => { set_wifi(enable, &mut context); - }, + } Event::Select(EntryId::ToggleWifi) => { set_wifi(!context.settings.wifi, &mut context); - }, + } Event::Select(EntryId::TakeScreenshot) => { let name = Local::now().format("screenshot-%Y%m%d_%H%M%S.png"); let msg = match context.fb.save(&name.to_string()) { @@ -1028,7 +1053,7 @@ pub fn run() -> Result<(), Error> { }; let notif = Notification::new(msg, &tx, &mut rq, &mut context); view.children_mut().push(Box::new(notif) as Box); - }, + } Event::CheckFetcher(..) | Event::FetcherAddDocument(..) | Event::FetcherRemoveDocument(..) | @@ -1037,44 +1062,43 @@ pub fn run() -> Result<(), Error> { let (tx, _rx) = mpsc::channel(); entry.view.handle_event(&evt, &tx, &mut VecDeque::new(), &mut RenderQueue::new(), &mut context); } - }, + } Event::Notify(msg) => { let notif = Notification::new(msg, &tx, &mut rq, &mut context); view.children_mut().push(Box::new(notif) as Box); - }, + } Event::Select(EntryId::Reboot) => { exit_status = ExitStatus::Reboot; break; - }, + } Event::Select(EntryId::Quit) => { break; - }, + } Event::Select(EntryId::RebootInNickel) => { fs::remove_file("bootlock").map_err(|e| { eprintln!("Couldn't remove the bootlock file: {:#}.", e); }).ok(); exit_status = ExitStatus::Reboot; break; - }, + } Event::MightSuspend if context.settings.auto_suspend > 0 => { if context.shared || tasks.iter().any(|task| task.id == TaskId::PrepareSuspend || - task.id == TaskId::Suspend) { + task.id == TaskId::Suspend) { inactive_since = Instant::now(); continue; } let seconds = 60 * context.settings.auto_suspend as u64; if inactive_since.elapsed() > Duration::from_secs(seconds) { - view.handle_event(&Event::Suspend, &tx, &mut bus, &mut rq, &mut context); let interm = Intermission::new(context.fb.rect(), IntermKind::Suspend, &context); rq.add(RenderData::new(interm.id(), *interm.rect(), UpdateMode::Full)); schedule_task(TaskId::PrepareSuspend, Event::PrepareSuspend, PREPARE_SUSPEND_WAIT_DELAY, &tx, &mut tasks); view.children_mut().push(Box::new(interm) as Box); } - }, + } _ => { handle_event(view.as_mut(), &evt, &tx, &mut bus, &mut rq, &mut context); - }, + } } process_render_queue(view.as_ref(), &mut rq, &mut context, &mut updating); @@ -1103,11 +1127,11 @@ pub fn run() -> Result<(), Error> { ExitStatus::Reboot => { Command::new("sync").status().ok(); Command::new("reboot").status().ok(); - }, + } ExitStatus::PowerOff => { Command::new("sync").status().ok(); Command::new("poweroff").arg("-f").status().ok(); - }, + } _ => (), } diff --git a/src/emulator.rs b/src/emulator.rs index 6cd09925..15e9c6b1 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -31,7 +31,7 @@ use anyhow::{Error, Context as ResultExt}; use fxhash::FxHashMap; use chrono::Local; use sdl2::event::Event as SdlEvent; -use sdl2::keyboard::{Scancode, Keycode, Mod}; +use sdl2::keyboard::{Scancode, Keycode}; use sdl2::render::{WindowCanvas, BlendMode}; use sdl2::pixels::{Color as SdlColor, PixelFormatEnum}; use sdl2::mouse::MouseState; @@ -48,7 +48,6 @@ use crate::view::notification::Notification; use crate::view::dialog::Dialog; use crate::view::frontlight::FrontlightWindow; use crate::view::menu::{Menu, MenuKind}; -use crate::view::intermission::Intermission; use crate::view::dictionary::Dictionary; use crate::view::calculator::Calculator; use crate::view::sketch::Sketch; @@ -57,7 +56,7 @@ use crate::view::rotation_values::RotationValues; use crate::view::common::{locate, locate_by_id, transfer_notifications, overlapping_rectangle}; use crate::view::common::{toggle_input_history_menu, toggle_keyboard_layout_menu}; use crate::helpers::{load_toml, save_toml}; -use crate::settings::{Settings, SETTINGS_PATH, IntermKind}; +use crate::settings::{Settings, SETTINGS_PATH}; use crate::geom::{Rectangle, Axis}; use crate::gesture::{GestureEvent, gesture_events}; use crate::device::CURRENT_DEVICE; @@ -67,6 +66,7 @@ use crate::lightsensor::LightSensor; use crate::library::Library; use crate::font::Fonts; use crate::app::Context; +use png::Info; pub const APP_NAME: &str = "Plato"; const DEFAULT_ROTATION: i8 = 1; @@ -83,8 +83,10 @@ pub fn build_context(fb: Box) -> Result { let lightsensor = Box::new(0u16) as Box; let fonts = Fonts::load()?; + let last_read = Box::new(Info::default()); + Ok(Context::new(fb, None, library, settings, - fonts, battery, frontlight, lightsensor)) + fonts, battery, frontlight, lightsensor, last_read)) } #[inline] @@ -92,17 +94,6 @@ fn seconds(timestamp: u32) -> f64 { timestamp as f64 / 1000.0 } -impl IntermKind { - pub fn from_scancode(sc: Scancode) -> Option { - match sc { - Scancode::S => Some(IntermKind::Suspend), - Scancode::P => Some(IntermKind::PowerOff), - Scancode::C => Some(IntermKind::Share), - _ => None, - } - } -} - #[inline] pub fn device_event(event: SdlEvent) -> Option { match event { @@ -299,61 +290,39 @@ fn main() -> Result<(), Error> { if let Some(sdl_evt) = event_pump.wait_event_timeout(20) { match sdl_evt { SdlEvent::Quit { .. } | - SdlEvent::KeyDown { keycode: Some(Keycode::Escape), keymod: Mod::NOMOD, .. } => { + SdlEvent::KeyDown { keycode: Some(Keycode::Escape), .. } => { view.handle_event(&Event::Back, &tx, &mut VecDeque::new(), &mut RenderQueue::new(), &mut context); while let Some(mut view) = history.pop() { view.handle_event(&Event::Back, &tx, &mut VecDeque::new(), &mut RenderQueue::new(), &mut context); } break; }, - SdlEvent::KeyDown { scancode: Some(scancode), keymod, .. } => { - match keymod { - Mod::NOMOD => { - match scancode { - Scancode::LeftBracket => { - let rot = (3 + context.display.rotation) % 4; - ty.send(DeviceEvent::RotateScreen(rot)).ok(); - }, - Scancode::RightBracket => { - let rot = (5 + context.display.rotation) % 4; - ty.send(DeviceEvent::RotateScreen(rot)).ok(); - }, - Scancode::S => { - tx.send(Event::Select(EntryId::TakeScreenshot)).ok(); - }, - Scancode::I | Scancode::O => { - let mouse_state = MouseState::new(&event_pump); - let x = mouse_state.x() as i32; - let y = mouse_state.y() as i32; - let center = pt!(x, y); - if scancode == Scancode::I { - tx.send(Event::Gesture(GestureEvent::Spread { center, - factor: 2.0, - axis: Axis::Diagonal })).ok(); - } else { - tx.send(Event::Gesture(GestureEvent::Pinch { center, - factor: 0.5, - axis: Axis::Diagonal })).ok(); - } - }, - _ => (), - } + SdlEvent::KeyDown { scancode: Some(scancode), .. } => { + match scancode { + Scancode::LeftBracket => { + let rot = (3 + context.display.rotation) % 4; + ty.send(DeviceEvent::RotateScreen(rot)).ok(); + }, + Scancode::RightBracket => { + let rot = (5 + context.display.rotation) % 4; + ty.send(DeviceEvent::RotateScreen(rot)).ok(); + }, + Scancode::S => { + tx.send(Event::Select(EntryId::TakeScreenshot)).ok(); }, - Mod::LSHIFTMOD | Mod::RSHIFTMOD => { - match scancode { - Scancode::S | Scancode::P | Scancode::C => { - if let Some(index) = locate::(view.as_ref()) { - let rect = *view.child(index).rect(); - view.children_mut().remove(index); - rq.add(RenderData::expose(rect, UpdateMode::Full)); - } else if let Some(kind) = IntermKind::from_scancode(scancode) { - view.handle_event(&Event::Suspend, &tx, &mut VecDeque::new(), &mut RenderQueue::new(), &mut context); - let interm = Intermission::new(context.fb.rect(), kind, &context); - rq.add(RenderData::new(interm.id(), *interm.rect(), UpdateMode::Full)); - view.children_mut().push(Box::new(interm) as Box); - } - }, - _ => (), + Scancode::I | Scancode::O => { + let mouse_state = MouseState::new(&event_pump); + let x = mouse_state.x() as i32; + let y = mouse_state.y() as i32; + let center = pt!(x, y); + if scancode == Scancode::I { + tx.send(Event::Gesture(GestureEvent::Spread { center, + factor: 2.0, + axis: Axis::Diagonal })).ok(); + } else { + tx.send(Event::Gesture(GestureEvent::Pinch { center, + factor: 0.5, + axis: Axis::Diagonal })).ok(); } }, _ => (), diff --git a/src/fetcher.rs b/src/fetcher.rs index 68df08cd..91ddc4ed 100644 --- a/src/fetcher.rs +++ b/src/fetcher.rs @@ -134,22 +134,22 @@ fn main() -> Result<(), Error> { if !online { if !wifi { - let event = json!({ - "type": "notify", - "message": "Establishing a network connection.", - }); - println!("{}", event); + // let event = json!({ + // "type": "notify", + // "message": "Establishing a network connection.", + // }); + // println!("{}", event); let event = json!({ "type": "setWifi", "enable": true, }); println!("{}", event); } else { - let event = json!({ - "type": "notify", - "message": "Waiting for the network to come up.", - }); - println!("{}", event); + // let event = json!({ + // "type": "notify", + // "message": "Waiting for the network to come up.", + // }); + // println!("{}", event); } let mut line = String::new(); io::stdin().read_line(&mut line)?; @@ -185,16 +185,16 @@ fn main() -> Result<(), Error> { if let Ok(event) = serde_json::from_str::(&line) { if let Some(results) = event.get("results").and_then(JsonValue::as_array) { - let message = if results.is_empty() { - "No finished articles.".to_string() - } else { - format!("Found {} finished article{}.", results.len(), if results.len() != 1 { "s" } else { "" }) - }; - let event = json!({ - "type": "notify", - "message": &message, - }); - println!("{}", event); + // let message = if results.is_empty() { + // "No finished articles.".to_string() + // } else { + // format!("Found {} finished article{}.", results.len(), if results.len() != 1 { "s" } else { "" }) + // }; + // let event = json!({ + // "type": "notify", + // "message": &message, + // }); + // println!("{}", event); for entry in results { if sigterm.load(Ordering::Relaxed) { @@ -247,30 +247,30 @@ fn main() -> Result<(), Error> { if !results.is_empty() { if settings.sync_finished { - let message = if archivals_count > 0 { - format!("Marked {} finished article{} as read.", archivals_count, if archivals_count != 1 { "s" } else { "" }) - } else { - "No finished articles marked as read.".to_string() - }; - let event = json!({ - "type": "notify", - "message": &message, - }); - println!("{}", event); + // let message = if archivals_count > 0 { + // format!("Marked {} finished article{} as read.", archivals_count, if archivals_count != 1 { "s" } else { "" }) + // } else { + // "No finished articles marked as read.".to_string() + // }; + // let event = json!({ + // "type": "notify", + // "message": &message, + // }); + // println!("{}", event); } if settings.remove_finished { let removals_count = session.removals_count.saturating_sub(last_removals_count); - let message = if removals_count > 0 { - format!("Removed {} finished article{}.", removals_count, if removals_count != 1 { "s" } else { "" }) - } else { - "No finished articles removed.".to_string() - }; - let event = json!({ - "type": "notify", - "message": &message, - }); - println!("{}", event); + // let message = if removals_count > 0 { + // format!("Removed {} finished article{}.", removals_count, if removals_count != 1 { "s" } else { "" }) + // } else { + // "No finished articles removed.".to_string() + // }; + // let event = json!({ + // "type": "notify", + // "message": &message, + // }); + // println!("{}", event); } } } @@ -443,16 +443,16 @@ fn main() -> Result<(), Error> { if pages_count > 0 { let downloads_count = session.downloads_count .saturating_sub(last_downloads_count); - let message = if downloads_count > 0 { - format!("Downloaded {} article{}.", downloads_count, if downloads_count != 1 { "s" } else { "" }) - } else { - "No articles downloaded.".to_string() - }; - let event = json!({ - "type": "notify", - "message": &message, - }); - println!("{}", event); + // let message = if downloads_count > 0 { + // format!("Downloaded {} article{}.", downloads_count, if downloads_count != 1 { "s" } else { "" }) + // } else { + // "No articles downloaded.".to_string() + // }; + // let event = json!({ + // "type": "notify", + // "message": &message, + // }); + // println!("{}", event); } if !wifi { diff --git a/src/metadata.rs b/src/metadata.rs index ebf9685b..4ed4ecbe 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -16,7 +16,7 @@ use crate::document::asciify; use crate::document::epub::EpubDocument; use crate::helpers::datetime_format; -pub const DEFAULT_CONTRAST_EXPONENT: f32 = 1.0; +pub const DEFAULT_CONTRAST_EXPONENT: f32 = 5.0; pub const DEFAULT_CONTRAST_GRAY: f32 = 224.0; pub type Metadata = Vec; diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 2db5e4ec..468b94bf 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -1,11 +1,10 @@ mod preset; use std::env; -use std::ops::Index; use std::fmt::{self, Debug}; use std::path::PathBuf; use std::collections::BTreeMap; -use fxhash::FxHashSet; +use fxhash::{FxHashMap, FxHashSet}; use serde::{Serialize, Deserialize}; use crate::metadata::{SortMethod, TextAlign}; use crate::frontlight::LightLevels; @@ -19,8 +18,6 @@ pub const SETTINGS_PATH: &str = "Settings.toml"; pub const DEFAULT_FONT_PATH: &str = "/mnt/onboard/fonts"; pub const INTERNAL_CARD_ROOT: &str = "/mnt/onboard"; pub const EXTERNAL_CARD_ROOT: &str = "/mnt/sd"; -pub const LOGO_SPECIAL_PATH: &str = "logo:"; -pub const COVER_SPECIAL_PATH: &str = "cover:"; // Default font size in points. pub const DEFAULT_FONT_SIZE: f32 = 11.0; // Default margin width in millimeters. @@ -55,44 +52,6 @@ impl fmt::Display for ButtonScheme { } } -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum IntermKind { - Suspend, - PowerOff, - Share, -} - -impl IntermKind { - pub fn text(&self) -> &str { - match self { - IntermKind::Suspend => "Sleeping", - IntermKind::PowerOff => "Powered off", - IntermKind::Share => "Shared", - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct Intermissions { - suspend: PathBuf, - power_off: PathBuf, - share: PathBuf, -} - -impl Index for Intermissions { - type Output = PathBuf; - - fn index(&self, key: IntermKind) -> &Self::Output { - match key { - IntermKind::Suspend => &self.suspend, - IntermKind::PowerOff => &self.power_off, - IntermKind::Share => &self.share, - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] pub struct Settings { @@ -111,7 +70,8 @@ pub struct Settings { pub date_format: String, #[serde(skip_serializing_if = "Vec::is_empty")] pub libraries: Vec, - pub intermissions: Intermissions, + #[serde(skip_serializing_if = "FxHashMap::is_empty")] + pub intermission_images: FxHashMap, #[serde(skip_serializing_if = "Vec::is_empty")] pub frontlight_presets: Vec, pub home: HomeSettings, @@ -409,7 +369,8 @@ impl Default for ImportSettings { startup_trigger: true, extract_epub_metadata: true, allowed_kinds: ["pdf", "djvu", "epub", "fb2", - "xps", "oxps", "cbz"].iter().map(|k| k.to_string()).collect(), + "xps", "oxps", "html", "htm", + "cbz", "png", "jpg", "jpeg"].iter().map(|k| k.to_string()).collect(), } } } @@ -459,11 +420,7 @@ impl Default for Settings { auto_power_off: 3, time_format: "%H:%M".to_string(), date_format: "%A, %B %-d, %Y".to_string(), - intermissions: Intermissions { - suspend: PathBuf::from(LOGO_SPECIAL_PATH), - power_off: PathBuf::from(LOGO_SPECIAL_PATH), - share: PathBuf::from(LOGO_SPECIAL_PATH), - }, + intermission_images: FxHashMap::default(), home: HomeSettings::default(), reader: ReaderSettings::default(), import: ImportSettings::default(), diff --git a/src/view/home/mod.rs b/src/view/home/mod.rs index 661e2e11..f7c476ca 100644 --- a/src/view/home/mod.rs +++ b/src/view/home/mod.rs @@ -18,6 +18,7 @@ use fxhash::FxHashMap; use rand_core::RngCore; use serde_json::{json, Value as JsonValue}; use anyhow::{Error, format_err}; +use rand::seq::SliceRandom; use crate::library::Library; use crate::framebuffer::{Framebuffer, UpdateMode}; use crate::metadata::{Info, Metadata, SortMethod, BookQuery, SimpleStatus, sort}; @@ -34,6 +35,7 @@ use crate::view::menu::{Menu, MenuKind}; use crate::view::menu_entry::MenuEntry; use crate::view::search_bar::SearchBar; use crate::view::notification::Notification; +use crate::view::intermission::IntermKind; use super::top_bar::TopBar; use self::address_bar::AddressBar; use self::navigation_bar::NavigationBar; @@ -47,6 +49,7 @@ use crate::unit::scale_by_dpi; use crate::color::BLACK; use crate::font::Fonts; use crate::app::Context; +use std::ffi::OsStr; pub const TRASH_DIRNAME: &str = ".trash"; @@ -239,8 +242,8 @@ impl Home { let nav_bar = self.children[index].as_mut().downcast_mut::().unwrap(); nav_bar.set_path(&self.current_directory, &dirs, rq, context); self.adjust_shelf_top_edge(); - rq.add(RenderData::new(self.child(index+1).id(), - *self.child(index+1).rect(), + rq.add(RenderData::new(self.child(index + 1).id(), + *self.child(index + 1).rect(), UpdateMode::Partial)); rq.add(RenderData::new(self.child(index).id(), *self.child(index).rect(), @@ -249,13 +252,38 @@ impl Home { self.update_shelf(true, hub, rq, context); self.update_bottom_bar(rq, context); + + let folder_name = path.file_name(); + match folder_name { + None => {} + Some(folder_name) => { + let folder_name_args = folder_name.to_str().unwrap().split("!").collect::>(); + if folder_name_args.len() > 1 { + for i in 0..folder_name_args.len() { + match folder_name_args[i] { + "random" => { + let random_file = self.visible_books.choose(&mut rand::thread_rng()); + + match random_file { + None => {} + Some(random_file) => { + hub.send(Event::Open(Box::new(random_file.clone()))).ok(); + } + } + } + _ => {} + } + } + } + } + } } fn adjust_shelf_top_edge(&mut self) { let index = self.shelf_index - 2; - let y_shift = self.children[index].rect().max.y - self.children[index+1].rect().min.y; - *self.children[index+1].rect_mut() += pt!(0, y_shift); - self.children[index+2].rect_mut().min.y = self.children[index+1].rect().max.y; + let y_shift = self.children[index].rect().max.y - self.children[index + 1].rect().min.y; + *self.children[index + 1].rect_mut() += pt!(0, y_shift); + self.children[index + 2].rect_mut().min.y = self.children[index + 1].rect().max.y; } fn toggle_select_directory(&mut self, path: &Path, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { @@ -281,10 +309,10 @@ impl Home { match dir { CycleDir::Next if self.current_page < self.pages_count.saturating_sub(1) => { self.current_page += 1; - }, + } CycleDir::Previous if self.current_page > 0 => { self.current_page -= 1; - }, + } _ => return, } @@ -307,12 +335,12 @@ impl Home { let status = self.visible_books[book_index].simple_status(); let page = match dir { - CycleDir::Next => self.visible_books[book_index+1..].iter() - .position(|info| info.simple_status() != status) - .map(|delta| self.current_page + 1 + delta / max_lines), + CycleDir::Next => self.visible_books[book_index + 1..].iter() + .position(|info| info.simple_status() != status) + .map(|delta| self.current_page + 1 + delta / max_lines), CycleDir::Previous => self.visible_books[..book_index].iter().rev() - .position(|info| info.simple_status() != status) - .map(|delta| self.current_page - 1 - delta / max_lines), + .position(|info| info.simple_status() != status) + .map(|delta| self.current_page - 1 - delta / max_lines), }; if let Some(page) = page { @@ -336,7 +364,7 @@ impl Home { self.pages_count = (self.visible_books.len() as f32 / max_lines as f32).ceil() as usize; - if reset_page { + if reset_page { self.current_page = 0; } else if self.current_page >= self.pages_count { self.current_page = self.pages_count.saturating_sub(1); @@ -351,21 +379,21 @@ impl Home { fn update_first_column(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { let selected_library = context.settings.selected_library; self.children[self.shelf_index].as_mut().downcast_mut::().unwrap() - .set_first_column(context.settings.libraries[selected_library].first_column); + .set_first_column(context.settings.libraries[selected_library].first_column); self.update_shelf(false, hub, rq, context); } fn update_second_column(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { let selected_library = context.settings.selected_library; self.children[self.shelf_index].as_mut().downcast_mut::().unwrap() - .set_second_column(context.settings.libraries[selected_library].second_column); + .set_second_column(context.settings.libraries[selected_library].second_column); self.update_shelf(false, hub, rq, context); } fn update_thumbnail_previews(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { let selected_library = context.settings.selected_library; self.children[self.shelf_index].as_mut().downcast_mut::().unwrap() - .set_thumbnail_previews(context.settings.libraries[selected_library].thumbnail_previews); + .set_thumbnail_previews(context.settings.libraries[selected_library].thumbnail_previews); self.update_shelf(false, hub, rq, context); } @@ -381,7 +409,7 @@ impl Home { 0.0 } else { self.current_page as f32 * (shelf.max_lines as f32 / - self.visible_books.len() as f32) + self.visible_books.len() as f32) }; let mut page_guess = page_position * self.visible_books.len() as f32 / max_lines as f32; @@ -414,7 +442,7 @@ impl Home { if let Some(index) = rlocate::(self) { let bottom_bar = self.children[index].as_mut().downcast_mut::().unwrap(); let filter = self.query.is_some() || - self.current_directory != context.library.home; + self.current_directory != context.library.home; let selected_library = context.settings.selected_library; let library_settings = &context.settings.libraries[selected_library]; bottom_bar.update_library_label(&library_settings.name, self.visible_books.len(), filter, rq); @@ -429,23 +457,23 @@ impl Home { scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32); let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32; let (small_thickness, big_thickness) = halves(thickness); - let has_search_bar = self.children[self.shelf_index+2].is::(); + let has_search_bar = self.children[self.shelf_index + 2].is::(); if let Some(index) = rlocate::(self) { if enable { return; } - let y_min = self.child(self.shelf_index+1).rect().min.y; + let y_min = self.child(self.shelf_index + 1).rect().min.y; let mut rect = *self.child(index).rect(); - rect.absorb(self.child(index-1).rect()); + rect.absorb(self.child(index - 1).rect()); - self.children.drain(index - 1 ..= index); + self.children.drain(index - 1..=index); let delta_y = rect.height() as i32; if has_search_bar { - for i in self.shelf_index+1..=self.shelf_index+2 { + for i in self.shelf_index + 1..=self.shelf_index + 2 { let shifted_rect = *self.child(i).rect() + pt!(0, delta_y); self.child_mut(i).resize(shifted_rect, hub, rq, context); } @@ -480,7 +508,7 @@ impl Home { let delta_y = kb_rect.height() as i32 + thickness; if has_search_bar { - for i in self.shelf_index+1..=self.shelf_index+2 { + for i in self.shelf_index + 1..=self.shelf_index + 2 { let shifted_rect = *self.child(i).rect() + pt!(0, -delta_y); self.child_mut(i).resize(shifted_rect, hub, rq, context); } @@ -490,17 +518,17 @@ impl Home { if update { if enable { if has_search_bar { - for i in self.shelf_index+1..=self.shelf_index+4 { + for i in self.shelf_index + 1..=self.shelf_index + 4 { let update_mode = if (i - self.shelf_index) == 1 { UpdateMode::Partial } else { UpdateMode::Gui }; rq.add(RenderData::new(self.child(i).id(), *self.child(i).rect(), update_mode)); } } else { - for i in self.shelf_index+1..=self.shelf_index+2 { + for i in self.shelf_index + 1..=self.shelf_index + 2 { rq.add(RenderData::new(self.child(i).id(), *self.child(i).rect(), UpdateMode::Gui)); } } } else if has_search_bar { - for i in self.shelf_index+1..=self.shelf_index+2 { + for i in self.shelf_index + 1..=self.shelf_index + 2 { rq.add(RenderData::new(self.child(i).id(), *self.child(i).rect(), UpdateMode::Gui)); } } @@ -523,19 +551,19 @@ impl Home { } // Remove the address bar and its separator. - self.children.drain(index ..= index + 1); + self.children.drain(index..=index + 1); self.shelf_index -= 2; context.settings.home.address_bar = false; // Move the navigation bar up. if context.settings.home.navigation_bar { - let nav_bar = self.children[self.shelf_index-2] - .downcast_mut::().unwrap(); + let nav_bar = self.children[self.shelf_index - 2] + .downcast_mut::().unwrap(); nav_bar.shift(pt!(0, -small_height)); } // Move the separator above the shelf up. - *self.children[self.shelf_index-1].rect_mut() += pt!(0, -small_height); + *self.children[self.shelf_index - 1].rect_mut() += pt!(0, -small_height); // Move the shelf's top edge up. self.children[self.shelf_index].rect_mut().min.y -= small_height; @@ -561,7 +589,7 @@ impl Home { context.settings.home.address_bar = true; // Move the separator above the shelf down. - *self.children[self.shelf_index-1].rect_mut() += pt!(0, small_height); + *self.children[self.shelf_index - 1].rect_mut() += pt!(0, small_height); // Move the shelf's top edge down. self.children[self.shelf_index].rect_mut().min.y += small_height; @@ -569,8 +597,8 @@ impl Home { if context.settings.home.navigation_bar { let rect = *self.children[self.shelf_index].rect(); let y_shift = rect.height() as i32 - (big_height - thickness); - let nav_bar = self.children[self.shelf_index-2] - .downcast_mut::().unwrap(); + let nav_bar = self.children[self.shelf_index - 2] + .downcast_mut::().unwrap(); // Move the navigation bar down. nav_bar.shift(pt!(0, small_height)); @@ -578,7 +606,7 @@ impl Home { if y_shift < 0 { let y_shift = nav_bar.shrink(y_shift, &mut context.fonts); self.children[self.shelf_index].rect_mut().min.y += y_shift; - *self.children[self.shelf_index-1].rect_mut() += pt!(0, y_shift); + *self.children[self.shelf_index - 1].rect_mut() += pt!(0, y_shift); } } } @@ -606,11 +634,11 @@ impl Home { } let mut rect = *self.child(index).rect(); - rect.absorb(self.child(index+1).rect()); + rect.absorb(self.child(index + 1).rect()); let delta_y = rect.height() as i32; // Remove the navigation bar and its separator. - self.children.drain(index ..= index + 1); + self.children.drain(index..=index + 1); self.shelf_index -= 2; context.settings.home.navigation_bar = false; @@ -625,7 +653,7 @@ impl Home { let sp_rect = *self.child(sep_index).rect() + pt!(0, small_height); let separator = Filler::new(sp_rect, BLACK); - self.children.insert(sep_index+1, Box::new(separator) as Box); + self.children.insert(sep_index + 1, Box::new(separator) as Box); let mut nav_bar = NavigationBar::new(rect![self.rect.min.x, sp_rect.min.y - small_height + thickness, @@ -635,7 +663,7 @@ impl Home { context.settings.home.max_levels); let (_, dirs) = context.library.list(&self.current_directory, None, true); nav_bar.set_path(&self.current_directory, &dirs, rq, context); - self.children.insert(sep_index+1, Box::new(nav_bar) as Box); + self.children.insert(sep_index + 1, Box::new(nav_bar) as Box); self.shelf_index += 2; context.settings.home.navigation_bar = true; @@ -672,14 +700,14 @@ impl Home { } // Remove the search bar and its separator. - self.children.drain(index - 1 ..= index); + self.children.drain(index - 1..=index); // Move the shelf's bottom edge. self.children[self.shelf_index].rect_mut().max.y += delta_y; if context.settings.home.navigation_bar { - let nav_bar = self.children[self.shelf_index-2] - .downcast_mut::().unwrap(); + let nav_bar = self.children[self.shelf_index - 2] + .downcast_mut::().unwrap(); nav_bar.vertical_limit += delta_y; } @@ -690,17 +718,17 @@ impl Home { return; } - let sp_rect = *self.child(self.shelf_index+1).rect() - pt!(0, delta_y); + let sp_rect = *self.child(self.shelf_index + 1).rect() - pt!(0, delta_y); let search_bar = SearchBar::new(rect![self.rect.min.x, sp_rect.max.y, self.rect.max.x, sp_rect.max.y + delta_y - thickness], ViewId::HomeSearchInput, "Title, author, series", "", context); - self.children.insert(self.shelf_index+1, Box::new(search_bar) as Box); + self.children.insert(self.shelf_index + 1, Box::new(search_bar) as Box); let separator = Filler::new(sp_rect, BLACK); - self.children.insert(self.shelf_index+1, Box::new(separator) as Box); + self.children.insert(self.shelf_index + 1, Box::new(separator) as Box); // Move the shelf's bottom edge. self.children[self.shelf_index].rect_mut().max.y -= delta_y; @@ -708,15 +736,15 @@ impl Home { if context.settings.home.navigation_bar { let rect = *self.children[self.shelf_index].rect(); let y_shift = rect.height() as i32 - (big_height - thickness); - let nav_bar = self.children[self.shelf_index-2] - .downcast_mut::().unwrap(); + let nav_bar = self.children[self.shelf_index - 2] + .downcast_mut::().unwrap(); nav_bar.vertical_limit -= delta_y; // Shrink the nav bar. if y_shift < 0 { let y_shift = nav_bar.shrink(y_shift, &mut context.fonts); self.children[self.shelf_index].rect_mut().min.y += y_shift; - *self.children[self.shelf_index-1].rect_mut() += pt!(0, y_shift); + *self.children[self.shelf_index - 1].rect_mut() += pt!(0, y_shift); } } @@ -740,9 +768,9 @@ impl Home { self.update_top_bar(search_visible, rq); if search_visible { - rq.add(RenderData::new(self.child(self.shelf_index-1).id(), *self.child(self.shelf_index-1).rect(), UpdateMode::Partial)); + rq.add(RenderData::new(self.child(self.shelf_index - 1).id(), *self.child(self.shelf_index - 1).rect(), UpdateMode::Partial)); let mut rect = *self.child(self.shelf_index).rect(); - rect.max.y = self.child(self.shelf_index+1).rect().min.y; + rect.max.y = self.child(self.shelf_index + 1).rect().min.y; // Render the part of the shelf that isn't covered. self.update_shelf(true, hub, &mut RenderQueue::new(), context); rq.add(RenderData::new(self.child(self.shelf_index).id(), rect, UpdateMode::Partial)); @@ -752,7 +780,7 @@ impl Home { rect.max.y = self.child(end_index).rect().max.y; rq.add(RenderData::expose(rect, UpdateMode::Partial)); } else { - for i in self.shelf_index - 1 ..= self.shelf_index + 1 { + for i in self.shelf_index - 1..=self.shelf_index + 1 { if i == self.shelf_index { self.update_shelf(true, hub, rq, context); continue; @@ -785,8 +813,8 @@ impl Home { ViewId::RenameDocumentInput, 21, context); if let Some(text) = self.target_document.as_ref() - .and_then(|path| path.file_name()) - .and_then(|file_name| file_name.to_str()) { + .and_then(|path| path.file_name()) + .and_then(|file_name| file_name.to_str()) { ren_doc.set_text(text, rq, context); } rq.add(RenderData::new(ren_doc.id(), *ren_doc.rect(), UpdateMode::Gui)); @@ -896,6 +924,7 @@ impl Home { let book_index = self.book_index(index); let info = &self.visible_books[book_index]; let path = &info.file.path; + let full_path = context.library.home.join(path); let mut entries = Vec::new(); @@ -921,15 +950,29 @@ impl Home { let submenu = submenu.iter().map(|s| EntryKind::Command(s.to_string(), EntryId::SetStatus(path.clone(), *s))) - .collect(); + .collect(); entries.push(EntryKind::SubMenu("Mark As".to_string(), submenu)); - entries.push(EntryKind::Separator); + { + let images = &context.settings.intermission_images; + let submenu = [IntermKind::Suspend, + IntermKind::PowerOff, + IntermKind::Share].iter().map(|k| { + EntryKind::CheckBox(k.label().to_string(), + EntryId::ToggleIntermissionImage(*k, path.clone()), + images.get(k.key()) == Some(&full_path)) + }).collect::>(); + + + entries.push(EntryKind::SubMenu("Set As".to_string(), submenu)) + } + + entries.push(EntryKind::Separator); let selected_library = context.settings.selected_library; let libraries = context.settings.libraries.iter().enumerate() - .filter(|(index, _)| *index != selected_library) - .map(|(index, lib)| (index, lib.name.clone())) - .collect::>(); + .filter(|(index, _)| *index != selected_library) + .map(|(index, lib)| (index, lib.name.clone())) + .collect::>(); if !libraries.is_empty() { let copy_to = libraries.iter().map(|(index, name)| { EntryKind::Command(name.clone(), @@ -983,10 +1026,10 @@ impl Home { }; let filesystem = if library_settings.mode == LibraryMode::Filesystem { - vec![EntryKind::CheckBox("Show Hidden".to_string(), EntryId::ToggleShowHidden, context.library.show_hidden), - EntryKind::Separator, - EntryKind::Command("Clean Up".to_string(), EntryId::CleanUp), - EntryKind::Command("Flush".to_string(), EntryId::Flush)] + vec![EntryKind::CheckBox("Show Hidden".to_string(), EntryId::ToggleShowHidden, context.library.show_hidden), + EntryKind::Separator, + EntryKind::Command("Clean Up".to_string(), EntryId::CleanUp), + EntryKind::Command("Flush".to_string(), EntryId::Flush)] } else { Vec::new() }; @@ -1003,8 +1046,8 @@ impl Home { let hooks: Vec = context.settings.libraries[selected_library].hooks.iter() - .map(|v| EntryKind::Command(v.path.to_string_lossy().into_owned(), - EntryId::ToggleSelectDirectory(context.library.home.join(&v.path)))).collect(); + .map(|v| EntryKind::Command(v.path.to_string_lossy().into_owned(), + EntryId::ToggleSelectDirectory(context.library.home.join(&v.path)))).collect(); if !hooks.is_empty() { entries.push(EntryKind::SubMenu("Toggle Select".to_string(), hooks)); @@ -1014,13 +1057,13 @@ impl Home { let first_column = library_settings.first_column; entries.push(EntryKind::SubMenu("First Column".to_string(), - vec![EntryKind::RadioButton("Title and Author".to_string(), EntryId::FirstColumn(FirstColumn::TitleAndAuthor), first_column == FirstColumn::TitleAndAuthor), - EntryKind::RadioButton("File Name".to_string(), EntryId::FirstColumn(FirstColumn::FileName), first_column == FirstColumn::FileName)])); + vec![EntryKind::RadioButton("Title and Author".to_string(), EntryId::FirstColumn(FirstColumn::TitleAndAuthor), first_column == FirstColumn::TitleAndAuthor), + EntryKind::RadioButton("File Name".to_string(), EntryId::FirstColumn(FirstColumn::FileName), first_column == FirstColumn::FileName)])); let second_column = library_settings.second_column; entries.push(EntryKind::SubMenu("Second Column".to_string(), - vec![EntryKind::RadioButton("Progress".to_string(), EntryId::SecondColumn(SecondColumn::Progress), second_column == SecondColumn::Progress), - EntryKind::RadioButton("Year".to_string(), EntryId::SecondColumn(SecondColumn::Year), second_column == SecondColumn::Year)])); + vec![EntryKind::RadioButton("Progress".to_string(), EntryId::SecondColumn(SecondColumn::Progress), second_column == SecondColumn::Progress), + EntryKind::RadioButton("Year".to_string(), EntryId::SecondColumn(SecondColumn::Year), second_column == SecondColumn::Year)])); entries.push(EntryKind::CheckBox("Thumbnail Previews".to_string(), EntryId::ThumbnailPreviews, @@ -1097,6 +1140,7 @@ impl Home { } let mut trash = Library::new(trash_path, LibraryMode::Database); context.library.move_to(path, &mut trash)?; + context.settings.intermission_images.retain(|_, path| path != &full_path); let (mut files, _) = trash.list(&trash.home, None, false); let mut size = files.iter().map(|info| info.file.size).sum::(); if size > context.settings.home.max_trash_size { @@ -1235,27 +1279,26 @@ impl Home { if fetcher.full_path == path { unsafe { libc::kill(*id as libc::pid_t, libc::SIGTERM) }; fetcher.process.wait().ok(); + + let selected_library = context.settings.selected_library; + if let Some(sort_method) = fetcher.sort_method { + context.settings.libraries[selected_library].sort_method = sort_method; + } + if let Some(first_column) = fetcher.first_column { + context.settings.libraries[selected_library].first_column = first_column; + } + if let Some(second_column) = fetcher.second_column { + context.settings.libraries[selected_library].second_column = second_column; + } + if update { if let Some(sort_method) = fetcher.sort_method { hub.send(Event::Select(EntryId::Sort(sort_method))).ok(); - } - if let Some(first_column) = fetcher.first_column { + } else if let Some(first_column) = fetcher.first_column { hub.send(Event::Select(EntryId::FirstColumn(first_column))).ok(); - } - if let Some(second_column) = fetcher.second_column { + } else if let Some(second_column) = fetcher.second_column { hub.send(Event::Select(EntryId::SecondColumn(second_column))).ok(); } - } else { - let selected_library = context.settings.selected_library; - if let Some(sort_method) = fetcher.sort_method { - context.settings.libraries[selected_library].sort_method = sort_method; - } - if let Some(first_column) = fetcher.first_column { - context.settings.libraries[selected_library].first_column = first_column; - } - if let Some(second_column) = fetcher.second_column { - context.settings.libraries[selected_library].second_column = second_column; - } } false } else { @@ -1283,9 +1326,15 @@ impl Home { hub.send(Event::Select(EntryId::SecondColumn(second_column))).ok(); } self.background_fetchers.insert(process.id(), - Fetcher { path: hook.path.clone(), full_path: save_path, process, - sort_method, first_column, second_column }); - }, + Fetcher { + path: hook.path.clone(), + full_path: save_path, + process, + sort_method, + first_column, + second_column, + }); + } Err(e) => eprintln!("Can't spawn child: {:#}.", e), } } @@ -1293,18 +1342,18 @@ impl Home { fn spawn_child(&mut self, library_path: &Path, save_path: &Path, program: &Path, wifi: bool, online: bool, hub: &Hub) -> Result { let path = program.canonicalize()?; let parent = path.parent() - .unwrap_or_else(|| Path::new("")); + .unwrap_or_else(|| Path::new("")); let mut process = Command::new(&path) - .current_dir(parent) - .arg(library_path) - .arg(save_path) - .arg(wifi.to_string()) - .arg(online.to_string()) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; + .current_dir(parent) + .arg(library_path) + .arg(save_path) + .arg(wifi.to_string()) + .arg(online.to_string()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; let stdout = process.stdout.take() - .ok_or_else(|| format_err!("can't take stdout"))?; + .ok_or_else(|| format_err!("can't take stdout"))?; let id = process.id(); let hub2 = hub.clone(); thread::spawn(move || { @@ -1313,44 +1362,44 @@ impl Home { if let Ok(line) = line_res { if let Ok(event) = serde_json::from_str::(&line) { match event.get("type") - .and_then(JsonValue::as_str) { + .and_then(JsonValue::as_str) { Some("notify") => { if let Some(msg) = event.get("message") - .and_then(JsonValue::as_str) { + .and_then(JsonValue::as_str) { hub2.send(Event::Notify(msg.to_string())).ok(); } - }, + } Some("setWifi") => { if let Some(enable) = event.get("enable") - .and_then(JsonValue::as_bool) { + .and_then(JsonValue::as_bool) { hub2.send(Event::SetWifi(enable)).ok(); } - }, + } Some("addDocument") => { if let Some(info) = event.get("info") - .map(ToString::to_string) - .and_then(|v| serde_json::from_str(&v).ok()) { + .map(ToString::to_string) + .and_then(|v| serde_json::from_str(&v).ok()) { hub2.send(Event::FetcherAddDocument(id, Box::new(info))).ok(); } - }, + } Some("removeDocument") => { if let Some(path) = event.get("path") - .and_then(JsonValue::as_str) { + .and_then(JsonValue::as_str) { hub2.send(Event::FetcherRemoveDocument(id, PathBuf::from(path))).ok(); } - }, + } Some("search") => { let path = event.get("path") - .and_then(JsonValue::as_str) - .map(PathBuf::from); + .and_then(JsonValue::as_str) + .map(PathBuf::from); let query = event.get("query") - .and_then(JsonValue::as_str) - .map(String::from); + .and_then(JsonValue::as_str) + .map(String::from); let sort_by = event.get("sortBy") - .map(ToString::to_string) - .and_then(|v| serde_json::from_str(&v).ok()); + .map(ToString::to_string) + .and_then(|v| serde_json::from_str(&v).ok()); hub2.send(Event::FetcherSearch { id, path, query, sort_by }).ok(); - }, + } _ => (), } } @@ -1383,46 +1432,46 @@ impl View for Home { Event::Gesture(GestureEvent::Swipe { dir, start, end, .. }) => { match dir { Dir::South if self.children[0].rect().includes(start) && - self.children[self.shelf_index].rect().includes(end) => { + self.children[self.shelf_index].rect().includes(end) => { if !context.settings.home.navigation_bar { self.toggle_navigation_bar(Some(true), true, hub, rq, context); } else if !context.settings.home.address_bar { self.toggle_address_bar(Some(true), true, hub, rq, context); } - }, + } Dir::North if self.children[self.shelf_index].rect().includes(start) && - self.children[0].rect().includes(end) => { + self.children[0].rect().includes(end) => { if context.settings.home.address_bar { self.toggle_address_bar(Some(false), true, hub, rq, context); } else if context.settings.home.navigation_bar { self.toggle_navigation_bar(Some(false), true, hub, rq, context); } - }, + } _ => (), } true - }, + } Event::Gesture(GestureEvent::Rotate { quarter_turns, .. }) if quarter_turns != 0 => { let (_, dir) = CURRENT_DEVICE.mirroring_scheme(); let n = (4 + (context.display.rotation - dir * quarter_turns)) % 4; hub.send(Event::Select(EntryId::Rotate(n))).ok(); true - }, + } Event::Gesture(GestureEvent::Arrow { dir, .. }) => { match dir { Dir::West => self.go_to_page(0, hub, rq, context), Dir::East => { let pages_count = self.pages_count; self.go_to_page(pages_count.saturating_sub(1), hub, rq, context); - }, + } Dir::North => { let path = context.library.home.clone(); self.select_directory(&path, hub, rq, context); - }, + } Dir::South => self.toggle_search_bar(None, true, hub, rq, context), }; true - }, + } Event::Gesture(GestureEvent::Corner { dir, .. }) => { match dir { DiagDir::NorthWest | @@ -1431,7 +1480,7 @@ impl View for Home { DiagDir::SouthEast => self.go_to_status_change(CycleDir::Next, hub, rq, context), }; true - }, + } Event::Focus(v) => { if self.focus != v { self.focus = v; @@ -1440,135 +1489,135 @@ impl View for Home { } } true - }, + } Event::Show(ViewId::Keyboard) => { self.toggle_keyboard(true, true, None, hub, rq, context); true - }, + } Event::Toggle(ViewId::GoToPage) => { self.toggle_go_to_page(None, hub, rq, context); true - }, + } Event::Toggle(ViewId::SearchBar) => { self.toggle_search_bar(None, true, hub, rq, context); true - }, + } Event::ToggleNear(ViewId::TitleMenu, rect) => { self.toggle_sort_menu(rect, None, rq, context); true - }, + } Event::ToggleBookMenu(rect, index) => { self.toggle_book_menu(index, rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::MainMenu, rect) => { toggle_main_menu(self, rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::BatteryMenu, rect) => { toggle_battery_menu(self, rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::ClockMenu, rect) => { toggle_clock_menu(self, rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::LibraryMenu, rect) => { self.toggle_library_menu(rect, None, rq, context); true - }, + } Event::Close(ViewId::AddressBar) => { self.toggle_address_bar(Some(false), true, hub, rq, context); true - }, + } Event::Close(ViewId::SearchBar) => { self.toggle_search_bar(Some(false), true, hub, rq, context); true - }, + } Event::Close(ViewId::SortMenu) => { self.toggle_sort_menu(Rectangle::default(), Some(false), rq, context); true - }, + } Event::Close(ViewId::LibraryMenu) => { self.toggle_library_menu(Rectangle::default(), Some(false), rq, context); true - }, + } Event::Close(ViewId::MainMenu) => { toggle_main_menu(self, Rectangle::default(), Some(false), rq, context); true - }, + } Event::Close(ViewId::GoToPage) => { self.toggle_go_to_page(Some(false), hub, rq, context); true - }, + } Event::Close(ViewId::RenameDocument) => { self.toggle_rename_document(Some(false), hub, rq, context); true - }, + } Event::Select(EntryId::Sort(sort_method)) => { let selected_library = context.settings.selected_library; context.settings.libraries[selected_library].sort_method = sort_method; self.set_sort_method(sort_method, hub, rq, context); true - }, + } Event::Select(EntryId::ReverseOrder) => { let next_value = !self.reverse_order; self.set_reverse_order(next_value, hub, rq, context); true - }, + } Event::Select(EntryId::LoadLibrary(index)) => { self.load_library(index, hub, rq, context); true - }, + } Event::Select(EntryId::Import) => { self.import(hub, rq, context); true - }, + } Event::Select(EntryId::CleanUp) => { self.clean_up(hub, rq, context); true - }, + } Event::Select(EntryId::Flush) => { self.flush(context); true - }, + } Event::FetcherAddDocument(_, ref info) => { self.add_document(*info.clone(), hub, rq, context); true - }, + } Event::Select(EntryId::SetStatus(ref path, status)) => { self.set_status(path, status, hub, rq, context); true - }, + } Event::Select(EntryId::FirstColumn(first_column)) => { let selected_library = context.settings.selected_library; context.settings.libraries[selected_library].first_column = first_column; self.update_first_column(hub, rq, context); true - }, + } Event::Select(EntryId::SecondColumn(second_column)) => { let selected_library = context.settings.selected_library; context.settings.libraries[selected_library].second_column = second_column; self.update_second_column(hub, rq, context); true - }, + } Event::Select(EntryId::ThumbnailPreviews) => { let selected_library = context.settings.selected_library; context.settings.libraries[selected_library].thumbnail_previews = !context.settings.libraries[selected_library].thumbnail_previews; self.update_thumbnail_previews(hub, rq, context); true - }, + } Event::Submit(ViewId::AddressBarInput, ref addr) => { self.toggle_keyboard(false, true, None, hub, rq, context); self.select_directory(&Path::new(addr), hub, rq, context); true - }, + } Event::Submit(ViewId::HomeSearchInput, ref text) => { self.query = BookQuery::new(text); if self.query.is_some() { self.toggle_keyboard(false, false, None, hub, rq, context); // Render the search bar and its separator. - for i in self.shelf_index + 1 ..= self.shelf_index + 2 { + for i in self.shelf_index + 1..=self.shelf_index + 2 { rq.add(RenderData::new(self.child(i).id(), *self.child(i).rect(), UpdateMode::Gui)); } self.refresh_visibles(true, true, hub, rq, context); @@ -1578,7 +1627,7 @@ impl View for Home { self.children.push(Box::new(notif) as Box); } true - }, + } Event::Submit(ViewId::GoToPageInput, ref text) => { if text == "(" { self.go_to_page(0, hub, rq, context); @@ -1591,7 +1640,7 @@ impl View for Home { self.go_to_page(index.saturating_sub(1), hub, rq, context); } true - }, + } Event::Submit(ViewId::RenameDocumentInput, ref file_name) => { if let Some(ref path) = self.target_document.take() { self.rename(path, file_name, hub, rq, context) @@ -1599,7 +1648,7 @@ impl View for Home { .ok(); } true - }, + } Event::NavigationBarResized(_) => { self.adjust_shelf_top_edge(); self.update_shelf(true, hub, rq, context); @@ -1608,49 +1657,49 @@ impl View for Home { rq.add(RenderData::new(self.child(i).id(), *self.child(i).rect(), UpdateMode::Gui)); } true - }, + } Event::Select(EntryId::EmptyTrash) => { self.empty_trash(hub, rq, context); true - }, + } Event::Select(EntryId::Rename(ref path)) => { self.target_document = Some(path.clone()); self.toggle_rename_document(Some(true), hub, rq, context); true - }, + } Event::Select(EntryId::Remove(ref path)) | Event::FetcherRemoveDocument(_, ref path) => { self.remove(path, hub, rq, context) .map_err(|e| eprintln!("Can't remove document: {:#}.", e)) .ok(); true - }, + } Event::Select(EntryId::CopyTo(ref path, index)) => { self.copy_to(path, index, context) .map_err(|e| eprintln!("Can't copy document: {:#}.", e)) .ok(); true - }, + } Event::Select(EntryId::MoveTo(ref path, index)) => { self.move_to(path, index, hub, rq, context) .map_err(|e| eprintln!("Can't move document: {:#}.", e)) .ok(); true - }, + } Event::Select(EntryId::ToggleShowHidden) => { context.library.show_hidden = !context.library.show_hidden; self.refresh_visibles(true, false, hub, rq, context); true - }, + } Event::SelectDirectory(ref path) | Event::Select(EntryId::SelectDirectory(ref path)) => { self.select_directory(path, hub, rq, context); true - }, + } Event::ToggleSelectDirectory(ref path) | Event::Select(EntryId::ToggleSelectDirectory(ref path)) => { self.toggle_select_directory(path, hub, rq, context); true - }, + } Event::Select(EntryId::SearchAuthor(ref author)) => { let text = format!("'a {}", author); let query = BookQuery::new(&text); @@ -1658,21 +1707,21 @@ impl View for Home { self.query = query; self.toggle_search_bar(Some(true), false, hub, rq, context); self.toggle_keyboard(false, false, None, hub, rq, context); - if let Some(search_bar) = self.children[self.shelf_index+2].downcast_mut::() { + if let Some(search_bar) = self.children[self.shelf_index + 2].downcast_mut::() { search_bar.set_text(&text, rq, context); } // Render the search bar and its separator. - for i in self.shelf_index + 1 ..= self.shelf_index + 2 { + for i in self.shelf_index + 1..=self.shelf_index + 2 { rq.add(RenderData::new(self.child(i).id(), *self.child(i).rect(), UpdateMode::Gui)); } self.refresh_visibles(true, true, hub, rq, context); } true - }, + } Event::GoTo(location) => { self.go_to_page(location as usize, hub, rq, context); true - }, + } Event::Chapter(dir) => { let pages_count = self.pages_count; match dir { @@ -1680,19 +1729,19 @@ impl View for Home { CycleDir::Next => self.go_to_page(pages_count.saturating_sub(1), hub, rq, context), } true - }, + } Event::Page(dir) => { self.go_to_neighbor(dir, hub, rq, context); true - }, + } Event::Device(DeviceEvent::Button { code: ButtonCode::Backward, status: ButtonStatus::Pressed, .. }) => { self.go_to_neighbor(CycleDir::Previous, hub, rq, context); true - }, + } Event::Device(DeviceEvent::Button { code: ButtonCode::Forward, status: ButtonStatus::Pressed, .. }) => { self.go_to_neighbor(CycleDir::Next, hub, rq, context); true - }, + } Event::Device(DeviceEvent::NetUp) => { for fetcher in self.background_fetchers.values_mut() { if let Some(stdin) = fetcher.process.stdin.as_mut() { @@ -1700,7 +1749,7 @@ impl View for Home { } } true - }, + } Event::FetcherSearch { id, ref path, ref query, ref sort_by } => { let path = path.as_ref().unwrap_or(&context.library.home); let query = query.as_ref().and_then(|text| BookQuery::new(text)); @@ -1719,7 +1768,7 @@ impl View for Home { } } true - }, + } Event::CheckFetcher(id) => { if let Some(fetcher) = self.background_fetchers.get_mut(&id) { if let Ok(exit_status) = fetcher.process.wait() { @@ -1731,24 +1780,23 @@ impl View for Home { } } true - }, + } Event::ToggleFrontlight => { if let Some(index) = locate::(self) { self.child_mut(index).downcast_mut::().unwrap() .update_frontlight_icon(rq, context); } true - }, + } Event::Reseed => { self.reseed(hub, rq, context); true - }, + } _ => false, } } - fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) { - } + fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {} fn resize(&mut self, rect: Rectangle, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { let dpi = CURRENT_DEVICE.dpi; @@ -1788,7 +1836,7 @@ impl View for Home { // Navigation bar. if context.settings.home.navigation_bar { - let count = if self.children[self.shelf_index+2].is::() { 2 } else { 1 }; + let count = if self.children[self.shelf_index + 2].is::() { 2 } else { 1 }; let nav_bar = self.children[index].as_mut().downcast_mut::().unwrap(); let (_, dirs) = context.library.list(&self.current_directory, None, true); nav_bar.clear(); @@ -1812,7 +1860,7 @@ impl View for Home { let separator_rect = rect![rect.min.x, rect.max.y - small_height - small_thickness, rect.max.x, rect.max.y - small_height + big_thickness]; - self.children[index-1].resize(separator_rect, hub, rq, context); + self.children[index - 1].resize(separator_rect, hub, rq, context); let bottom_bar_rect = rect![rect.min.x, rect.max.y - small_height + big_thickness, rect.max.x, rect.max.y]; @@ -1830,20 +1878,20 @@ impl View for Home { rect.max.y - small_height - small_thickness]; self.children[index].resize(kb_rect, hub, rq, context); let s_max_y = self.children[index].rect().min.y; - self.children[index-1].resize(rect![rect.min.x, s_max_y - thickness, + self.children[index - 1].resize(rect![rect.min.x, s_max_y - thickness, rect.max.x, s_max_y], - hub, rq, context); + hub, rq, context); index -= 2; } // Search bar. if self.children[index].is::() { - let sp_rect = *self.children[index+1].rect() - pt!(0, small_height); + let sp_rect = *self.children[index + 1].rect() - pt!(0, small_height); self.children[index].resize(rect![rect.min.x, sp_rect.max.y, rect.max.x, sp_rect.max.y + small_height - thickness], hub, rq, context); - self.children[index-1].resize(sp_rect, hub, rq, context); + self.children[index - 1].resize(sp_rect, hub, rq, context); shelf_max_y -= small_height; } } @@ -1857,7 +1905,7 @@ impl View for Home { self.update_bottom_bar(&mut RenderQueue::new(), context); // Floating windows. - for i in bottom_bar_index+1..self.children.len() { + for i in bottom_bar_index + 1..self.children.len() { self.children[i].resize(rect, hub, rq, context); } diff --git a/src/view/home/shelf.rs b/src/view/home/shelf.rs index 05f27d83..86b0c90e 100644 --- a/src/view/home/shelf.rs +++ b/src/view/home/shelf.rs @@ -62,7 +62,9 @@ impl Shelf { self.thumbnail_previews = thumbnail_previews; } - pub fn update(&mut self, metadata: &[Info], hub: &Hub, rq: &mut RenderQueue, context: &Context) { + pub fn + + update(&mut self, metadata: &[Info], hub: &Hub, rq: &mut RenderQueue, context: &Context) { self.children.clear(); let dpi = CURRENT_DEVICE.dpi; let big_height = scale_by_dpi(BIG_BAR_HEIGHT, dpi) as i32; diff --git a/src/view/intermission.rs b/src/view/intermission.rs index a61b7f14..5a544002 100644 --- a/src/view/intermission.rs +++ b/src/view/intermission.rs @@ -1,12 +1,10 @@ use std::path::PathBuf; use crate::device::CURRENT_DEVICE; -use crate::document::{Location, open}; +use crate::document::pdf::PdfOpener; use crate::geom::Rectangle; use crate::font::{Fonts, font_from_style, DISPLAY_STYLE}; use super::{View, Event, Hub, Bus, Id, ID_FEEDER, RenderQueue}; use crate::framebuffer::Framebuffer; -use crate::settings::{IntermKind, LOGO_SPECIAL_PATH, COVER_SPECIAL_PATH}; -use crate::metadata::{SortMethod, BookQuery, sort}; use crate::color::{TEXT_NORMAL, TEXT_INVERTED_HARD}; use crate::app::Context; @@ -21,28 +19,48 @@ pub struct Intermission { pub enum Message { Text(String), Image(PathBuf), - Cover(PathBuf), } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IntermKind { + Suspend, + PowerOff, + Share, +} + +impl IntermKind { + pub fn text(&self) -> &str { + match self { + IntermKind::Suspend => "Sleeping", + IntermKind::PowerOff => "Powered off", + IntermKind::Share => "Shared", + } + } + + pub fn label(&self) -> &str { + match self { + IntermKind::Suspend => "Suspend Image", + IntermKind::PowerOff => "Power Off Image", + IntermKind::Share => "Share Image", + } + } + + pub fn key(&self) -> &str { + match self { + IntermKind::Suspend => "suspend", + IntermKind::PowerOff => "power-off", + IntermKind::Share => "share", + } + } +} + + impl Intermission { pub fn new(rect: Rectangle, kind: IntermKind, context: &Context) -> Intermission { - let path = &context.settings.intermissions[kind]; - let message = match path.to_str() { - Some(LOGO_SPECIAL_PATH) => Message::Text(kind.text().to_string()), - Some(COVER_SPECIAL_PATH) => { - let query = BookQuery { - reading: Some(true), - .. Default::default() - }; - let (mut files, _) = context.library.list(&context.library.home, Some(&query), false); - sort(&mut files, SortMethod::Opened, true); - if !files.is_empty() { - Message::Cover(context.library.home.join(&files[0].file.path)) - } else { - Message::Text(kind.text().to_string()) - } - }, - _ => Message::Image(path.clone()), + let message = if let Some(path) = context.settings.intermission_images.get(kind.key()) { + Message::Image(path.clone()) + } else { + Message::Text(kind.text().to_string()) }; Intermission { id: ID_FEEDER.next(), @@ -91,10 +109,11 @@ impl View for Intermission { font.render(fb, scheme[1], &plan, pt!(dx, dy)); - let mut doc = open("icons/dodecahedron.svg").unwrap(); - let (width, height) = doc.dims(0).unwrap(); + let doc = PdfOpener::new().and_then(|o| o.open("icons/dodecahedron.svg")).unwrap(); + let page = doc.page(0).unwrap(); + let (width, height) = page.dims(); let scale = (plan.width as f32 / width.max(height) as f32) / 4.0; - let (pixmap, _) = doc.pixmap(Location::Exact(0), scale).unwrap(); + let pixmap = page.pixmap(scale).unwrap(); let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2; let dy = dy + 2 * x_height; let pt = self.rect.min + pt!(dx, dy); @@ -102,12 +121,13 @@ impl View for Intermission { fb.draw_blended_pixmap(&pixmap, pt, scheme[1]); }, Message::Image(ref path) => { - if let Some(mut doc) = open(path) { - if let Some((width, height)) = doc.dims(0) { + if let Some(doc) = PdfOpener::new().and_then(|o| o.open(path)) { + if let Some(page) = doc.page(0) { + let (width, height) = page.dims(); let w_ratio = self.rect.width() as f32 / width; let h_ratio = self.rect.height() as f32 / height; let scale = w_ratio.min(h_ratio); - if let Some((pixmap, _)) = doc.pixmap(Location::Exact(0), scale) { + if let Some(pixmap) = page.pixmap(scale) { let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2; let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2; let pt = self.rect.min + pt!(dx, dy); @@ -116,16 +136,6 @@ impl View for Intermission { } } }, - Message::Cover(ref path) => { - if let Some(mut doc) = open(path) { - if let Some(pixmap) = doc.preview_pixmap(self.rect.width() as f32, self.rect.height() as f32) { - let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2; - let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2; - let pt = self.rect.min + pt!(dx, dy); - fb.draw_pixmap(&pixmap, pt); - } - } - }, } } diff --git a/src/view/mod.rs b/src/view/mod.rs index 7d91c926..70349383 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -62,6 +62,7 @@ use crate::input::{DeviceEvent, FingerStatus}; use crate::gesture::GestureEvent; use self::calculator::LineOrigin; use self::key::KeyKind; +use self::intermission::IntermKind; use crate::app::Context; // Border thicknesses in pixels, at 300 DPI. @@ -412,6 +413,7 @@ pub enum ViewId { AboutDialog, ShareDialog, MarginCropper, + Rotate, TopBottomBars, TableOfContents, MessageNotif(Id), @@ -506,6 +508,7 @@ pub enum EntryId { ToggleSelectDirectory(PathBuf), SetStatus(PathBuf, SimpleStatus), SearchAuthor(String), + ToggleIntermissionImage(IntermKind, PathBuf), RemovePreset(usize), FirstColumn(FirstColumn), SecondColumn(SecondColumn), diff --git a/src/view/notification.rs b/src/view/notification.rs index 76719504..a2e1ce15 100644 --- a/src/view/notification.rs +++ b/src/view/notification.rs @@ -12,7 +12,7 @@ use crate::input::DeviceEvent; use crate::unit::scale_by_dpi; use crate::app::Context; -const NOTIFICATION_CLOSE_DELAY: Duration = Duration::from_secs(4); +const NOTIFICATION_CLOSE_DELAY: Duration = Duration::from_secs(2); pub struct Notification { id: Id, diff --git a/src/view/reader/mod.rs b/src/view/reader/mod.rs index faa2524d..815aa35b 100644 --- a/src/view/reader/mod.rs +++ b/src/view/reader/mod.rs @@ -57,8 +57,8 @@ use crate::app::Context; const HISTORY_SIZE: usize = 32; const RECT_DIST_JITTER: f32 = 24.0; -const ANNOTATION_DRIFT: u8 = 0x44; -const HIGHLIGHT_DRIFT: u8 = 0x22; +const ANNOTATION_DRIFT: u8 = 0x44; +const HIGHLIGHT_DRIFT: u8 = 0x22; const MEM_SCHEME: &str = "mem:"; pub struct Reader { @@ -66,10 +66,14 @@ pub struct Reader { rect: Rectangle, children: Vec>, doc: Arc>>, - cache: BTreeMap, // Cached page pixmaps. - chunks: Vec, // Chunks of pages being rendered. - text: FxHashMap>, // Text of the current chunks. - annotations: FxHashMap>, // Annotations for the current chunks. + cache: BTreeMap, + // Cached page pixmaps. + chunks: Vec, + // Chunks of pages being rendered. + text: FxHashMap>, + // Text of the current chunks. + annotations: FxHashMap>, + // Annotations for the current chunks. focus: Option, search: Option, search_direction: LinearDir, @@ -93,7 +97,8 @@ pub struct Reader { #[derive(Debug)] struct ViewPort { zoom_mode: ZoomMode, - page_offset: Point, // Offset relative to the top left corner of a resource's frame. + page_offset: Point, + // Offset relative to the top left corner of a resource's frame. margin_width: i32, } @@ -124,14 +129,16 @@ struct Selection { #[derive(Debug)] struct Resource { pixmap: Pixmap, - frame: Rectangle, // The pixmap's rectangle minus the cropping margins. + frame: Rectangle, + // The pixmap's rectangle minus the cropping margins. scale: f32, } #[derive(Debug, Clone)] struct RenderChunk { location: usize, - frame: Rectangle, // A subrectangle of the corresponding resource's frame. + frame: Rectangle, + // A subrectangle of the corresponding resource's frame. position: Point, scale: f32, } @@ -187,7 +194,7 @@ fn scaling_factor(rect: &Rectangle, cropping_margin: &Margin, screen_margin_widt let frame_height = (1.0 - (cropping_margin.top + cropping_margin.bottom)) * page_height; let height_ratio = surface_height / frame_height; width_ratio.min(height_ratio) - }, + } ZoomMode::FitToWidth => width_ratio, ZoomMode::Custom(_) => unreachable!(), } @@ -206,7 +213,7 @@ fn find_cut(frame: &Rectangle, y_pos: i32, scale: f32, dir: LinearDir, lines: &[ for line in lines { if frame_u.contains(&line.rect) && line.rect.height() <= max_line_height && - y_pos_u >= line.rect.min.y && y_pos_u < line.rect.max.y { + y_pos_u >= line.rect.min.y && y_pos_u < line.rect.max.y { rect_a = Some(line.rect); break; } @@ -230,33 +237,33 @@ impl Reader { open(&path).and_then(|mut doc| { let (width, height) = context.display.dims; let font_size = info.reader.as_ref().and_then(|r| r.font_size) - .unwrap_or(settings.reader.font_size); + .unwrap_or(settings.reader.font_size); doc.layout(width, height, font_size, CURRENT_DEVICE.dpi); let margin_width = info.reader.as_ref().and_then(|r| r.margin_width) - .unwrap_or(settings.reader.margin_width); + .unwrap_or(settings.reader.margin_width); if margin_width != DEFAULT_MARGIN_WIDTH { doc.set_margin_width(margin_width); } let font_family = info.reader.as_ref().and_then(|r| r.font_family.as_ref()) - .unwrap_or(&settings.reader.font_family); + .unwrap_or(&settings.reader.font_family); if font_family != DEFAULT_FONT_FAMILY { doc.set_font_family(font_family, &settings.reader.font_path); } let line_height = info.reader.as_ref().and_then(|r| r.line_height) - .unwrap_or(settings.reader.line_height); + .unwrap_or(settings.reader.line_height); if (line_height - DEFAULT_LINE_HEIGHT).abs() > f32::EPSILON { doc.set_line_height(line_height); } let text_align = info.reader.as_ref().and_then(|r| r.text_align) - .unwrap_or(settings.reader.text_align); + .unwrap_or(settings.reader.text_align); if text_align != DEFAULT_TEXT_ALIGN { doc.set_text_align(text_align); @@ -292,7 +299,7 @@ impl Reader { } current_page = doc.resolve_location(Location::Exact(r.current_page)) - .unwrap_or(first_location); + .unwrap_or(first_location); if let Some(zoom_mode) = r.zoom_mode { view_port.zoom_mode = zoom_mode; @@ -320,7 +327,7 @@ impl Reader { info.reader = Some(ReaderInfo { current_page, pages_count, - .. Default::default() + ..Default::default() }); } @@ -371,7 +378,7 @@ impl Reader { kind: "html".to_string(), size: html.len() as u64, }, - .. Default::default() + ..Default::default() }; let mut doc = HtmlDocument::new_from_memory(html); @@ -432,9 +439,9 @@ impl Reader { let mut doc = self.doc.lock().unwrap(); let cropping_margin = self.info.reader.as_ref() - .and_then(|r| r.cropping_margins.as_ref() - .map(|c| c.margin(location))) - .cloned().unwrap_or_default(); + .and_then(|r| r.cropping_margins.as_ref() + .map(|c| c.margin(location))) + .cloned().unwrap_or_default(); let dims = doc.dims(location).unwrap(); let screen_margin_width = self.view_port.margin_width; let scale = scaling_factor(&self.rect, &cropping_margin, screen_margin_width, dims, self.view_port.zoom_mode); @@ -461,8 +468,8 @@ impl Reader { let mut doc = self.doc.lock().unwrap(); let loc = Location::Exact(location); let words = doc.words(loc) - .map(|(words, _)| words) - .unwrap_or_default(); + .map(|(words, _)| words) + .unwrap_or_default(); self.text.insert(location, words); } @@ -500,17 +507,17 @@ impl Reader { let loc = { let mut doc = self.doc.lock().unwrap(); if let Some(toc) = self.toc() - .or_else(|| doc.toc()) { + .or_else(|| doc.toc()) { let chap_offset = if dir == CycleDir::Previous { - doc.chapter(current_page, &toc) - .and_then(|chap| doc.resolve_location(chap.location.clone())) - .and_then(|chap_offset| if chap_offset < current_page { Some(chap_offset) } else { None }) + doc.chapter(current_page, &toc) + .and_then(|chap| doc.resolve_location(chap.location.clone())) + .and_then(|chap_offset| if chap_offset < current_page { Some(chap_offset) } else { None }) } else { None }; chap_offset.or_else(|| doc.chapter_relative(current_page, dir, &toc) - .and_then(|rel_chap| doc.resolve_location(rel_chap.location.clone()))) + .and_then(|rel_chap| doc.resolve_location(rel_chap.location.clone()))) } else { None } @@ -551,10 +558,10 @@ impl Reader { fn go_to_bookmark(&mut self, dir: CycleDir, hub: &Hub, rq: &mut RenderQueue, context: &Context) { let loc_bkm = self.info.reader.as_ref().and_then(|r| { match dir { - CycleDir::Next => r.bookmarks.range(self.current_page+1 ..) - .next().cloned(), - CycleDir::Previous => r.bookmarks.range(.. self.current_page) - .next_back().cloned(), + CycleDir::Next => r.bookmarks.range(self.current_page + 1..) + .next().cloned(), + CycleDir::Previous => r.bookmarks.range(..self.current_page) + .next_back().cloned(), } }); @@ -568,15 +575,15 @@ impl Reader { match dir { CycleDir::Next => self.text_location_range().and_then(|[_, max]| { r.annotations.iter() - .filter(|annot| annot.selection[0] > max) - .map(|annot| annot.selection[0]).min() - .map(|tl| tl.location()) + .filter(|annot| annot.selection[0] > max) + .map(|annot| annot.selection[0]).min() + .map(|tl| tl.location()) }), CycleDir::Previous => self.text_location_range().and_then(|[min, _]| { r.annotations.iter() - .filter(|annot| annot.selection[1] < min) - .map(|annot| annot.selection[1]).max() - .map(|tl| tl.location()) + .filter(|annot| annot.selection[1] < min) + .map(|annot| annot.selection[1]).max() + .map(|tl| tl.location()) }), } }); @@ -730,11 +737,11 @@ impl Reader { self.view_port.page_offset.y = next_top_offset; Location::Exact(location) - }, + } ZoomMode::Custom(_) => { self.view_port.page_offset = pt!(0); Location::Previous(current_page) - }, + } } } else { match self.view_port.zoom_mode { @@ -752,11 +759,11 @@ impl Reader { self.view_port.page_offset.y = next_top_offset; Location::Exact(location) } - }, + } ZoomMode::Custom(_) => { self.view_port.page_offset = pt!(0); Location::Next(current_page) - }, + } } }; let mut doc = self.doc.lock().unwrap(); @@ -775,7 +782,7 @@ impl Reader { if self.search.is_some() { self.update_results_bar(rq); } - }, + } _ => { match dir { CycleDir::Next => { @@ -790,20 +797,20 @@ impl Reader { let notif = Notification::new("No next page.".to_string(), hub, rq, context); self.children.push(Box::new(notif) as Box); - }, + } FinishedAction::Close => { self.quit(context); hub.send(Event::Back).ok(); - }, + } } - }, + } CycleDir::Previous => { let notif = Notification::new("No previous page.".to_string(), hub, rq, context); self.children.push(Box::new(notif) as Box); - }, + } } - }, + } } } @@ -827,10 +834,10 @@ impl Reader { fn go_to_results_neighbor(&mut self, dir: CycleDir, hub: &Hub, rq: &mut RenderQueue, context: &Context) { let loc = self.search.as_ref().and_then(|s| { match dir { - CycleDir::Next => s.highlights.range(self.current_page+1..) - .next().map(|e| *e.0), + CycleDir::Next => s.highlights.range(self.current_page + 1..) + .next().map(|e| *e.0), CycleDir::Previous => s.highlights.range(..self.current_page) - .next_back().map(|e| *e.0), + .next_back().map(|e| *e.0), } }); if let Some(location) = loc { @@ -850,9 +857,9 @@ impl Reader { let current_page = self.current_page; let mut doc = self.doc.lock().unwrap(); let chapter = self.toc().or_else(|| doc.toc()) - .as_ref().and_then(|toc| doc.chapter(current_page, toc)) - .map(|c| c.title.clone()) - .unwrap_or_default(); + .as_ref().and_then(|toc| doc.chapter(current_page, toc)) + .map(|c| c.title.clone()) + .unwrap_or_default(); let bottom_bar = self.children[index].as_mut().downcast_mut::().unwrap(); let neighbors = Neighbors { previous_page: doc.resolve_location(Location::Previous(current_page)), @@ -870,20 +877,20 @@ impl Reader { let settings = &context.settings; if self.reflowable { let font_family = self.info.reader.as_ref() - .and_then(|r| r.font_family.clone()) - .unwrap_or_else(|| settings.reader.font_family.clone()); + .and_then(|r| r.font_family.clone()) + .unwrap_or_else(|| settings.reader.font_family.clone()); tool_bar.update_font_family(font_family, rq); let font_size = self.info.reader.as_ref() - .and_then(|r| r.font_size) - .unwrap_or(settings.reader.font_size); + .and_then(|r| r.font_size) + .unwrap_or(settings.reader.font_size); tool_bar.update_font_size_slider(font_size, rq); let text_align = self.info.reader.as_ref() - .and_then(|r| r.text_align) - .unwrap_or(settings.reader.text_align); + .and_then(|r| r.text_align) + .unwrap_or(settings.reader.text_align); tool_bar.update_text_align_icon(text_align, rq); let line_height = self.info.reader.as_ref() - .and_then(|r| r.line_height) - .unwrap_or(settings.reader.line_height); + .and_then(|r| r.line_height) + .unwrap_or(settings.reader.line_height); tool_bar.update_line_height(line_height, rq); } else { tool_bar.update_contrast_exponent_slider(self.contrast.exponent, rq); @@ -891,8 +898,8 @@ impl Reader { } let reflowable = self.reflowable; let margin_width = self.info.reader.as_ref() - .and_then(|r| if reflowable { r.margin_width } else { r.screen_margin_width }) - .unwrap_or_else(|| if reflowable { settings.reader.margin_width } else { 0 }); + .and_then(|r| if reflowable { r.margin_width } else { r.screen_margin_width }) + .unwrap_or_else(|| if reflowable { settings.reader.margin_width } else { 0 }); tool_bar.update_margin_width(margin_width, rq); } } @@ -924,8 +931,8 @@ impl Reader { } for annot in annotations { let [start, end] = annot.selection; - if (start >= words[0].location && start <= words[words.len()-1].location) || - (end >= words[0].location && end <= words[words.len()-1].location) { + if (start >= words[0].location && start <= words[words.len() - 1].location) || + (end >= words[0].location && end <= words[words.len() - 1].location) { self.annotations.entry(chunk.location) .or_insert_with(Vec::new) .push(annot.clone()); @@ -962,7 +969,7 @@ impl Reader { let dx = smw + ((self.rect.width() - frame.width()) as i32 - 2 * smw) / 2; let dy = smw + ((self.rect.height() - frame.height()) as i32 - 2 * smw) / 2; self.chunks.push(RenderChunk { frame, location, position: pt!(dx, dy), scale }); - }, + } ZoomMode::FitToWidth => { let available_height = self.rect.height() as i32 - 2 * smw; let mut height = 0; @@ -1002,7 +1009,7 @@ impl Reader { chunk.position.y += dy; } } - }, + } ZoomMode::Custom(_) => { self.load_pixmap(location); self.load_text(location); @@ -1014,7 +1021,7 @@ impl Reader { let position = pt!(smw) + rect.min - vpr.min; self.chunks.push(RenderChunk { frame: rect, location, position, scale }); } - }, + } } rq.add(RenderData::new(self.id, self.rect, update_mode)); @@ -1023,7 +1030,7 @@ impl Reader { while self.cache.len() > 3 { let left_count = self.cache.range(..first_location).count(); - let right_count = self.cache.range(last_location+1..).count(); + let right_count = self.cache.range(last_location + 1..).count(); let extremum = if left_count >= right_count { self.cache.keys().next().cloned().unwrap() } else { @@ -1035,7 +1042,7 @@ impl Reader { self.update_annotations(); if self.view_port.zoom_mode == ZoomMode::FitToPage || - self.view_port.zoom_mode == ZoomMode::FitToWidth { + self.view_port.zoom_mode == ZoomMode::FitToWidth { let doc2 = self.doc.clone(); let hub2 = hub.clone(); thread::spawn(move || { @@ -1058,7 +1065,7 @@ impl Reader { fn search(&mut self, text: &str, query: Regex, hub: &Hub, rq: &mut RenderQueue) { let s = Search { query: text.to_string(), - .. Default::default() + ..Default::default() }; let hub2 = hub.clone(); @@ -1098,9 +1105,9 @@ impl Reader { text += &word.text; } for m in query.find_iter(&text) { - if let Some((first, _)) = rects.range(..= m.start()).next_back() { + if let Some((first, _)) = rects.range(..=m.start()).next_back() { let mut match_rects = Vec::new(); - for (_, rect) in rects.range(*first .. m.end()) { + for (_, rect) in rects.range(*first..m.end()) { match_rects.push(*rect); } hub2.send(Event::SearchResult(location, match_rects)).ok(); @@ -1114,7 +1121,7 @@ impl Reader { } else { loc = match search_direction { LinearDir::Forward => Location::Exact(0), - LinearDir::Backward => Location::Exact(doc.pages_count()-1), + LinearDir::Backward => Location::Exact(doc.pages_count() - 1), }; } @@ -1139,20 +1146,20 @@ impl Reader { } let mut rect = *self.child(index).rect(); - rect.absorb(self.child(index-1).rect()); + rect.absorb(self.child(index - 1).rect()); if index == 1 { - rect.absorb(self.child(index+1).rect()); - self.children.drain(index - 1 ..= index + 1); + rect.absorb(self.child(index + 1).rect()); + self.children.drain(index - 1..=index + 1); rq.add(RenderData::expose(rect, UpdateMode::Gui)); } else { - self.children.drain(index - 1 ..= index); + self.children.drain(index - 1..=index); - let start_index = locate::(self).map(|index| index+2).unwrap_or(0); + let start_index = locate::(self).map(|index| index + 2).unwrap_or(0); let y_min = self.child(start_index).rect().min.y; let delta_y = rect.height() as i32; - for i in start_index..index-1 { + for i in start_index..index - 1 { let shifted_rect = *self.child(i).rect() + pt!(0, delta_y); self.child_mut(i).resize(shifted_rect, hub, rq, context); rq.add(RenderData::new(self.child(i).id(), shifted_rect, UpdateMode::Gui)); @@ -1201,16 +1208,16 @@ impl Reader { self.children.insert(index, Box::new(separator) as Box); if index == 0 { - for i in index..index+3 { + for i in index..index + 3 { rq.add(RenderData::new(self.child(i).id(), *self.child(i).rect(), UpdateMode::Gui)); } } else { - for i in index..index+2 { + for i in index..index + 2 { rq.add(RenderData::new(self.child(i).id(), *self.child(i).rect(), UpdateMode::Gui)); } let delta_y = kb_rect.height() as i32 + thickness; - let start_index = locate::(self).map(|index| index+2).unwrap_or(0); + let start_index = locate::(self).map(|index| index + 2).unwrap_or(0); for i in start_index..index { let shifted_rect = *self.child(i).rect() + pt!(0, -delta_y); @@ -1229,7 +1236,7 @@ impl Reader { let mut rect = *self.child(index).rect(); rect.absorb(self.child(index - 1).rect()); - self.children.drain(index - 1 ..= index); + self.children.drain(index - 1..=index); rq.add(RenderData::expose(rect, UpdateMode::Gui)); } else { if !enable { @@ -1264,7 +1271,7 @@ impl Reader { let mut rect = *self.child(index).rect(); rect.absorb(self.child(index - 1).rect()); - self.children.drain(index - 1 ..= index); + self.children.drain(index - 1..=index); rq.add(RenderData::expose(rect, UpdateMode::Gui)); } else { if !enable { @@ -1274,7 +1281,7 @@ impl Reader { let dpi = CURRENT_DEVICE.dpi; let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32; let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32; - let index = locate::(self).map(|index| index+2).unwrap_or(0); + let index = locate::(self).map(|index| index + 2).unwrap_or(0); let sp_rect = *self.child(index).rect() - pt!(0, small_height); let y_min = sp_rect.max.y; @@ -1308,9 +1315,9 @@ impl Reader { self.toggle_bars(Some(false), hub, rq, context); } else { let mut rect = *self.child(index).rect(); - rect.absorb(self.child(index-1).rect()); - rect.absorb(self.child(index+1).rect()); - self.children.drain(index - 1 ..= index + 1); + rect.absorb(self.child(index - 1).rect()); + rect.absorb(self.child(index + 1).rect()); + self.children.drain(index - 1..=index + 1); rq.add(RenderData::expose(rect, UpdateMode::Gui)); } } else { @@ -1324,7 +1331,7 @@ impl Reader { let thickness = scale_by_dpi(THICKNESS_MEDIUM, dpi) as i32; let (small_thickness, big_thickness) = halves(thickness); let small_height = scale_by_dpi(SMALL_BAR_HEIGHT, dpi) as i32; - let index = locate::(self).map(|index| index+2).unwrap_or(0); + let index = locate::(self).map(|index| index + 2).unwrap_or(0); if index == 0 { let sp_rect = rect![self.rect.min.x, self.rect.max.y - small_height - small_thickness, @@ -1345,10 +1352,10 @@ impl Reader { self.children.insert(index, Box::new(separator) as Box); rq.add(RenderData::new(self.child(index).id(), *self.child(index).rect(), UpdateMode::Gui)); - rq.add(RenderData::new(self.child(index+1).id(), *self.child(index+1).rect(), UpdateMode::Gui)); + rq.add(RenderData::new(self.child(index + 1).id(), *self.child(index + 1).rect(), UpdateMode::Gui)); if index == 0 { - rq.add(RenderData::new(self.child(index+2).id(), *self.child(index+2).rect(), UpdateMode::Gui)); + rq.add(RenderData::new(self.child(index + 2).id(), *self.child(index + 2).rect(), UpdateMode::Gui)); } self.toggle_keyboard(true, Some(ViewId::ReaderSearchInput), hub, rq, context); @@ -1364,9 +1371,9 @@ impl Reader { if let Some(bottom_index) = locate::(self) { let mut top_rect = *self.child(top_index).rect(); - top_rect.absorb(self.child(top_index+1).rect()); + top_rect.absorb(self.child(top_index + 1).rect()); let mut bottom_rect = *self.child(bottom_index).rect(); - for i in top_index+2 .. bottom_index { + for i in top_index + 2..bottom_index { bottom_rect.absorb(self.child(i).rect()); } @@ -1520,9 +1527,9 @@ impl Reader { self.rect.max - pt!(padding)]; let margin = self.info.reader.as_ref() - .and_then(|r| r.cropping_margins.as_ref() - .map(|c| c.margin(self.current_page))) - .cloned().unwrap_or_default(); + .and_then(|r| r.cropping_margins.as_ref() + .map(|c| c.margin(self.current_page))) + .cloned().unwrap_or_default(); let mut doc = self.doc.lock().unwrap(); let (pixmap, _) = build_pixmap(&pixmap_rect, doc.as_mut(), self.current_page); @@ -1708,15 +1715,15 @@ impl Reader { let zoom_mode = self.view_port.zoom_mode; let sf = if let ZoomMode::Custom(sf) = zoom_mode { sf } else { 1.0 }; vec![EntryKind::SubMenu("Zoom Mode".to_string(), vec![ - EntryKind::RadioButton("Fit to Page".to_string(), - EntryId::SetZoomMode(ZoomMode::FitToPage), - zoom_mode == ZoomMode::FitToPage), - EntryKind::RadioButton("Fit to Width".to_string(), - EntryId::SetZoomMode(ZoomMode::FitToWidth), - zoom_mode == ZoomMode::FitToWidth), - EntryKind::RadioButton(format!("Custom ({:.1}%)", 100.0 * sf), - EntryId::SetZoomMode(ZoomMode::Custom(sf)), - zoom_mode == ZoomMode::Custom(sf))])] + EntryKind::RadioButton("Fit to Page".to_string(), + EntryId::SetZoomMode(ZoomMode::FitToPage), + zoom_mode == ZoomMode::FitToPage), + EntryKind::RadioButton("Fit to Width".to_string(), + EntryId::SetZoomMode(ZoomMode::FitToWidth), + zoom_mode == ZoomMode::FitToWidth), + EntryKind::RadioButton(format!("Custom ({:.1}%)", 100.0 * sf), + EntryId::SetZoomMode(ZoomMode::Custom(sf)), + zoom_mode == ZoomMode::Custom(sf))])] }; if self.info.reader.as_ref().map_or(false, |r| !r.annotations.is_empty()) { @@ -1755,11 +1762,11 @@ impl Reader { } let mut families = family_names(&context.settings.reader.font_path) - .map_err(|e| eprintln!("Can't get family names: {:#}.", e)) - .unwrap_or_default(); + .map_err(|e| eprintln!("Can't get family names: {:#}.", e)) + .unwrap_or_default(); let current_family = self.info.reader.as_ref() - .and_then(|r| r.font_family.clone()) - .unwrap_or_else(|| context.settings.reader.font_family.clone()); + .and_then(|r| r.font_family.clone()) + .unwrap_or_else(|| context.settings.reader.font_family.clone()); families.insert(DEFAULT_FONT_FAMILY.to_string()); let entries = families.iter().map(|f| EntryKind::RadioButton(f.clone(), EntryId::SetFontFamily(f.clone()), @@ -1784,7 +1791,7 @@ impl Reader { } let font_size = self.info.reader.as_ref().and_then(|r| r.font_size) - .unwrap_or(context.settings.reader.font_size); + .unwrap_or(context.settings.reader.font_size); let min_font_size = context.settings.reader.font_size / 2.0; let max_font_size = 3.0 * context.settings.reader.font_size / 2.0; let entries = (0..=20).filter_map(|v| { @@ -1817,7 +1824,7 @@ impl Reader { } let text_align = self.info.reader.as_ref().and_then(|r| r.text_align) - .unwrap_or(context.settings.reader.text_align); + .unwrap_or(context.settings.reader.text_align); let choices = [TextAlign::Justify, TextAlign::Left, TextAlign::Right, TextAlign::Center]; let entries = choices.iter().map(|v| { EntryKind::RadioButton(v.to_string(), @@ -1844,7 +1851,7 @@ impl Reader { } let line_height = self.info.reader.as_ref() - .and_then(|r| r.line_height).unwrap_or(context.settings.reader.line_height); + .and_then(|r| r.line_height).unwrap_or(context.settings.reader.line_height); let entries = (0..=10).map(|x| { let lh = 1.0 + x as f32 / 10.0; EntryKind::RadioButton(format!("{:.1}", lh), @@ -1922,11 +1929,11 @@ impl Reader { let reflowable = self.reflowable; let margin_width = self.info.reader.as_ref() - .and_then(|r| if reflowable { r.margin_width } else { r.screen_margin_width }) - .unwrap_or_else(|| if reflowable { context.settings.reader.margin_width } else { 0 }); + .and_then(|r| if reflowable { r.margin_width } else { r.screen_margin_width }) + .unwrap_or_else(|| if reflowable { context.settings.reader.margin_width } else { 0 }); let entries = (0..=10).map(|mw| EntryKind::RadioButton(format!("{}", mw), - EntryId::SetMarginWidth(mw), - mw == margin_width)).collect(); + EntryId::SetMarginWidth(mw), + mw == margin_width)).collect(); let margin_width_menu = Menu::new(rect, ViewId::MarginWidthMenu, MenuKind::DropDown, entries, context); rq.add(RenderData::new(margin_width_menu.id(), *margin_width_menu.rect(), UpdateMode::Gui)); self.children.push(Box::new(margin_width_menu) as Box); @@ -1947,17 +1954,17 @@ impl Reader { } let has_name = self.info.reader.as_ref() - .map_or(false, |r| r.page_names.contains_key(&self.current_page)); + .map_or(false, |r| r.page_names.contains_key(&self.current_page)); let mut entries = vec![EntryKind::Command("Name".to_string(), EntryId::SetPageName)]; if has_name { entries.push(EntryKind::Command("Remove Name".to_string(), EntryId::RemovePageName)); } let names = self.info.reader.as_ref() - .map(|r| r.page_names.iter() - .map(|(i, s)| EntryKind::Command(s.to_string(), EntryId::GoTo(*i))) - .collect::>()) - .unwrap_or_default(); + .map(|r| r.page_names.iter() + .map(|(i, s)| EntryKind::Command(s.to_string(), EntryId::GoTo(*i))) + .collect::>()) + .unwrap_or_default(); if !names.is_empty() { entries.push(EntryKind::Separator); entries.push(EntryKind::SubMenu("Go To".to_string(), names)); @@ -1984,8 +1991,8 @@ impl Reader { let current_page = self.current_page; let is_split = self.info.reader.as_ref() - .and_then(|r| r.cropping_margins - .as_ref().map(CroppingMargins::is_split)); + .and_then(|r| r.cropping_margins + .as_ref().map(CroppingMargins::is_split)); let mut entries = vec![EntryKind::RadioButton("Any".to_string(), EntryId::ApplyCroppings(current_page, PageScheme::Any), @@ -1995,11 +2002,11 @@ impl Reader { is_split.is_some() && is_split.unwrap())]; let is_applied = self.info.reader.as_ref() - .map(|r| r.cropping_margins.is_some()) - .unwrap_or(false); + .map(|r| r.cropping_margins.is_some()) + .unwrap_or(false); if is_applied { entries.extend_from_slice(&[EntryKind::Separator, - EntryKind::Command("Remove".to_string(), EntryId::RemoveCroppings)]); + EntryKind::Command("Remove".to_string(), EntryId::RemoveCroppings)]); } let margin_cropper_menu = Menu::new(rect, ViewId::MarginCropperMenu, MenuKind::DropDown, entries, context); @@ -2051,7 +2058,7 @@ impl Reader { if self.synthetic { let current_page = self.current_page.min(doc.pages_count() - 1); - if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { + if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { self.current_page = location; } } else { @@ -2083,7 +2090,7 @@ impl Reader { if self.synthetic { let current_page = self.current_page.min(doc.pages_count() - 1); - if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { + if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { self.current_page = location; } } else { @@ -2120,7 +2127,7 @@ impl Reader { if self.synthetic { let current_page = self.current_page.min(doc.pages_count() - 1); - if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { + if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { self.current_page = location; } } else { @@ -2151,7 +2158,7 @@ impl Reader { if self.synthetic { let current_page = self.current_page.min(doc.pages_count() - 1); - if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { + if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { self.current_page = location; } } else { @@ -2190,7 +2197,7 @@ impl Reader { if self.synthetic { let current_page = self.current_page.min(doc.pages_count() - 1); - if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { + if let Some(location) = doc.resolve_location(Location::Exact(current_page)) { self.current_page = location; } } else { @@ -2202,7 +2209,7 @@ impl Reader { if self.view_port.zoom_mode == ZoomMode::FitToWidth { // Apply the scale change. let ratio = (self.rect.width() as i32 - 2 * next_margin_width) as f32 / - (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32; + (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32; self.view_port.page_offset.y = (self.view_port.page_offset.y as f32 * ratio) as i32; } else { // Keep the center still. @@ -2315,7 +2322,7 @@ impl Reader { self.find_page_by_name(&uri[1..]) .map(Location::Exact) .unwrap_or_else(|| location.clone().into()) - }, + } _ => location.clone().into(), }; let current_index = *index; @@ -2330,7 +2337,7 @@ impl Reader { index: current_index, children: current_children, }); - }, + } } } toc @@ -2340,24 +2347,24 @@ impl Reader { self.info.reader.as_ref().and_then(|r| { if let Ok(a) = u32::from_str_radix(name, 10) { r.page_names - .iter().filter_map(|(i, s)| u32::from_str_radix(s, 10).ok().map(|b| (b, i))) - .filter(|(b, _)| *b <= a) - .max_by(|x, y| x.0.cmp(&y.0)) - .map(|(b, i)| *i + (a - b) as usize) + .iter().filter_map(|(i, s)| u32::from_str_radix(s, 10).ok().map(|b| (b, i))) + .filter(|(b, _)| *b <= a) + .max_by(|x, y| x.0.cmp(&y.0)) + .map(|(b, i)| *i + (a - b) as usize) } else if let Some(a) = name.chars().next().and_then(|c| c.to_alphabetic_digit()) { r.page_names - .iter().filter_map(|(i, s)| s.chars().next() - .and_then(|c| c.to_alphabetic_digit()) - .map(|c| (c, i))) - .filter(|(b, _)| *b <= a) - .max_by(|x, y| x.0.cmp(&y.0)) - .map(|(b, i)| *i + (a - b) as usize) + .iter().filter_map(|(i, s)| s.chars().next() + .and_then(|c| c.to_alphabetic_digit()) + .map(|c| (c, i))) + .filter(|(b, _)| *b <= a) + .max_by(|x, y| x.0.cmp(&y.0)) + .map(|(b, i)| *i + (a - b) as usize) } else if let Ok(a) = Roman::from_str(name) { r.page_names - .iter().filter_map(|(i, s)| Roman::from_str(s).ok().map(|b| (*b, i))) - .filter(|(b, _)| *b <= *a) - .max_by(|x, y| x.0.cmp(&y.0)) - .map(|(b, i)| *i + (*a - b) as usize) + .iter().filter_map(|(i, s)| Roman::from_str(s).ok().map(|b| (*b, i))) + .filter(|(b, _)| *b <= *a) + .max_by(|x, y| x.0.cmp(&y.0)) + .map(|(b, i)| *i + (*a - b) as usize) } else { None } @@ -2367,8 +2374,8 @@ impl Reader { fn text_excerpt(&self, sel: [TextLocation; 2]) -> Option { let [start, end] = sel; let parts = self.text.values().flatten() - .filter(|bnd| bnd.location >= start && bnd.location <= end) - .map(|bnd| bnd.text.as_str()).collect::>(); + .filter(|bnd| bnd.location >= start && bnd.location <= end) + .map(|bnd| bnd.text.as_str()).collect::>(); if parts.is_empty() { return None; @@ -2442,13 +2449,13 @@ impl Reader { fn find_annotation_ref(&mut self, sel: [TextLocation; 2]) -> Option<&Annotation> { self.info.reader.as_ref() .and_then(|r| r.annotations.iter() - .find(|a| a.selection[0] == sel[0] && a.selection[1] == sel[1])) + .find(|a| a.selection[0] == sel[0] && a.selection[1] == sel[1])) } fn find_annotation_mut(&mut self, sel: [TextLocation; 2]) -> Option<&mut Annotation> { self.info.reader.as_mut() .and_then(|r| r.annotations.iter_mut() - .find(|a| a.selection[0] == sel[0] && a.selection[1] == sel[1])) + .find(|a| a.selection[0] == sel[0] && a.selection[1] == sel[1])) } fn reseed(&mut self, hub: &Hub, rq: &mut RenderQueue, context: &mut Context) { @@ -2522,7 +2529,7 @@ impl Reader { let frame = self.cache[&chunk.location].frame; self.current_page = chunk.location; self.view_port.page_offset = Point::from(factor * Vec2::from(center - chunk.position + chunk.frame.min - frame.min)) - - pt!(self.rect.width() as i32 / 2 - smw, + pt!(self.rect.width() as i32 / 2 - smw, self.rect.height() as i32 / 2 - smw); self.set_zoom_mode(ZoomMode::Custom(current_factor * factor), false, hub, rq, context); @@ -2538,7 +2545,7 @@ impl View for Reader { let n = (4 + (context.display.rotation - dir * quarter_turns)) % 4; hub.send(Event::Select(EntryId::Rotate(n))).ok(); true - }, + } Event::Gesture(GestureEvent::Swipe { dir, start, end }) if self.rect.includes(start) => { match self.view_port.zoom_mode { ZoomMode::FitToPage | ZoomMode::FitToWidth => { @@ -2547,43 +2554,42 @@ impl View for Reader { Dir::East => self.go_to_neighbor(CycleDir::Previous, hub, rq, context), Dir::South | Dir::North => self.page_scroll(start.y - end.y, hub, rq, context), }; - }, + } ZoomMode::Custom(_) => { match dir { Dir::West | Dir::East => self.screen_scroll(pt!(start.x - end.x, 0), hub, rq, context), Dir::South | Dir::North => self.screen_scroll(pt!(0,start.y - end.y), hub, rq, context), }; - }, + } } true - }, + } Event::Gesture(GestureEvent::SlantedSwipe { start, end, .. }) if self.rect.includes(start) => { if let ZoomMode::Custom(_) = self.view_port.zoom_mode { self.screen_scroll(start - end, hub, rq, context); } true - }, + } Event::Gesture(GestureEvent::Spread { axis: Axis::Horizontal, center, .. }) if self.rect.includes(center) => { if !self.reflowable { self.set_zoom_mode(ZoomMode::FitToWidth, true, hub, rq, context); } true - - }, + } Event::Gesture(GestureEvent::Pinch { axis: Axis::Horizontal, center, .. }) if self.rect.includes(center) => { if !self.reflowable { self.set_zoom_mode(ZoomMode::FitToPage, true, hub, rq, context); } true - }, + } Event::Gesture(GestureEvent::Spread { axis: Axis::Diagonal, center, factor }) | Event::Gesture(GestureEvent::Pinch { axis: Axis::Diagonal, center, factor }) if factor.is_finite() && - self.rect.includes(center) => { + self.rect.includes(center) => { if !self.reflowable { self.scale_page(center, factor, hub, rq, context); } true - }, + } Event::Gesture(GestureEvent::Arrow { dir, .. }) => { match dir { Dir::West => { @@ -2592,7 +2598,7 @@ impl View for Reader { } else { self.go_to_results_page(0, hub, rq, context); } - }, + } Dir::East => { if self.search.is_none() { self.go_to_chapter(CycleDir::Next, hub, rq, context); @@ -2600,25 +2606,25 @@ impl View for Reader { let last_page = self.search.as_ref().unwrap().highlights.len() - 1; self.go_to_results_page(last_page, hub, rq, context); } - }, + } Dir::North => { self.search_direction = LinearDir::Backward; self.toggle_search_bar(true, hub, rq, context); - }, + } Dir::South => { self.search_direction = LinearDir::Forward; self.toggle_search_bar(true, hub, rq, context); - }, + } } true - }, + } Event::Gesture(GestureEvent::Corner { dir, .. }) => { match dir { DiagDir::NorthWest => self.go_to_bookmark(CycleDir::Previous, hub, rq, context), DiagDir::NorthEast => self.go_to_bookmark(CycleDir::Next, hub, rq, context), DiagDir::SouthEast => { hub.send(Event::Select(EntryId::ToggleDithered)).ok(); - }, + } DiagDir::SouthWest => { if context.settings.frontlight_presets.len() > 1 { if context.settings.frontlight { @@ -2636,10 +2642,10 @@ impl View for Reader { } else { hub.send(Event::ToggleFrontlight).ok(); } - }, + } }; true - }, + } Event::Gesture(GestureEvent::MultiCorner { dir, .. }) => { match dir { DiagDir::NorthWest => self.go_to_annotation(CycleDir::Previous, hub, rq, context), @@ -2647,16 +2653,18 @@ impl View for Reader { _ => (), } true - }, + } Event::Gesture(GestureEvent::Cross(_)) => { + context.last_read = Box::new(self.info.clone()); + self.quit(context); hub.send(Event::Back).ok(); true - }, + } Event::Gesture(GestureEvent::Diamond(_)) => { self.toggle_bars(None, hub, rq, context); true - }, + } Event::Gesture(GestureEvent::HoldButtonShort(code, ..)) => { match code { ButtonCode::Backward => self.go_to_chapter(CycleDir::Previous, hub, rq, context), @@ -2665,7 +2673,7 @@ impl View for Reader { } self.held_buttons.insert(code); true - }, + } Event::Device(DeviceEvent::Button { code, status: ButtonStatus::Released, .. }) => { if !self.held_buttons.remove(&code) { match code { @@ -2675,19 +2683,19 @@ impl View for Reader { } else { self.go_to_results_neighbor(CycleDir::Previous, hub, rq, context); } - }, + } ButtonCode::Forward => { if self.search.is_none() { self.go_to_neighbor(CycleDir::Next, hub, rq, context); } else { self.go_to_results_neighbor(CycleDir::Next, hub, rq, context); } - }, + } _ => (), } } true - }, + } Event::Device(DeviceEvent::Finger { position, status: FingerStatus::Motion, id, .. }) if self.state == State::Selection(id) => { let mut nearest_word = None; let mut dmin = u32::MAX; @@ -2724,10 +2732,10 @@ impl View for Reader { if let Some(mut i) = rects.iter().position(|(_, loc)| *loc == start_low) { let mut rect = rects[i].0; while rects[i].1 < start_high { - let next_rect = rects[i+1].0; + let next_rect = rects[i + 1].0; if rect.max.y.min(next_rect.max.y) - rect.min.y.max(next_rect.min.y) > - rect.height().min(next_rect.height()) as i32 / 2 { - if rects[i+1].1 == start_high { + rect.height().min(next_rect.height()) as i32 / 2 { + if rects[i + 1].1 == start_high { if rect.min.x < next_rect.min.x { rect.max.x = next_rect.min.x; } else { @@ -2752,10 +2760,10 @@ impl View for Reader { if let Some(mut i) = rects.iter().rposition(|(_, loc)| *loc == end_high) { let mut rect = rects[i].0; while rects[i].1 > end_low { - let prev_rect = rects[i-1].0; + let prev_rect = rects[i - 1].0; if rect.max.y.min(prev_rect.max.y) - rect.min.y.max(prev_rect.min.y) > - rect.height().min(prev_rect.height()) as i32 / 2 { - if rects[i-1].1 == end_low { + rect.height().min(prev_rect.height()) as i32 / 2 { + if rects[i - 1].1 == end_low { if rect.min.x > prev_rect.min.x { rect.min.x = prev_rect.max.x; } else { @@ -2780,13 +2788,13 @@ impl View for Reader { selection.end = end; } true - }, + } Event::Device(DeviceEvent::Finger { status: FingerStatus::Up, position, id, .. }) if self.state == State::Selection(id) => { self.state = State::Idle; let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32; self.toggle_selection_menu(Rectangle::from_disk(position, radius), Some(true), rq, context); true - }, + } Event::Gesture(GestureEvent::Tap(center)) if self.state == State::AdjustSelection && self.rect.includes(center) => { let mut found = None; let mut dmin = u32::MAX; @@ -2825,7 +2833,7 @@ impl View for Reader { } else { (word.location, old_end) } - }, + } (Some(..), None) => (word.location, old_end), (None, Some(..)) => (old_start, word.location), (None, None) => (old_start, old_end) @@ -2843,9 +2851,9 @@ impl View for Reader { if let Some(mut i) = rects.iter().position(|(_, loc)| *loc == start_low) { let mut rect = rects[i].0; while i < rects.len() - 1 && rects[i].1 < start_high { - let next_rect = rects[i+1].0; + let next_rect = rects[i + 1].0; if rect.min.y < next_rect.max.y && next_rect.min.y < rect.max.y { - if rects[i+1].1 == start_high { + if rects[i + 1].1 == start_high { if rect.min.x < next_rect.min.x { rect.max.x = next_rect.min.x; } else { @@ -2870,9 +2878,9 @@ impl View for Reader { if let Some(mut i) = rects.iter().rposition(|(_, loc)| *loc == end_high) { let mut rect = rects[i].0; while i > 0 && rects[i].1 > end_low { - let prev_rect = rects[i-1].0; + let prev_rect = rects[i - 1].0; if rect.min.y < prev_rect.max.y && prev_rect.min.y < rect.max.y { - if rects[i-1].1 == end_low { + if rects[i - 1].1 == end_low { if rect.min.x > prev_rect.min.x { rect.min.x = prev_rect.max.x; } else { @@ -2897,7 +2905,7 @@ impl View for Reader { selection.end = end; } true - }, + } Event::Gesture(GestureEvent::Tap(center)) if self.rect.includes(center) => { if self.focus.is_some() { return true; @@ -2909,8 +2917,8 @@ impl View for Reader { for chunk in &self.chunks { let (links, _) = self.doc.lock().ok() - .and_then(|mut doc| doc.links(Location::Exact(chunk.location))) - .unwrap_or((Vec::new(), 0)); + .and_then(|mut doc| doc.links(Location::Exact(chunk.location))) + .unwrap_or((Vec::new(), 0)); for link in links { let rect = (link.rect * chunk.scale).to_rect() - chunk.frame.min + chunk.position; let d = center.rdist2(&rect); @@ -2927,12 +2935,14 @@ impl View for Reader { if let Some(caps) = toc_page.captures(&link.text) { let loc_opt = if caps[1].chars().all(|c| c.is_digit(10)) { caps[1].parse::() - .map(Location::Exact) - .ok() + .map(Location::Exact) + .ok() } else { Some(Location::Uri(caps[1].to_string())) }; if let Some(location) = loc_opt { + context.last_read = Box::new(self.info.clone()); + self.quit(context); hub.send(Event::Back).ok(); hub.send(Event::GoToLocation(location)).ok(); @@ -2966,7 +2976,7 @@ impl View for Reader { DiagDir::SouthWest => self.screen_scroll(pt!(-dx, dy), hub, rq, context), DiagDir::NorthWest => self.screen_scroll(pt!(-dx, -dy), hub, rq, context), } - }, + } Region::Strip(dir) => { match dir { Dir::North => self.screen_scroll(pt!(0, -dy), hub, rq, context), @@ -2974,7 +2984,7 @@ impl View for Reader { Dir::South => self.screen_scroll(pt!(0, dy), hub, rq, context), Dir::West => self.screen_scroll(pt!(-dx, 0), hub, rq, context), } - }, + } Region::Center => self.toggle_bars(None, hub, rq, context), } @@ -2986,25 +2996,41 @@ impl View for Reader { context.settings.reader.corner_width) { Region::Corner(diag_dir) => { match diag_dir { - DiagDir::NorthWest => self.go_to_last_page(hub, rq, context), + DiagDir::NorthWest => { + // self.go_to_last_page(hub, rq, context); + + context.last_read = Box::new(self.info.clone()); + + self.quit(context); + hub.send(Event::Back).ok(); + } DiagDir::NorthEast => self.toggle_bookmark(rq), DiagDir::SouthEast => { if self.search.is_none() { match context.settings.reader.south_east_corner { SouthEastCornerAction::GoToPage => { - hub.send(Event::Toggle(ViewId::GoToPage)).ok(); - }, + // hub.send(Event::Toggle(ViewId::GoToPage)).ok(); + + self.quit(context); + hub.send(Event::Back).ok(); + + hub.send(Event::Open(context.last_read.clone())).ok(); + + context.last_read = Box::new(self.info.clone()); + } SouthEastCornerAction::NextPage => { self.go_to_neighbor(CycleDir::Next, hub, rq, context); - }, + } } } else { self.go_to_neighbor(CycleDir::Next, hub, rq, context); } - }, + } DiagDir::SouthWest => { if self.search.is_none() { if self.ephemeral && self.info.file.path == PathBuf::from(MEM_SCHEME) { + context.last_read = Box::new(self.info.clone()); + self.quit(context); hub.send(Event::Back).ok(); } else { @@ -3013,9 +3039,9 @@ impl View for Reader { } else { self.go_to_neighbor(CycleDir::Previous, hub, rq, context); } - }, + } } - }, + } Region::Strip(dir) => { match dir { Dir::West => { @@ -3024,22 +3050,22 @@ impl View for Reader { } else { self.go_to_results_neighbor(CycleDir::Previous, hub, rq, context); } - }, + } Dir::East => { if self.search.is_none() { self.go_to_neighbor(CycleDir::Next, hub, rq, context); } else { self.go_to_results_neighbor(CycleDir::Next, hub, rq, context); } - }, + } Dir::South | Dir::North => self.toggle_bars(None, hub, rq, context), } - }, + } Region::Center => self.toggle_bars(None, hub, rq, context), } true - }, + } Event::Gesture(GestureEvent::HoldFingerShort(center, id)) if self.rect.includes(center) => { if self.focus.is_some() { return true; @@ -3073,7 +3099,7 @@ impl View for Reader { if let Some((nearest_word, rect)) = found { let anchor = nearest_word.location; if let Some(annot) = self.annotations.values().flatten() - .find(|annot| anchor >= annot.selection[0] && anchor <= annot.selection[1]).cloned() { + .find(|annot| anchor >= annot.selection[0] && anchor <= annot.selection[1]).cloned() { let radius = scale_by_dpi(24.0, CURRENT_DEVICE.dpi) as i32; self.toggle_annotation_menu(&annot, Rectangle::from_disk(center, radius), Some(true), rq, context); } else { @@ -3088,7 +3114,7 @@ impl View for Reader { } true - }, + } Event::Gesture(GestureEvent::HoldFingerLong(center, _)) if self.rect.includes(center) => { if let Some(text) = self.selected_text() { let query = text.trim_matches(|c: char| !c.is_alphanumeric()).to_string(); @@ -3098,15 +3124,15 @@ impl View for Reader { self.selection = None; self.state = State::Idle; true - }, + } Event::Update(mode) => { self.update(Some(mode), hub, rq, context); true - }, + } Event::LoadPixmap(location) => { self.load_pixmap(location); true - }, + } Event::Submit(ViewId::GoToPageInput, ref text) => { let re = Regex::new(r#"^([-+'])?(.+)$"#).unwrap(); if let Some(caps) = re.captures(text) { @@ -3140,13 +3166,13 @@ impl View for Reader { } } true - }, + } Event::Submit(ViewId::GoToResultsPageInput, ref text) => { if let Ok(index) = text.parse::() { self.go_to_results_page(index.saturating_sub(1), hub, rq, context); } true - }, + } Event::Submit(ViewId::NamePageInput, ref text) => { if !text.is_empty() { if let Some(ref mut r) = self.info.reader { @@ -3155,7 +3181,7 @@ impl View for Reader { } self.toggle_keyboard(false, None, hub, rq, context); true - }, + } Event::Submit(ViewId::EditNoteInput, ref note) => { let selection = self.selection.take().map(|sel| [sel.start, sel.end]); @@ -3187,30 +3213,30 @@ impl View for Reader { self.update_annotations(); self.toggle_keyboard(false, None, hub, rq, context); true - }, + } Event::Submit(ViewId::ReaderSearchInput, ref text) => { match make_query(text) { Some(query) => { self.search(text, query, hub, rq); self.toggle_keyboard(false, None, hub, rq, context); self.toggle_results_bar(true, rq, context); - }, + } None => { let notif = Notification::new("Invalid search query.".to_string(), hub, rq, context); self.children.push(Box::new(notif) as Box); - }, + } } true - }, + } Event::Page(dir) => { self.go_to_neighbor(dir, hub, rq, context); true - }, + } Event::GoTo(location) | Event::Select(EntryId::GoTo(location)) => { self.go_to_page(location, true, hub, rq, context); true - }, + } Event::GoToLocation(ref location) => { let offset_opt = { let mut doc = self.doc.lock().unwrap(); @@ -3220,104 +3246,104 @@ impl View for Reader { self.go_to_page(offset, true, hub, rq, context); } true - }, + } Event::Chapter(dir) => { self.go_to_chapter(dir, hub, rq, context); true - }, + } Event::ResultsPage(dir) => { self.go_to_results_neighbor(dir, hub, rq, context); true - }, + } Event::CropMargins(ref margin) => { let current_page = self.current_page; self.crop_margins(current_page, margin.as_ref(), hub, rq, context); true - }, + } Event::Toggle(ViewId::TopBottomBars) => { self.toggle_bars(None, hub, rq, context); true - }, + } Event::Toggle(ViewId::GoToPage) => { self.toggle_go_to_page(None, ViewId::GoToPage, hub, rq, context); true - }, + } Event::Toggle(ViewId::GoToResultsPage) => { self.toggle_go_to_page(None, ViewId::GoToResultsPage, hub, rq, context); true - }, + } Event::Slider(SliderId::FontSize, font_size, FingerStatus::Up) => { self.set_font_size(font_size, hub, rq, context); true - }, + } Event::Slider(SliderId::ContrastExponent, exponent, FingerStatus::Up) => { self.set_contrast_exponent(exponent, hub, rq, context); true - }, + } Event::Slider(SliderId::ContrastGray, gray, FingerStatus::Up) => { self.set_contrast_gray(gray, hub, rq, context); true - }, + } Event::ToggleNear(ViewId::TitleMenu, rect) => { self.toggle_title_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::MainMenu, rect) => { toggle_main_menu(self, rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::BatteryMenu, rect) => { toggle_battery_menu(self, rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::ClockMenu, rect) => { toggle_clock_menu(self, rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::MarginCropperMenu, rect) => { self.toggle_margin_cropper_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::SearchMenu, rect) => { self.toggle_search_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::FontFamilyMenu, rect) => { self.toggle_font_family_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::FontSizeMenu, rect) => { self.toggle_font_size_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::TextAlignMenu, rect) => { self.toggle_text_align_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::MarginWidthMenu, rect) => { self.toggle_margin_width_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::LineHeightMenu, rect) => { self.toggle_line_height_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::ContrastExponentMenu, rect) => { self.toggle_contrast_exponent_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::ContrastGrayMenu, rect) => { self.toggle_contrast_gray_menu(rect, None, rq, context); true - }, + } Event::ToggleNear(ViewId::PageMenu, rect) => { self.toggle_page_menu(rect, None, rq, context); true - }, + } Event::Close(ViewId::MainMenu) => { toggle_main_menu(self, Rectangle::default(), Some(false), rq, context); true - }, + } Event::Close(ViewId::SearchBar) => { self.toggle_results_bar(false, rq, context); self.toggle_search_bar(false, hub, rq, context); @@ -3327,15 +3353,15 @@ impl View for Reader { self.search = None; } true - }, + } Event::Close(ViewId::GoToPage) => { self.toggle_go_to_page(Some(false), ViewId::GoToPage, hub, rq, context); true - }, + } Event::Close(ViewId::GoToResultsPage) => { self.toggle_go_to_page(Some(false), ViewId::GoToResultsPage, hub, rq, context); true - }, + } Event::Close(ViewId::SelectionMenu) => { if self.state == State::Idle && self.target_annotation.is_none() { if let Some(rect) = self.selection_rect() { @@ -3344,7 +3370,7 @@ impl View for Reader { } } false - }, + } Event::Close(ViewId::EditNote) => { self.toggle_edit_note(None, Some(false), hub, rq, context); if let Some(rect) = self.selection_rect() { @@ -3353,19 +3379,30 @@ impl View for Reader { } self.target_annotation = None; false - }, + } Event::Close(ViewId::NamePage) => { self.toggle_keyboard(false, None, hub, rq, context); false - }, + } + Event::Show(ViewId::Rotate) => { + let is_landscape = context.display.rotation == 0 || context.display.rotation == 2; + + let new_n = if is_landscape { 3 } else { 0 }; + hub.send(Event::Select(EntryId::Rotate(new_n))).ok(); + + let new_zoom_mode = if is_landscape { ZoomMode::FitToPage } else { ZoomMode::FitToWidth }; + hub.send(Event::Select(EntryId::SetZoomMode(new_zoom_mode))).ok(); + + true + } Event::Show(ViewId::TableOfContents) => { { self.toggle_bars(Some(false), hub, rq, context); } let mut doc = self.doc.lock().unwrap(); if let Some(toc) = self.toc() - .or_else(|| doc.toc()) - .filter(|toc| !toc.is_empty()) { + .or_else(|| doc.toc()) + .filter(|toc| !toc.is_empty()) { let chap = doc.chapter(self.current_page, &toc); let chap_index = chap.map_or(usize::MAX, |chap| chap.index); let html = toc_as_html(&toc, chap_index); @@ -3379,46 +3416,46 @@ impl View for Reader { hub.send(Event::OpenHtml(html, link_uri)).ok(); } true - }, + } Event::Select(EntryId::Annotations) => { self.toggle_bars(Some(false), hub, rq, context); let mut starts = self.annotations.values().flatten() - .map(|annot| annot.selection[0]).collect::>(); + .map(|annot| annot.selection[0]).collect::>(); starts.sort(); let active_range = starts.first().cloned().zip(starts.last().cloned()); if let Some(mut annotations) = self.info.reader.as_ref().map(|r| &r.annotations).cloned() { annotations.sort_by(|a, b| a.selection[0].cmp(&b.selection[0])); let html = annotations_as_html(&annotations, active_range); let link_uri = annotations.iter() - .filter(|annot| annot.selection[0].location() <= self.current_page) - .max_by_key(|annot| annot.selection[0]) - .map(|annot| format!("@{}", annot.selection[0].location())); + .filter(|annot| annot.selection[0].location() <= self.current_page) + .max_by_key(|annot| annot.selection[0]) + .map(|annot| format!("@{}", annot.selection[0].location())); hub.send(Event::OpenHtml(html, link_uri)).ok(); } true - }, + } Event::Select(EntryId::Bookmarks) => { self.toggle_bars(Some(false), hub, rq, context); if let Some(bookmarks) = self.info.reader.as_ref().map(|r| &r.bookmarks) { let html = bookmarks_as_html(&bookmarks, self.current_page, self.synthetic); - let link_uri = bookmarks.range(..= self.current_page).next_back() - .map(|index| format!("@{}", index)); + let link_uri = bookmarks.range(..=self.current_page).next_back() + .map(|index| format!("@{}", index)); hub.send(Event::OpenHtml(html, link_uri)).ok(); } true - }, + } Event::Show(ViewId::SearchBar) => { self.toggle_search_bar(true, hub, rq, context); true - }, + } Event::Show(ViewId::MarginCropper) => { self.toggle_margin_cropper(true, hub, rq, context); true - }, + } Event::Close(ViewId::MarginCropper) => { self.toggle_margin_cropper(false, hub, rq, context); true - }, + } Event::SearchResult(location, ref rects) => { if self.search.is_none() { return true; @@ -3447,10 +3484,10 @@ impl View for Reader { } true - }, + } Event::EndOfSearch => { let results_count = self.search.as_ref().map(|s| s.results_count) - .unwrap_or(usize::MAX); + .unwrap_or(usize::MAX); if results_count == 0 { let notif = Notification::new("No search results.".to_string(), hub, rq, context); @@ -3459,11 +3496,11 @@ impl View for Reader { hub.send(Event::Focus(Some(ViewId::ReaderSearchInput))).ok(); } true - }, + } Event::Select(EntryId::AnnotateSelection) => { self.toggle_edit_note(None, Some(true), hub, rq, context); true - }, + } Event::Select(EntryId::HighlightSelection) => { if let Some(sel) = self.selection.take() { let text = self.text_excerpt([sel.start, sel.end]).unwrap(); @@ -3482,7 +3519,7 @@ impl View for Reader { } true - }, + } Event::Select(EntryId::DefineSelection) => { if let Some(text) = self.selected_text() { let query = text.trim_matches(|c: char| !c.is_alphanumeric()).to_string(); @@ -3491,19 +3528,19 @@ impl View for Reader { } self.selection = None; true - }, + } Event::Select(EntryId::SearchForSelection) => { if let Some(text) = self.selected_text() { let text = text.trim_matches(|c: char| !c.is_alphanumeric()); match make_query(text) { Some(query) => { self.search(text, query, hub, rq); - }, + } None => { let notif = Notification::new("Invalid search query.".to_string(), hub, rq, context); self.children.push(Box::new(notif) as Box); - }, + } } } if let Some(rect) = self.selection_rect() { @@ -3511,13 +3548,13 @@ impl View for Reader { } self.selection = None; true - }, + } Event::Select(EntryId::GoToSelectedPageName) => { if let Some(loc) = self.selected_text().and_then(|text| { let end = text.find(|c: char| !c.is_ascii_digit() && - Digit::from_char(c).is_err() && - !c.is_ascii_uppercase()) - .unwrap_or_else(|| text.len()); + Digit::from_char(c).is_err() && + !c.is_ascii_uppercase()) + .unwrap_or_else(|| text.len()); self.find_page_by_name(&text[..end]) }) { self.go_to_page(loc, true, hub, rq, context); @@ -3527,17 +3564,17 @@ impl View for Reader { } self.selection = None; true - }, + } Event::Select(EntryId::AdjustSelection) => { self.state = State::AdjustSelection; true - }, + } Event::Select(EntryId::EditAnnotationNote(sel)) => { let text = self.find_annotation_ref(sel).map(|annot| annot.note.clone()); self.toggle_edit_note(text, Some(true), hub, rq, context); self.target_annotation = Some(sel); true - }, + } Event::Select(EntryId::RemoveAnnotationNote(sel)) => { if let Some(annot) = self.find_annotation_mut(sel) { annot.note.clear(); @@ -3548,21 +3585,21 @@ impl View for Reader { rq.add(RenderData::new(self.id, rect, UpdateMode::Gui)); } true - }, + } Event::Select(EntryId::RemoveAnnotation(sel)) => { if let Some(annotations) = self.info.reader.as_mut().map(|r| &mut r.annotations) { - annotations.retain(|annot| annot.selection[0] != sel[0] || annot.selection[1] != sel[1]); + annotations.retain(|annot| annot.selection[0] != sel[0] || annot.selection[1] != sel[1]); self.update_annotations(); } if let Some(rect) = self.text_rect(sel) { rq.add(RenderData::new(self.id, rect, UpdateMode::Gui)); } true - }, + } Event::Select(EntryId::SetZoomMode(zoom_mode)) => { self.set_zoom_mode(zoom_mode, true, hub, rq, context); true - }, + } Event::Select(EntryId::Save) => { let name = format!("{}-{}.{}", self.info.title.to_lowercase().replace(' ', "_"), Local::now().format("%Y%m%d_%H%M%S"), @@ -3575,7 +3612,7 @@ impl View for Reader { let notif = Notification::new(msg, hub, rq, context); self.children.push(Box::new(notif) as Box); true - }, + } Event::Select(EntryId::ApplyCroppings(index, scheme)) => { self.info.reader.as_mut().map(|r| { if r.cropping_margins.is_none() { @@ -3584,7 +3621,7 @@ impl View for Reader { r.cropping_margins.as_mut().map(|c| c.apply(index, scheme)) }); true - }, + } Event::Select(EntryId::RemoveCroppings) => { if let Some(r) = self.info.reader.as_mut() { r.cropping_margins = None; @@ -3592,80 +3629,83 @@ impl View for Reader { self.cache.clear(); self.update(None, hub, rq, context); true - }, + } Event::Select(EntryId::SearchDirection(dir)) => { self.search_direction = dir; true - }, + } Event::Select(EntryId::SetFontFamily(ref font_family)) => { self.set_font_family(font_family, hub, rq, context); true - }, + } Event::Select(EntryId::SetTextAlign(text_align)) => { self.set_text_align(text_align, hub, rq, context); true - }, + } Event::Select(EntryId::SetFontSize(v)) => { let font_size = self.info.reader.as_ref() - .and_then(|r| r.font_size) - .unwrap_or(context.settings.reader.font_size); + .and_then(|r| r.font_size) + .unwrap_or(context.settings.reader.font_size); let font_size = font_size - 1.0 + v as f32 / 10.0; self.set_font_size(font_size, hub, rq, context); true - }, + } Event::Select(EntryId::SetMarginWidth(width)) => { self.set_margin_width(width, hub, rq, context); true - }, + } Event::Select(EntryId::SetLineHeight(v)) => { let line_height = 1.0 + v as f32 / 10.0; self.set_line_height(line_height, hub, rq, context); true - }, + } Event::Select(EntryId::SetContrastExponent(v)) => { let exponent = 1.0 + v as f32 / 2.0; self.set_contrast_exponent(exponent, hub, rq, context); true - }, + } Event::Select(EntryId::SetContrastGray(v)) => { let gray = ((1 << 8) - (1 << (8 - v))) as f32; self.set_contrast_gray(gray, hub, rq, context); true - }, + } Event::Select(EntryId::SetPageName) => { self.toggle_name_page(None, hub, rq, context); true - }, + } Event::Select(EntryId::RemovePageName) => { if let Some(ref mut r) = self.info.reader { r.page_names.remove(&self.current_page); } true - }, + } Event::Reseed => { self.reseed(hub, rq, context); true - }, + } Event::ToggleFrontlight => { if let Some(index) = locate::(self) { self.child_mut(index).downcast_mut::().unwrap() .update_frontlight_icon(rq, context); } true - }, + } Event::Device(DeviceEvent::Button { code: ButtonCode::Home, status: ButtonStatus::Pressed, .. }) => { + context.last_read = Box::new(self.info.clone()); + self.quit(context); hub.send(Event::Back).ok(); true - }, + } Event::Select(EntryId::Quit) | Event::Select(EntryId::Reboot) | Event::Select(EntryId::RebootInNickel) | - Event::Back | - Event::Suspend => { + Event::Back => { + context.last_read = Box::new(self.info.clone()); + self.quit(context); false - }, + } Event::Focus(v) => { if self.focus != v { if let Some(ViewId::ReaderSearchInput) = v { @@ -3682,7 +3722,7 @@ impl View for Reader { } } true - }, + } _ => false, } } @@ -3709,7 +3749,7 @@ impl View for Reader { } if let Some(last) = last_rect { if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y) > rect.height().min(last.height()) as i32 / 2 && - (last.max.x < rect.min.x || rect.max.x < last.min.x) { + (last.max.x < rect.min.x || rect.max.x < last.min.x) { let space = if last.max.x < rect.min.x { rect![last.max.x, (last.min.y + rect.min.y) / 2, rect.min.x, (last.max.y + rect.max.y) / 2] @@ -3741,7 +3781,7 @@ impl View for Reader { if let Some(last) = last_rect { // Are `rect` and `last` on the same line? if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y) > rect.height().min(last.height()) as i32 / 2 && - (last.max.x < rect.min.x || rect.max.x < last.min.x) { + (last.max.x < rect.min.x || rect.max.x < last.min.x) { let space = if last.max.x < rect.min.x { rect![last.max.x, (last.min.y + rect.min.y) / 2, rect.min.x, (last.max.y + rect.max.y) / 2] @@ -3770,7 +3810,7 @@ impl View for Reader { } if let Some(last) = last_rect { if rect.max.y.min(last.max.y) - rect.min.y.max(last.min.y) > rect.height().min(last.height()) as i32 / 2 && - (last.max.x < rect.min.x || rect.max.x < last.min.x) { + (last.max.x < rect.min.x || rect.max.x < last.min.x) { let space = if last.max.x < rect.min.x { rect![last.max.x, (last.min.y + rect.min.y) / 2, rect.min.x, (last.max.y + rect.max.y) / 2] @@ -3836,9 +3876,9 @@ impl View for Reader { rect.max.x, rect.max.y - (3 * big_height + small_height) as i32 - small_thickness]; self.children[index].resize(sb_rect, hub, rq, context); - self.children[index-1].resize(rect![rect.min.x, sb_rect.min.y - thickness, + self.children[index - 1].resize(rect![rect.min.x, sb_rect.min.y - thickness, rect.max.x, sb_rect.min.y], - hub, rq, context); + hub, rq, context); index += 2; } if self.children[index].is::() { @@ -3847,13 +3887,13 @@ impl View for Reader { rect.max.x, rect.max.y - small_height - small_thickness]; self.children[index].resize(kb_rect, hub, rq, context); - self.children[index+1].resize(rect![rect.min.x, kb_rect.max.y, + self.children[index + 1].resize(rect![rect.min.x, kb_rect.max.y, rect.max.x, kb_rect.max.y + thickness], - hub, rq, context); + hub, rq, context); let kb_rect = *self.children[index].rect(); - self.children[index-1].resize(rect![rect.min.x, kb_rect.min.y - thickness, + self.children[index - 1].resize(rect![rect.min.x, kb_rect.min.y - thickness, rect.max.x, kb_rect.min.y], - hub, rq, context); + hub, rq, context); index += 2; } floating_layer_start = index; @@ -3865,7 +3905,7 @@ impl View for Reader { rect.max.y - small_height - small_thickness, rect.max.x, rect.max.y - small_height + big_thickness]; - self.children[index-1].resize(separator_rect, hub, rq, context); + self.children[index - 1].resize(separator_rect, hub, rq, context); let bottom_bar_rect = rect![rect.min.x, rect.max.y - small_height + big_thickness, rect.max.x, @@ -3883,7 +3923,7 @@ impl View for Reader { small_height } as i32; - let y_max = self.children[index+1].rect().min.y; + let y_max = self.children[index + 1].rect().min.y; let bar_rect = rect![rect.min.x, y_max - bar_height + thickness, rect.max.x, @@ -3894,7 +3934,7 @@ impl View for Reader { y_max - thickness, rect.max.x, y_max]; - self.children[index-1].resize(sp_rect, hub, rq, context); + self.children[index - 1].resize(sp_rect, hub, rq, context); index -= 2; } @@ -3909,14 +3949,14 @@ impl View for Reader { ZoomMode::FitToWidth => { // Apply the scale change. let ratio = (rect.width() as i32 - 2 * self.view_port.margin_width) as f32 / - (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32; + (self.rect.width() as i32 - 2 * self.view_port.margin_width) as f32; self.view_port.page_offset.y = (self.view_port.page_offset.y as f32 * ratio) as i32; - }, + } ZoomMode::Custom(_) => { // Keep the center still. self.view_port.page_offset += pt!(self.rect.width() as i32 - rect.width() as i32, self.rect.height() as i32 - rect.height() as i32) / 2; - }, + } _ => (), } @@ -3924,8 +3964,8 @@ impl View for Reader { if self.reflowable { let font_size = self.info.reader.as_ref() - .and_then(|r| r.font_size) - .unwrap_or(context.settings.reader.font_size); + .and_then(|r| r.font_size) + .unwrap_or(context.settings.reader.font_size); let mut doc = self.doc.lock().unwrap(); doc.layout(rect.width(), rect.height(), font_size, CURRENT_DEVICE.dpi); let current_page = self.current_page.min(doc.pages_count() - 1); diff --git a/src/view/reader/tool_bar.rs b/src/view/reader/tool_bar.rs index 20daaa3b..6481ee96 100644 --- a/src/view/reader/tool_bar.rs +++ b/src/view/reader/tool_bar.rs @@ -203,11 +203,11 @@ impl ToolBar { Event::Show(ViewId::SearchBar)); children.push(Box::new(search_icon) as Box); - let toc_icon = Icon::new("toc", + let rotate_icon = Icon::new("gray", rect![rect.max.x - side, rect.max.y - side, rect.max.x, rect.max.y], - Event::Show(ViewId::TableOfContents)); - children.push(Box::new(toc_icon) as Box); + Event::Show(ViewId::Rotate)); + children.push(Box::new(rotate_icon) as Box); ToolBar { id, diff --git a/src/xkcd.rs b/src/xkcd.rs new file mode 100644 index 00000000..5072b3ab --- /dev/null +++ b/src/xkcd.rs @@ -0,0 +1,228 @@ +mod helpers; + +use std::io; +use std::env; +use std::fs::{self, File}; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use chrono::{Duration, Utc, Local, DateTime}; +use serde::{Serialize, Deserialize}; +use serde_json::{json, Value as JsonValue}; +use reqwest::blocking::Client; +use anyhow::{Error, Context, format_err}; +use self::helpers::{load_toml, load_json, save_json, decode_entities}; + +const SETTINGS_PATH: &str = "Settings.toml"; +const SESSION_PATH: &str = ".session.json"; +const BASE_URL: &str = "https://xkcd.com/"; + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(default, rename_all = "kebab-case")] +struct Settings { + num_comics_to_download: usize, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +struct Session { + downloads_count: usize, + last_opened: String, +} + +impl Default for Session { + fn default() -> Self { + Session { + downloads_count: 0, + last_opened: "0000-00-00 00:00:00".to_string(), + } + } +} + +fn main() -> Result<(), Error> { + let mut args = env::args().skip(1); + let library_path = PathBuf::from(args.next() + .ok_or_else(|| format_err!("missing argument: library path"))?); + let save_path = PathBuf::from(args.next() + .ok_or_else(|| format_err!("missing argument: save path"))?); + let wifi = args.next() + .ok_or_else(|| format_err!("missing argument: wifi status")) + .and_then(|v| v.parse::().map_err(Into::into))?; + let online = args.next() + .ok_or_else(|| format_err!("missing argument: online status")) + .and_then(|v| v.parse::().map_err(Into::into))?; + let settings = load_toml::(SETTINGS_PATH) + .with_context(|| format!("can't load settings from {}", SETTINGS_PATH))?; + let mut session = load_json::(SESSION_PATH) + .unwrap_or_default(); + + if !online { + if !wifi { + let event = json!({ + "type": "notify", + "message": "Establishing a network connection.", + }); + // println!("{}", event); + let event = json!({ + "type": "setWifi", + "enable": true, + }); + println!("{}", event); + } else { + let event = json!({ + "type": "notify", + "message": "Waiting for the network to come up.", + }); + // println!("{}", event); + } + let mut line = String::new(); + io::stdin().read_line(&mut line)?; + } + + if !save_path.exists() { + fs::create_dir(&save_path)?; + } + + let client = Client::new(); + + let sigterm = Arc::new(AtomicBool::new(false)); + signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&sigterm))?; + + let mut current_num = 1; + let last_downloads_count = session.downloads_count; + + { + let url = format!("{}/info.0.json", &BASE_URL); + + let entries: JsonValue = client.get(&url) + .send()? + .json()?; + + if entries.get("num").is_none() { + let message = "Something went wrong getting today's comic."; + let event = json!({ + "type": "notify", + "message": &message, + }); + println!("{}", event); + } else { + let num = entries.get("num") + .and_then(|v| v.as_u64()) + .unwrap(); + let message = format!("Today's comic: {}.", num); + let event = json!({ + "type": "notify", + "message": &message, + }); + // println!("{}", event); + + current_num = num; + } + } + + for num in current_num - settings.num_comics_to_download as u64..current_num + 1 { + + let comic_path = save_path.join(&format!("{}.png", num)); + if comic_path.exists() { + continue; + } + + let url = format!("{}/{}/info.0.json", &BASE_URL, &num); + + let data: JsonValue = client.get(&url) + .send()? + .json()?; + + if data.get("num").is_none() { + continue; + } + + let title = data.get("title") + .and_then(JsonValue::as_str) + .map(decode_entities) + .map(String::from) + .unwrap_or_default(); + + let safe_title = data.get("safe_title") + .and_then(JsonValue::as_str) + .map(decode_entities) + .map(String::from) + .unwrap_or_default(); + + let comic_url = data.get("img") + .and_then(JsonValue::as_str) + .map(decode_entities) + .map(String::from) + .unwrap_or_default(); + + let mut file = File::create(&comic_path)?; + + let response = client.get(&comic_url) + .send() + .and_then(|mut body| body.copy_to(&mut file)); + + if let Err(err) = response { + eprintln!("Can't download {}: {:#}.", &num, err); + fs::remove_file(comic_path).ok(); + continue; + } + + session.downloads_count = session.downloads_count.wrapping_add(1); + + if let Ok(path) = comic_path.strip_prefix(&library_path) { + let file_info = json!({ + "path": path, + "kind": "png", + "size": file.metadata().ok() + .map_or(0, |m| m.len()), + }); + + let info = json!({ + "title": title, + "author": "", + "identifier": num.to_string(), + "file": file_info, + }); + + let event = json!({ + "type": "addDocument", + "info": &info, + }); + + println!("{}", event); + } + } + + // if pages_count > 0 { + // let downloads_count = session.downloads_count + // .saturating_sub(last_downloads_count); + // let message = if downloads_count > 0 { + // format!("Downloaded {} comic{}.", downloads_count, if downloads_count != 1 { "s" } else { "" }) + // } else { + // "No comics downloaded.".to_string() + // }; + // let event = json!({ + // "type": "notify", + // "message": &message, + // }); + // println!("{}", event); + // } + + let message = "Done!".to_string(); + let event = json!({ + "type": "notify", + "message": &message, + }); + println!("{}", event); + + if !wifi { + let event = json!({ + "type": "setWifi", + "enable": false, + }); + println!("{}", event); + } + + save_json(&session, SESSION_PATH).context("can't save session")?; + Ok(()) +}