diff --git a/src/lib.rs b/src/lib.rs index 13eb776..11b2eec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,175 +1,28 @@ -pub mod drag_n_drop; -pub mod screenshot; +pub mod plugin; pub mod shader_utils; -pub mod system_clipboard; -pub mod texture_tooling; +pub mod system; pub mod ui; pub mod utils; -pub mod system { - //! Logic and Helpers etc for dealing with the system Shadplay is running on, i.e - //! the app's default config, long-lived settings and the clipboard interactions. - use bevy::{ - log, - prelude::{Query, ResMut, Resource}, - window::{Window, WindowLevel}, +pub mod prelude { + //! The Shadplay Prelude, you'll probably find yourself wanting things from/adding things to this, if you're working on Shadplay. + pub use crate::{ + shader_utils::{ + common::ShadplayShaderLibrary, + texture_tooling::{self, swap_2d_tex_from_idx, swap_3d_tex_from_idx, SetNewTexture}, + DragNDropShader, MousePos, YourShader, YourShader2D, + }, + system::drag_n_drop::{ + add_and_set_dropped_file, file_drag_and_drop_listener, override_current_shader, + TexHandleQueue, UserAddedTexture, + }, + system::screenshot::screenshot_and_version_shader_on_spacebar, + ui::colour_picker_plugin::ColourPickerPlugin, + utils::{ + self, cam_switch_system, cleanup_2d, cleanup_3d, init_shapes, quit, rotate, setup_2d, + setup_3d, size_quad, switch_level, switch_shape, toggle_rotate, toggle_transparency, + toggle_window_passthrough, update_mouse_pos, AppState, MonitorsSpecs, Rotating, + ShadplayWindowDims, ShapeOptions, TransparencySet, + }, }; - use directories::ProjectDirs; - use serde::{Deserialize, Serialize}; - use std::{ - fs, - io::{self, Read}, - path::{Path, PathBuf}, - time::UNIX_EPOCH, - }; - - #[derive(Resource, Debug, Serialize, PartialEq, PartialOrd, Deserialize)] - pub struct UserConfig { - window_dims: (f32, f32), - decorations: bool, - always_on_top: bool, - last_updated: u64, //Toml doesn't supprot u128 - } - - impl UserConfig { - pub fn get_config_path() -> PathBuf { - match ProjectDirs::from("", "", "shadplay") { - Some(proj_dirs) => { - let config_path_full = proj_dirs.config_dir().join("config.toml"); - log::info!("Config directory is: {}", config_path_full.display()); - - if !proj_dirs.config_dir().exists() { - log::error!("config_dir doesn't exist, creating it...",); - if let Err(e) = fs::create_dir_all(&proj_dirs.config_dir()) { - log::error!("Failed to create directory: {:?}", e); - } - log::info!("config_dir created.") - } - - config_path_full - } - None => { - log::error!("Unable to find or create the config directory."); - unreachable!("If you see this error, shadpaly was unable to write the default user configuration to your system, please open an issue, on GH.") - } - } - } - - pub fn save_to_toml>(&self, path: P) -> io::Result<()> { - fs::write( - path, - toml::to_string(self).expect("Failed to serialize UserConfig to TOML"), - )?; - - Ok(()) - } - - pub fn load_from_toml>(path: P) -> io::Result { - let mut file_contents = String::new(); - fs::File::open(path)?.read_to_string(&mut file_contents)?; - let config: UserConfig = - toml::from_str(&file_contents).expect("Failed to deserialize TOML into UserConfig"); - - Ok(config) - } - - pub fn create_window_settings(&self) -> Window { - Window { - title: "shadplay".into(), - resolution: self.window_dims.into(), - transparent: true, - decorations: self.decorations, - // Mac only - #[cfg(target_os = "macos")] - composite_alpha_mode: CompositeAlphaMode::PostMultiplied, - window_level: self.window_level(), - ..bevy::prelude::default() - } - } - - fn window_level(&self) -> WindowLevel { - match self.always_on_top { - true => WindowLevel::AlwaysOnTop, - _ => WindowLevel::Normal, - } - } - - /// System: When the screen dims change, we update the Self we have in the bevy [`Resource`]s. - pub fn runtime_updater(mut user_config: ResMut, windows: Query<&Window>) { - let win = windows - .get_single() - .expect("Should be impossible to NOT get a window"); - - let (width, height) = (win.width(), win.height()); - - user_config.decorations = win.decorations; - user_config.always_on_top = match win.window_level { - WindowLevel::AlwaysOnTop => true, - _ => false, - }; - user_config.window_dims = (width, height); - user_config.last_updated = std::time::SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs(); - - match user_config.save_to_toml(Self::get_config_path()) { - Ok(_) => log::info!("User's config.toml's screen dims were updated."), - Err(e) => log::error!("Failed to update user's config {}", e), - } - } - } - - impl Default for UserConfig { - fn default() -> Self { - Self { - window_dims: (720.0, 480.0), - decorations: true, - always_on_top: true, - last_updated: std::time::SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs(), - } - } - } - - #[cfg(test)] - mod tests { - use super::UserConfig; - use std::fs; - - #[test] - fn save_and_load_user_config() { - let test_config = UserConfig { - window_dims: (1024.0, 768.0), - decorations: false, - always_on_top: false, - last_updated: 1635900000, - }; - - let temp_path = "./temp_config.toml"; - test_config - .save_to_toml(temp_path) - .expect("Failed to save test config to TOML"); - - let loaded_config = UserConfig::load_from_toml(temp_path) - .expect("Failed to load test config from TOML"); - assert_eq!(test_config, loaded_config); - - fs::remove_file(temp_path).expect("Failed to remove temporary test config file"); - } - - #[test] - fn config_path_for_user_config() { - let p = UserConfig::get_config_path(); - let test_config = UserConfig { - window_dims: (1024.0, 768.0), - decorations: false, - always_on_top: true, - last_updated: 1635900000, - }; - test_config.save_to_toml(p).unwrap(); - } - } } diff --git a/src/main.rs b/src/main.rs index c42a4f5..1fe2d1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,15 @@ -#[cfg(target_os = "macos")] -use bevy::window::CompositeAlphaMode; - +/// +/// ShadPlay +/// use bevy::{ asset::ChangeWatcher, input::keyboard::KeyboardInput, prelude::*, - sprite::Material2dPlugin, utils::Duration, - window::{WindowLevel, WindowPlugin, WindowResized}, + window::{WindowPlugin, WindowResized}, }; -use bevy_panorbit_camera::PanOrbitCameraPlugin; - -#[cfg(feature = "ui")] -use shadplay::ui::help_ui::HelpUIPlugin; -use shadplay::{ - drag_n_drop::{self, TexHandleQueue, UserAddedTexture}, - screenshot, - shader_utils::{self, DragNDropShader}, - system::UserConfig, - texture_tooling, - ui::colour_picker_plugin, - utils::{self, AppState, MonitorsSpecs, Rotating, ShapeOptions, TransparencySet}, -}; +use shadplay::{plugin::ShadPlayPlugin, system::config::UserConfig, utils::AppState}; fn main() -> anyhow::Result<()> { // Get UserConfig for the Shadplay window dimensions, decorations toggle etc. @@ -33,6 +20,8 @@ fn main() -> anyhow::Result<()> { let shadplay = app .add_state::() + .insert_resource(user_config) + .insert_resource(ClearColor(Color::NONE)) .add_plugins(( DefaultPlugins .set(AssetPlugin { @@ -43,81 +32,17 @@ fn main() -> anyhow::Result<()> { primary_window: Some(user_cfg_window), // From UserConfig ..default() }), - // + ShadPlayPlugin, )) - .add_plugins(shader_utils::common::ShadplayShaderLibrary) // Something of a library with common functions. - .add_plugins(colour_picker_plugin::ColourPickerPlugin) - .add_plugins(MaterialPlugin::::default()) - .add_plugins(Material2dPlugin::::default()) - // Resources - .insert_resource(MonitorsSpecs::default()) - .insert_resource(user_config) //UserConfig - .insert_resource(TexHandleQueue::default()) - .insert_resource(utils::ShadplayWindowDims::default()) - .insert_resource(ShapeOptions::default()) - .insert_resource(TransparencySet(true)) - .insert_resource(Rotating(false)) - .insert_resource(ClearColor(Color::NONE)) - .add_plugins(PanOrbitCameraPlugin) - //events: - .add_event::() - .add_event::() - // 3D - .add_systems(OnEnter(AppState::ThreeD), utils::setup_3d) - .add_systems(OnExit(AppState::ThreeD), utils::cleanup_3d) - // 2D - .add_systems(OnEnter(AppState::TwoD), utils::setup_2d) - .add_systems(OnExit(AppState::TwoD), utils::cleanup_2d) - // Setups. - .add_systems(PreStartup, utils::init_shapes) - // 3d Cam Systems .add_systems( Update, ( - utils::rotate.run_if(resource_equals::(shadplay::utils::Rotating(true))), - utils::switch_shape, - texture_tooling::swap_3d_tex_from_idx, - utils::toggle_rotate, - ) - .run_if(in_state(AppState::ThreeD)), - ) - // All the time systems - .add_systems( - Update, - ( - // DEBUG: - // #[cfg(debug_assertions)] - // drag_n_drop::debug_tex_keys, - // - drag_n_drop::file_drag_and_drop_listener, - drag_n_drop::add_and_set_dropped_file.run_if(on_event::()), - drag_n_drop::override_current_shader.run_if(on_event::()), - screenshot::screenshot_and_version_shader_on_spacebar, - utils::cam_switch_system, - utils::quit, - utils::switch_level, - utils::toggle_transparency, - utils::toggle_window_passthrough, UserConfig::runtime_updater.run_if(on_event::()), UserConfig::runtime_updater.run_if(on_event::()), ), - ) - // 2d Only Sytsems - .add_systems( - Update, - ( - // utils::max_mon_res, // We're currently not using the maximum resolution of the primary monitor. - utils::update_mouse_pos, - texture_tooling::swap_2d_tex_from_idx, - utils::size_quad - .run_if(in_state(AppState::TwoD)) - .run_if(on_event::()), - ), + // ); - #[cfg(feature = "ui")] - shadplay.add_plugins(HelpUIPlugin); - shadplay.run(); Ok(()) diff --git a/src/plugin.rs b/src/plugin.rs new file mode 100644 index 0000000..9fc4e24 --- /dev/null +++ b/src/plugin.rs @@ -0,0 +1,82 @@ +use bevy::{ + input::keyboard::KeyboardInput, prelude::*, sprite::Material2dPlugin, window::WindowResized, +}; +use bevy_panorbit_camera::PanOrbitCameraPlugin; + +use crate::prelude::*; + +/// Plugin: The main ShadPlay plugin +pub struct ShadPlayPlugin; + +impl Plugin for ShadPlayPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + app.add_plugins(ShadplayShaderLibrary) // Something of a library with common functions. + .add_plugins(ColourPickerPlugin) + .add_plugins(MaterialPlugin::::default()) + .add_plugins(Material2dPlugin::::default()) + // Resources + .insert_resource(MonitorsSpecs::default()) + .insert_resource(TexHandleQueue::default()) + .insert_resource(ShadplayWindowDims::default()) + .insert_resource(ShapeOptions::default()) + .insert_resource(TransparencySet(true)) + .insert_resource(Rotating(false)) + .add_plugins(PanOrbitCameraPlugin) + //events: + .add_event::() + .add_event::() + // 3D + .add_systems(OnEnter(AppState::ThreeD), setup_3d) + .add_systems(OnExit(AppState::ThreeD), cleanup_3d) + // 2D + .add_systems(OnEnter(AppState::TwoD), setup_2d) + .add_systems(OnExit(AppState::TwoD), cleanup_2d) + // Setups. + .add_systems(PreStartup, init_shapes) + // 3d Cam Systems + .add_systems( + Update, + ( + rotate.run_if(resource_equals::(Rotating(true))), + switch_shape, + swap_3d_tex_from_idx.run_if(on_event::()), + toggle_rotate, + ) + .run_if(in_state(AppState::ThreeD)), + ) + // All the time systems + .add_systems( + Update, + ( + // DEBUG: + // #[cfg(debug_assertions)] + // drag_n_drop::debug_tex_keys, + // + file_drag_and_drop_listener, + add_and_set_dropped_file.run_if(on_event::()), + override_current_shader.run_if(on_event::()), + screenshot_and_version_shader_on_spacebar, + cam_switch_system, + quit, + switch_level, + toggle_transparency, + toggle_window_passthrough, + ), + ) + // 2d Only Sytsems + .add_systems( + Update, + ( + // utils::max_mon_res, // We're currently not using the maximum resolution of the primary monitor. + update_mouse_pos, + swap_2d_tex_from_idx.run_if(on_event::()), + size_quad + .run_if(in_state(AppState::TwoD)) + .run_if(on_event::()), + ), + ); + + #[cfg(feature = "ui")] + app.add_plugins(crate::ui::help_ui::HelpUIPlugin); + } +} diff --git a/src/shader_utils.rs b/src/shader_utils/mod.rs similarity index 99% rename from src/shader_utils.rs rename to src/shader_utils/mod.rs index e96d9e6..b1bc1b6 100644 --- a/src/shader_utils.rs +++ b/src/shader_utils/mod.rs @@ -2,6 +2,7 @@ //! Most of the boilerplate to make a custom shader work lives here. //! use std::path::PathBuf; +pub mod texture_tooling; use bevy::{ prelude::*, diff --git a/src/texture_tooling.rs b/src/shader_utils/texture_tooling.rs similarity index 91% rename from src/texture_tooling.rs rename to src/shader_utils/texture_tooling.rs index 42ad741..2e842c5 100644 --- a/src/texture_tooling.rs +++ b/src/shader_utils/texture_tooling.rs @@ -2,9 +2,7 @@ use bevy::input::keyboard::KeyboardInput; use bevy::input::ButtonState; use bevy::prelude::*; -use crate::drag_n_drop::TexHandleQueue; -use crate::shader_utils::YourShader; -use crate::shader_utils::YourShader2D; +use crate::prelude::*; /// Implement this on your own shaders you create, should you want them to take advantage of the texture updater system(s). /// It's exteremly easy to do, just see the examples in `src/texture_tooling.rs` @@ -28,7 +26,10 @@ impl SetNewTexture for YourShader { user_added_textures: &TexHandleQueue, ) { let Some(new_tex) = user_added_textures.0.get(&idx) else { - error!("No handle, it could still be loading your texture into the ECS!"); + let num_texs = user_added_textures.0.len(); + error!("No handle, it could still be loading your texture into the ECS!\nThere are currently {}, textures available on keys 0..{}", + num_texs, num_texs.min(9)); + return; }; shader_mat.img = new_tex.clone(); // Cloning handles is fine. @@ -53,7 +54,7 @@ pub fn swap_3d_tex_from_idx( match v { KeyCode::Key0 => YourShader::set_current_tex(shad_mat, 0, &user_textures), KeyCode::Key1 => YourShader::set_current_tex(shad_mat, 1, &user_textures), - KeyCode::Key2 => YourShader::set_current_tex(shad_mat, 1, &user_textures), + KeyCode::Key2 => YourShader::set_current_tex(shad_mat, 2, &user_textures), KeyCode::Key3 => YourShader::set_current_tex(shad_mat, 3, &user_textures), KeyCode::Key4 => YourShader::set_current_tex(shad_mat, 4, &user_textures), KeyCode::Key5 => YourShader::set_current_tex(shad_mat, 5, &user_textures), @@ -80,6 +81,10 @@ impl SetNewTexture for YourShader2D { ) { let Some(new_tex) = user_added_textures.0.get(&idx) else { error!("Expected a texture at idx: {}, but none was found.", idx); + let num_texs = user_added_textures.0.len(); + error!("No handle, it could still be loading your texture into the ECS!\nThere are currently {}, textures available on keys 0..{}", + num_texs, num_texs.min(9)); + return; }; shader_mat.img = new_tex.clone(); // Cloning handles is fine. diff --git a/src/system_clipboard.rs b/src/system/clipboard.rs similarity index 95% rename from src/system_clipboard.rs rename to src/system/clipboard.rs index daca830..a753340 100644 --- a/src/system_clipboard.rs +++ b/src/system/clipboard.rs @@ -27,9 +27,10 @@ impl SystemClipboard { //TODO: we probably never actually need this.. so maybe remove? // it would maybe be nice though to be able to take an image for the texture system from the clipboard tho~.... #[cfg(debug_assertions)] + #[allow(dead_code)] pub(super) fn read_current(&mut self) -> Option { match self.most_recent_copypasta.get_contents() { - Ok(v) => Some(v.into()), + Ok(v) => Some(v), Err(e) => { error!("{e}"); None diff --git a/src/system/config.rs b/src/system/config.rs new file mode 100644 index 0000000..fb41bf7 --- /dev/null +++ b/src/system/config.rs @@ -0,0 +1,162 @@ +//! Logic and Helpers etc for dealing with the system Shadplay is running on, i.e +//! the app's default config, long-lived settings and the clipboard interactions. +use bevy::{ + log, + prelude::{Query, ResMut, Resource}, + window::{Window, WindowLevel}, +}; +use directories::ProjectDirs; +use serde::{Deserialize, Serialize}; +use std::{ + fs, + io::{self, Read}, + path::{Path, PathBuf}, + time::UNIX_EPOCH, +}; + +#[derive(Resource, Debug, Serialize, PartialEq, PartialOrd, Deserialize)] +pub struct UserConfig { + window_dims: (f32, f32), + decorations: bool, + always_on_top: bool, + last_updated: u64, //Toml doesn't supprot u128 +} + +impl UserConfig { + pub fn get_config_path() -> PathBuf { + match ProjectDirs::from("", "", "shadplay") { + Some(proj_dirs) => { + let config_path_full = proj_dirs.config_dir().join("config.toml"); + log::info!("Config directory is: {}", config_path_full.display()); + + if !proj_dirs.config_dir().exists() { + log::error!("config_dir doesn't exist, creating it...",); + if let Err(e) = fs::create_dir_all(proj_dirs.config_dir()) { + log::error!("Failed to create directory: {:?}", e); + } + log::info!("config_dir created.") + } + + config_path_full + } + None => { + log::error!("Unable to find or create the config directory."); + unreachable!("If you see this error, shadpaly was unable to write the default user configuration to your system, please open an issue, on GH.") + } + } + } + + pub fn save_to_toml>(&self, path: P) -> io::Result<()> { + fs::write( + path, + toml::to_string(self).expect("Failed to serialize UserConfig to TOML"), + )?; + + Ok(()) + } + + pub fn load_from_toml>(path: P) -> io::Result { + let mut file_contents = String::new(); + fs::File::open(path)?.read_to_string(&mut file_contents)?; + let config: UserConfig = + toml::from_str(&file_contents).expect("Failed to deserialize TOML into UserConfig"); + + Ok(config) + } + + pub fn create_window_settings(&self) -> Window { + Window { + title: "shadplay".into(), + resolution: self.window_dims.into(), + transparent: true, + decorations: self.decorations, + // Mac only + #[cfg(target_os = "macos")] + composite_alpha_mode: CompositeAlphaMode::PostMultiplied, + window_level: self.window_level(), + ..bevy::prelude::default() + } + } + + fn window_level(&self) -> WindowLevel { + match self.always_on_top { + true => WindowLevel::AlwaysOnTop, + _ => WindowLevel::Normal, + } + } + + /// System: When the screen dims change, we update the Self we have in the bevy [`Resource`]s. + pub fn runtime_updater(mut user_config: ResMut, windows: Query<&Window>) { + let win = windows + .get_single() + .expect("Should be impossible to NOT get a window"); + + let (width, height) = (win.width(), win.height()); + + user_config.decorations = win.decorations; + user_config.always_on_top = matches!(win.window_level, WindowLevel::AlwaysOnTop); + user_config.window_dims = (width, height); + user_config.last_updated = std::time::SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + + match user_config.save_to_toml(Self::get_config_path()) { + Ok(_) => log::info!("User's config.toml's screen dims were updated."), + Err(e) => log::error!("Failed to update user's config {}", e), + } + } +} + +impl Default for UserConfig { + fn default() -> Self { + Self { + window_dims: (720.0, 480.0), + decorations: true, + always_on_top: true, + last_updated: std::time::SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs(), + } + } +} + +#[cfg(test)] +mod tests { + use super::UserConfig; + use std::fs; + + #[test] + fn save_and_load_user_config() { + let test_config = UserConfig { + window_dims: (1024.0, 768.0), + decorations: false, + always_on_top: false, + last_updated: 1635900000, + }; + + let temp_path = "./temp_config.toml"; + test_config + .save_to_toml(temp_path) + .expect("Failed to save test config to TOML"); + + let loaded_config = + UserConfig::load_from_toml(temp_path).expect("Failed to load test config from TOML"); + assert_eq!(test_config, loaded_config); + + fs::remove_file(temp_path).expect("Failed to remove temporary test config file"); + } + + #[test] + fn config_path_for_user_config() { + let p = UserConfig::get_config_path(); + let test_config = UserConfig { + window_dims: (1024.0, 768.0), + decorations: false, + always_on_top: true, + last_updated: 1635900000, + }; + test_config.save_to_toml(p).unwrap(); + } +} diff --git a/src/drag_n_drop.rs b/src/system/drag_n_drop.rs similarity index 97% rename from src/drag_n_drop.rs rename to src/system/drag_n_drop.rs index 358a6da..7f85702 100644 --- a/src/drag_n_drop.rs +++ b/src/system/drag_n_drop.rs @@ -2,9 +2,8 @@ use bevy::prelude::*; use std::collections::HashMap; use std::path::PathBuf; -use crate::shader_utils::{DragNDropShader, YourShader, YourShader2D}; -use crate::texture_tooling::SetNewTexture; -use crate::utils::AppState; +use crate::prelude::*; +use crate::shader_utils::texture_tooling::SetNewTexture; /// Resource: Representing the index (the default texture will be at 0), and a Texture handle that we can pass to a shader #[derive(Default, Resource, Deref, DerefMut, Clone, Debug)] diff --git a/src/system/mod.rs b/src/system/mod.rs new file mode 100644 index 0000000..d094c61 --- /dev/null +++ b/src/system/mod.rs @@ -0,0 +1,4 @@ +pub mod clipboard; +pub mod config; +pub mod drag_n_drop; +pub mod screenshot; diff --git a/src/screenshot.rs b/src/system/screenshot.rs similarity index 100% rename from src/screenshot.rs rename to src/system/screenshot.rs diff --git a/src/ui/colour_picker_plugin.rs b/src/ui/colour_picker_plugin.rs index 57906fe..498a617 100644 --- a/src/ui/colour_picker_plugin.rs +++ b/src/ui/colour_picker_plugin.rs @@ -3,7 +3,7 @@ use bevy_egui::egui::epaint::Shadow; use bevy_egui::egui::{Align2, Color32, RichText, Rounding, Vec2}; use bevy_egui::{egui, EguiContexts, EguiPlugin}; -use crate::system_clipboard::SystemClipboard; +use crate::system::clipboard::SystemClipboard; #[derive(Resource, Default, Debug, PartialEq)] pub struct ColourPickerTool { diff --git a/src/ui.rs b/src/ui/mod.rs similarity index 100% rename from src/ui.rs rename to src/ui/mod.rs diff --git a/src/utils.rs b/src/utils.rs index fa522b8..3e1ac8f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,10 +5,7 @@ use bevy::{ }; use bevy_panorbit_camera::PanOrbitCamera; -use crate::{ - drag_n_drop::TexHandleQueue, - shader_utils::{MousePos, YourShader, YourShader2D}, -}; +use crate::prelude::*; /// State: Used to transition between 2d and 3d mode. /// Used by: cam_switch_system, screenshot