diff --git a/README.md b/README.md index 2b09c64b..0f6c6cf1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,9 @@ ## Alloy + Image viewer based on (now-discontinued) [Emulsion]. Alloy targets Windows, Mac, and Linux (with more targets to come!). -A note for Linux users: Wayland support is limited (for now), so for example -expect high CPU usage and the title text not being shown. However X is fully -supported. - Releases will be made as needed, with no set schedule. Merging of bugfix PRs will warrant an immediate new release. Related features may be grouped together in a release. @@ -36,6 +33,303 @@ Alloy is not currently distributed officially on crates.io or through GitHub releases (for Mac and Windows) and Flathub (for Linux), but will be in the future. +## Configuration + +The `config.toml` file allows for some modifications in the behaviour of +alloy. + +Depending on the platform, this file can be created at one of the following +locations. + +- Windows: `%appdata%\Alloy\config\config.toml` +- MacOS: `$HOME/Library/Application Support/Alloy/config.toml` +- Linux: `$XDG_CONFIG_HOME/alloy/config.toml` or `$HOME/.config/alloy/config.toml` + +The contents of the `config.toml` file may for example be the following: + +```toml +[window] +title_folders = 1 +mode = "Fullscreen" + +[bindings] +img_next = ["j"] +img_prev = ["k"] +``` + +All sections in this file are optional, meaning that if for example only +`[window]` is specified then every other section will be using their default +values. + +## Section `[window]` + +Field name | Default | Description +--------------|------------|------------------------------------------------- +title_folders | `0` | Number of folders from the path to display in title +mode | `"Normal"` | Window mode to start: `"Fullscreen"` / `"Maximized"` + +## Section `[image]` + +Field name | Default | Description +-------------|-----------|------------ +scaling | `"Fixed"` | Scaling mode: `"FitStretch"` / `"FitMin"` +antialiasing | `"Auto"` | Antialias mode: `"Always"` / `"Never"` + +## Section `[bindings]` + +Input bindings can be overridden in this section. These are the default +values: + +```toml +img_next = ["d", "right", "pagedown"] +img_prev = ["a", "left", "pageup"] +img_orig = ["q", "1"] +img_fit_best = ["e"] +img_fit = ["f"] +img_del = ["delete"] +img_copy = ["cmdctrl+C"] + +pan = ["space"] +play_anim = ["alt+a", "alt+v"] +play_present = ["p"] +play_present_rnd = ["alt+p"] +toggle_fullscreen = ["F11", "return"] +toggle_antialias = ["s"] +automatic_antialias = ["alt+s"] +escape = ["Escape"] + +# Zoom and pan the camera using keyboard input +# (Not bound by default) +zoom_in = [] +zoom_out = [] +pan_left = [] +pan_right = [] +pan_up = [] +pan_down = [] +``` + +Note that all items in this section are optional so it’s fully valid to only +specify one of the actions. In this case all the rest will use the default +bindings. For example + +```toml +[bindings] +img_next = ["space", "right"] +pan = [] +``` + +The names of the actions are case sensitive but the input strings are not. + +It is valid to specify an empty array like `img_del = []` in which case the +action will never be triggered. + +A config file with bindings will look like the following. + +```toml +[bindings] +img_next = ["d", "right"] +img_prev = ["a", "left"] +img_orig = ["q"] +img_fit = ["f"] +img_del = ["delete"] +pan = ["space"] +play_anim = ["alt+a", "alt+v"] +play_present = ["p"] +play_present_rnd = ["alt+p"] +``` + +Modifiers may be specified separated by `+` characters. For example `"ctrl+x"` +or `"ctrl+alt+u"`. Spaces are trimmed from each element and so `" ctrl+ x"` or +`"ctrl + alt+u "` are equally valid. + +The following modifiers are valid: + +- `alt`: The Alt key +- `ctrl`: The Control key +- `logo`: The Command key on macOS; the Windows key on Windows +- `cmdctrl`: The Command key on macOS; the Control key elsewhere + +There are a few special cases for typeable characters: + +- `' '` must be specified as `space` +- `'+'` must be specified as `add` +- `'-'` must be specified as `subtract` + +The following list contains all supported non-typeable key names. + +```txt +# The Escape key +Escape, + +F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, +F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, + +# Print Screen/SysRq +Snapshot, +# Scroll Lock +Scroll, +# Pause/Break key +Pause, + +# `Insert`, next to Backspace +Insert, +Home, +Delete, +End, +PageDown, +PageUp, + +Left, +Up, +Right, +Down, + +Back, +# The Enter key +Return, + +# The "Compose" key on Linux +Compose, + +Numlock, + +Numpad0, Numpad1, Numpad2, Numpad3, Numpad4, +Numpad5, Numpad6, Numpad7, Numpad8, Numpad9, + +Apps, +Ax, +Calculator, +Capital, +Convert, +Decimal, +Kana, +Kanji, +LAlt, +LControl, +LShift, +LWin, +Mail, +MediaSelect, +MediaStop, +Mute, +MyComputer, +NavigateForward, # also called "Prior" +NavigateBackward, # also called "Next" +NextTrack, +NoConvert, +OEM102, +PlayPause, +Power, +PrevTrack, +RAlt, +RControl, +RShift, +RWin, +Sleep, +Stop, +Sysrq, +Unlabeled, +VolumeDown, +VolumeUp, +Wake, +WebBack, +WebFavorites, +WebForward, +WebHome, +WebRefresh, +WebSearch, +WebStop, +Yen, +Copy, +Paste, +Cut, +``` + +## Commands + +Any number of `[[commands]]` sections may exist. + +To add a shortcut for opening the current image with Gimp on Windows, add the +following: + +```toml +# Note the double brackets! +[[commands]] +input = ["alt+t", "u"] +program = "cmd" +# Note that the Gimp exe path is between single quotation marks (') +args = ["/C", "start", "", 'C:\Program Files\GIMP 2\bin\gimp-2.10.exe', "${img}"] +``` + +A very simple command might look like the one below. + +```toml +# Note the double brackets! +[[commands]] +input = ["alt+k"] +program = "git" +``` + +With the above added to the `config.toml` file, whenever the `alt+k` key +combination is pressed, alloy executes git which prints the default git cli +help message to the standard output. As you can see input is an array, meaning +that a single command can be bound to any number of different inputs. See the +bindings section for more on specifying inputs. + +Any command is only executed when there’s an image open. + +It’s important that alloy doesn’t execute these commands in a particular shell. +This means that many programs which are available from your preferred command +line interface, are not available to alloy. With that said it is possible to +execute shell commands if we specify the shell as the program itself. For +example the following will print “Hello World” to the “hello.txt” file when +executed from Windows. + +```toml +[[commands]] +input = ["alt+k"] +program = "cmd" +args = ["/C", "echo Hello World > hello.txt"] +``` + +As it was previously stated, any number of `[[commands]]` can be specified. + +```toml +[[commands]] +input = ["alt+k"] +program = "git" + +# Every command definition must start with [[commands]], even +# if the previous section was also a [[commands]] section. +[[commands]] +input = ["alt+l"] +program = "git" +args = ["status"] +``` + +There are two more parameters for each command. + +- `args`: an array of arguments passed on to the program +- `envs`: an array of environment variable definitions + +Within the args, one may use `${img}` and `${folder}` for the currently open +image file path and its parent folder path respectively. Note that these are +substituted with a simple find and replace so there’s no need to escape dollar +signs ($) and they have to be typed in the exact format specified here. + +The following example specifies a single environment variable and invokes cmd +with three command line arguments. + +```toml +[[commands]] +input = ["alt+t", "u"] +program = "cmd" +args = ["/C", "echo", "%TEST_VAR% ${img}"] +envs = [{name = "TEST_VAR", value = "Wohoo :D"}] +``` + +This might for example print: `Wohoo :D \\?\D:\MyImages\mountain.jpg` + ## Reporting Bugs If Alloy closes unexpectedly please locate the `"panic.txt"` file. This file has 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