diff --git a/src/configuration.rs b/src/configuration.rs index a5b8c055..676f1ff9 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,28 +1,27 @@ -//! There are two files that store properties for Alloy, the *cache* and the *config*. -//! -//! The most important distinction between these is that Alloy never writes to the *config* -//! but it does write to the *cache* to save portions of the state of the program (e.g. window size -//! and position). -//! -//! Furthermore it's generally true that the user will only edit the *config* to specify their -//! preferences. -//! use directories::ProjectDirs; use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, collections::BTreeMap, fs, path::Path, path::PathBuf}; +use std::{collections::BTreeMap, fs, path::PathBuf}; /// Application name for project directories const APPLICATION: &str = "Alloy"; -#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub enum WindowMode { + #[default] + Normal, + Maximized, + Fullscreen, +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub enum Theme { + #[default] Light, Dark, } impl Theme { - pub fn switch_theme(self) -> Self { + pub fn toggle(self) -> Self { match self { Theme::Dark => Theme::Light, Theme::Light => Theme::Dark, @@ -30,10 +29,15 @@ impl Theme { } } -#[derive( - Copy, Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize, -)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub enum ScalingMode { + #[default] + Fixed, + FitStretch, + FitMin, +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub enum Antialias { #[default] Auto, @@ -41,113 +45,26 @@ pub enum Antialias { Never, } -#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct CacheImageSection { - pub fit_stretches: bool, - pub antialiasing: Antialias, -} - -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize)] -pub struct ConfigImageSection { - pub antialiasing: Option, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct CacheWindowSection { - pub dark: bool, - pub win_w: u32, - pub win_h: u32, - pub win_x: i32, - pub win_y: i32, -} - -impl Default for CacheWindowSection { - fn default() -> Self { - Self { - dark: false, - win_w: 580, - win_h: 558, - win_x: 64, - win_y: 64, - } - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct ConfigWindowSection { - pub start_fullscreen: Option, - pub start_maximized: Option, - pub show_bottom_bar: Option, +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct ConfigWindow { + pub title_folders: Option, + pub mode: Option, pub theme: Option, - pub use_last_window_area: Option, - pub win_w: Option, - pub win_h: Option, - pub win_x: Option, - pub win_y: Option, -} - -#[derive(Deserialize)] -struct IncompleteCache { - pub window: Option, - pub image: Option, } -#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize)] -pub struct Cache { - pub window: CacheWindowSection, - pub image: CacheImageSection, +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct ConfigImage { + pub scaling: Option, + pub antialiasing: Option, } -impl From for Cache { - fn from(cache: IncompleteCache) -> Self { - Self { - window: cache.window.unwrap_or_default(), - image: cache.image.unwrap_or_default(), - } - } -} - -impl Cache { - pub fn theme(&self) -> Theme { - if self.window.dark { - Theme::Dark - } else { - Theme::Light - } - } - - pub fn set_theme(&mut self, theme: Theme) { - self.window.dark = theme == Theme::Dark; - } - - pub fn load() -> Result { - let file_path = cache_file(); - let cfg_str = fs::read_to_string(&file_path).map_err(|_| { - format!("Could not read cache from {file_path:?}") - })?; - let result: IncompleteCache = - toml::from_str(&cfg_str).map_err(|e| format!("{e}"))?; - //println!("Read cache from file:\n{:#?}", result); - Ok(result.into()) - } - - pub fn save(&self) -> Result<(), String> { - let file_path = cache_file(); - let string = toml::to_string(self).map_err(|e| format!("{e}"))?; - fs::write(&file_path, string).map_err(|_| { - format!("Could not write to cache file {:?}", file_path) - })?; - Ok(()) - } -} - -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct EnvVar { pub name: String, pub value: String, } -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct Command { pub input: Vec, pub program: String, @@ -155,58 +72,16 @@ pub struct Command { pub envs: Option>, } -#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize)] -pub struct TitleSection { - pub displayed_folders: Option, - pub show_program_name: Option, -} - -impl TitleSection { - pub fn format_file_path<'a>(&self, file_path: &'a Path) -> Cow<'a, str> { - match self.displayed_folders { - Some(0) | None => file_path.file_name().unwrap().to_string_lossy(), - Some(n) => { - let mut component_count = 0; - // On Windows the root can be the second component, when a - // `Prefix` is the first. - let mut root_index = 0; - for (idx, c) in file_path.components().enumerate() { - component_count += 1; - if c == std::path::Component::RootDir { - root_index = idx as u32; - } - } - let path = if (component_count - root_index) <= (1 + n) { - file_path - .to_string_lossy() - .trim_start_matches("\\\\?\\") - .to_owned() - .into() - } else { - let ancestor = file_path - .ancestors() - .take(2 + n as usize) - .last() - .unwrap(); - file_path.strip_prefix(ancestor).unwrap().to_string_lossy() - }; - path - } - } - } -} - -#[derive(Debug, Default, Clone, Deserialize)] +#[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct Configuration { + pub window: Option, + pub image: Option, pub bindings: Option>>, pub commands: Option>, - pub title: Option, - pub image: Option, - pub window: Option, } impl Configuration { - pub fn load() -> Result { + pub fn load() -> Result { let file_path = config_file(); let cfg_str = fs::read_to_string(&file_path) .map_err(|_| format!("Could not read config from {file_path:?}"))?; @@ -215,6 +90,64 @@ impl Configuration { //println!("Read config from file:\n{:#?}", result); Ok(result) } + + pub fn save(&self) -> Result<(), String> { + let file_path = config_file(); + let cfg_str = toml::to_string(self).map_err(|e| format!("{e}"))?; + fs::write(&file_path, cfg_str).map_err(|_| { + format!("Could not write to config file {file_path:?}") + })?; + Ok(()) + } + + pub fn scaling(&self) -> ScalingMode { + self.image.as_ref().and_then(|i| i.scaling).unwrap_or_default() + } + + pub fn set_scaling(&mut self, scaling: ScalingMode) { + if self.image.is_none() { + self.image = Some(ConfigImage::default()); + } + if let Some(image) = &mut self.image { + image.scaling = Some(scaling); + } + } + + pub fn antialiasing(&self) -> Antialias { + self.image.as_ref().and_then(|i| i.antialiasing).unwrap_or_default() + } + + pub fn set_antialiasing(&mut self, antialias: Antialias) { + if self.image.is_none() { + self.image = Some(ConfigImage::default()); + } + if let Some(image) = &mut self.image { + image.antialiasing = Some(antialias); + } + } + + pub fn title_folders(&self) -> u32 { + self.window.as_ref().and_then(|w| w.title_folders).unwrap_or_default() + } + + pub fn window_mode(&self) -> WindowMode { + self.window.as_ref().and_then(|w| w.mode).unwrap_or_default() + } + + pub fn theme(&self) -> Theme { + self.window.as_ref().and_then(|w| w.theme).unwrap_or_default() + } + + pub fn set_theme(&mut self, theme: Theme) { + self.window_config().theme = Some(theme); + } + + fn window_config(&mut self) -> &mut ConfigWindow { + if self.window.is_none() { + self.window = Some(ConfigWindow::default()); + } + self.window.as_mut().unwrap() + } } fn project_dir_fallback() -> PathBuf { @@ -231,18 +164,7 @@ fn config_file() -> PathBuf { if !config_dir.exists() { std::fs::create_dir_all(&config_dir).unwrap(); } - config_dir.join("cfg.toml") -} - -fn cache_file() -> PathBuf { - let cache_dir = match ProjectDirs::from("", "", APPLICATION) { - Some(proj) => proj.cache_dir().to_owned(), - None => project_dir_fallback(), - }; - if !cache_dir.exists() { - std::fs::create_dir_all(&cache_dir).unwrap(); - } - cache_dir.join("cache.toml") + config_dir.join("config.toml") } pub fn data_dir() -> PathBuf { diff --git a/src/gelatin/application.rs b/src/gelatin/application.rs index 996e43c3..e1d7feb2 100644 --- a/src/gelatin/application.rs +++ b/src/gelatin/application.rs @@ -97,16 +97,6 @@ impl Application { self.windows.insert(window.get_id(), window); } - #[allow(dead_code)] - pub fn add_global_event_handler< - F: FnMut(&Event<()>) -> NextUpdate + 'static, - >( - &mut self, - fun: F, - ) { - self.global_handlers.push(Box::new(fun)); - } - pub fn start_event_loop(self) -> ! { let mut windows = self.windows; let mut at_exit = self.at_exit; diff --git a/src/gelatin/window.rs b/src/gelatin/window.rs index 90b2449c..683d9edc 100644 --- a/src/gelatin/window.rs +++ b/src/gelatin/window.rs @@ -262,14 +262,6 @@ impl Window { resulting_window } - pub fn add_global_event_handler( - &self, - fun: F, - ) { - let mut borrowed = self.data.borrow_mut(); - borrowed.global_event_handlers.push(Box::new(fun)); - } - pub fn set_root(&self, widget: Rc) { let mut borrowed = self.data.borrow_mut(); widget.set_valid_ref(borrowed.render_validity.clone()); diff --git a/src/input_handling.rs b/src/input_handling.rs index 6ac0f28d..9d7e0077 100644 --- a/src/input_handling.rs +++ b/src/input_handling.rs @@ -1,4 +1,5 @@ -use std::{cell::RefCell, collections::HashMap, process::Command, rc::Rc}; +use std::{collections::HashMap, process::Command}; +use std::sync::{Arc, Mutex}; use glium::glutin::event::ModifiersState; use lazy_static::lazy_static; @@ -79,13 +80,13 @@ fn substitute_command_parameters( /// wouldn't be able to construct a command from them if they cannot be converted to /// valid UTF-8. pub fn execute_triggered_commands( - config: Rc>, + config: Arc>, input_key: &str, modifiers: ModifiersState, img_path: &str, folder_path: &str, ) { - let config = config.borrow(); + let config = config.lock().unwrap(); if let Some(ref commands) = config.commands { let mut var_map = HashMap::with_capacity(2); var_map.insert("${img}", img_path); @@ -158,12 +159,12 @@ pub fn keys_triggered>( } pub fn action_triggered( - config: &Rc>, + config: &Arc>, action_name: &str, input_key: &str, modifiers: ModifiersState, ) -> bool { - let config = config.borrow(); + let config = config.lock().unwrap(); let bindings = config.bindings.as_ref(); if let Some(Some(keys)) = bindings.map(|b| b.get(action_name)) { keys_triggered(keys.as_slice(), input_key, modifiers) diff --git a/src/main.rs b/src/main.rs index 7fa1cb81..4be20557 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::{ - cell::{Cell, RefCell}, + cell::Cell, f32, rc::Rc, sync::{Arc, Mutex}, @@ -10,14 +10,10 @@ use std::{ use log::trace; use crate::{ - configuration::{Cache, ConfigWindowSection, Configuration, Theme}, + configuration::{Configuration, Theme, WindowMode}, gelatin::{ application::*, - glium::glutin::{ - dpi::{PhysicalPosition, PhysicalSize}, - event::WindowEvent, - window::Icon, - }, + glium::glutin::{window::Icon}, image, label::*, line_layout_container::*, @@ -58,91 +54,25 @@ fn main() { let args = cmd_line::parse_args(); - let cache = Cache::load(); - let first_launch = cache.is_err(); - let cache = Arc::new(Mutex::new(cache.unwrap_or_default())); let config = Configuration::load(); - let config = Rc::new(RefCell::new(config.unwrap_or_default())); + let first_launch = config.is_err(); + let config = Arc::new(Mutex::new(config.unwrap_or_default())); let mut application = Application::new(); let window: Rc = { - let window_cache = &mut cache.lock().unwrap().window; - let window_cfg = &config.borrow().window; - let window_defaults = configuration::CacheWindowSection::default(); - - if let Some(ConfigWindowSection { - use_last_window_area: Some(false), - win_x, - win_y, - win_w, - win_h, - .. - }) = window_cfg - { - window_cache.win_x = if let Some(x) = win_x { - *x - } else { - window_defaults.win_x - }; - window_cache.win_y = if let Some(y) = win_y { - *y - } else { - window_defaults.win_y - }; - window_cache.win_w = if let Some(w) = win_w { - *w - } else { - window_defaults.win_w - }; - window_cache.win_h = if let Some(h) = win_h { - *h - } else { - window_defaults.win_h - }; - } else { - let right = window_cache.win_x as i64 + window_cache.win_w as i64; - if right < 20 { - window_cache.win_w = window_defaults.win_w; - window_cache.win_x = window_defaults.win_x; - } - if window_cache.win_y < 20 { - window_cache.win_y = window_defaults.win_y; - } - } - let pos = PhysicalPosition::new(window_cache.win_x, window_cache.win_y); - let size = PhysicalSize::new(window_cache.win_w, window_cache.win_h); + let cfg = &mut config.lock().unwrap(); let window_desc = WindowDescriptor::builder() .icon(Some(make_icon())) - .size(size) - .position(Some(pos)) .build(); let window = Window::new(&mut application, window_desc); - // This is just to fix the bug on Linux that the window doesn't start up at - // the specified position when the position is specified during initialization - window - .display_mut() - .gl_window() - .window() - .set_outer_position(pos); - - if let Some(ConfigWindowSection { - start_maximized: Some(true), - .. - }) = window_cfg - { - window.set_maximized(true); - } - if let Some(ConfigWindowSection { - start_fullscreen: Some(true), - .. - }) = window_cfg - { - window.set_fullscreen(true); + match cfg.window_mode() { + WindowMode::Fullscreen => window.set_fullscreen(true), + WindowMode::Maximized => window.set_maximized(true), + _ => (), } window }; - add_window_movement_listener(&window, cache.clone()); let usage_img = Picture::from_encoded_bytes(USAGE); let help_screen = Rc::new(HelpScreen::new(usage_img)); @@ -152,14 +82,13 @@ fn main() { let copy_notifications_widget = Rc::new(Label::new()); let copy_notifications = CopyNotifications::new(©_notifications_widget); - let bottom_bar = Rc::new(BottomBar::new(&config.borrow())); + let bottom_bar = Rc::new(BottomBar::new()); let picture_widget = make_picture_widget( &window, bottom_bar.clone(), left_to_pan_hint.clone(), copy_notifications, config.clone(), - cache.clone(), ); if let Some(file_path) = args.file_path { @@ -176,24 +105,15 @@ fn main() { root_container.add_child(picture_area_container); root_container.add_child(bottom_bar.widget.clone()); - let theme = { - Rc::new(Cell::new(match &config.borrow().window { - Some(ConfigWindowSection { - theme: Some(theme_cfg), - .. - }) => *theme_cfg, - _ => cache.lock().unwrap().theme(), - })) - }; - let set_theme = { let picture_widget = picture_widget.clone(); let window = window.clone(); - let theme = theme.clone(); let bottom_bar = bottom_bar.clone(); + let config = config.clone(); Rc::new(move || { - match theme.get() { + let theme = config.lock().unwrap().theme(); + match theme { Theme::Light => { picture_widget.set_bright_shade(0.96); window.set_bg_color([0.85, 0.85, 0.85, 1.0]); @@ -203,17 +123,16 @@ fn main() { window.set_bg_color([0.03, 0.03, 0.03, 1.0]); } } - bottom_bar.set_theme(theme.get()); + bottom_bar.set_theme(theme); }) }; set_theme(); { - let cache = cache.clone(); let set_theme = set_theme; + let config = config.clone(); bottom_bar.theme_button.set_on_click(move || { - let new_theme = theme.get().switch_theme(); - theme.set(new_theme); - cache.lock().unwrap().set_theme(new_theme); + let theme = config.lock().unwrap().theme().toggle(); + config.lock().unwrap().set_theme(theme); set_theme(); }); } @@ -257,7 +176,7 @@ fn main() { window.set_root(root_container); application.set_at_exit(Some(move || { - cache.lock().unwrap().save().unwrap(); + config.lock().unwrap().save().unwrap(); })); application.start_event_loop(); } @@ -272,22 +191,6 @@ fn make_icon() -> Icon { Icon::from_rgba(rgba.into_raw(), w, h).unwrap() } -fn add_window_movement_listener(window: &Window, cache: Arc>) { - window.add_global_event_handler(move |event| match event { - WindowEvent::Resized(new_size) => { - let mut cache = cache.lock().unwrap(); - cache.window.win_w = new_size.width; - cache.window.win_h = new_size.height; - } - WindowEvent::Moved(new_pos) => { - let mut cache = cache.lock().unwrap(); - cache.window.win_x = new_pos.x; - cache.window.win_y = new_pos.y; - } - _ => (), - }); -} - fn make_root_container() -> Rc { let container = Rc::new(VerticalLayoutContainer::new()); container.set_margin_all(0.0); @@ -321,8 +224,7 @@ fn make_picture_widget( bottom_bar: Rc, left_to_pan_hint: Rc, copy_notifications: CopyNotifications, - config: Rc>, - cache: Arc>, + config: Arc>, ) -> Rc { let picture_widget = Rc::new(PictureWidget::new( &window.display_mut(), @@ -331,7 +233,6 @@ fn make_picture_widget( left_to_pan_hint, copy_notifications, config, - cache, )); picture_widget.set_height(Length::Stretch { min: 0.0, diff --git a/src/widgets/bottom_bar.rs b/src/widgets/bottom_bar.rs index 2a5dddad..79cefa06 100644 --- a/src/widgets/bottom_bar.rs +++ b/src/widgets/bottom_bar.rs @@ -7,9 +7,7 @@ use crate::gelatin::{ picture::Picture, slider::Slider, }; - -use super::picture_widget::ScalingMode; -use crate::{ConfigWindowSection, Configuration, Theme}; +use crate::configuration::{ScalingMode, Theme}; static MOON: &[u8] = include_bytes!("../../resource/moon.png"); static LIGHT: &[u8] = include_bytes!("../../resource/light.png"); @@ -42,10 +40,6 @@ pub struct BottomBar { pub theme_button: Rc