diff --git a/Cargo.toml b/Cargo.toml index e5d1022..eb74fc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "icy_view" -version = "0.6.0" +version = "0.6.1" edition = "2021" description = "A fast ansi art viewer." authors = ["Mike Krüger "] diff --git a/src/main.rs b/src/main.rs index 55d363a..a1e34ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use clap::Parser; use semver::Version; -use view_library::MainWindow; +use view_library::{options::Options, MainWindow}; lazy_static::lazy_static! { static ref VERSION: Version = Version::parse( env!("CARGO_PKG_VERSION")).unwrap(); @@ -23,14 +23,22 @@ lazy_static::lazy_static! { } #[derive(Parser, Debug)] +#[command(version, about, long_about = None)] pub struct Cli { path: Option, + + #[clap(long, default_value_t = false, help = "Enable auto-scrolling")] + auto: bool, } fn main() { let args = Cli::parse(); + let mut options = Options::load_options(); + if args.auto { + options.auto_scroll_enabled = true; + } - let options = eframe::NativeOptions { + let native_options = eframe::NativeOptions { //initial_window_size: Some(egui::vec2(1284. + 8., 839.)), multisampling: 0, renderer: eframe::Renderer::Glow, @@ -40,16 +48,16 @@ fn main() { // options.viewport.icon = Some(IconData::from( &include_bytes!("../build/linux/256x256.png")[..]).unwrap()); eframe::run_native( &DEFAULT_TITLE, - options, + native_options, Box::new(|cc| { let gl = cc.gl.as_ref().expect("You need to run eframe with the glow backend"); egui_extras::install_image_loaders(&cc.egui_ctx); - let mut fd = MainWindow::new(gl, args.path); + let mut fd = MainWindow::new(gl, args.path, options); + fd.store_options = true; if *VERSION < *LATEST_VERSION { fd.file_view.upgrade_version = Some(LATEST_VERSION.to_string()); } - let cmd = fd.file_view.refresh(); fd.handle_command(cmd); Box::new(fd) diff --git a/view_library/Cargo.toml b/view_library/Cargo.toml index 347e4f2..dc45688 100644 --- a/view_library/Cargo.toml +++ b/view_library/Cargo.toml @@ -23,6 +23,8 @@ egui-modal = "0.3.3" egui-notify = "0.13.0" thiserror = "1.0" anyhow = "1.0.75" +serde = "1.0.197" +toml = "0.8.10" image = { version = "0.24", features = ["jpeg", "png", "gif", "bmp"] } icy_engine = { git = "https://github.com/mkrueger/icy_engine.git" } diff --git a/view_library/src/ui/file_view.rs b/view_library/src/ui/file_view.rs index bbf9fc2..73b5164 100644 --- a/view_library/src/ui/file_view.rs +++ b/view_library/src/ui/file_view.rs @@ -14,6 +14,8 @@ use std::{ path::{Path, PathBuf}, }; +use super::options::{Options, ScrollSpeed}; + pub enum Message { Select(usize, bool), Open(usize), @@ -103,14 +105,13 @@ pub struct FileView { pub files: Vec, pub upgrade_version: Option, - pub auto_scroll_enabled: bool, - pub scroll_speed: usize, + pub options: super::options::Options, pub filter: String, pre_select_file: Option, } impl FileView { - pub fn new(initial_path: Option) -> Self { + pub fn new(initial_path: Option, options: Options) -> Self { let mut path = if let Some(path) = initial_path { path } else if let Some(user_dirs) = UserDirs::new() { @@ -138,8 +139,7 @@ impl FileView { scroll_pos: None, files: Vec::new(), filter: String::new(), - auto_scroll_enabled: true, - scroll_speed: 1, + options, upgrade_version: None, } } @@ -228,18 +228,17 @@ impl FileView { ui.close_menu(); } ui.separator(); - let mut b = self.auto_scroll_enabled; + let mut b = self.options.auto_scroll_enabled; if ui.checkbox(&mut b, fl!(crate::LANGUAGE_LOADER, "menu-item-auto-scroll")).clicked() { command = Some(Message::ToggleAutoScroll); ui.close_menu(); } - let title = match self.scroll_speed { - 2 => fl!(crate::LANGUAGE_LOADER, "menu-item-scroll-speed-slow"), - 0 => { + let title = match self.options.scroll_speed { + ScrollSpeed::Slow => fl!(crate::LANGUAGE_LOADER, "menu-item-scroll-speed-slow"), + ScrollSpeed::Medium => { fl!(crate::LANGUAGE_LOADER, "menu-item-scroll-speed-medium") } - 1 => fl!(crate::LANGUAGE_LOADER, "menu-item-scroll-speed-fast"), - _ => panic!(), + ScrollSpeed::Fast => fl!(crate::LANGUAGE_LOADER, "menu-item-scroll-speed-fast"), }; let r = ui.selectable_label(false, title); diff --git a/view_library/src/ui/mod.rs b/view_library/src/ui/mod.rs index d9cd8f5..33465b3 100644 --- a/view_library/src/ui/mod.rs +++ b/view_library/src/ui/mod.rs @@ -15,10 +15,14 @@ use std::{ time::Duration, }; -use self::file_view::{FileEntry, FileView, Message}; +use self::{ + file_view::{FileEntry, FileView, Message}, + options::{Options, ScrollSpeed}, +}; mod file_view; mod help_dialog; +pub mod options; mod sauce_dialog; pub struct MainWindow<'a> { @@ -45,11 +49,10 @@ pub struct MainWindow<'a> { toasts: egui_notify::Toasts, is_closed: bool, pub opened_file: Option, - + pub store_options: bool, // animations animation: Option>>, } -const SCROLL_SPEED: [f32; 3] = [80.0, 160.0, 320.0]; const EXT_WHITE_LIST: [&str; 5] = ["seq", "diz", "nfo", "ice", "bbs"]; const EXT_BLACK_LIST: [&str; 8] = ["zip", "rar", "gz", "tar", "7z", "pdf", "exe", "com"]; @@ -73,7 +76,7 @@ impl<'a> App for MainWindow<'a> { ui.set_enabled(self.sauce_dialog.is_none() && self.help_dialog.is_none()); self.paint_main_area(ui) }); - self.in_scroll &= self.file_view.auto_scroll_enabled; + self.in_scroll &= self.file_view.options.auto_scroll_enabled; if self.in_scroll { // ctx.request_repaint_after(Duration::from_millis(10)); ctx.request_repaint(); @@ -121,10 +124,17 @@ impl<'a> App for MainWindow<'a> { } } } + + fn on_exit(&mut self, _gl: Option<&glow::Context>) { + println!("store options: {}", self.store_options); + if self.store_options { + self.file_view.options.store_options(); + } + } } impl<'a> MainWindow<'a> { - pub fn new(gl: &Arc, mut initial_path: Option) -> Self { + pub fn new(gl: &Arc, mut initial_path: Option, options: Options) -> Self { let mut view = BufferView::new(gl); view.interactive = false; @@ -137,9 +147,10 @@ impl<'a> MainWindow<'a> { } } } + Self { buffer_view: Arc::new(eframe::epaint::mutex::Mutex::new(view)), - file_view: FileView::new(initial_path), + file_view: FileView::new(initial_path, options), in_scroll: false, retained_image: None, texture_handle: None, @@ -157,6 +168,7 @@ impl<'a> MainWindow<'a> { opened_file: None, is_closed: false, animation: None, + store_options: false, } } @@ -193,7 +205,7 @@ impl<'a> MainWindow<'a> { .inner_margin(egui::style::Margin::same(0.0)) .fill(Color32::BLACK); egui::CentralPanel::default().frame(frame_no_margins).show(ctx, |ui| self.paint_main_area(ui)); - self.in_scroll &= self.file_view.auto_scroll_enabled; + self.in_scroll &= self.file_view.options.auto_scroll_enabled; if self.in_scroll { // ctx.request_repaint_after(Duration::from_millis(10)); ctx.request_repaint(); @@ -389,7 +401,7 @@ impl<'a> MainWindow<'a> { let dt = ui.input(|i| i.unstable_dt); let sp = if self.in_scroll { - (self.cur_scroll_pos + SCROLL_SPEED[self.file_view.scroll_speed] * dt).round() + (self.cur_scroll_pos + self.file_view.options.scroll_speed.get_speed() * dt).round() } else { self.cur_scroll_pos.round() }; @@ -514,9 +526,7 @@ impl<'a> MainWindow<'a> { result.buffer_type = icy_engine::BufferType::Unicode; } match parse_with_parser(&mut result, &mut rip_parser, &text, true) { - Ok(_) => { - rip_parser - } + Ok(_) => rip_parser, Err(err) => { log::error!("Error while parsing rip file: {err}"); rip_parser @@ -607,10 +617,10 @@ impl<'a> MainWindow<'a> { } } Message::ToggleAutoScroll => { - self.file_view.auto_scroll_enabled = !self.in_scroll; - self.in_scroll = self.file_view.auto_scroll_enabled; + self.file_view.options.auto_scroll_enabled = !self.file_view.options.auto_scroll_enabled; + self.in_scroll = self.file_view.options.auto_scroll_enabled; - if self.file_view.auto_scroll_enabled { + if self.file_view.options.auto_scroll_enabled { self.toasts .info(fl!(crate::LANGUAGE_LOADER, "toast-auto-scroll-on")) .set_duration(Some(Duration::from_secs(3))); @@ -631,25 +641,24 @@ impl<'a> MainWindow<'a> { self.help_dialog = Some(help_dialog::HelpDialog::new()); } Message::ChangeScrollSpeed => { - self.file_view.scroll_speed = (self.file_view.scroll_speed + 1) % SCROLL_SPEED.len(); + self.file_view.options.scroll_speed = self.file_view.options.scroll_speed.next(); - match self.file_view.scroll_speed { - 0 => { + match self.file_view.options.scroll_speed { + ScrollSpeed::Slow => { self.toasts .info(fl!(crate::LANGUAGE_LOADER, "toast-scroll-slow")) .set_duration(Some(Duration::from_secs(3))); } - 1 => { + ScrollSpeed::Medium => { self.toasts .info(fl!(crate::LANGUAGE_LOADER, "toast-scroll-medium")) .set_duration(Some(Duration::from_secs(3))); } - 2 => { + ScrollSpeed::Fast => { self.toasts .info(fl!(crate::LANGUAGE_LOADER, "toast-scroll-fast")) .set_duration(Some(Duration::from_secs(3))); } - _ => {} } } } diff --git a/view_library/src/ui/options.rs b/view_library/src/ui/options.rs new file mode 100644 index 0000000..f8d3f5b --- /dev/null +++ b/view_library/src/ui/options.rs @@ -0,0 +1,81 @@ +use serde::{Deserialize, Serialize}; +use std::fs; + +const SCROLL_SPEED: [f32; 3] = [80.0, 160.0, 320.0]; + +#[derive(Serialize, Deserialize, Debug)] +pub enum ScrollSpeed { + Slow, + Medium, + Fast, +} + +impl ScrollSpeed { + pub fn get_speed(&self) -> f32 { + match self { + ScrollSpeed::Slow => SCROLL_SPEED[0], + ScrollSpeed::Medium => SCROLL_SPEED[1], + ScrollSpeed::Fast => SCROLL_SPEED[2], + } + } + + pub(crate) fn next(&self) -> ScrollSpeed { + match self { + ScrollSpeed::Slow => ScrollSpeed::Medium, + ScrollSpeed::Medium => ScrollSpeed::Fast, + ScrollSpeed::Fast => ScrollSpeed::Slow, + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Options { + pub auto_scroll_enabled: bool, + pub scroll_speed: ScrollSpeed, +} + +impl Default for Options { + fn default() -> Self { + Self { + auto_scroll_enabled: true, + scroll_speed: ScrollSpeed::Medium, + } + } +} + +impl Options { + pub fn load_options() -> Self { + if let Some(proj_dirs) = directories::ProjectDirs::from("com", "GitHub", "icy_view") { + if !proj_dirs.config_dir().exists() && fs::create_dir_all(proj_dirs.config_dir()).is_err() { + log::error!("Can't create configuration directory {:?}", proj_dirs.config_dir()); + return Self::default(); + } + let options_file = proj_dirs.config_dir().join("options.toml"); + if options_file.exists() { + match fs::read_to_string(options_file) { + Ok(txt) => { + if let Ok(result) = toml::from_str(&txt) { + return result; + } + } + Err(err) => log::error!("Error reading options file: {}", err), + } + } + } + Self::default() + } + + pub fn store_options(&self) { + if let Some(proj_dirs) = directories::ProjectDirs::from("com", "GitHub", "icy_view") { + let file_name = proj_dirs.config_dir().join("options.toml"); + match toml::to_string(self) { + Ok(text) => { + if let Err(err) = fs::write(file_name, text) { + log::error!("Error writing options file: {}", err); + } + } + Err(err) => log::error!("Error writing options file: {}", err), + } + } + } +}