diff --git a/Cargo.lock b/Cargo.lock index d4a8fc4..04a5d4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,38 @@ name = "constant_time_eq" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dirs" version = "1.0.4" @@ -179,6 +211,11 @@ dependencies = [ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -201,6 +238,11 @@ dependencies = [ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "itoa" version = "0.4.3" @@ -301,6 +343,14 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quick-error" version = "1.2.2" @@ -314,6 +364,14 @@ dependencies = [ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.3.22" @@ -440,6 +498,16 @@ dependencies = [ "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rider-derive" +version = "0.1.0" +dependencies = [ + "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rider-editor" version = "0.1.0" @@ -449,7 +517,9 @@ dependencies = [ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "rider-config 0.1.0", + "rider-derive 0.1.0", "rider-lexers 0.1.0", + "rider-match-widget 0.1.0", "rider-themes 0.1.0", "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", @@ -483,6 +553,16 @@ dependencies = [ "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rider-match-widget" +version = "0.1.0" +dependencies = [ + "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rider-themes" version = "0.1.0" @@ -566,6 +646,11 @@ dependencies = [ "term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "0.14.9" @@ -586,6 +671,16 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "synstructure" version = "0.10.1" @@ -652,6 +747,11 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "utf8-ranges" version = "1.0.2" @@ -731,13 +831,18 @@ dependencies = [ "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" +"checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +"checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +"checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a" "checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum lalr 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "106d7548f95adbe3019b4fc4954554d7b72535867aa9ce326d2f766b68958de7" "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" @@ -752,8 +857,10 @@ dependencies = [ "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum plex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "59d79bd74b3d0be2619e58217e8b2b96372e3feca8426e5c560623205d70c146" "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" +"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" +"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" "checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" "checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" @@ -774,8 +881,10 @@ dependencies = [ "checksum serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d6115a3ca25c224e409185325afc16a0d5aaaabc15c42b09587d6f1ba39a5b" "checksum serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "bdf540260cfee6da923831f4776ddc495ada940c30117977c70f1313a6130545" "checksum simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e95345f185d5adeb8ec93459d2dc99654e294cc6ccf5b75414d8ea262de9a13" +"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" "checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" +"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561" "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" @@ -784,6 +893,7 @@ dependencies = [ "checksum time 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "847da467bf0db05882a9e2375934a8a55cffdc9db0d128af1518200260ba1f6c" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dab5c5526c5caa3d106653401a267fed923e7046f35895ffcb5ca42db64942e6" "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" diff --git a/Cargo.toml b/Cargo.toml index 24dafda..c034938 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,16 @@ members = [ "rider-config", "rider-themes", "rider-lexers", - "rider-editor" + "rider-editor", + "rider-match-widget" ] default-members = [ "rider-generator", "rider-config", "rider-themes", "rider-lexers", - "rider-editor" + "rider-editor", + "rider-match-widget" ] [dependencies] diff --git a/rider-config/src/config.rs b/rider-config/src/config.rs index 56fe9b8..a92a2a4 100644 --- a/rider-config/src/config.rs +++ b/rider-config/src/config.rs @@ -102,20 +102,15 @@ impl Config { impl Config { pub fn load_theme(&self, theme_name: String) -> Theme { let home_dir = dirs::config_dir().unwrap(); - let mut config_dir = home_dir; - config_dir.push("rider"); - fs::create_dir_all(&config_dir) + #[cfg_attr(tarpaulin, skip)] + fs::create_dir_all(&home_dir.join("rider")) .unwrap_or_else(|_| panic!("Cannot create config directory")); self.load_theme_content(format!("{}.json", theme_name).as_str()) } fn load_theme_content(&self, file_name: &str) -> Theme { - let mut config_file = self.directories.themes_dir.clone(); - config_file.push(file_name); - let contents = match fs::read_to_string(&config_file) { - Ok(s) => s, - Err(_) => fs::read_to_string(&config_file).unwrap_or_else(|_| "".to_owned()), - }; + let config_file = self.directories.themes_dir.clone(); + let contents = fs::read_to_string(&config_file.join(file_name)).unwrap_or_default(); serde_json::from_str(&contents).unwrap_or_default() } } @@ -194,21 +189,21 @@ mod test_getters { assert_eq!(result, expected); } - // #[test] - // fn assert_editor_config() { - // let config = Config::new(); - // let result = config.editor_config(); - // let expected = 1; - // assert_eq!(result, expected); - // } + #[test] + fn assert_editor_config() { + let config = Config::new(); + let result = config.editor_config(); + let expected = EditorConfig::new(&Directories::new(None, None)); + assert_eq!(result, &expected); + } - // #[test] - // fn assert_theme() { - // let config = Config::new(); - // let result = config.theme(); - // let expected = 1; - // assert_eq!(result, expected); - // } + #[test] + fn assert_theme() { + let config = Config::new(); + let result = config.theme(); + let expected = Theme::default(); + assert_eq!(result, &expected); + } #[test] fn assert_menu_height() { diff --git a/rider-config/src/directories.rs b/rider-config/src/directories.rs index 1b2abe0..eccf519 100644 --- a/rider-config/src/directories.rs +++ b/rider-config/src/directories.rs @@ -21,10 +21,8 @@ impl Directories { config_dir.push(path); config_dir.push("rider"); - let path = match project_dir { - Some(s) => s, - None => dirs::runtime_dir().unwrap().to_str().unwrap().to_owned(), - }; + let path = project_dir + .unwrap_or_else(|| dirs::runtime_dir().unwrap().to_str().unwrap().to_owned()); let mut project_dir = PathBuf::new(); project_dir.push(path); project_dir.push(".rider"); @@ -56,19 +54,12 @@ pub fn themes_dir(config_dir: &PathBuf) -> PathBuf { } pub fn fonts_dir(config_dir: &PathBuf) -> PathBuf { - let path = config_dir.to_str().unwrap().to_owned(); - let mut path_buf = PathBuf::new(); - path_buf.push(path); - path_buf.push("fonts"); - path_buf + PathBuf::from(config_dir.to_str().unwrap().to_owned()).join("fonts") } +#[cfg_attr(tarpaulin, skip)] pub fn project_dir() -> PathBuf { - let path = dirs::runtime_dir().unwrap().to_str().unwrap().to_owned(); - let mut path_buf = PathBuf::new(); - path_buf.push(path); - path_buf.push(".rider"); - path_buf + PathBuf::from(dirs::runtime_dir().unwrap().to_str().unwrap().to_owned()).join(".rider") } #[cfg_attr(tarpaulin, skip)] @@ -110,11 +101,10 @@ pub fn binaries_directory() -> Result { Err("Cannot find binaries!".to_string()) } +#[cfg_attr(tarpaulin, skip)] pub fn get_binary_path(name: &str) -> Result { if cfg!(test) { use std::fs; - println!("#[cfg(test)]"); - let mut current_dir = env::current_dir().unwrap(); current_dir.push("target"); current_dir.push("debug"); @@ -132,7 +122,6 @@ pub fn get_binary_path(name: &str) -> Result { } Err(format!("Cannot find {:?}", name)) } else { - println!("#[cfg(not(test))]"); let r = binaries_directory(); let mut binaries: PathBuf = r.unwrap_or_else(|e| panic!(e)); binaries.push(name.to_string()); diff --git a/rider-config/src/editor_config.rs b/rider-config/src/editor_config.rs index 5903cd1..0c234d5 100644 --- a/rider-config/src/editor_config.rs +++ b/rider-config/src/editor_config.rs @@ -1,5 +1,6 @@ use crate::directories::Directories; +#[cfg_attr(test, derive(PartialEq))] #[derive(Debug, Clone)] pub struct EditorConfig { character_size: u16, @@ -26,8 +27,15 @@ impl EditorConfig { self.character_size } - pub fn font_path(&self) -> &String { - &self.font_path + pub fn font_path(&self) -> &str { + self.font_path.as_str() + } + + pub fn set_font_path(&mut self, path: S) + where + S: Into, + { + self.font_path = path.into(); } pub fn current_theme(&self) -> &String { @@ -49,9 +57,14 @@ mod test { #[test] fn assert_font_path() { let directories = Directories::new(Some("/tmp".to_owned()), None); - let config = EditorConfig::new(&directories); - let path = config.font_path().to_owned(); - let expected: String = "/tmp/rider/fonts/DejaVuSansMono.ttf".to_owned(); + let mut config = EditorConfig::new(&directories); + let path = config.font_path(); + let expected = "/tmp/rider/fonts/DejaVuSansMono.ttf"; + assert_eq!(path, expected); + + config.set_font_path("/a"); + let path = config.font_path(); + let expected = "/a"; assert_eq!(path, expected); } diff --git a/rider-config/src/scroll_config.rs b/rider-config/src/scroll_config.rs index 1c3059a..c3896d7 100644 --- a/rider-config/src/scroll_config.rs +++ b/rider-config/src/scroll_config.rs @@ -53,6 +53,14 @@ impl Default for ScrollConfig { mod tests { use super::*; + #[test] + fn assert_default() { + let config = ScrollConfig::default(); + assert_eq!(config.width, 4); + assert_eq!(config.margin_right, 5); + assert_eq!(config.speed, 10); + } + #[test] fn assert_width() { let config = ScrollConfig::new(); diff --git a/rider-derive/Cargo.toml b/rider-derive/Cargo.toml new file mode 100644 index 0000000..94609c5 --- /dev/null +++ b/rider-derive/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rider-derive" +version = "0.1.0" +authors = ["Adrian Woźniak "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +darling = { version = "*" } + +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0" +features = ["full", "extra-traits"] diff --git a/rider-derive/src/lib.rs b/rider-derive/src/lib.rs new file mode 100644 index 0000000..6a3949d --- /dev/null +++ b/rider-derive/src/lib.rs @@ -0,0 +1,27 @@ +use proc_macro; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; + +#[proc_macro] +pub fn build_test_renderer(input: TokenStream) -> TokenStream { + let renderer_name = format_ident!("{}", input.to_string()); + + let exp = quote! { + let mut character_sizes = std::collections::HashMap::new(); + let mut canvas = CanvasMock::new(); + let config = build_config(); + let mut surface = + sdl2::surface::Surface::new(512, 512, sdl2::pixels::PixelFormatEnum::RGB24).unwrap(); + let mut surface_canvas = sdl2::render::Canvas::from_surface(surface).unwrap(); + let mut texture_creator = surface_canvas.texture_creator(); + let mut texture_manager = crate::renderer::managers::TextureManager::new(&texture_creator); + let mut ttf_context = sdl2::ttf::Sdl2TtfContext {}; + let mut #renderer_name = SimpleRendererMock { + config: config.clone(), + ttf: ttf_context, + character_sizes, + texture_manager, + }; + }; + exp.into() +} diff --git a/rider-editor/Cargo.toml b/rider-editor/Cargo.toml index f5541e1..eca1034 100644 --- a/rider-editor/Cargo.toml +++ b/rider-editor/Cargo.toml @@ -8,6 +8,8 @@ edition = "2018" rider-config = { path = "../rider-config", version = "0.1.0" } rider-lexers = { path = "../rider-lexers", version = "0.1.0" } rider-themes = { path = "../rider-themes", version = "0.1.0" } +rider-derive = { path = "../rider-derive", version = "0.1.0" } +rider-match-widget = { path = "../rider-match-widget", version = "0.1.0" } rand = "0.5" dirs = "*" serde = "*" diff --git a/rider-editor/src/app/app_state.rs b/rider-editor/src/app/app_state.rs index 8a7c832..3be0378 100644 --- a/rider-editor/src/app/app_state.rs +++ b/rider-editor/src/app/app_state.rs @@ -21,13 +21,13 @@ pub struct AppState { impl AppState { pub fn new(config: Arc>) -> Self { Self { - menu_bar: MenuBar::new(Arc::clone(&config)), + menu_bar: MenuBar::new(config.clone()), project_tree: ProjectTreeSidebar::new( Application::current_working_directory(), config.clone(), ), files: vec![], - file_editor: FileEditor::new(Arc::clone(&config)), + file_editor: FileEditor::new(config.clone()), modal: None, config, } @@ -67,7 +67,7 @@ impl AppState { pub fn open_settings(&mut self, renderer: &mut R) -> Result<(), String> where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { match self.modal { None => { @@ -87,7 +87,7 @@ impl AppState { pub fn open_directory(&mut self, dir_path: String, renderer: &mut R) where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { match self.modal.as_mut() { Some(ModalType::OpenFile(modal)) => modal.open_directory(dir_path, renderer), @@ -145,14 +145,16 @@ impl AppState { R: Renderer + ConfigHolder + CharacterSizeManager, { // file editor - self.file_editor.render(canvas, renderer); + self.file_editor + .render(canvas, renderer, &RenderContext::Nothing); // menu bar self.menu_bar .render(canvas, renderer, &RenderContext::Nothing); // project tree - self.project_tree.render(canvas, renderer); + self.project_tree + .render(canvas, renderer, &RenderContext::Nothing); // settings modal match self.modal.as_ref() { @@ -168,7 +170,7 @@ impl AppState { pub fn prepare_ui(&mut self, renderer: &mut R) where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { self.menu_bar.prepare_ui(); self.project_tree.prepare_ui(renderer); @@ -189,7 +191,7 @@ impl AppState { self.menu_bar.update(ticks, context); // sidebar - self.project_tree.update(ticks); + self.project_tree.update(ticks, context); // file editor let context = UpdateContext::ParentPosition( @@ -253,21 +255,21 @@ impl ConfigHolder for AppState { #[cfg(test)] mod tests { use super::*; - use crate::tests::support; - use std::sync::Arc; + use crate::tests::*; + use rider_derive::*; #[test] fn must_return_none_for_default_file() { - let config = support::build_config(); - let state = AppState::new(Arc::clone(&config)); + let config = build_config(); + let state = AppState::new(config.clone()); let file = state.file_editor().file(); assert_eq!(file.is_none(), true); } #[test] fn must_scroll_file_when_no_modal() { - let config = support::build_config(); - let mut state = AppState::new(Arc::clone(&config)); + let config = build_config(); + let mut state = AppState::new(config.clone()); let old_scroll = state.file_editor().scroll(); state.set_open_file_modal(None); state.scroll_by(10, 10); @@ -276,9 +278,9 @@ mod tests { #[test] fn must_scroll_modal_when_modal_was_set() { - let config = support::build_config(); - let mut state = AppState::new(Arc::clone(&config)); - let modal = OpenFile::new("/".to_owned(), 100, 100, Arc::clone(&config)); + let config = build_config(); + let mut state = AppState::new(config.clone()); + let modal = OpenFile::new("/".to_owned(), 100, 100, config.clone()); let file_scroll = state.file_editor().scroll(); let old_scroll = state.file_editor().scroll(); state.set_open_file_modal(Some(modal)); @@ -295,8 +297,8 @@ mod tests { #[test] fn must_fail_save_file_when_none_is_open() { - let config = support::build_config(); - let state = AppState::new(Arc::clone(&config)); + let config = build_config(); + let state = AppState::new(config.clone()); let result = state.save_file(); assert_eq!(result, Err(format!("No buffer found"))); } @@ -313,9 +315,8 @@ mod tests { true ); - let config = support::build_config(); - let mut renderer = support::SimpleRendererMock::new(config.clone()); - let mut state = AppState::new(Arc::clone(&config)); + build_test_renderer!(renderer); + let mut state = AppState::new(config.clone()); let result = state.open_file( format!("/tmp/must_succeed_save_file_when_file_is_open.md"), &mut renderer, @@ -337,9 +338,8 @@ mod tests { true ); - let config = support::build_config(); - let mut renderer = support::SimpleRendererMock::new(config.clone()); - let mut state = AppState::new(Arc::clone(&config)); + build_test_renderer!(renderer); + let mut state = AppState::new(config.clone()); let result = state.open_file( format!("/tmp/must_succeed_save_file_when_file_does_not_exists.md"), &mut renderer, @@ -351,34 +351,32 @@ mod tests { #[test] fn must_close_modal_when_no_modal_is_open() { - let config = support::build_config(); - let mut state = AppState::new(Arc::clone(&config)); + let config = build_config(); + let mut state = AppState::new(config.clone()); assert_eq!(state.close_modal(), Ok(())); } #[test] fn must_close_modal_when_some_modal_is_open() { - let config = support::build_config(); - let mut state = AppState::new(Arc::clone(&config)); - let modal = OpenFile::new("/".to_owned(), 100, 100, Arc::clone(&config)); + let config = build_config(); + let mut state = AppState::new(config.clone()); + let modal = OpenFile::new("/".to_owned(), 100, 100, config.clone()); state.set_open_file_modal(Some(modal)); assert_eq!(state.close_modal(), Ok(())); } #[test] fn open_settings_when_there_is_no_other_modal() { - let config = support::build_config(); - let mut renderer = support::SimpleRendererMock::new(config.clone()); - let mut state = AppState::new(Arc::clone(&config)); + build_test_renderer!(renderer); + let mut state = AppState::new(config.clone()); assert_eq!(state.open_settings(&mut renderer), Ok(())); } #[test] fn open_settings_when_other_modal_is_open() { - let config = support::build_config(); - let mut renderer = support::SimpleRendererMock::new(config.clone()); - let mut state = AppState::new(Arc::clone(&config)); - let modal = OpenFile::new("/".to_owned(), 100, 100, Arc::clone(&config)); + build_test_renderer!(renderer); + let mut state = AppState::new(config.clone()); + let modal = OpenFile::new("/".to_owned(), 100, 100, config.clone()); state.set_open_file_modal(Some(modal)); assert_eq!(state.open_settings(&mut renderer), Ok(())); } @@ -390,9 +388,8 @@ mod tests { true ); - let config = support::build_config(); - let mut renderer = support::SimpleRendererMock::new(config.clone()); - let mut state = AppState::new(Arc::clone(&config)); + build_test_renderer!(renderer); + let mut state = AppState::new(config.clone()); state.open_directory("/must_open_directory".to_owned(), &mut renderer); } } diff --git a/rider-editor/src/app/caret_manager.rs b/rider-editor/src/app/caret_manager.rs index 3d850fa..69db0c1 100644 --- a/rider-editor/src/app/caret_manager.rs +++ b/rider-editor/src/app/caret_manager.rs @@ -1,6 +1,9 @@ use crate::ui::*; -pub fn move_caret_right(file_editor: &mut FileEditor) -> Option { +pub fn move_caret_right(file_editor: &mut C) -> Option +where + C: CaretAccess + FileAccess + ?Sized, +{ let text_character: TextCharacter = file_editor .file() .map(|file| file.get_character_at(file_editor.caret().text_position() + 1))??; @@ -12,7 +15,10 @@ pub fn move_caret_right(file_editor: &mut FileEditor) -> Option { Some(text_character) } -pub fn move_caret_left(file_editor: &mut FileEditor) -> Option { +pub fn move_caret_left(file_editor: &mut C) -> Option +where + C: CaretAccess + FileAccess + ?Sized, +{ if file_editor.caret().text_position() == 0 { return None; } @@ -28,7 +34,10 @@ pub fn move_caret_left(file_editor: &mut FileEditor) -> Option { Some(text_character) } -pub fn move_caret_down(file_editor: &mut FileEditor) -> Option { +pub fn move_caret_down(file_editor: &mut C) -> Option +where + C: CaretAccess + FileAccess + ?Sized, +{ if file_editor.caret().text_position() == 0 { return None; } @@ -68,7 +77,10 @@ pub fn move_caret_down(file_editor: &mut FileEditor) -> Option { Some(text_character.clone()) } -pub fn move_caret_up(file_editor: &mut FileEditor) -> Option { +pub fn move_caret_up(file_editor: &mut C) -> Option +where + C: CaretAccess + FileAccess + ?Sized, +{ if file_editor.caret().text_position() == 0 { return None; } @@ -121,12 +133,12 @@ pub fn move_caret_up(file_editor: &mut FileEditor) -> Option { #[cfg(test)] mod test_move_right { use super::*; - use crate::tests::support; - use crate::tests::support::SimpleRendererMock; + use crate::tests::*; + use rider_derive::*; #[test] fn assert_move_with_no_file() { - let config = support::build_config(); + let config = build_config(); let mut editor = FileEditor::new(config); assert_eq!(move_caret_right(&mut editor).is_some(), false); @@ -134,8 +146,7 @@ mod test_move_right { #[test] fn assert_move_caret_with_empty_file() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut editor = FileEditor::new(config.clone()); let mut file = EditorFile::new("test.txt".to_owned(), "".to_owned(), config); file.prepare_ui(&mut renderer); @@ -147,8 +158,7 @@ mod test_move_right { #[test] fn assert_move_caret_with_filled_file() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut editor = FileEditor::new(config.clone()); let mut file = EditorFile::new("test.txt".to_owned(), "hello".to_owned(), config); file.prepare_ui(&mut renderer); @@ -162,12 +172,12 @@ mod test_move_right { #[cfg(test)] mod test_move_left { use super::*; - use crate::tests::support; - use crate::tests::support::SimpleRendererMock; + use crate::tests::*; + use rider_derive::*; #[test] fn assert_move_caret_without_file() { - let config = support::build_config(); + let config = build_config(); let mut editor = FileEditor::new(config); assert_eq!(move_caret_left(&mut editor).is_some(), false); @@ -175,8 +185,7 @@ mod test_move_left { #[test] fn assert_move_caret_with_empty_file() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut editor = FileEditor::new(config.clone()); let mut file = EditorFile::new("test.txt".to_owned(), "".to_owned(), config); file.prepare_ui(&mut renderer); @@ -188,8 +197,7 @@ mod test_move_left { #[test] fn assert_move_caret_with_filled_file() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut editor = FileEditor::new(config.clone()); let mut file = EditorFile::new("test.txt".to_owned(), "hello".to_owned(), config); file.prepare_ui(&mut renderer); @@ -209,13 +217,12 @@ mod test_move_left { #[cfg(test)] mod test_move_up { use super::*; - use crate::tests::support; - use crate::tests::support::SimpleRendererMock; + use crate::tests::*; + use rider_derive::*; #[test] fn assert_move_caret_with_top_of_filled_file() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut editor = FileEditor::new(config.clone()); let mut file = EditorFile::new("test.txt".to_owned(), "he\nll\no".to_owned(), config); file.prepare_ui(&mut renderer); @@ -227,8 +234,7 @@ mod test_move_up { #[test] fn assert_move_caret_with_filled_file() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut editor = FileEditor::new(config.clone()); let mut file = EditorFile::new("test.txt".to_owned(), "he\nll\no".to_owned(), config); file.prepare_ui(&mut renderer); @@ -249,13 +255,12 @@ mod test_move_up { #[cfg(test)] mod test_move_down { use super::*; - use crate::tests::support; - use crate::tests::support::SimpleRendererMock; + use crate::tests::*; + use rider_derive::*; #[test] fn assert_move_caret_with_bottom_of_filled_file() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut editor = FileEditor::new(config.clone()); let mut file = EditorFile::new("test.txt".to_owned(), "he\nll\no".to_owned(), config); file.prepare_ui(&mut renderer); @@ -270,8 +275,9 @@ mod test_move_down { #[test] fn assert_move_caret_with_filled_file() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + use crate::tests::*; + + build_test_renderer!(renderer); let mut editor = FileEditor::new(config.clone()); let mut file = EditorFile::new("test.txt".to_owned(), "he\nll\nod".to_owned(), config); file.prepare_ui(&mut renderer); diff --git a/rider-editor/src/app/file_content_manager.rs b/rider-editor/src/app/file_content_manager.rs index 1dca640..68248ad 100644 --- a/rider-editor/src/app/file_content_manager.rs +++ b/rider-editor/src/app/file_content_manager.rs @@ -34,7 +34,7 @@ where let move_to = file_editor .file() .and_then(|f| f.get_character_at(position.text_position())) - .map(|character| character.dest()) + .map(|character| character.dest().clone()) .map(|dest| (position, Point::new(dest.x(), dest.y()))); match move_to { Some((position, point)) => file_editor.caret_mut().move_caret(position, point), @@ -171,7 +171,7 @@ mod tests { use super::*; use crate::renderer::managers::FontDetails; use crate::renderer::managers::TextDetails; - use crate::tests::support; + use crate::tests::*; use sdl2::rect::Rect; use sdl2::render::Texture; use sdl2::ttf::Font; @@ -226,7 +226,7 @@ mod tests { #[test] fn must_return_empty_string_when_no_file() { - let config = support::build_config(); + let config = build_config(); let mut editor = FileEditor::new(config); let result = current_file_path(&mut editor); assert_eq!(result, String::new()); @@ -234,7 +234,7 @@ mod tests { #[test] fn must_return_path_string_when_file_was_set() { - let config = support::build_config(); + let config = build_config(); let mut editor = FileEditor::new(Arc::clone(&config)); let file = EditorFile::new( "/foo/bar".to_owned(), @@ -252,7 +252,7 @@ mod tests { #[test] fn assert_insert_text_without_file() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); widget.prepare_ui(&mut renderer); @@ -263,7 +263,7 @@ mod tests { #[test] fn assert_insert_text_to_empty_file() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); let file = EditorFile::new("".to_owned(), "".to_owned(), config.clone()); @@ -276,7 +276,7 @@ mod tests { #[test] fn assert_insert_text_to_file_without_new_line() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); let mut file = EditorFile::new("".to_owned(), "bar".to_owned(), config.clone()); @@ -294,7 +294,7 @@ mod tests { #[test] fn assert_insert_text_to_file_with_new_line() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); let mut file = EditorFile::new("".to_owned(), "bar\n".to_owned(), config.clone()); @@ -312,7 +312,7 @@ mod tests { #[test] fn assert_insert_text_to_file_with_new_line_with_caret_at_new_line() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); let mut file = EditorFile::new("".to_owned(), "old content\n".to_owned(), config.clone()); @@ -335,7 +335,7 @@ mod tests { #[test] fn assert_insert_new_line_without_file() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); widget.prepare_ui(&mut renderer); @@ -344,12 +344,12 @@ mod tests { let expected = CaretPosition::new(0, 0, 0); assert_eq!(widget.caret().position(), &expected); let expected = Rect::new(0, 0, 6, 15); - assert_eq!(widget.caret().dest(), expected); + assert_eq!(widget.caret().dest(), &expected); } #[test] fn assert_insert_new_line_to_empty_file() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); let file = EditorFile::new("".to_owned(), "".to_owned(), config.clone()); @@ -360,12 +360,12 @@ mod tests { let expected = CaretPosition::new(1, 1, 0); assert_eq!(widget.caret().position(), &expected); let expected = Rect::new(0, 13, 6, 15); - assert_eq!(widget.caret().dest(), expected); + assert_eq!(widget.caret().dest(), &expected); } #[test] fn assert_insert_new_line_to_file_at_beginning() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); let file = EditorFile::new("".to_owned(), "foo".to_owned(), config.clone()); @@ -376,14 +376,14 @@ mod tests { let expected = CaretPosition::new(1, 1, 0); assert_eq!(widget.caret().position(), &expected); let expected = Rect::new(0, 13, 6, 15); - assert_eq!(widget.caret().dest(), expected); + assert_eq!(widget.caret().dest(), &expected); assert_eq!(widget.file().is_some(), true); assert_eq!(widget.file().unwrap().buffer(), "\nfoo".to_owned()); } #[test] fn assert_insert_new_line_to_file_in_middle() { - let config = support::build_config(); + let config = build_config(); let mut renderer = RendererMock::new(config.clone()); let mut widget = FileEditor::new(config.clone()); let mut file = EditorFile::new("hello.txt".to_owned(), "abcd".to_owned(), config.clone()); @@ -397,7 +397,7 @@ mod tests { let expected = CaretPosition::new(3, 1, 0); assert_eq!(widget.caret().position(), &expected); let expected = Rect::new(0, 13, 6, 15); - assert_eq!(widget.caret().dest(), expected); + assert_eq!(widget.caret().dest(), &expected); assert_eq!(widget.file().is_some(), true); assert_eq!(widget.file().unwrap().buffer(), "ab\ncd".to_owned()); } diff --git a/rider-editor/src/main.rs b/rider-editor/src/main.rs index 0b5b81b..b2b191c 100644 --- a/rider-editor/src/main.rs +++ b/rider-editor/src/main.rs @@ -1,10 +1,17 @@ -#![feature(clamp)] +#![feature( + clamp, + associated_type_bounds, + crate_visibility_modifier, + proc_macro_hygiene +)] extern crate dirs; #[macro_use] extern crate log; +extern crate lazy_static; extern crate rand; extern crate rider_config; +extern crate rider_derive; extern crate rider_lexers; extern crate rider_themes; extern crate sdl2; @@ -20,14 +27,12 @@ use std::fs::File; pub mod app; pub mod renderer; -#[cfg(test)] +#[macro_use] pub mod tests; pub mod ui; #[cfg_attr(tarpaulin, skip)] fn init_logger(directories: &Directories) { - // use simplelog::SharedLogger; - let mut log_file_path = directories.log_dir.clone(); log_file_path.push("rider.log"); diff --git a/rider-editor/src/renderer/managers.rs b/rider-editor/src/renderer/managers.rs index 4921725..b717eb9 100644 --- a/rider-editor/src/renderer/managers.rs +++ b/rider-editor/src/renderer/managers.rs @@ -41,7 +41,13 @@ pub struct FontDetails { impl From<&EditorConfig> for FontDetails { fn from(config: &EditorConfig) -> Self { - FontDetails::new(config.font_path().as_str(), config.character_size().clone()) + FontDetails::new(config.font_path(), config.character_size().clone()) + } +} + +impl From<(&str, u16)> for FontDetails { + fn from((path, size): (&str, u16)) -> Self { + FontDetails::new(path, size) } } @@ -97,18 +103,10 @@ impl<'a> From<&'a FontDetails> for FontDetails { #[cfg_attr(tarpaulin, skip)] //noinspection RsWrongLifetimeParametersNumber -pub type TextureManager<'l> = - ResourceManager<'l, String, Texture<'l>, TextureCreator>; +pub type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator>; #[cfg_attr(tarpaulin, skip)] pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>; -#[cfg_attr(tarpaulin, skip)] -pub trait ManagersHolder<'l> { - fn font_manager(&mut self) -> &mut FontManager<'l>; - - fn texture_manager(&mut self) -> &mut TextureManager<'l>; -} - #[cfg_attr(tarpaulin, skip)] #[derive(Clone)] pub struct ResourceManager<'l, K, R, L> @@ -160,7 +158,7 @@ impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator { type Args = str; fn load(&'l self, path: &str) -> Result { - println!("Loading {}...", path); + debug!("Loading {}...", path); self.load_texture(path) } } @@ -170,7 +168,7 @@ impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext { type Args = FontDetails; fn load(&'l self, data: &FontDetails) -> Result, String> { - info!("Loading font {}...", data.path); + debug!("Loading font {}...", data.path); self.load_font(&data.path, data.size) } } @@ -186,7 +184,7 @@ pub trait TextTextureManager<'l> { } #[cfg_attr(tarpaulin, skip)] -impl<'l> TextTextureManager<'l> for TextureManager<'l> { +impl<'l, T> TextTextureManager<'l> for TextureManager<'l, T> { //noinspection RsWrongLifetimeParametersNumber fn load_text( &mut self, @@ -203,9 +201,6 @@ impl<'l> TextTextureManager<'l> for TextureManager<'l> { let texture = self.loader.create_texture_from_surface(&surface).unwrap(); let resource = Rc::new(texture); self.cache.insert(key, resource.clone()); - // for c in details.text.chars() { - // info!("texture for '{:?}' created", c); - // } Ok(resource) }, Ok, diff --git a/rider-editor/src/renderer/renderer.rs b/rider-editor/src/renderer/renderer.rs index 7df8e58..f48033b 100644 --- a/rider-editor/src/renderer/renderer.rs +++ b/rider-editor/src/renderer/renderer.rs @@ -26,7 +26,7 @@ pub trait Renderer { pub struct CanvasRenderer<'l> { config: ConfigAccess, font_manager: FontManager<'l>, - texture_manager: TextureManager<'l>, + texture_manager: TextureManager<'l, sdl2::video::WindowContext>, character_sizes: HashMap, } @@ -58,13 +58,13 @@ impl<'l> CharacterSizeManager for CanvasRenderer<'l> { let (font_path, font_size) = { let config = self.config().read().unwrap(); ( - config.editor_config().font_path().clone(), + config.editor_config().font_path().to_string(), config.editor_config().character_size().clone(), ) }; let details = TextCharacterDetails { c: c.clone(), - font_path, + font_path: font_path.to_string(), font_size, }; self.character_sizes @@ -80,17 +80,6 @@ impl<'l> CharacterSizeManager for CanvasRenderer<'l> { } } -#[cfg_attr(tarpaulin, skip)] -impl<'l> ManagersHolder<'l> for CanvasRenderer<'l> { - fn font_manager(&mut self) -> &mut FontManager<'l> { - &mut self.font_manager - } - - fn texture_manager(&mut self) -> &mut TextureManager<'l> { - &mut self.texture_manager - } -} - #[cfg_attr(tarpaulin, skip)] impl<'l> ConfigHolder for CanvasRenderer<'l> { fn config(&self) -> &ConfigAccess { @@ -101,7 +90,7 @@ impl<'l> ConfigHolder for CanvasRenderer<'l> { #[cfg_attr(tarpaulin, skip)] impl<'l> Renderer for CanvasRenderer<'l> { fn load_font(&mut self, details: FontDetails) -> Rc { - self.font_manager() + self.font_manager .load(&details) .unwrap_or_else(|_| panic!("Font not found {:?}", details)) } @@ -113,11 +102,10 @@ impl<'l> Renderer for CanvasRenderer<'l> { ) -> Result, String> { use crate::renderer::managers::*; let font = self - .font_manager() + .font_manager .load(&font_details) .unwrap_or_else(|_| panic!("Font not found {:?}", details)); - let tex_manager = self.texture_manager(); - tex_manager.load_text(details, font) + self.texture_manager.load_text(details, font) } fn load_image(&mut self, path: String) -> Result, String> { diff --git a/rider-editor/src/tests.rs b/rider-editor/src/tests.rs deleted file mode 100644 index b9ff82b..0000000 --- a/rider-editor/src/tests.rs +++ /dev/null @@ -1,280 +0,0 @@ -#[cfg(test)] -pub mod support { - use crate::renderer::managers::FontDetails; - use crate::renderer::managers::TextDetails; - use crate::renderer::renderer::Renderer; - use crate::ui::text_character::CharacterSizeManager; - use crate::ui::CanvasAccess; - use rider_config::Config; - use rider_config::ConfigAccess; - use rider_config::ConfigHolder; - use sdl2::pixels::Color; - use sdl2::rect::Point; - use sdl2::rect::Rect; - use sdl2::render::Texture; - use sdl2::ttf::Font; - use std::collections::HashMap; - use std::fmt::Debug; - use std::fmt::Error; - use std::fmt::Formatter; - use std::rc::Rc; - use std::sync::*; - - pub fn build_path(path: String) { - use std::fs; - - fs::create_dir_all(path.as_str()).unwrap(); - fs::write((path.clone() + &"/file1".to_owned()).as_str(), "foo").unwrap(); - fs::write((path.clone() + &"/file2".to_owned()).as_str(), "bar").unwrap(); - fs::create_dir_all((path.clone() + &"/dir1".to_owned()).as_str()).unwrap(); - fs::create_dir_all((path.clone() + &"/dir2".to_owned()).as_str()).unwrap(); - } - - pub fn build_config() -> Arc> { - let mut config = Config::new(); - config.set_theme(config.editor_config().current_theme().clone()); - Arc::new(RwLock::new(config)) - } - - #[cfg_attr(tarpaulin, skip)] - #[derive(Debug, PartialEq)] - pub enum CanvasShape { - Line, - Border, - Rectangle, - Image(Rect, Rect, String), - } - - #[derive(Debug, PartialEq)] - pub struct RendererRect { - pub rect: Rect, - pub color: Color, - pub shape: CanvasShape, - } - - #[cfg_attr(tarpaulin, skip)] - impl RendererRect { - pub fn new(rect: Rect, color: Color, shape: CanvasShape) -> Self { - Self { rect, color, shape } - } - } - - #[cfg_attr(tarpaulin, skip)] - pub struct CanvasMock { - pub rects: Vec, - pub borders: Vec, - pub lines: Vec, - pub clippings: Vec>, - pub character_sizes: HashMap, - } - - #[cfg_attr(tarpaulin, skip)] - impl Debug for CanvasMock { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - write!( - f, - "CanvasMock {{ {:?} {:?} {:?} }}", - self.rects, self.lines, self.clippings - ) - } - } - - #[cfg_attr(tarpaulin, skip)] - impl PartialEq for CanvasMock { - fn eq(&self, other: &CanvasMock) -> bool { - self.rects == other.rects - && self.borders == other.borders - && self.clippings == other.clippings - && self.lines == other.lines - } - } - - #[cfg_attr(tarpaulin, skip)] - impl CanvasMock { - pub fn new() -> Self { - Self { - rects: vec![], - borders: vec![], - lines: vec![], - clippings: vec![], - character_sizes: HashMap::new(), - } - } - } - - #[cfg_attr(tarpaulin, skip)] - impl CanvasAccess for CanvasMock { - fn render_rect(&mut self, rect: Rect, color: Color) -> Result<(), String> { - self.rects.push(RendererRect { - rect, - color, - shape: CanvasShape::Rectangle, - }); - Ok(()) - } - - fn render_border(&mut self, rect: Rect, color: Color) -> Result<(), String> { - self.borders.push(RendererRect { - rect, - color, - shape: CanvasShape::Border, - }); - Ok(()) - } - - fn render_image(&mut self, _tex: Rc, src: Rect, dest: Rect) -> Result<(), String> { - self.rects.push(RendererRect::new( - dest.clone(), - Color::RGBA(0, 0, 0, 255), - CanvasShape::Image(src.clone(), dest.clone(), format!("_tex: Rc")), - )); - Ok(()) - } - - fn render_line(&mut self, start: Point, end: Point, color: Color) -> Result<(), String> { - self.lines.push(RendererRect { - rect: Rect::new(start.x(), start.y(), end.x() as u32, end.y() as u32), - color, - shape: CanvasShape::Line, - }); - Ok(()) - } - - fn set_clipping(&mut self, rect: Rect) { - self.clippings.push(Some(rect)); - } - - fn set_clip_rect(&mut self, rect: Option) { - self.clippings.push(rect); - } - - fn clip_rect(&self) -> Option { - self.clippings.last().cloned().unwrap_or_else(|| None) - } - } - - impl CharacterSizeManager for CanvasMock { - fn load_character_size(&mut self, c: char) -> Rect { - match self.character_sizes.get(&c) { - Some(r) => r.clone(), - None => { - self.character_sizes.insert(c, Rect::new(0, 0, 1, 1)); - self.character_sizes.get(&c).cloned().unwrap() - } - } - } - } - - impl CanvasMock { - pub fn set_character_rect(&mut self, c: char, rect: Rect) { - self.character_sizes.insert(c, rect); - } - - pub fn find_pixel_with_color( - &self, - point: sdl2::rect::Point, - color: sdl2::pixels::Color, - ) -> Option<&RendererRect> { - for rect in self.rects.iter() { - if rect.rect.contains_point(point.clone()) && rect.color == color { - return Some(rect.clone()); - } - } - for rect in self.borders.iter() { - if rect.rect.contains_point(point.clone()) && rect.color == color { - return Some(rect.clone()); - } - } - for rect in self.lines.iter() { - if rect.rect.contains_point(point.clone()) && rect.color == color { - return Some(rect.clone()); - } - } - None - } - - pub fn find_rect_with_color( - &self, - subject: sdl2::rect::Rect, - color: sdl2::pixels::Color, - ) -> Option<&RendererRect> { - for rect in self.rects.iter() { - if rect.rect == subject && rect.color == color { - return Some(rect.clone()); - } - } - None - } - - pub fn find_line_with_color( - &self, - subject: sdl2::rect::Rect, - color: sdl2::pixels::Color, - ) -> Option<&RendererRect> { - for rect in self.lines.iter() { - if rect.rect == subject && rect.color == color { - return Some(rect.clone()); - } - } - None - } - - pub fn find_border_with_color( - &self, - subject: sdl2::rect::Rect, - color: sdl2::pixels::Color, - ) -> Option<&RendererRect> { - for rect in self.borders.iter() { - if rect.rect == subject && rect.color == color { - return Some(rect.clone()); - } - } - None - } - } - - #[cfg_attr(tarpaulin, skip)] - pub struct SimpleRendererMock { - config: ConfigAccess, - } - - #[cfg_attr(tarpaulin, skip)] - impl SimpleRendererMock { - pub fn new(config: ConfigAccess) -> Self { - Self { config } - } - } - - #[cfg_attr(tarpaulin, skip)] - impl Renderer for SimpleRendererMock { - fn load_font(&mut self, _details: FontDetails) -> Rc { - unimplemented!() - } - - fn load_text_tex( - &mut self, - _details: &mut TextDetails, - _font_details: FontDetails, - ) -> Result, String> { - Err("skip text texture".to_owned()) - } - - fn load_image(&mut self, _path: String) -> Result, String> { - Err("skip img texture".to_owned()) - } - } - - #[cfg_attr(tarpaulin, skip)] - impl CharacterSizeManager for SimpleRendererMock { - fn load_character_size(&mut self, _c: char) -> Rect { - Rect::new(0, 0, 13, 14) - } - } - - #[cfg_attr(tarpaulin, skip)] - impl ConfigHolder for SimpleRendererMock { - fn config(&self) -> &Arc> { - &self.config - } - } -} diff --git a/rider-editor/src/tests/mod.rs b/rider-editor/src/tests/mod.rs new file mode 100644 index 0000000..7a3d179 --- /dev/null +++ b/rider-editor/src/tests/mod.rs @@ -0,0 +1,402 @@ +use crate::renderer::managers::FontDetails; +use crate::renderer::managers::TextDetails; +use crate::renderer::renderer::Renderer; +use crate::renderer::TextureManager; +use crate::ui::text_character::CharacterSizeManager; +use crate::ui::CanvasAccess; +use rider_config::Config; +use rider_config::ConfigAccess; +use rider_config::ConfigHolder; +use sdl2::pixels::{Color, PixelFormatEnum}; +use sdl2::rect::Point; +use sdl2::rect::Rect; +use sdl2::render::{Texture, TextureCreator}; +use sdl2::ttf::{Font, Sdl2TtfContext}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::fmt::Error; +use std::fmt::Formatter; +use std::path::PathBuf; +use std::rc::Rc; +use std::sync::*; + +#[cfg_attr(tarpaulin, skip)] +pub fn build_path(path: String) { + use std::fs; + + fs::create_dir_all(path.as_str()).unwrap(); + fs::write((path.clone() + &"/file1".to_owned()).as_str(), "foo").unwrap(); + fs::write((path.clone() + &"/file2".to_owned()).as_str(), "bar").unwrap(); + fs::create_dir_all((path.clone() + &"/dir1".to_owned()).as_str()).unwrap(); + fs::create_dir_all((path.clone() + &"/dir2".to_owned()).as_str()).unwrap(); +} + +#[cfg_attr(tarpaulin, skip)] +pub fn build_config() -> Arc> { + let mut config = Config::new(); + config.set_theme(config.editor_config().current_theme().clone()); + Arc::new(RwLock::new(config)) +} + +#[cfg_attr(tarpaulin, skip)] +#[derive(Debug, PartialEq)] +pub enum CanvasShape { + Line, + Border, + Rectangle, + Image(Rect, Rect, String), +} + +#[cfg_attr(tarpaulin, skip)] +#[derive(Debug, PartialEq)] +pub struct RendererRect { + pub rect: Rect, + pub color: Color, + pub shape: CanvasShape, +} + +#[cfg_attr(tarpaulin, skip)] +impl RendererRect { + pub fn new(rect: Rect, color: Color, shape: CanvasShape) -> Self { + Self { rect, color, shape } + } +} + +#[cfg_attr(tarpaulin, skip)] +pub struct CanvasMock { + pub rects: Vec, + pub borders: Vec, + pub lines: Vec, + pub clippings: Vec>, + pub character_sizes: HashMap, +} + +#[cfg_attr(tarpaulin, skip)] +impl Debug for CanvasMock { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!( + f, + "CanvasMock {{ {:?} {:?} {:?} }}", + self.rects, self.lines, self.clippings + ) + } +} + +#[cfg_attr(tarpaulin, skip)] +impl PartialEq for CanvasMock { + fn eq(&self, other: &CanvasMock) -> bool { + self.rects == other.rects + && self.borders == other.borders + && self.clippings == other.clippings + && self.lines == other.lines + } +} + +#[cfg_attr(tarpaulin, skip)] +impl CanvasMock { + pub fn new() -> Self { + Self { + rects: vec![], + borders: vec![], + lines: vec![], + clippings: vec![], + character_sizes: HashMap::new(), + } + } +} + +#[cfg_attr(tarpaulin, skip)] +impl CanvasAccess for CanvasMock { + fn render_rect(&mut self, rect: Rect, color: Color) -> Result<(), String> { + self.rects.push(RendererRect { + rect, + color, + shape: CanvasShape::Rectangle, + }); + Ok(()) + } + + fn render_border(&mut self, rect: Rect, color: Color) -> Result<(), String> { + self.borders.push(RendererRect { + rect, + color, + shape: CanvasShape::Border, + }); + Ok(()) + } + + fn render_image(&mut self, _tex: Rc, src: Rect, dest: Rect) -> Result<(), String> { + self.rects.push(RendererRect::new( + dest.clone(), + Color::RGBA(0, 0, 0, 255), + CanvasShape::Image(src.clone(), dest.clone(), format!("_tex: Rc")), + )); + Ok(()) + } + + fn render_line(&mut self, start: Point, end: Point, color: Color) -> Result<(), String> { + self.lines.push(RendererRect { + rect: Rect::new(start.x(), start.y(), end.x() as u32, end.y() as u32), + color, + shape: CanvasShape::Line, + }); + Ok(()) + } + + fn set_clipping(&mut self, rect: Rect) { + self.clippings.push(Some(rect)); + } + + fn set_clip_rect(&mut self, rect: Option) { + self.clippings.push(rect); + } + + fn clip_rect(&self) -> Option { + self.clippings.last().cloned().unwrap_or_else(|| None) + } +} + +#[cfg_attr(tarpaulin, skip)] +impl CharacterSizeManager for CanvasMock { + fn load_character_size(&mut self, c: char) -> Rect { + match self.character_sizes.get(&c) { + Some(r) => r.clone(), + None => { + self.character_sizes.insert(c, Rect::new(0, 0, 1, 1)); + self.character_sizes.get(&c).cloned().unwrap() + } + } + } +} + +#[cfg_attr(tarpaulin, skip)] +pub trait CanvasTester { + fn set_character_rect(&mut self, c: char, rect: Rect); + + fn find_pixel_with_color( + &self, + point: sdl2::rect::Point, + color: sdl2::pixels::Color, + ) -> Option<&RendererRect>; + + fn find_rect_with_color( + &self, + subject: sdl2::rect::Rect, + color: sdl2::pixels::Color, + ) -> Option<&RendererRect>; + + fn find_line_with_color( + &self, + subject: sdl2::rect::Rect, + color: sdl2::pixels::Color, + ) -> Option<&RendererRect>; + + fn find_border_with_color( + &self, + subject: sdl2::rect::Rect, + color: sdl2::pixels::Color, + ) -> Option<&RendererRect>; +} + +#[cfg_attr(tarpaulin, skip)] +impl CanvasTester for CanvasMock { + fn set_character_rect(&mut self, c: char, rect: Rect) { + self.character_sizes.insert(c, rect); + } + + fn find_pixel_with_color( + &self, + point: sdl2::rect::Point, + color: sdl2::pixels::Color, + ) -> Option<&RendererRect> { + for rect in self.rects.iter() { + if rect.rect.contains_point(point.clone()) && rect.color == color { + return Some(rect.clone()); + } + } + for rect in self.borders.iter() { + if rect.rect.contains_point(point.clone()) && rect.color == color { + return Some(rect.clone()); + } + } + for rect in self.lines.iter() { + if rect.rect.contains_point(point.clone()) && rect.color == color { + return Some(rect.clone()); + } + } + None + } + + fn find_rect_with_color( + &self, + subject: sdl2::rect::Rect, + color: sdl2::pixels::Color, + ) -> Option<&RendererRect> { + for rect in self.rects.iter() { + if rect.rect == subject && rect.color == color { + return Some(rect.clone()); + } + } + None + } + + fn find_line_with_color( + &self, + subject: sdl2::rect::Rect, + color: sdl2::pixels::Color, + ) -> Option<&RendererRect> { + for rect in self.lines.iter() { + if rect.rect == subject && rect.color == color { + return Some(rect.clone()); + } + } + None + } + + fn find_border_with_color( + &self, + subject: sdl2::rect::Rect, + color: sdl2::pixels::Color, + ) -> Option<&RendererRect> { + for rect in self.borders.iter() { + if rect.rect == subject && rect.color == color { + return Some(rect.clone()); + } + } + None + } +} + +#[cfg_attr(tarpaulin, skip)] +pub struct SimpleRendererMock<'l> { + pub config: ConfigAccess, + pub ttf: Sdl2TtfContext, + pub character_sizes: HashMap, + pub texture_manager: TextureManager<'l, sdl2::surface::SurfaceContext<'l>>, +} + +#[cfg_attr(tarpaulin, skip)] +impl<'l> SimpleRendererMock<'l> { + pub fn set_character_rect(&mut self, c: char, rect: Rect) { + self.character_sizes.insert(c, rect); + } + + pub fn texture_creator(&self) -> &TextureCreator> { + self.texture_manager.loader() + } +} + +#[cfg_attr(tarpaulin, skip)] +impl<'l> Renderer for SimpleRendererMock<'l> { + fn load_font(&mut self, details: FontDetails) -> Rc { + Rc::new( + self.ttf + .load_font(details.path, details.size) + .unwrap_or_else(|e| panic!("{:?}", e)), + ) + } + + fn load_text_tex( + &mut self, + _details: &mut TextDetails, + _font_details: FontDetails, + ) -> Result, String> { + self.texture_creator() + .create_texture( + PixelFormatEnum::RGB24, + sdl2::render::TextureAccess::Target, + 24, + 24, + ) + .map_err(|e| format!("{:?}", e)) + .map(|t| Rc::new(t)) + } + + fn load_image(&mut self, path: String) -> Result, String> { + self.texture_manager.load(path.as_str()) + } +} + +#[cfg_attr(tarpaulin, skip)] +impl<'l> CharacterSizeManager for SimpleRendererMock<'l> { + fn load_character_size(&mut self, c: char) -> Rect { + match self.character_sizes.get(&c) { + Some(r) => r.clone(), + _ => { + let rect = Rect::new(0, 0, 13, 14); + self.set_character_rect(c.clone(), rect.clone()); + rect + } + } + } +} + +#[cfg_attr(tarpaulin, skip)] +impl<'l> ConfigHolder for SimpleRendererMock<'l> { + fn config(&self) -> &Arc> { + &self.config + } +} + +#[cfg_attr(tarpaulin, skip)] +pub type TestCanvas<'r> = sdl2::render::Canvas>; + +#[cfg_attr(tarpaulin, skip)] +impl<'r> CanvasAccess for TestCanvas<'r> { + fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> { + self.set_draw_color(color); + self.fill_rect(rect) + } + + fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> { + self.set_draw_color(color); + self.draw_rect(rect) + } + + fn render_image(&mut self, tex: Rc, src: Rect, dest: Rect) -> Result<(), String> { + self.copy_ex(&tex, Some(src), Some(dest), 0.0, None, false, false) + } + + fn render_line( + &mut self, + start: Point, + end: Point, + color: sdl2::pixels::Color, + ) -> Result<(), String> { + self.set_draw_color(color); + self.draw_line(start, end) + } + + fn set_clipping(&mut self, rect: Rect) { + self.set_clip_rect(rect); + } + + fn set_clip_rect(&mut self, rect: Option) { + self.set_clip_rect(rect); + } + + fn clip_rect(&self) -> Option { + self.clip_rect() + } +} + +#[cfg_attr(tarpaulin, skip)] +pub trait DumpImage { + fn dump_ui(&self, path: S) + where + S: Into; +} + +#[cfg_attr(tarpaulin, skip)] +impl<'r> DumpImage for TestCanvas<'r> { + fn dump_ui(&self, path: S) + where + S: Into, + { + let p = std::path::PathBuf::from(path.into()); + std::fs::create_dir_all(p.parent().unwrap()).unwrap(); + self.surface() + .save_bmp(p) + .expect("Failed to save canvas as BMP file"); + } +} diff --git a/rider-editor/src/ui/buttons/save_button.rs b/rider-editor/src/ui/buttons/save_button.rs index 9d9d334..6081381 100644 --- a/rider-editor/src/ui/buttons/save_button.rs +++ b/rider-editor/src/ui/buttons/save_button.rs @@ -11,6 +11,7 @@ pub struct SaveButton { inner: WidgetInner, } +#[cfg_attr(tarpaulin, skip)] impl std::ops::Deref for SaveButton { type Target = WidgetInner; @@ -19,6 +20,7 @@ impl std::ops::Deref for SaveButton { } } +#[cfg_attr(tarpaulin, skip)] impl std::ops::DerefMut for SaveButton { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner @@ -79,11 +81,11 @@ impl SaveButton { mod test { use super::*; use crate::app::UpdateResult; - use crate::tests::support; + use crate::tests::*; #[test] fn must_return_save_on_left_click() { - let config = support::build_config(); + let config = build_config(); let mut widget = SaveButton::new(config); assert_eq!( widget.on_left_click(&Point::new(0, 0), &UpdateContext::Nothing), @@ -93,7 +95,7 @@ mod test { #[test] fn must_use_inner() { - let config = support::build_config(); + let config = build_config(); let mut widget = SaveButton::new(config); assert_eq!( @@ -113,7 +115,7 @@ mod test { #[test] fn must_have_padding() { - let config = support::build_config(); + let config = build_config(); let widget = SaveButton::new(config); assert_eq!(widget.padding_width(), ICON_DEST_WIDTH); assert_eq!(widget.padding_height(), ICON_DEST_HEIGHT); diff --git a/rider-editor/src/ui/buttons/settings_button.rs b/rider-editor/src/ui/buttons/settings_button.rs index 39c40d6..be2f1d5 100644 --- a/rider-editor/src/ui/buttons/settings_button.rs +++ b/rider-editor/src/ui/buttons/settings_button.rs @@ -80,11 +80,11 @@ impl SettingsButton { mod test { use super::*; use crate::app::UpdateResult; - use crate::tests::support; + use crate::tests::*; #[test] fn must_return_open_settings_on_left_click() { - let config = support::build_config(); + let config = build_config(); let mut widget = SettingsButton::new(config); assert_eq!( widget.on_left_click(&Point::new(0, 0), &UpdateContext::Nothing), @@ -94,7 +94,7 @@ mod test { #[test] fn must_use_inner() { - let config = support::build_config(); + let config = build_config(); let mut widget = SettingsButton::new(config); assert_eq!( @@ -114,7 +114,7 @@ mod test { #[test] fn must_have_padding() { - let config = support::build_config(); + let config = build_config(); let widget = SettingsButton::new(config); assert_eq!(widget.padding_width(), ICON_DEST_WIDTH); assert_eq!(widget.padding_height(), ICON_DEST_HEIGHT); diff --git a/rider-editor/src/ui/caret/caret.rs b/rider-editor/src/ui/caret/caret.rs index 99ab1e8..6ae203d 100644 --- a/rider-editor/src/ui/caret/caret.rs +++ b/rider-editor/src/ui/caret/caret.rs @@ -1,6 +1,7 @@ -use crate::app::UpdateResult as UR; +use crate::app::UpdateResult; +use crate::renderer::Renderer; use crate::ui::*; -use rider_config::ConfigAccess; +use rider_config::{ConfigAccess, ConfigHolder}; use sdl2::rect::{Point, Rect}; use std::ops::{Deref, DerefMut}; @@ -69,11 +70,56 @@ impl DerefMut for Caret { } } -#[cfg_attr(tarpaulin, skip)] -impl Caret { - pub fn render(&self, canvas: &mut T, context: &RenderContext) +impl Widget for Caret { + fn texture_path(&self) -> Option { + None + } + + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn source(&self) -> &Rect { + &self.dest + } + + fn set_source(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult { + self.blink_delay += 1; + if self.blink_delay >= 15 { + self.blink_delay = 0; + self.toggle_state(); + } + UpdateResult::NoOp + } + + fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { + UR::NoOp + } + + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + match context { + &UpdateContext::ParentPosition(p) => move_render_point(p, &self.dest), + _ => self.dest().clone(), + } + .contains_point(point.clone()) + } + + fn render_start_point(&self) -> Point { + self.dest().top_left() + } + + fn render(&self, canvas: &mut C, _renderer: &mut R, context: &RenderContext) where - T: CanvasAccess, + C: CanvasAccess, + R: Renderer + CharacterSizeManager + ConfigHolder, { use std::borrow::*; @@ -92,54 +138,15 @@ impl Caret { .render_line(start, end, color) .unwrap_or_else(|_| panic!("Failed to draw a caret")); } - - pub fn prepare_ui(&mut self, renderer: &mut T) + fn prepare_ui<'l, T>(&mut self, renderer: &mut T) where - T: CharacterSizeManager, + T: Renderer + CharacterSizeManager + ConfigHolder, { let rect = renderer.load_character_size('I'); self.dest.set_height(rect.height()); } } -impl Caret { - pub fn update(&mut self) -> UR { - self.blink_delay += 1; - if self.blink_delay >= 15 { - self.blink_delay = 0; - self.toggle_state(); - } - UR::NoOp - } -} - -impl ClickHandler for Caret { - fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { - UR::NoOp - } - - fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { - let dest = self.dest(); - is_in_rect( - point, - &match context { - &UpdateContext::ParentPosition(p) => move_render_point(p, &dest), - _ => dest, - }, - ) - } -} - -impl RenderBox for Caret { - fn render_start_point(&self) -> Point { - self.dest().top_left() - } - - fn dest(&self) -> Rect { - self.dest.clone() - } -} - #[cfg(test)] mod test_own_methods { use crate::tests::*; @@ -149,7 +156,7 @@ mod test_own_methods { #[test] fn assert_move_caret() { - let config = support::build_config(); + let config = build_config(); let mut widget = Caret::new(Arc::clone(&config)); widget.move_caret(widget.moved(10, 21, 34), Point::new(10, 20)); let result = ( @@ -164,7 +171,7 @@ mod test_own_methods { #[test] fn assert_reset() { - let config = support::build_config(); + let config = build_config(); let mut widget = Caret::new(Arc::clone(&config)); widget.reset_caret(); let result = ( @@ -179,7 +186,7 @@ mod test_own_methods { #[test] fn assert_toggle_state() { - let config = support::build_config(); + let config = build_config(); let mut widget = Caret::new(Arc::clone(&config)); let old = widget.state().clone(); @@ -208,7 +215,7 @@ mod test_deref { #[test] fn must_deref_text_position() { - let config = support::build_config(); + let config = build_config(); let mut widget = Caret::new(Arc::clone(&config)); widget.move_caret(widget.moved(10, 21, 34), Point::new(0, 0)); let result = widget.text_position(); @@ -218,7 +225,7 @@ mod test_deref { #[test] fn must_deref_line_number() { - let config = support::build_config(); + let config = build_config(); let mut widget = Caret::new(Arc::clone(&config)); widget.move_caret(widget.moved(10, 21, 34), Point::new(0, 0)); let result = widget.line_number(); @@ -228,7 +235,7 @@ mod test_deref { #[test] fn must_deref_line_position() { - let config = support::build_config(); + let config = build_config(); let mut widget = Caret::new(Arc::clone(&config)); widget.move_caret(widget.moved(10, 21, 34), Point::new(0, 0)); let result = widget.line_position(); @@ -246,7 +253,7 @@ mod test_render_box { #[test] fn must_return_top_left_point() { - let config = support::build_config(); + let config = build_config(); let widget = Caret::new(Arc::clone(&config)); let result = widget.render_start_point(); let expected = Point::new(0, 0); @@ -264,7 +271,7 @@ mod test_click_handler { #[test] fn refute_when_not_click_target() { - let config = support::build_config(); + let config = build_config(); let widget = Caret::new(Arc::clone(&config)); let point = Point::new(9999, 9999); let context = UpdateContext::Nothing; @@ -274,7 +281,7 @@ mod test_click_handler { #[test] fn assert_when_click_target() { - let config = support::build_config(); + let config = build_config(); let widget = Caret::new(Arc::clone(&config)); let point = Point::new(0, 0); @@ -285,7 +292,7 @@ mod test_click_handler { #[test] fn refute_when_not_click_target_because_parent() { - let config = support::build_config(); + let config = build_config(); let widget = Caret::new(Arc::clone(&config)); let point = Point::new(20, 30); let context = UpdateContext::ParentPosition(Point::new(9999, 9999)); @@ -295,7 +302,7 @@ mod test_click_handler { #[test] fn assert_when_click_target_because_parent() { - let config = support::build_config(); + let config = build_config(); let widget = Caret::new(Arc::clone(&config)); let point = Point::new(10, 10); let context = UpdateContext::ParentPosition(Point::new(10, 10)); @@ -305,7 +312,7 @@ mod test_click_handler { #[test] fn assert_on_click_do_nothing() { - let config = support::build_config(); + let config = build_config(); let mut widget = Caret::new(Arc::clone(&config)); let point = Point::new(12, 34); let context = UpdateContext::ParentPosition(Point::new(678, 293)); @@ -317,41 +324,43 @@ mod test_click_handler { #[cfg(test)] mod test_render { - use crate::tests::support; - use crate::tests::support::build_config; + use crate::tests::*; use crate::ui::*; + use rider_derive::*; use sdl2::rect::{Point, Rect}; #[test] fn assert_render_line() { - let config = build_config(); let context = RenderContext::ParentPosition(Point::new(10, 14)); - let mut canvas = support::CanvasMock::new(); + build_test_renderer!(renderer); + let mut widget = Caret::new(config); canvas.set_character_rect('I', Rect::new(11, 12, 6, 23)); widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(23, 23)); - widget.render(&mut canvas, &context); + widget.render(&mut canvas, &mut renderer, &context); assert_eq!( canvas.find_pixel_with_color( Point::new(33, 37), sdl2::pixels::Color::RGBA(121, 121, 121, 0) ), - Some(&support::RendererRect::new( + Some(&RendererRect::new( Rect::new(33, 37, 33, 38), sdl2::pixels::Color::RGBA(121, 121, 121, 0), - support::CanvasShape::Line + CanvasShape::Line )) ); } #[test] fn assert_prepare_ui() { - let config = build_config(); - let mut canvas = support::CanvasMock::new(); - canvas.set_character_rect('I', Rect::new(11, 12, 6, 23)); + build_test_renderer!(renderer); + renderer + .character_sizes + .insert('I', Rect::new(11, 12, 6, 23)); + let mut widget = Caret::new(config); widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(11, 12)); - widget.prepare_ui(&mut canvas); - assert_eq!(widget.dest(), Rect::new(11, 12, 6, 23)); + widget.prepare_ui(&mut renderer); + assert_eq!(widget.dest(), &Rect::new(11, 12, 6, 23)); } } diff --git a/rider-editor/src/ui/file/editor_file.rs b/rider-editor/src/ui/file/editor_file.rs index aeb717c..2ff7267 100644 --- a/rider-editor/src/ui/file/editor_file.rs +++ b/rider-editor/src/ui/file/editor_file.rs @@ -63,10 +63,6 @@ impl EditorFile { self.line_height } - pub fn render_position(&self) -> &Rect { - &self.dest - } - pub fn get_section_at_mut(&mut self, index: usize) -> Option<&mut EditorFileSection> { self.sections.get_mut(index) } @@ -97,9 +93,8 @@ impl TextCollection for EditorFile { fn get_line(&self, line: &usize) -> Option> { let mut vec: Vec<&TextCharacter> = vec![]; for section in self.sections.iter() { - match section.get_line(line) { - Some(v) => vec.append(&mut v.clone()), - _ => (), + if let Some(v) = section.get_line(line) { + vec.append(&mut v.clone()); } } @@ -146,32 +141,27 @@ impl TextCollection for EditorFile { } } -impl EditorFile { - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) - where - R: Renderer + ConfigHolder, - C: CanvasAccess, - { - for section in self.sections.iter() { - section.render(canvas, renderer, context); - } +impl Widget for EditorFile { + fn texture_path(&self) -> Option { + None } - pub fn prepare_ui(&mut self, renderer: &mut R) - where - R: ConfigHolder + CharacterSizeManager + Renderer, - { - for section in self.sections.iter_mut() { - section.prepare_ui(renderer); - } - - let r = renderer.load_character_size('W'); - self.line_height = r.height(); - self.refresh_characters_position(); + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn source(&self) -> &Rect { + self.dest() + } + + fn set_source(&mut self, rect: &Rect) { + self.set_dest(rect); } -} -impl Update for EditorFile { fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { let mut result = UR::NoOp; for section in self.sections.iter_mut() { @@ -179,9 +169,7 @@ impl Update for EditorFile { } result } -} -impl ClickHandler for EditorFile { fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { let mut index = -1; for (i, section) in self.sections.iter().enumerate() { @@ -207,6 +195,29 @@ impl ClickHandler for EditorFile { } false } + + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + C: CanvasAccess, + R: Renderer + CharacterSizeManager + ConfigHolder, + { + for section in self.sections.iter() { + section.render(canvas, renderer, context); + } + } + + fn prepare_ui<'l, T>(&mut self, renderer: &mut T) + where + T: Renderer + CharacterSizeManager + ConfigHolder, + { + for section in self.sections.iter_mut() { + section.prepare_ui(renderer); + } + + let r = renderer.load_character_size('W'); + self.line_height = r.height(); + self.refresh_characters_position(); + } } impl TextWidget for EditorFile { @@ -230,16 +241,6 @@ impl TextWidget for EditorFile { } } -impl RenderBox for EditorFile { - fn render_start_point(&self) -> Point { - self.dest.top_left() - } - - fn dest(&self) -> Rect { - self.dest.clone() - } -} - pub struct EditorFileIterator<'a> { current_section: usize, current_token: usize, @@ -298,19 +299,34 @@ impl<'a> Iterator for EditorFileIterator<'a> { #[cfg(test)] mod tests { - use crate::tests::support; - use crate::tests::support::SimpleRendererMock; + use crate::tests::*; + + use crate::app::UpdateResult; use crate::ui::*; + use rider_derive::*; use sdl2::rect::{Point, Rect}; + #[test] + fn check_get_line() { + build_test_renderer!(renderer); + let mut file = + EditorFile::new("./foo.txt".to_owned(), "a b c d".to_owned(), config.clone()); + file.prepare_ui(&mut renderer); + + let result = file.get_line(&0); + assert_eq!(result.is_some(), true); + + let result = file.get_line(&1); + assert_eq!(result.is_some(), false); + } + //################################################## // iterator //################################################## #[test] fn assert_simple_iterations() { - let config = support::build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut file = EditorFile::new("./foo.txt".to_owned(), "a b c d".to_owned(), config.clone()); file.prepare_ui(&mut renderer); @@ -334,7 +350,7 @@ mod tests { #[test] fn assert_path_txt() { - let config = support::build_config(); + let config = build_config(); let buffer = "".to_owned(); let path = "/example.txt".to_owned(); let widget = EditorFile::new(path, buffer, config); @@ -343,7 +359,7 @@ mod tests { #[test] fn assert_path_rs() { - let config = support::build_config(); + let config = build_config(); let buffer = "".to_owned(); let path = "/example.rs".to_owned(); let widget = EditorFile::new(path, buffer, config); @@ -356,7 +372,7 @@ mod tests { #[test] fn assert_empty_buffer() { - let config = support::build_config(); + let config = build_config(); let buffer = "".to_owned(); let path = "/example.txt".to_owned(); let widget = EditorFile::new(path, buffer, config); @@ -365,7 +381,7 @@ mod tests { #[test] fn assert_some_buffer() { - let config = support::build_config(); + let config = build_config(); let buffer = "fn main(){}".to_owned(); let path = "some.rs".to_owned(); let widget = EditorFile::new(path, buffer, config); @@ -378,7 +394,7 @@ mod tests { #[test] fn assert_initial_line_height() { - let config = support::build_config(); + let config = build_config(); let buffer = "".to_owned(); let path = "/example.txt".to_owned(); let widget = EditorFile::new(path, buffer, config); @@ -391,7 +407,7 @@ mod tests { #[test] fn assert_dest() { - let config = support::build_config(); + let config = build_config(); let buffer = "".to_owned(); let path = "/example.txt".to_owned(); let widget = EditorFile::new(path, buffer, config); @@ -402,7 +418,7 @@ mod tests { #[test] fn assert_render_start_point() { - let config = support::build_config(); + let config = build_config(); let buffer = "".to_owned(); let path = "/example.txt".to_owned(); let widget = EditorFile::new(path, buffer, config); @@ -410,4 +426,129 @@ mod tests { let expected = Point::new(0, 0); assert_eq!(result, expected); } + + #[test] + fn check_get_section_at_mut() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFile::new(path, buffer, config); + let result = widget.get_section_at_mut(12); + assert!(result.is_none()); + } + + //####################################################### + // widget + //####################################################### + + #[test] + fn check_texture_path() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let widget = EditorFile::new(path, buffer, config); + let result = widget.texture_path(); + assert!(result.is_none()); + } + + #[test] + fn check_set_dest() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFile::new(path, buffer, config); + let rect = Rect::new(2, 4, 6, 8); + widget.set_dest(&rect); + assert_eq!(format!("{:?}", widget.dest()), format!("{:?}", rect)); + } + + #[test] + fn check_source() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFile::new(path, buffer, config); + let rect = Rect::new(2, 4, 6, 8); + widget.set_source(&rect); + assert_eq!(format!("{:?}", widget.source()), format!("{:?}", rect)); + assert_eq!( + format!("{:?}", widget.source()), + format!("{:?}", widget.dest()) + ); + } + + #[test] + fn check_update() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFile::new(path, buffer, config); + let result = widget.update(0, &UpdateContext::Nothing); + assert_eq!(result, UpdateResult::NoOp); + } + + #[test] + fn check_on_left_click() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFile::new(path, buffer, config); + let result = widget.on_left_click(&Point::new(0, 0), &UpdateContext::Nothing); + assert_eq!(result, UpdateResult::NoOp); + } + + #[test] + fn check_on_left_click_with_sections() { + build_test_renderer!(renderer); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFile::new(path, buffer, config.clone()); + widget.sections = vec![EditorFileSection::new( + "a b c d".to_string(), + "".to_string(), + config, + )]; + widget.prepare_ui(&mut renderer); + let result = widget.on_left_click(&Point::new(0, 0), &UpdateContext::Nothing); + assert_eq!( + result, + UpdateResult::MoveCaret(Rect::new(0, 0, 13, 14), CaretPosition::new(0, 0, 0)) + ); + } + + #[test] + fn check_is_left_click_target() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let widget = EditorFile::new(path, buffer, config); + let result = widget.is_left_click_target(&Point::new(0, 0), &UpdateContext::Nothing); + assert_eq!(result, false); + } + + #[test] + fn check_is_left_click_target_with_sections() { + build_test_renderer!(renderer); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFile::new(path, buffer, config.clone()); + widget.sections = vec![EditorFileSection::new( + "a b c d".to_string(), + "".to_string(), + config, + )]; + widget.prepare_ui(&mut renderer); + let result = widget.is_left_click_target(&Point::new(0, 0), &UpdateContext::Nothing); + assert_eq!(result, true); + } + + #[test] + fn check_render() { + build_test_renderer!(renderer); + let buffer = "a b c".to_owned(); + let path = "/example.txt".to_owned(); + let widget = EditorFile::new(path, buffer, config.clone()); + widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + assert!(true); + } } diff --git a/rider-editor/src/ui/file/editor_file_section.rs b/rider-editor/src/ui/file/editor_file_section.rs index 35aef2a..12eb9f0 100644 --- a/rider-editor/src/ui/file/editor_file_section.rs +++ b/rider-editor/src/ui/file/editor_file_section.rs @@ -16,6 +16,7 @@ pub struct EditorFileSection { tokens: Vec, language: Language, config: Arc>, + dest: Rect, } impl EditorFileSection { @@ -48,6 +49,7 @@ impl EditorFileSection { tokens, language, config, + dest: Rect::new(0, 0, 0, 0), } } @@ -61,25 +63,6 @@ impl EditorFileSection { } } - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) - where - R: Renderer + ConfigHolder, - C: CanvasAccess, - { - for token in self.tokens.iter() { - token.render(canvas, renderer, context); - } - } - - pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T) - where - T: ConfigHolder + CharacterSizeManager + Renderer, - { - for token in self.tokens.iter_mut() { - token.prepare_ui(renderer); - } - } - #[inline] pub fn iter_char(&self) -> EditorFileSectionIterator { EditorFileSectionIterator::new(self) @@ -90,6 +73,83 @@ impl EditorFileSection { } } +impl Widget for EditorFileSection { + fn texture_path(&self) -> Option { + None + } + + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn source(&self) -> &Rect { + &self.dest + } + + fn set_source(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { + let mut result = UR::NoOp; + for token in self.tokens.iter_mut() { + result = token.update(ticks, context) + } + result + } + + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { + for token in self.tokens.iter_mut() { + if token.is_left_click_target(point, context) { + return token.on_left_click(point, context); + } + } + UR::NoOp + } + + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + let mut i = 0; + loop { + if i == self.tokens.len() { + break; + } + match self.tokens.get(i) { + Some(token) => { + if token.is_left_click_target(point, context) { + return true; + } + } + None => break, + } + i += 1; + } + false + } + + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + C: CanvasAccess, + R: Renderer + CharacterSizeManager + ConfigHolder, + { + for token in self.tokens.iter() { + token.render(canvas, renderer, context); + } + } + + fn prepare_ui<'l, T>(&mut self, renderer: &mut T) + where + T: Renderer + CharacterSizeManager + ConfigHolder, + { + for token in self.tokens.iter_mut() { + token.prepare_ui(renderer); + } + } +} + impl TextWidget for EditorFileSection { fn full_rect(&self) -> Rect { let mut current_line_width = 0; @@ -157,46 +217,6 @@ impl TextCollection for EditorFileSection { } } -impl Update for EditorFileSection { - fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { - let mut result = UR::NoOp; - for token in self.tokens.iter_mut() { - result = token.update(ticks, context) - } - result - } -} - -impl ClickHandler for EditorFileSection { - fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { - for token in self.tokens.iter_mut() { - if token.is_left_click_target(point, context) { - return token.on_left_click(point, context); - } - } - UR::NoOp - } - - fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { - let mut i = 0; - loop { - if i == self.tokens.len() { - break; - } - match self.tokens.get(i) { - Some(token) => { - if token.is_left_click_target(point, context) { - return true; - } - } - None => break, - } - i += 1; - } - false - } -} - pub struct EditorFileSectionIterator<'a> { section: &'a EditorFileSection, current_token: usize, @@ -243,7 +263,8 @@ impl<'a> std::iter::Iterator for EditorFileSectionIterator<'a> { #[cfg(test)] mod tests { use super::*; - use crate::tests::support::{build_config, SimpleRendererMock}; + use crate::tests::*; + use rider_derive::*; impl EditorFileSection { pub fn tokens_count(&self) -> usize { @@ -269,8 +290,7 @@ mod tests { #[test] fn assert_simple_char_iteration() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut section = EditorFileSection::new("a b c d".to_owned(), ".txt".to_owned(), config); section.prepare_ui(&mut renderer); for (index, c) in section.iter_char().enumerate() { @@ -289,8 +309,7 @@ mod tests { #[test] fn assert_complex_char_iteration() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut section = EditorFileSection::new("let a = 1".to_owned(), ".rs".to_owned(), config); section.prepare_ui(&mut renderer); assert_eq!(section.tokens.len(), 7); @@ -309,4 +328,40 @@ mod tests { } } } + + #[test] + fn check_texture_path() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let widget = EditorFileSection::new(path, buffer, config); + let result = widget.texture_path(); + assert!(result.is_none()); + } + + #[test] + fn check_set_dest() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFileSection::new(path, buffer, config); + let rect = Rect::new(2, 4, 6, 8); + widget.set_dest(&rect); + assert_eq!(format!("{:?}", widget.dest()), format!("{:?}", rect)); + } + + #[test] + fn check_source() { + let config = build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let mut widget = EditorFileSection::new(path, buffer, config); + let rect = Rect::new(2, 4, 6, 8); + widget.set_source(&rect); + assert_eq!(format!("{:?}", widget.source()), format!("{:?}", rect)); + assert_eq!( + format!("{:?}", widget.source()), + format!("{:?}", widget.dest()) + ); + } } diff --git a/rider-editor/src/ui/file/editor_file_token.rs b/rider-editor/src/ui/file/editor_file_token.rs index 04501ca..42a384e 100644 --- a/rider-editor/src/ui/file/editor_file_token.rs +++ b/rider-editor/src/ui/file/editor_file_token.rs @@ -14,6 +14,7 @@ pub struct EditorFileToken { characters: Vec, token_type: TokenType, config: Arc>, + dest: Rect, } impl EditorFileToken { @@ -23,6 +24,7 @@ impl EditorFileToken { characters: vec![], token_type: token_type.clone(), config, + dest: Rect::new(0, 0, 0, 0), } } @@ -132,22 +134,68 @@ impl TextCollection for EditorFileToken { } } -impl EditorFileToken { +impl Widget for EditorFileToken { + fn texture_path(&self) -> Option { + None + } + + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn source(&self) -> &Rect { + &self.dest + } + + fn set_source(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { + for text_character in self.characters.iter_mut() { + text_character.update(ticks, context); + } + self.set_dest(&self.full_rect()); + UR::NoOp + } + + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { + for text_character in self.characters.iter_mut() { + if text_character.is_left_click_target(point, context) { + return text_character.on_left_click(point, context); + } + } + UR::NoOp + } + + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + for text_character in self.characters.iter() { + if text_character.is_left_click_target(point, context) { + return true; + } + } + false + } + /** * Must first create targets so even if new line appear renderer will know * where move render starting point */ - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where - R: Renderer + ConfigHolder, C: CanvasAccess, + R: Renderer + CharacterSizeManager + ConfigHolder, { for text_character in self.characters.iter() { text_character.render(canvas, renderer, context); } } - pub fn prepare_ui(&mut self, renderer: &mut R) + fn prepare_ui(&mut self, renderer: &mut R) where R: ConfigHolder + CharacterSizeManager + Renderer, { @@ -172,35 +220,6 @@ impl EditorFileToken { } } -impl Update for EditorFileToken { - fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { - for text_character in self.characters.iter_mut() { - text_character.update(ticks, context); - } - UR::NoOp - } -} - -impl ClickHandler for EditorFileToken { - fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { - for text_character in self.characters.iter_mut() { - if text_character.is_left_click_target(point, context) { - return text_character.on_left_click(point, context); - } - } - UR::NoOp - } - - fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { - for text_character in self.characters.iter() { - if text_character.is_left_click_target(point, context) { - return true; - } - } - false - } -} - #[derive(Clone)] pub struct EditorFileTokenIterator<'a> { editor_file_token: &'a EditorFileToken, @@ -230,8 +249,8 @@ impl<'a> std::iter::Iterator for EditorFileTokenIterator<'a> { #[cfg(test)] mod tests { use super::*; - use crate::tests::support::build_config; - use crate::tests::support::CanvasMock; + + use crate::tests::*; use rider_lexers::Token; use sdl2::pixels::PixelFormatEnum; use sdl2::render::Texture; diff --git a/rider-editor/src/ui/file_editor/mod.rs b/rider-editor/src/ui/file_editor/mod.rs index 237db36..c19888d 100644 --- a/rider-editor/src/ui/file_editor/mod.rs +++ b/rider-editor/src/ui/file_editor/mod.rs @@ -1,4 +1,3 @@ -use crate::app::UpdateResult as UR; use crate::app::*; use crate::renderer::renderer::Renderer; use crate::ui::caret::caret::Caret; @@ -11,20 +10,19 @@ use crate::ui::scroll_bar::horizontal_scroll_bar::*; use crate::ui::scroll_bar::vertical_scroll_bar::*; use crate::ui::scroll_bar::ScrollWidget; use crate::ui::text_character::CharacterSizeManager; -use crate::ui::CanvasAccess; -use crate::ui::ClickHandler; -use crate::ui::RenderBox; use crate::ui::RenderContext; -use crate::ui::Update; use crate::ui::UpdateContext; use crate::ui::{move_render_point, ScrollView}; +use crate::ui::{CanvasAccess, Widget}; use sdl2::rect::Point; use sdl2::rect::Rect; use std::mem; use std::sync::*; pub trait FileAccess { - fn has_file(&self) -> bool; + fn has_file(&self) -> bool { + self.file().is_some() + } fn file(&self) -> Option<&EditorFile>; @@ -34,17 +32,55 @@ pub trait FileAccess { fn drop_file(&mut self) -> Option; - fn replace_current_file(&mut self, file: EditorFile); + fn replace_current_file(&mut self, file: EditorFile) { + self.open_file(file); + } } -pub trait CaretAccess { +pub trait CaretAccess: FileAccess { fn caret(&self) -> &Caret; fn caret_mut(&mut self) -> &mut Caret; - fn move_caret(&mut self, dir: MoveDirection); + fn move_caret(&mut self, dir: MoveDirection) { + match dir { + MoveDirection::Left => caret_manager::move_caret_left(self), + MoveDirection::Right => caret_manager::move_caret_right(self), + MoveDirection::Up => caret_manager::move_caret_up(self), + MoveDirection::Down => caret_manager::move_caret_down(self), + }; + } - fn set_caret_to_end_of_line(&mut self, line: i32); + fn set_caret_to_end_of_line(&mut self, line: i32) { + let file = match self.file_mut() { + Some(f) => f, + _ => return, + }; + let mut line = line; + while line >= 0 { + match file.get_last_at_line(line.clone() as usize) { + Some(text_character) => { + let rect = text_character.dest(); + let position = + CaretPosition::new(text_character.position() + 1, line as usize, 0); + let p = if text_character.is_new_line() { + file.get_character_at(text_character.position() + 1) + .map_or_else( + || text_character.dest().top_left(), + |tc| tc.dest().top_left(), + ) + } else { + rect.top_right() + }; + self.caret_mut().move_caret(position, p); + break; + } + _ => { + line -= 1; + } + } + } + } } pub struct FileEditor { @@ -57,19 +93,152 @@ pub struct FileEditor { horizontal_scroll_bar: HorizontalScrollBar, } -impl FileEditor { - pub fn new(config: ConfigAccess) -> Self { - let dest = { - let c = config.read().unwrap(); - Rect::new( - c.editor_left_margin(), - c.editor_top_margin(), - c.width() - c.editor_left_margin() as u32, - c.height() - c.editor_top_margin() as u32, +impl Widget for FileEditor { + fn texture_path(&self) -> Option { + None + } + + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn source(&self) -> &Rect { + &self.dest + } + + fn set_source(&mut self, _rect: &Rect) {} + + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult { + let (width, height, editor_left_margin, editor_top_margin, scroll_width, scroll_margin) = { + let config: RwLockReadGuard = self.config.read().unwrap(); + ( + config.width(), + config.height(), + config.editor_left_margin() as u32, + config.editor_top_margin() as u32, + config.scroll().width(), + config.scroll().margin_right(), ) }; + let editor_left_margin = match context { + UpdateContext::ParentPosition(p) => p.x() as u32, + _ => editor_left_margin as u32, + }; + self.dest.set_x(editor_left_margin.clone() as i32); + self.dest.set_width(width - editor_left_margin); + self.dest.set_height(height - editor_top_margin); + + self.vertical_scroll_bar + .set_full_size(self.full_rect.height()); + self.vertical_scroll_bar.set_viewport(self.dest.height()); + self.vertical_scroll_bar + .set_location(self.dest.width() as i32 - (scroll_width as i32 + scroll_margin)); + self.vertical_scroll_bar.update(ticks, context); + + self.horizontal_scroll_bar + .set_full_size(self.full_rect.width()); + self.horizontal_scroll_bar.set_viewport(self.dest.width()); + self.horizontal_scroll_bar + .set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin)); + self.horizontal_scroll_bar.update(ticks, context); + + self.caret.update(ticks, context); + match self.file_mut() { + Some(file) => file.update(ticks, context), + _ => UpdateResult::NoOp, + } + } + + fn on_left_click(&mut self, point: &Point, _context: &UpdateContext) -> UpdateResult { + let it = match self.file() { + Some(f) => f.iter_char(), + _ => return UpdateResult::NoOp, + }; + let scroll = self.scroll(); + let render_point = self.render_start_point(); + let moved_by = self.scroll().offset(render_point.x(), render_point.y()); + let scroll_context = UpdateContext::ScrolledBy(moved_by.clone()); + let mut target: Option<(Point, CaretPosition)> = None; + for char in it { + if char.is_left_click_target(point, &scroll_context) { + let position = CaretPosition::new(char.position(), char.line(), 0); + let point = char.dest().top_left(); + target = Some((point, position)); + break; + } + } + if let Some((point, position)) = target { + self.caret.move_caret(position, point); + } else { + self.set_caret_to_end_of_line( + self.resolve_line_from_point(&point.offset(-scroll.x(), -scroll.y())), + ); + } + UpdateResult::NoOp + } + + fn is_left_click_target(&self, point: &Point, _context: &UpdateContext) -> bool { + self.is_text_character_clicked(point) || self.is_editor_clicked(point) + } + + fn use_clipping(&self) -> bool { + true + } + + fn render(&self, canvas: &mut C, renderer: &mut R, _context: &RenderContext) + where + C: CanvasAccess, + R: Renderer + CharacterSizeManager + ConfigHolder, + { + if self.use_clipping() { + canvas.set_clipping(self.dest.clone()); + } + match self.file() { + Some(file) => file.render( + canvas, + renderer, + &RenderContext::ParentPosition(self.render_start_point() + self.scroll()), + ), + _ => (), + }; + self.caret.render( + canvas, + renderer, + &RenderContext::ParentPosition(self.render_start_point() + self.scroll()), + ); + self.vertical_scroll_bar + .render(canvas, &RenderContext::ParentPosition(self.dest.top_left())); + self.horizontal_scroll_bar + .render(canvas, &RenderContext::ParentPosition(self.dest.top_left())); + } + + fn prepare_ui(&mut self, renderer: &mut T) + where + T: Renderer + CharacterSizeManager + ConfigHolder, + { + if let Some(ref mut file) = self.file { + file.prepare_ui(renderer); + } + self.caret.prepare_ui(renderer); + } +} + +impl FileEditor { + pub fn new(config: ConfigAccess) -> Self { Self { - dest, + dest: { + let c = config.read().unwrap(); + Rect::new( + c.editor_left_margin(), + c.editor_top_margin(), + c.width() - c.editor_left_margin() as u32, + c.height() - c.editor_top_margin() as u32, + ) + }, full_rect: Rect::new(0, 0, 0, 0), caret: Caret::new(Arc::clone(&config)), vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)), @@ -115,14 +284,25 @@ impl FileEditor { } fn is_text_character_clicked(&self, point: &Point) -> bool { - let context = UpdateContext::ParentPosition(self.render_start_point()); - self.file() - .map_or(false, |file| file.is_left_click_target(point, &context)) + let file = match self.file() { + Some(f) => f, + _ => return false, + }; + let moved_by = self + .scroll() + .offset(self.render_start_point().x(), self.render_start_point().y()); + let scroll_context = UpdateContext::ScrolledBy(moved_by.clone()); + for char in file.iter_char() { + if char.is_left_click_target(point, &scroll_context) { + return true; + } + } + false } fn is_editor_clicked(&self, point: &Point) -> bool { - self.dest - .contains_point(move_render_point(point.clone(), &self.dest).top_left()) + self.dest() + .contains_point(move_render_point(point.clone(), self.dest()).top_left()) } fn resolve_line_from_point(&self, point: &Point) -> i32 { @@ -130,11 +310,12 @@ impl FileEditor { Some(f) => f, _ => return 0, }; - let mut y = point.y() - self.render_start_point().y(); - if y < 0 { - y = 0; + let y = point.y() - self.render_start_point().y(); + match (y, file.line_height()) { + (y, _) if y <= 0 => 0, + (_, 0) => 0, + (_, line_height) => y / (line_height as i32), } - y / (file.line_height() as i32) } } @@ -157,10 +338,6 @@ impl ScrollView for FileEditor { } impl FileAccess for FileEditor { - fn has_file(&self) -> bool { - self.file.is_some() - } - fn file(&self) -> Option<&EditorFile> { self.file.as_ref() } @@ -172,8 +349,8 @@ impl FileAccess for FileEditor { fn open_file(&mut self, file: EditorFile) -> Option { let new_path = file.path(); let mut file = Some(file); - let old_path = match self.file { - Some(ref f) => f.path(), + let old_path = match self.file() { + Some(f) => f.path(), _ => format!(""), }; mem::swap(&mut self.file, &mut file); @@ -196,10 +373,6 @@ impl FileAccess for FileEditor { None } } - - fn replace_current_file(&mut self, file: EditorFile) { - self.open_file(file); - } } impl CaretAccess for FileEditor { @@ -210,160 +383,6 @@ impl CaretAccess for FileEditor { fn caret_mut(&mut self) -> &mut Caret { &mut self.caret } - - fn move_caret(&mut self, dir: MoveDirection) { - match dir { - MoveDirection::Left => caret_manager::move_caret_left(self), - MoveDirection::Right => caret_manager::move_caret_right(self), - MoveDirection::Up => caret_manager::move_caret_up(self), - MoveDirection::Down => caret_manager::move_caret_down(self), - }; - } - - fn set_caret_to_end_of_line(&mut self, line: i32) { - let file = match self.file_mut() { - Some(f) => f, - _ => return, - }; - let mut line = line; - while line >= 0 { - match file.get_last_at_line(line.clone() as usize) { - Some(text_character) => { - let rect = text_character.dest(); - let position = - CaretPosition::new(text_character.position() + 1, line as usize, 0); - let p = if text_character.is_new_line() { - file.get_character_at(text_character.position() + 1) - .map_or_else( - || text_character.dest().top_left(), - |tc| tc.dest().top_left(), - ) - } else { - rect.top_right() - }; - self.caret.move_caret(position, p); - break; - } - _ => { - line -= 1; - } - } - } - } -} - -impl FileEditor { - pub fn render(&self, canvas: &mut C, renderer: &mut R) - where - R: Renderer + ConfigHolder, - C: CanvasAccess, - { - canvas.set_clipping(self.dest.clone()); - match self.file() { - Some(file) => file.render( - canvas, - renderer, - &RenderContext::ParentPosition(self.render_start_point()), - ), - _ => (), - }; - self.caret.render( - canvas, - &RenderContext::ParentPosition(self.render_start_point()), - ); - self.vertical_scroll_bar - .render(canvas, &RenderContext::ParentPosition(self.dest.top_left())); - self.horizontal_scroll_bar - .render(canvas, &RenderContext::ParentPosition(self.dest.top_left())); - } - - pub fn prepare_ui(&mut self, renderer: &mut T) - where - T: CharacterSizeManager, - { - self.caret.prepare_ui(renderer); - } -} - -impl Update for FileEditor { - fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { - let (width, height, editor_left_margin, editor_top_margin, scroll_width, scroll_margin) = { - let config: RwLockReadGuard = self.config.read().unwrap(); - ( - config.width(), - config.height(), - config.editor_left_margin() as u32, - config.editor_top_margin() as u32, - config.scroll().width(), - config.scroll().margin_right(), - ) - }; - let editor_left_margin = match context { - UpdateContext::ParentPosition(p) => p.x() as u32, - _ => editor_left_margin as u32, - }; - self.dest.set_x(editor_left_margin.clone() as i32); - self.dest.set_width(width - editor_left_margin); - self.dest.set_height(height - editor_top_margin); - - self.vertical_scroll_bar - .set_full_size(self.full_rect.height()); - self.vertical_scroll_bar.set_viewport(self.dest.height()); - self.vertical_scroll_bar - .set_location(self.dest.width() as i32 - (scroll_width as i32 + scroll_margin)); - self.vertical_scroll_bar.update(ticks, context); - - self.horizontal_scroll_bar - .set_full_size(self.full_rect.width()); - self.horizontal_scroll_bar.set_viewport(self.dest.width()); - self.horizontal_scroll_bar - .set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin)); - self.horizontal_scroll_bar.update(ticks, context); - - self.caret.update(); - match self.file_mut() { - Some(file) => file.update(ticks, context), - _ => UR::NoOp, - } - } -} - -impl ClickHandler for FileEditor { - fn on_left_click(&mut self, point: &Point, _context: &UpdateContext) -> UR { - let context = UpdateContext::ParentPosition(self.render_start_point()); - - if self.is_text_character_clicked(point) { - let file = if let Some(file) = self.file_mut() { - file - } else { - return UR::NoOp; - }; - match file.on_left_click(point, &context) { - UR::MoveCaret(rect, position) => { - self.caret - .move_caret(position, Point::new(rect.x(), rect.y())); - } - _ => {} - } - } else { - self.set_caret_to_end_of_line(self.resolve_line_from_point(point)); - } - UR::NoOp - } - - fn is_left_click_target(&self, point: &Point, _context: &UpdateContext) -> bool { - self.is_text_character_clicked(point) || self.is_editor_clicked(point) - } -} - -impl RenderBox for FileEditor { - fn render_start_point(&self) -> Point { - self.dest.top_left() + self.scroll() - } - - fn dest(&self) -> Rect { - self.dest.clone() - } } impl ConfigHolder for FileEditor { @@ -374,57 +393,15 @@ impl ConfigHolder for FileEditor { #[cfg(test)] mod tests { + use crate::app::UpdateResult; + use crate::tests::*; use crate::ui::*; - use rider_config::Config; - use std::sync::*; - - #[test] - fn replace_file() { - let config = Arc::new(RwLock::new(Config::new())); - let mut editor = FileEditor::new(Arc::clone(&config)); - let first_file = - EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone()); - let second_file = - EditorFile::new("./bar.txt".to_string(), "bar".to_string(), config.clone()); - editor.open_file(first_file.clone()); - let result = editor.open_file(second_file.clone()); - assert_eq!(result.is_some(), true); - let file = result.as_ref().unwrap(); - assert_eq!(file.path(), first_file.path()); - assert_eq!(file.buffer(), first_file.buffer()); - } -} - -#[cfg(test)] -mod test_config_holder { - use crate::app::*; - use crate::tests::support; - use crate::ui::*; - use std::sync::*; - - #[test] - fn assert_config() { - let config = support::build_config(); - let widget = FileEditor::new(Arc::clone(&config)); - let result = widget.config(); - { - let mut w = config.write().unwrap(); - w.set_height(1240); - w.set_width(1024); - } - let local = config.read().unwrap(); - let widget_config = result.read().unwrap(); - assert_eq!(widget_config.width(), local.width()); - assert_eq!(widget_config.height(), local.height()); - } -} - -#[cfg(test)] -mod test_render_box { - use crate::tests::support; - use crate::ui::*; + use rider_config::{Config, ConfigHolder}; + use rider_derive::*; use sdl2::rect::{Point, Rect}; + use std::sync::*; + #[cfg_attr(tarpaulin, skip)] impl FileEditor { pub fn set_full_rect(&mut self, r: Rect) { self.full_rect = r; @@ -435,9 +412,19 @@ mod test_render_box { } } + // Widget + + #[test] + fn assert_texture_path() { + let config = build_config(); + let widget = FileEditor::new(config); + let result = widget.texture_path(); + assert!(result.is_none()); + } + #[test] fn assert_dest() { - let config = support::build_config(); + let config = build_config(); let (x, y, mw, mh) = { let c = config.read().unwrap(); ( @@ -454,23 +441,300 @@ mod test_render_box { } #[test] - fn assert_render_start_point() { - let config = support::build_config(); - let (x, y, ss) = { - let c = config.read().unwrap(); - ( - c.editor_left_margin(), - c.editor_top_margin(), - c.scroll().speed(), - ) - }; + fn assert_set_dest() { + let config = build_config(); let mut widget = FileEditor::new(config); - widget.set_dest(Rect::new(x.clone(), y.clone(), 999, 999)); - widget.set_full_rect(Rect::new(0, 0, 99999, 99999)); - widget.update(1, &UpdateContext::Nothing); - widget.scroll_by(30, 40); - let result = widget.render_start_point().clone(); - let expected = Point::new(x - (ss * 30), y - (ss * 40)); - assert_eq!(result, expected); + widget.set_dest(Rect::new(100, 200, 300, 400)); + assert_eq!(widget.dest(), &Rect::new(100, 200, 300, 400)); + } + + #[test] + fn assert_source() { + let config = build_config(); + let widget = FileEditor::new(config); + let result = widget.source(); + assert_eq!(result, &Rect::new(10, 50, 1014, 810)); + } + + #[test] + fn assert_set_source() { + let config = build_config(); + let mut widget = FileEditor::new(config); + widget.set_source(&Rect::new(1, 2, 3, 4)); + assert_ne!(widget.source(), &Rect::new(1, 2, 3, 4)); + } + + #[test] + fn assert_update() { + let config = build_config(); + let mut widget = FileEditor::new(config); + let result = widget.update(0, &UpdateContext::Nothing); + assert_eq!(result, UpdateResult::NoOp); + } + + #[test] + fn assert_on_left_click() { + let config = build_config(); + let mut widget = FileEditor::new(config); + let result = widget.on_left_click(&Point::new(600, 800), &UpdateContext::Nothing); + assert_eq!(result, UpdateResult::NoOp); + } + + #[test] + fn assert_is_left_click_target() { + let config = build_config(); + let widget = FileEditor::new(config); + let result = widget.is_left_click_target(&Point::new(600, 800), &UpdateContext::Nothing); + assert_eq!(result, true); + } + + #[test] + fn assert_use_clipping() { + let config = build_config(); + let widget = FileEditor::new(config); + let result = widget.use_clipping(); + assert_eq!(result, true); + } + + #[test] + fn assert_render() { + build_test_renderer!(renderer); + let mut canvas = CanvasMock::new(); + let widget = FileEditor::new(config); + let result = widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + assert_eq!(result, ()); + } + + #[test] + fn assert_prepare_ui() { + build_test_renderer!(renderer); + let mut widget = FileEditor::new(config); + let result = widget.prepare_ui(&mut renderer); + assert_eq!(result, ()); + } + + // File manipulation + + #[test] + fn assert_has_file() { + let config = build_config(); + let widget = FileEditor::new(config); + assert_eq!(widget.has_file(), false); + } + + #[test] + fn drop_file_when_no_file_is_selected() { + let config = build_config(); + let mut widget = FileEditor::new(config); + let result = widget.drop_file(); + assert!(result.is_none()); + } + + #[test] + fn drop_file_when_file_is_selected() { + let config = build_config(); + let mut widget = FileEditor::new(config.clone()); + let file = EditorFile::new( + "/tmp/drop_file_when_file_is_selected".to_owned(), + "foo bar".to_owned(), + config, + ); + widget.open_file(file); + let result = widget.drop_file(); + assert!(result.is_some()) + } + + #[test] + fn mut_file_when_no_file_is_selected() { + let config = build_config(); + let mut widget = FileEditor::new(config); + let result = widget.file_mut(); + assert!(result.is_none()); + } + + #[test] + fn mut_file_when_file_is_selected() { + let config = build_config(); + let mut widget = FileEditor::new(config.clone()); + let file = EditorFile::new( + "/tmp/drop_file_when_file_is_selected".to_owned(), + "foo bar".to_owned(), + config, + ); + widget.open_file(file); + let result = widget.file_mut(); + assert!(result.is_some()) + } + + #[test] + fn file_when_no_file_is_selected() { + let config = build_config(); + let widget = FileEditor::new(config); + let result = widget.file(); + assert!(result.is_none()); + } + + #[test] + fn file_when_file_is_selected() { + let config = build_config(); + let mut widget = FileEditor::new(config.clone()); + let file = EditorFile::new( + "/tmp/drop_file_when_file_is_selected".to_owned(), + "foo bar".to_owned(), + config, + ); + widget.open_file(file); + let result = widget.file(); + assert!(result.is_some()) + } + + #[test] + fn replace_file() { + let config = Arc::new(RwLock::new(Config::new())); + let mut editor = FileEditor::new(Arc::clone(&config)); + let first_file = + EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone()); + let second_file = + EditorFile::new("./bar.txt".to_string(), "bar".to_string(), config.clone()); + editor.open_file(first_file.clone()); + let result = editor.open_file(second_file.clone()); + assert_eq!(result.is_some(), true); + let file = result.as_ref().unwrap(); + assert_eq!(file.path(), first_file.path()); + assert_eq!(file.buffer(), first_file.buffer()); + } + + // config + + #[test] + fn assert_config() { + let config = build_config(); + let widget = FileEditor::new(config.clone()); + assert!(widget + .config() + .write() + .and_then(|ref mut w| { + w.set_height(1240); + w.set_width(1024); + Ok(()) + }) + .is_ok()); + let local: (u32, u32) = config + .read() + .map_or_else(|_| (0, 0), |ref w| (w.width(), w.height())); + let widget_config: (u32, u32) = widget + .config() + .read() + .map_or_else(|_| (0, 0), |ref w| (w.width(), w.height())); + assert_eq!(widget_config, local); + } +} + +#[cfg(test)] +mod own_methods_tests { + use crate::tests::*; + use crate::ui::{CaretAccess, EditorFile, FileAccess, FileEditor}; + use rider_config::ConfigAccess; + use rider_derive::*; + use sdl2::rect::Point; + + fn build_testable_file(buffer: S, config: ConfigAccess) -> EditorFile + where + S: Into + Sized, + { + EditorFile::new( + "/tmp/drop_file_when_file_is_selected".to_owned(), + buffer.into(), + config, + ) + } + + #[test] + fn delete_front() { + build_test_renderer!(renderer); + let mut widget = FileEditor::new(config.clone()); + widget.open_file(build_testable_file("foo bar", config)); + widget.caret_mut().set_text_position(1); + widget.delete_front(&mut renderer); + let buffer = widget + .file() + .map_or_else(|| "".to_owned(), |ref f| f.buffer()); + assert_eq!(buffer, "oo bar".to_owned()); + } + + #[test] + fn delete_back() { + build_test_renderer!(renderer); + let mut widget = FileEditor::new(config.clone()); + widget.open_file(build_testable_file("foo bar", config)); + widget.caret_mut().set_text_position(6); + widget.delete_back(&mut renderer); + let buffer = widget + .file() + .map_or_else(|| "".to_owned(), |ref f| f.buffer()); + assert_eq!(buffer, "foo ba".to_owned()); + } + + #[test] + fn insert_text() { + build_test_renderer!(renderer); + let mut widget = FileEditor::new(config.clone()); + widget.open_file(build_testable_file("foo bar", config)); + widget.insert_text("hello world ".to_owned(), &mut renderer); + let buffer = widget + .file() + .map_or_else(|| "".to_owned(), |ref f| f.buffer()); + assert_eq!(buffer, "hello world foo bar".to_owned()); + } + + #[test] + fn insert_new_line() { + build_test_renderer!(renderer); + let mut widget = FileEditor::new(config.clone()); + widget.open_file(build_testable_file("foo bar", config)); + assert!(widget.insert_new_line(&mut renderer).is_ok()); + let buffer = widget + .file() + .map_or_else(|| "".to_owned(), |ref f| f.buffer()); + assert_eq!(buffer, "\nfoo bar".to_owned()); + } + + #[test] + fn delete_current_line() { + build_test_renderer!(renderer); + let mut widget = FileEditor::new(config.clone()); + widget.open_file(build_testable_file("foo bar\nfoz baz", config)); + assert!(widget.delete_current_line(&mut renderer).is_ok()); + let buffer = widget + .file() + .map_or_else(|| "".to_owned(), |ref f| f.buffer()); + assert_eq!(buffer, "foz baz"); + } + + #[test] + fn is_text_character_clicked() { + let config = build_config(); + let mut widget = FileEditor::new(config.clone()); + widget.open_file(build_testable_file("foo bar", config)); + assert_eq!( + widget.is_text_character_clicked(&Point::new(1000, 1000)), + false + ); + } + + #[test] + fn is_editor_clicked() { + let config = build_config(); + let mut widget = FileEditor::new(config.clone()); + widget.open_file(build_testable_file("foo bar", config)); + assert_eq!(widget.is_editor_clicked(&Point::new(1000, 1000)), false); + } + + #[test] + fn resolve_line_from_point() { + let config = build_config(); + let mut widget = FileEditor::new(config.clone()); + widget.open_file(build_testable_file("foo bar", config)); + assert_eq!(widget.resolve_line_from_point(&Point::new(100, 100)), 0); } } diff --git a/rider-editor/src/ui/filesystem/directory.rs b/rider-editor/src/ui/filesystem/directory.rs index 577c6d5..8f7f215 100644 --- a/rider-editor/src/ui/filesystem/directory.rs +++ b/rider-editor/src/ui/filesystem/directory.rs @@ -155,7 +155,7 @@ impl Widget for DirectoryView { fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, C: CanvasAccess, { let mut dest = move_render_point( @@ -185,7 +185,7 @@ impl Widget for DirectoryView { fn prepare_ui(&mut self, renderer: &mut R) where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { let size = renderer.load_character_size('W'); self.icon.prepare_ui(renderer); @@ -271,7 +271,7 @@ impl DirectoryView { pub fn open_directory(&mut self, dir_path: String, renderer: &mut R) -> bool where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { match dir_path { _ if dir_path == self.path => { @@ -343,7 +343,7 @@ impl DirectoryView { fn read_directory(&mut self, renderer: &mut R) where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { let entries: fs::ReadDir = match fs::read_dir(self.path.clone()) { Ok(d) => d, @@ -387,7 +387,7 @@ impl DirectoryView { fn render_children(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect) where C: CanvasAccess, - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { if !self.expanded { return; @@ -447,10 +447,9 @@ impl ConfigHolder for DirectoryView { #[cfg(test)] mod tests { use super::*; - use crate::tests::support::CanvasMock; - use crate::tests::support::SimpleRendererMock; - use crate::tests::support::{build_config, build_path}; + use crate::tests::*; use crate::ui::Widget; + use rider_derive::*; //########################################################## // name_width @@ -465,8 +464,7 @@ mod tests { #[test] fn assert_prepared_name_width() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.name_width(), 39); @@ -485,8 +483,7 @@ mod tests { #[test] fn assert_prepared_icon_width() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.icon_width(), 14); @@ -505,8 +502,7 @@ mod tests { #[test] fn assert_prepared_height() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.height(), 14); @@ -525,8 +521,7 @@ mod tests { #[test] fn assert_prepared_name() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.name(), "foo".to_owned()); @@ -545,8 +540,7 @@ mod tests { #[test] fn assert_prepared_path() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.path(), "/foo".to_owned()); @@ -565,8 +559,7 @@ mod tests { #[test] fn assert_prepared_source() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.source(), &Rect::new(0, 0, 64, 64)); @@ -585,21 +578,37 @@ mod tests { #[test] fn assert_prepared_dest() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.dest(), &Rect::new(0, 0, 53, 14)); } + #[test] + fn assert_set_dest() { + let config = build_config(); + let mut widget = DirectoryView::new("/foo".to_owned(), config); + let rect = Rect::new(1, 2, 3, 4); + widget.set_dest(&rect); + assert_ne!(widget.dest(), &rect); + } + + #[test] + fn assert_set_source() { + let config = build_config(); + let mut widget = DirectoryView::new("/foo".to_owned(), config); + let rect = Rect::new(1, 2, 3, 4); + widget.set_source(&rect); + assert_ne!(widget.source(), &rect); + } + //########################################################## // update //########################################################## #[test] fn assert_update_when_doesnt_exists() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!( @@ -610,8 +619,7 @@ mod tests { #[test] fn assert_update_when_does_exists() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/tmp".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!( @@ -624,8 +632,7 @@ mod tests { fn assert_update_expanded() { build_path("/tmp/rider-editor/directory-view-test".to_owned()); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -648,8 +655,7 @@ mod tests { fn assert_render_no_expanded() { build_path("/tmp/rider-editor/directory-view-test".to_owned()); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); @@ -661,8 +667,7 @@ mod tests { fn assert_render_expanded() { build_path("/tmp/rider-editor/directory-view-test".to_owned()); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); @@ -683,8 +688,7 @@ mod tests { fn assert_is_left_click_target_when_target() { build_path("/tmp/rider-editor/directory-view-test".to_owned()); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -696,8 +700,7 @@ mod tests { #[test] fn assert_is_left_click_target_when_target_with_parent() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -711,8 +714,7 @@ mod tests { fn assert_is_left_click_target_expanded() { build_path("/tmp/rider-editor/directory-view-test".to_owned()); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); @@ -730,8 +732,7 @@ mod tests { #[test] fn refute_is_left_click_target_when_target() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -743,8 +744,7 @@ mod tests { #[test] fn refute_is_left_click_target_when_target_with_parent() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -762,8 +762,7 @@ mod tests { fn assert_on_left_click_when_target() { build_path("/tmp/rider-editor/directory-view-test".to_owned()); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); @@ -781,8 +780,7 @@ mod tests { fn assert_on_left_click_when_target_with_parent() { build_path("/tmp/rider-editor/directory-view-test".to_owned()); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); @@ -800,8 +798,7 @@ mod tests { fn assert_on_left_click_expanded() { build_path("/tmp/rider-editor/directory-view-test".to_owned()); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); @@ -812,6 +809,7 @@ mod tests { ); widget.prepare_ui(&mut renderer); widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + widget.expanded = true; let p = Point::new(0, 0); let context = UpdateContext::ParentPosition(Point::new(0, 0)); assert_eq!( @@ -819,4 +817,64 @@ mod tests { UpdateResult::OpenDirectory("/tmp/rider-editor/directory-view-test".to_owned()) ); } + + #[test] + fn assert_on_left_click_not_expanded() { + build_path("/tmp/rider-editor/directory-view-test".to_owned()); + + build_test_renderer!(renderer); + let mut canvas = CanvasMock::new(); + let mut widget = + DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); + widget.prepare_ui(&mut renderer); + widget.open_directory("/tmp".to_owned(), &mut renderer); + widget.open_directory("/tmp/rider-editor".to_owned(), &mut renderer); + widget.open_directory( + "/tmp/rider-editor/directory-view-test".to_owned(), + &mut renderer, + ); + widget.prepare_ui(&mut renderer); + widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + widget.expanded = false; + let p = Point::new(0, 0); + let context = UpdateContext::ParentPosition(Point::new(0, 0)); + assert_eq!( + widget.on_left_click(&p, &context), + UpdateResult::OpenDirectory("/tmp/rider-editor/directory-view-test".to_owned()) + ); + } + + #[test] + fn assert_on_left_click_expanded_with_sub() { + build_path("/tmp/rider-editor/directory-view-test/foo/bar".to_owned()); + + build_test_renderer!(renderer); + let mut widget = + DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); + widget.prepare_ui(&mut renderer); + widget.open_directory("/tmp".to_owned(), &mut renderer); + widget.open_directory("/tmp/rider-editor".to_owned(), &mut renderer); + widget.prepare_ui(&mut renderer); + widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + + let p = Point::new(0, 0); + let context = UpdateContext::ParentPosition(Point::new(0, 0)); + assert_eq!( + widget.on_left_click(&p, &context), + UpdateResult::OpenDirectory("/tmp/rider-editor/directory-view-test".to_owned()) + ); + } + + #[test] + fn check_config() { + build_path("/tmp/rider-editor/directory-view-test".to_owned()); + + build_test_renderer!(renderer); + let mut canvas = CanvasMock::new(); + let mut widget = + DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config); + widget.prepare_ui(&mut renderer); + widget.config(); + assert!(true) + } } diff --git a/rider-editor/src/ui/filesystem/file.rs b/rider-editor/src/ui/filesystem/file.rs index 46de503..e98f258 100644 --- a/rider-editor/src/ui/filesystem/file.rs +++ b/rider-editor/src/ui/filesystem/file.rs @@ -62,7 +62,7 @@ impl Widget for FileEntry { fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where C: CanvasAccess, - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { let dest = match context { &RenderContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()), @@ -82,7 +82,7 @@ impl Widget for FileEntry { fn prepare_ui(&mut self, renderer: &mut R) where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { let rect = renderer.load_character_size('W'); self.icon.prepare_ui(renderer); @@ -186,9 +186,9 @@ impl ConfigHolder for FileEntry { #[cfg(test)] mod tests { use super::*; - use crate::tests::support::build_config; - use crate::tests::support::CanvasMock; - use crate::tests::support::SimpleRendererMock; + use crate::tests::*; + use rider_derive::*; + use crate::ui::{UpdateContext, Widget}; //########################################################## @@ -204,8 +204,7 @@ mod tests { #[test] fn assert_prepared_name_width() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.name_width(), 91); @@ -224,8 +223,7 @@ mod tests { #[test] fn assert_prepared_icon_width() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.icon_width(), 14); @@ -244,8 +242,7 @@ mod tests { #[test] fn assert_prepared_height() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.height(), 16); @@ -264,8 +261,7 @@ mod tests { #[test] fn assert_prepared_name() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.name(), "bar.txt".to_owned()); @@ -284,8 +280,7 @@ mod tests { #[test] fn assert_prepared_path() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.path(), "/foo".to_owned()); @@ -304,8 +299,7 @@ mod tests { #[test] fn assert_prepared_source() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.source(), &Rect::new(0, 0, 64, 64)); @@ -324,8 +318,7 @@ mod tests { #[test] fn assert_prepared_dest() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.dest(), &Rect::new(0, 0, 105, 16)); @@ -344,8 +337,7 @@ mod tests { #[test] fn assert_prepared_full_dest() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!(widget.full_dest(), Rect::new(0, 0, 125, 16)); @@ -357,8 +349,7 @@ mod tests { #[test] fn assert_update_when_doesnt_exists() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!( @@ -369,8 +360,7 @@ mod tests { #[test] fn assert_update_when_does_exists() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = FileEntry::new("bar.txt".to_owned(), "/tmp".to_owned(), config); widget.prepare_ui(&mut renderer); assert_eq!( @@ -385,8 +375,7 @@ mod tests { #[test] fn assert_render() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -400,8 +389,7 @@ mod tests { #[test] fn assert_is_left_click_target_when_target() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -413,8 +401,7 @@ mod tests { #[test] fn assert_is_left_click_target_when_target_with_parent() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -426,8 +413,7 @@ mod tests { #[test] fn refute_is_left_click_target_when_target() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); @@ -439,8 +425,7 @@ mod tests { #[test] fn refute_is_left_click_target_when_target_with_parent() { - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); diff --git a/rider-editor/src/ui/icon.rs b/rider-editor/src/ui/icon.rs index 96091f9..85d5727 100644 --- a/rider-editor/src/ui/icon.rs +++ b/rider-editor/src/ui/icon.rs @@ -70,13 +70,13 @@ impl Widget for Icon { mod test { use super::*; use crate::app::UpdateResult; - use crate::tests::support; + use crate::tests::*; use crate::ui::UpdateContext; use sdl2::rect::Point; #[test] fn must_return_noop_on_left_click() { - let config = support::build_config(); + let config = build_config(); let dest = Rect::new(0, 10, 20, 30); let src = Rect::new(40, 50, 60, 70); let path = "/foo/bar.png".to_owned(); @@ -89,7 +89,7 @@ mod test { #[test] fn must_use_inner() { - let config = support::build_config(); + let config = build_config(); let dest = Rect::new(0, 10, 20, 30); let src = Rect::new(40, 50, 60, 70); let path = "/foo/bar.png".to_owned(); diff --git a/rider-editor/src/ui/label.rs b/rider-editor/src/ui/label.rs index 374bb68..79b5bc7 100644 --- a/rider-editor/src/ui/label.rs +++ b/rider-editor/src/ui/label.rs @@ -54,7 +54,7 @@ impl Widget for Label { fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where C: CanvasAccess, - R: Renderer, + R: Renderer + CharacterSizeManager + ConfigHolder, { let dest = match context { &RenderContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest), @@ -62,7 +62,9 @@ impl Widget for Label { }; let mut d = dest.clone(); d.set_x(dest.x() + NAME_MARGIN); - canvas.set_clipping(d.clone()); + if self.use_clipping() { + canvas.set_clipping(d.clone()); + } let font_details = build_font_details(self); for c in self.name.chars() { @@ -147,13 +149,14 @@ impl ConfigHolder for Label { mod test { use super::*; use crate::app::UpdateResult; - use crate::tests::support; + use crate::tests::*; use crate::ui::{UpdateContext, Widget}; + use rider_derive::*; use sdl2::rect::Point; #[test] fn must_return_noop_on_left_click() { - let config = support::build_config(); + let config = build_config(); let name = "Hello world".to_owned(); let mut widget = Label::new(name, config); assert_eq!( @@ -164,8 +167,7 @@ mod test { #[test] fn must_use_inner() { - let config = support::build_config(); - let mut renderer = support::SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let name = "Hello world".to_owned(); let mut widget = Label::new(name.clone(), config); let dest = Rect::new(0, 0, DEST_WIDTH, DEST_HEIGHT); diff --git a/rider-editor/src/ui/menu_bar.rs b/rider-editor/src/ui/menu_bar.rs index 224c703..04f7c6e 100644 --- a/rider-editor/src/ui/menu_bar.rs +++ b/rider-editor/src/ui/menu_bar.rs @@ -1,12 +1,12 @@ use crate::app::UpdateResult as UR; use crate::renderer::Renderer; use crate::ui::*; -use rider_config::ConfigAccess; +use rider_config::{ConfigAccess, ConfigHolder}; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; -const SAVE_BUTTON_OFFSET_LEFT: i32 = 16; -const SAVE_BUTTON_OFFSET_TOP: i32 = 10; +pub const SAVE_BUTTON_OFFSET_LEFT: i32 = 16; +pub const SAVE_BUTTON_OFFSET_TOP: i32 = 10; pub struct MenuBar { border_color: Color, @@ -45,7 +45,7 @@ impl MenuBar { pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where C: CanvasAccess, - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { use std::borrow::*; @@ -60,7 +60,7 @@ impl MenuBar { move_render_point(relative_position.clone(), &self.dest), self.background_color.clone(), ) - .unwrap_or_else(|_| panic!("Failed to draw main menu background")); + .expect("Failed to draw main menu background"); canvas .render_border( match context.borrow() { @@ -69,7 +69,7 @@ impl MenuBar { }, self.border_color.clone(), ) - .unwrap_or_else(|_| panic!("Failed to draw main menu background")); + .expect("Failed to draw main menu background"); self.save_button.render( canvas, @@ -158,7 +158,7 @@ mod test_getters { #[test] fn assert_background_color() { - let config = support::build_config(); + let config = build_config(); let widget = MenuBar::new(Arc::clone(&config)); let result = widget.background_color().clone(); let expected = Color::RGBA(18, 18, 18, 0); @@ -167,7 +167,7 @@ mod test_getters { #[test] fn assert_dest() { - let config = support::build_config(); + let config = build_config(); let (w, h) = { let c = config.read().unwrap(); (c.width() as u32, c.menu_height() as u32) @@ -188,7 +188,7 @@ mod test_render_box { #[test] fn must_return_top_left_point() { - let config = support::build_config(); + let config = build_config(); let widget = MenuBar::new(Arc::clone(&config)); let result = widget.render_start_point(); let expected = Point::new(0, 0); @@ -206,7 +206,7 @@ mod test_click_handler { #[test] fn refute_when_not_click_target() { - let config = support::build_config(); + let config = build_config(); let widget = MenuBar::new(Arc::clone(&config)); let point = Point::new(9999, 9999); let context = UpdateContext::Nothing; @@ -216,7 +216,7 @@ mod test_click_handler { #[test] fn assert_when_click_target() { - let config = support::build_config(); + let config = build_config(); let widget = MenuBar::new(Arc::clone(&config)); let point = Point::new(20, 30); let context = UpdateContext::Nothing; @@ -226,7 +226,7 @@ mod test_click_handler { #[test] fn refute_when_not_click_target_because_parent() { - let config = support::build_config(); + let config = build_config(); let widget = MenuBar::new(Arc::clone(&config)); let point = Point::new(20, 30); let context = UpdateContext::ParentPosition(Point::new(9999, 9999)); @@ -236,7 +236,7 @@ mod test_click_handler { #[test] fn assert_when_click_target_because_parent() { - let config = support::build_config(); + let config = build_config(); let (w, h) = { ( config.read().unwrap().width(), @@ -252,7 +252,7 @@ mod test_click_handler { #[test] fn assert_on_click_do_nothing() { - let config = support::build_config(); + let config = build_config(); let mut widget = MenuBar::new(Arc::clone(&config)); let point = Point::new(12, 34); let context = UpdateContext::ParentPosition(Point::new(678, 293)); @@ -264,38 +264,51 @@ mod test_click_handler { #[cfg(test)] mod test_render { - use crate::tests::support::SimpleRendererMock; + use crate::app::UpdateResult; use crate::tests::*; use crate::ui::*; - use sdl2::rect::Rect; + use rider_derive::*; + use rider_match_widget::*; + use sdl2::pixels::PixelFormatEnum; #[test] - fn assert_render() { - let rect_color = sdl2::pixels::Color::RGBA(18, 18, 18, 0); - let border_color = sdl2::pixels::Color::RGBA(200, 200, 200, 0); - let context = RenderContext::Nothing; - let config = support::build_config(); - let mut canvas = support::CanvasMock::new(); - let mut renderer = SimpleRendererMock::new(config.clone()); + #[match_widgets( + fn_name = "assert_save_button_child", + widget = "MenuBar", + contains = "save_button", + x = 16i32, + y = 10i32, + w = 16u32, + h = 16u32, + widget_dump_path = "/tmp/rider-images/MenuBar-saveButton.png", + child_dump_path = "/tmp/rider-images/SaveButton.png", + background_from = "background_color", + widget_variable = "widget" + )] + fn assert_save_button_child() {} + + #[test] + #[match_widgets( + fn_name = "assert_settings_button", + widget = "MenuBar", + contains = "settings_button", + x = 32i32, + y = 10i32, + w = 16u32, + h = 16u32, + widget_dump_path = "/tmp/rider-images/MenuBar-settingsButton.png", + child_dump_path = "/tmp/rider-images/SettingsButton.png", + background_from = "background_color", + widget_variable = "widget" + )] + fn assert_settings_button() {} + + #[test] + fn assert_update() { + build_test_renderer!(renderer); let mut widget = MenuBar::new(config.clone()); widget.prepare_ui(); - widget.render(&mut canvas, &mut renderer, &context); - assert_eq!(widget.dest(), Rect::new(0, 0, 1024, 40)); - assert_eq!( - canvas.find_rect_with_color(Rect::new(0, 0, 1024, 40), rect_color.clone()), - Some(&support::RendererRect::new( - Rect::new(0, 0, 1024, 40), - rect_color, - support::CanvasShape::Rectangle - )) - ); - assert_eq!( - canvas.find_border_with_color(Rect::new(0, 0, 1024, 40), border_color.clone()), - Some(&support::RendererRect::new( - Rect::new(0, 0, 1024, 40), - border_color, - support::CanvasShape::Border - )) - ); + let result = widget.update(0, &UpdateContext::Nothing); + assert_eq!(result, UpdateResult::NoOp); } } diff --git a/rider-editor/src/ui/mod.rs b/rider-editor/src/ui/mod.rs index 0e8e5a6..4def244 100644 --- a/rider-editor/src/ui/mod.rs +++ b/rider-editor/src/ui/mod.rs @@ -39,6 +39,7 @@ pub enum UpdateContext<'l> { Nothing, ParentPosition(Point), CurrentFile(&'l mut EditorFile), + ScrolledBy(Point), } #[derive(Clone, PartialEq, Debug)] @@ -47,6 +48,7 @@ pub enum RenderContext { ParentPosition(Point), } +#[cfg_attr(tarpaulin, skip)] pub trait CanvasAccess { fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>; fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>; @@ -63,6 +65,7 @@ pub trait CanvasAccess { fn clip_rect(&self) -> Option; } +#[cfg_attr(tarpaulin, skip)] impl CanvasAccess for WindowCanvas { fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> { self.set_draw_color(color); @@ -101,33 +104,29 @@ impl CanvasAccess for WindowCanvas { } } -#[inline] -pub fn is_in_rect(point: &Point, rect: &Rect) -> bool { - rect.contains_point(point.clone()) -} - #[inline] pub fn build_font_details(config_holder: &T) -> FontDetails where T: ConfigHolder, { let c = config_holder.config().read().unwrap(); - FontDetails::new( - c.editor_config().font_path().as_str(), + ( + c.editor_config().font_path(), c.editor_config().character_size(), ) + .into() } +#[cfg_attr(tarpaulin, skip)] pub fn get_text_character_rect<'l, T>(c: char, renderer: &mut T) -> Option where - T: ManagersHolder<'l> + ConfigHolder, + T: Renderer + ConfigHolder, { let font_details = renderer.config().read().unwrap().editor_config().into(); renderer - .font_manager() - .load(&font_details) + .load_font(font_details) + .size_of_char(c) .ok() - .and_then(|font| font.size_of_char(c).ok()) .and_then(|(width, height)| Some(Rect::new(0, 0, width, height))) } @@ -189,7 +188,9 @@ pub trait Widget { fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { match *context { - UpdateContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest()), + UpdateContext::ParentPosition(p) | UpdateContext::ScrolledBy(p) => { + move_render_point(p.clone(), &self.dest()) + } _ => self.dest().clone(), } .contains_point(point.clone()) @@ -216,17 +217,24 @@ pub trait Widget { 0 } + fn use_clipping(&self) -> bool { + false + } + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where C: CanvasAccess, - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { let mut dest = match context { &RenderContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest()), _ => self.dest().clone(), }; - canvas.set_clipping(self.clipping(&dest)); + if self.use_clipping() { + canvas.set_clipping(self.clipping(&dest)); + } + self.texture_path() .and_then(|path| renderer.load_image(path).ok()) .and_then(|texture| { @@ -234,14 +242,14 @@ pub trait Widget { dest.set_height(self.dest().height()); canvas .render_image(texture.clone(), self.source().clone(), dest.clone()) - .unwrap_or_else(|_| panic!("Failed to draw widget texture")); + .unwrap_or_else(|e| panic!("Failed to draw widget texture. {}", e)); Some(()) }); } fn prepare_ui<'l, T>(&mut self, _renderer: &mut T) where - T: Renderer + CharacterSizeManager, + T: Renderer + CharacterSizeManager + ConfigHolder, { } } @@ -249,7 +257,9 @@ pub trait Widget { #[cfg(test)] mod tests { use super::*; - use crate::tests::support; + use crate::tests::*; + use rider_derive::*; + use std::ops::{Deref, DerefMut}; struct ConfigWrapper { pub inner: ConfigAccess, @@ -261,18 +271,52 @@ mod tests { } } - #[test] - fn must_return_true_if_inside_rect() { - let rect = Rect::new(10, 10, 30, 30); - let point = Point::new(20, 20); - assert_eq!(is_in_rect(&point, &rect), true); + struct Dummy { + pub inner: WidgetInner, } - #[test] - fn must_return_not_if_not_inside_rect() { - let rect = Rect::new(10, 10, 30, 30); - let point = Point::new(41, 41); - assert_eq!(is_in_rect(&point, &rect), false); + impl Dummy { + pub fn new(config: ConfigAccess) -> Self { + Self { + inner: WidgetInner::new(config, Rect::new(0, 1, 2, 3), Rect::new(4, 5, 6, 7)), + } + } + } + + impl Deref for Dummy { + type Target = WidgetInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } + } + + impl DerefMut for Dummy { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } + } + + impl Widget for Dummy { + fn texture_path(&self) -> Option { + None + } + + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn source(&self) -> &Rect { + &self.source + } + + fn set_source(&mut self, rect: &Rect) { + self.source = rect.clone(); + } } #[test] @@ -284,7 +328,7 @@ mod tests { #[test] fn must_build_font_details() { - let config = support::build_config(); + let config = build_config(); let wrapper = ConfigWrapper { inner: config.clone(), }; @@ -293,4 +337,137 @@ mod tests { assert_eq!(details.path, c.editor_config().font_path().to_string()); assert_eq!(details.size, c.editor_config().character_size()); } + + // #[test] + // fn mut_return_character_rectangle() { + // let config = build_config(); + // let mut renderer = SimpleRendererMock::new(config); + // let result = get_text_character_rect('a', &mut renderer); + // assert_eq!(result, Some(Rect::new(0, 0, 10, 10))); + // } + + #[test] + fn check_texture_path() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!(widget.texture_path(), None); + } + + #[test] + fn check_dest() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!(widget.dest(), &Rect::new(4, 5, 6, 7)); + } + + #[test] + fn check_set_dest() { + let config = build_config(); + let mut widget = Dummy::new(config); + assert_eq!(widget.set_dest(&Rect::new(9, 8, 7, 6)), ()); + assert_eq!(widget.dest(), &Rect::new(9, 8, 7, 6)); + } + + #[test] + fn check_source() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!(widget.source(), &Rect::new(0, 1, 2, 3)); + } + + #[test] + fn check_set_source() { + let config = build_config(); + let mut widget = Dummy::new(config); + assert_eq!(widget.set_source(&Rect::new(9, 8, 7, 6)), ()); + assert_eq!(widget.source(), &Rect::new(9, 8, 7, 6)); + } + + #[test] + fn check_update() { + let config = build_config(); + let mut widget = Dummy::new(config); + assert_eq!( + widget.update(0, &UpdateContext::Nothing), + UpdateResult::NoOp + ); + } + + #[test] + fn check_on_left_click() { + let config = build_config(); + let mut widget = Dummy::new(config); + assert_eq!( + widget.on_left_click(&Point::new(0, 1), &UpdateContext::Nothing), + UpdateResult::NoOp + ); + } + + #[test] + fn check_is_left_click_target() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!( + widget.is_left_click_target(&Point::new(0, 1), &UpdateContext::Nothing), + false + ); + } + + #[test] + fn check_render_start_point() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!( + widget.render_start_point(), + Rect::new(4, 5, 6, 7).top_left() + ); + } + + #[test] + fn check_clipping() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!( + widget.clipping(&Rect::new(0, 0, 1, 1)), + Rect::new(0, 0, 1, 1) + ); + } + + #[test] + fn check_padding_width() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!(widget.padding_width(), 0); + } + + #[test] + fn check_padding_height() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!(widget.padding_height(), 0); + } + + #[test] + fn check_use_clipping() { + let config = build_config(); + let widget = Dummy::new(config); + assert_eq!(widget.use_clipping(), false); + } + + #[test] + fn check_render() { + build_test_renderer!(renderer); + let widget = Dummy::new(config); + assert_eq!( + widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing), + () + ); + } + + #[test] + fn check_prepare_ui() { + build_test_renderer!(renderer); + let mut widget = Dummy::new(config); + assert_eq!(widget.prepare_ui(&mut renderer), ()); + } } diff --git a/rider-editor/src/ui/modal/mod.rs b/rider-editor/src/ui/modal/mod.rs index 50ae674..ec1c7e0 100644 --- a/rider-editor/src/ui/modal/mod.rs +++ b/rider-editor/src/ui/modal/mod.rs @@ -9,6 +9,7 @@ pub enum ModalType { Settings(Settings), } +#[cfg_attr(tarpaulin, skip)] impl PartialEq for ModalType { fn eq(&self, other: &ModalType) -> bool { match (self, other) { @@ -19,6 +20,7 @@ impl PartialEq for ModalType { } } +#[cfg_attr(tarpaulin, skip)] impl std::fmt::Debug for ModalType { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { let name = match self { diff --git a/rider-editor/src/ui/modal/open_file.rs b/rider-editor/src/ui/modal/open_file.rs index 1481bd8..cfcae93 100644 --- a/rider-editor/src/ui/modal/open_file.rs +++ b/rider-editor/src/ui/modal/open_file.rs @@ -84,26 +84,46 @@ impl OpenFile { pub fn open_directory(&mut self, dir_path: String, renderer: &mut R) where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { self.directory_view.open_directory(dir_path, renderer); { - let dest = self.directory_view.dest(); - let full_dest = Rect::new( - dest.x(), - dest.y(), - dest.width() + (2 * CONTENT_MARGIN_LEFT as u32), - dest.height() + (2 * CONTENT_MARGIN_TOP as u32), + self.full_dest = Rect::new( + self.directory_view.dest().x(), + self.directory_view.dest().y(), + self.directory_view.dest().width() + (2 * CONTENT_MARGIN_LEFT as u32), + self.directory_view.dest().height() + (2 * CONTENT_MARGIN_TOP as u32), ); - self.full_dest = full_dest; } } pub fn full_rect(&self) -> &Rect { &self.full_dest } +} - pub fn update(&mut self, ticks: i32, context: &UC) -> UR { +impl Widget for OpenFile { + fn texture_path(&self) -> Option { + None + } + + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + fn source(&self) -> &Rect { + self.dest() + } + + fn set_source(&mut self, rect: &Rect) { + self.set_dest(rect) + } + + fn update(&mut self, ticks: i32, context: &UC) -> UR { let (window_width, window_height, color, scroll_width, scroll_margin) = { let c = self.config.read().unwrap(); ( @@ -141,7 +161,44 @@ impl OpenFile { UR::NoOp } - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RC) + fn on_left_click(&mut self, point: &Point, context: &UC) -> UR { + let dest = match context { + UC::ParentPosition(p) => move_render_point(*p, &self.dest), + _ => self.dest, + }; + let context = UC::ParentPosition( + dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(), + ); + let res = self.directory_view.on_left_click(point, &context); + { + let dest = self.directory_view.dest(); + let full_dest = Rect::new( + dest.x(), + dest.y(), + dest.width() + (2 * CONTENT_MARGIN_LEFT as u32), + dest.height() + (2 * CONTENT_MARGIN_TOP as u32), + ); + self.full_dest = full_dest; + } + res + } + + fn is_left_click_target(&self, point: &Point, context: &UC) -> bool { + let dest = match context { + UC::ParentPosition(p) => move_render_point(p.clone(), &self.dest()), + _ => self.dest().clone(), + }; + let p = + dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(); + let context = UC::ParentPosition(p); + if self.directory_view.is_left_click_target(point, &context) { + true + } else { + Rect::new(p.x(), p.y(), dest.width(), dest.height()).contains_point(point.clone()) + } + } + + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where C: CanvasAccess, R: Renderer + CharacterSizeManager + ConfigHolder, @@ -174,64 +231,19 @@ impl OpenFile { .render(canvas, &RenderContext::ParentPosition(self.dest.top_left())); } - pub fn prepare_ui(&mut self, renderer: &mut R) + fn prepare_ui<'l, T>(&mut self, renderer: &mut T) where - R: Renderer + CharacterSizeManager, + T: Renderer + CharacterSizeManager + ConfigHolder, { self.directory_view.prepare_ui(renderer); } - - pub fn render_start_point(&self) -> Point { - self.dest.top_left() - } - - pub fn dest(&self) -> Rect { - self.dest.clone() - } - - pub fn on_left_click(&mut self, point: &Point, context: &UC) -> UR { - let dest = match context { - UC::ParentPosition(p) => move_render_point(*p, &self.dest), - _ => self.dest, - }; - let context = UC::ParentPosition( - dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(), - ); - let res = self.directory_view.on_left_click(point, &context); - { - let dest = self.directory_view.dest(); - let full_dest = Rect::new( - dest.x(), - dest.y(), - dest.width() + (2 * CONTENT_MARGIN_LEFT as u32), - dest.height() + (2 * CONTENT_MARGIN_TOP as u32), - ); - self.full_dest = full_dest; - } - res - } - - pub fn is_left_click_target(&self, point: &Point, context: &UC) -> bool { - let dest = match context { - UC::ParentPosition(p) => move_render_point(p.clone(), &self.dest()), - _ => self.dest().clone(), - }; - let p = - dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(); - let context = UC::ParentPosition(p); - if self.directory_view.is_left_click_target(point, &context) { - true - } else { - Rect::new(p.x(), p.y(), dest.width(), dest.height()).contains_point(point.clone()) - } - } } #[cfg(test)] mod tests { use super::*; - use crate::tests::support::SimpleRendererMock; - use crate::tests::support::{build_config, CanvasMock}; + use crate::tests::*; + use rider_derive::*; use std::fs; //####################################################################### @@ -254,7 +266,7 @@ mod tests { fn assert_dest() { let config = build_config(); let widget = OpenFile::new("/tmp".to_owned(), 120, 130, config); - assert_eq!(widget.dest(), Rect::new(452, 365, 120, 130)); + assert_eq!(widget.dest(), &Rect::new(452, 365, 120, 130)); } //####################################################################### @@ -276,8 +288,7 @@ mod tests { fn assert_open_directory() { let path = "/tmp/rider/test-open-file/open-directory"; fs::create_dir_all(path).unwrap(); - let config = build_config(); - let mut renderer = SimpleRendererMock::new(config); + build_test_renderer!(renderer); let mut widget = OpenFile::new(path.to_owned(), 120, 130, renderer.config().clone()); widget.open_directory(path.to_owned(), &mut renderer); } @@ -383,9 +394,8 @@ mod tests { #[test] fn assert_render() { - let config = build_config(); let path = "/tmp/rider/test-open-file/open-directory"; - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut canvas = CanvasMock::new(); let widget = OpenFile::new(path.to_owned(), 100, 100, config); let p = Point::new(100, 100); @@ -399,9 +409,8 @@ mod tests { #[test] fn assert_prepare_ui() { - let config = build_config(); let path = "/tmp/rider/test-open-file/open-directory"; - let mut renderer = SimpleRendererMock::new(config.clone()); + build_test_renderer!(renderer); let mut widget = OpenFile::new(path.to_owned(), 100, 100, config); widget.prepare_ui(&mut renderer); } diff --git a/rider-editor/src/ui/modal/settings.rs b/rider-editor/src/ui/modal/settings.rs index 4841b74..2656281 100644 --- a/rider-editor/src/ui/modal/settings.rs +++ b/rider-editor/src/ui/modal/settings.rs @@ -27,73 +27,74 @@ pub struct Settings { character_size_value: Label, } -impl Settings { - pub fn new(config: ConfigAccess) -> Self { - let c = config - .read() - .unwrap_or_else(|_| panic!("Failed to read config")); - let theme = c.theme(); - let window_width = c.width(); - let window_height = c.height(); - let background_color = theme.background().into(); - let border_color = theme.border_color().into(); - Self { - vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)), - horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)), - dest: Rect::new( - CONTENT_MARGIN_LEFT, - CONTENT_MARGIN_TOP, - window_width - (CONTENT_MARGIN_LEFT * 2) as u32, - window_height - (CONTENT_MARGIN_TOP * 2) as u32, - ), - full_dest: Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE), - background_color, - border_color, - font_label: Label::new("Font path".into(), config.clone()), - font_value: Label::new(c.editor_config().font_path().clone(), config.clone()), - character_size_label: Label::new("Character size".into(), config.clone()), - character_size_value: Label::new( - format!("{}", c.editor_config().character_size()).to_owned(), - config.clone(), - ), - config: config.clone(), - } +impl Widget for Settings { + fn texture_path(&self) -> Option { + None } - pub fn full_rect(&self) -> &Rect { - &self.full_dest + fn dest(&self) -> &Rect { + &self.dest } - pub fn scroll_by(&mut self, x: i32, y: i32) { - let read_config = self.config.read().unwrap(); + fn set_dest(&mut self, _rect: &Rect) {} - let value_x = read_config.scroll().speed() * x; - let value_y = read_config.scroll().speed() * y; - let old_x = self.horizontal_scroll_bar.scroll_value(); - let old_y = self.vertical_scroll_bar.scroll_value(); - - if value_x + old_x >= 0 { - self.horizontal_scroll_bar.scroll_to(value_x + old_x); - if self.horizontal_scroll_bar.scrolled_part() > 1.0 { - self.horizontal_scroll_bar.scroll_to(old_x); - } - } - if value_y + old_y >= 0 { - self.vertical_scroll_bar.scroll_to(value_y + old_y); - if self.vertical_scroll_bar.scrolled_part() > 1.0 { - self.vertical_scroll_bar.scroll_to(old_y); - } - } + fn source(&self) -> &Rect { + &self.dest } - pub fn scroll(&self) -> Point { - Point::new( - -self.horizontal_scroll_bar.scroll_value(), - -self.vertical_scroll_bar.scroll_value(), - ) + fn set_source(&mut self, _rect: &Rect) {} + + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { + let (window_width, window_height, color, scroll_width, scroll_margin) = { + let c = self.config.read().unwrap(); + ( + c.width(), + c.height(), + c.theme().background().into(), + c.scroll().width(), + c.scroll().margin_right(), + ) + }; + + self.dest.set_x(CONTENT_MARGIN_LEFT); + self.dest + .set_width(window_width - (CONTENT_MARGIN_LEFT * 2) as u32); + self.dest.set_y(CONTENT_MARGIN_TOP); + self.dest + .set_height(window_height - (CONTENT_MARGIN_TOP * 2) as u32); + + self.background_color = color; + + // Scroll bars + self.vertical_scroll_bar + .set_full_size(self.full_dest.height()); // full dest + self.vertical_scroll_bar.set_viewport(self.dest.height()); + self.vertical_scroll_bar + .set_location(self.dest.width() as i32 - (scroll_width as i32 + scroll_margin)); + self.vertical_scroll_bar.update(ticks, context); + + self.horizontal_scroll_bar + .set_full_size(self.full_dest.width()); // full dest + self.horizontal_scroll_bar.set_viewport(self.dest.width()); + self.horizontal_scroll_bar + .set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin)); + self.horizontal_scroll_bar.update(ticks, context); + + // End + UR::NoOp } - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RC) + #[inline] + fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { + UR::NoOp + } + + #[inline] + fn is_left_click_target(&self, _point: &Point, _context: &UpdateContext) -> bool { + false + } + + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RC) where C: CanvasAccess, R: Renderer + CharacterSizeManager + ConfigHolder, @@ -165,9 +166,9 @@ impl Settings { .render(canvas, &RenderContext::ParentPosition(self.dest.top_left())); } - pub fn prepare_ui(&mut self, renderer: &mut R) + fn prepare_ui(&mut self, renderer: &mut R) where - R: Renderer + CharacterSizeManager, + R: Renderer + CharacterSizeManager + ConfigHolder, { self.font_label.prepare_ui(renderer); self.font_value.prepare_ui(renderer); @@ -176,66 +177,178 @@ impl Settings { } } -impl ClickHandler for Settings { - #[inline] - fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { - UR::NoOp +impl ScrollView for Settings { + fn mut_horizontal_scroll_handler(&mut self) -> Option<&mut HorizontalScrollBar> { + Some(&mut self.horizontal_scroll_bar) } - #[inline] - fn is_left_click_target(&self, _point: &Point, _context: &UpdateContext) -> bool { - false + fn horizontal_scroll_handler(&self) -> Option<&HorizontalScrollBar> { + Some(&self.horizontal_scroll_bar) + } + + fn mut_vertical_scroll_handler(&mut self) -> Option<&mut VerticalScrollBar> { + Some(&mut self.vertical_scroll_bar) + } + + fn vertical_scroll_handler(&self) -> Option<&VerticalScrollBar> { + Some(&self.vertical_scroll_bar) } } -impl RenderBox for Settings { - fn render_start_point(&self) -> Point { - self.dest.top_left() +impl Settings { + pub fn new(config: ConfigAccess) -> Self { + let c = config + .read() + .unwrap_or_else(|_| panic!("Failed to read config")); + let theme = c.theme(); + let window_width = c.width(); + let window_height = c.height(); + let background_color = theme.background().into(); + let border_color = theme.border_color().into(); + Self { + vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)), + horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)), + dest: Rect::new( + CONTENT_MARGIN_LEFT, + CONTENT_MARGIN_TOP, + window_width - (CONTENT_MARGIN_LEFT * 2) as u32, + window_height - (CONTENT_MARGIN_TOP * 2) as u32, + ), + full_dest: Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE), + background_color, + border_color, + font_label: Label::new("Font path".into(), config.clone()), + font_value: Label::new(c.editor_config().font_path().to_string(), config.clone()), + character_size_label: Label::new("Character size".into(), config.clone()), + character_size_value: Label::new( + format!("{}", c.editor_config().character_size()).to_owned(), + config.clone(), + ), + config: config.clone(), + } } - fn dest(&self) -> Rect { - self.dest.clone() + pub fn full_rect(&self) -> &Rect { + &self.full_dest } } -impl Update for Settings { - fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { - let (window_width, window_height, color, scroll_width, scroll_margin) = { - let c = self.config.read().unwrap(); - ( - c.width(), - c.height(), - c.theme().background().into(), - c.scroll().width(), - c.scroll().margin_right(), - ) - }; - - self.dest.set_x(CONTENT_MARGIN_LEFT); - self.dest - .set_width(window_width - (CONTENT_MARGIN_LEFT * 2) as u32); - self.dest.set_y(CONTENT_MARGIN_TOP); - self.dest - .set_height(window_height - (CONTENT_MARGIN_TOP * 2) as u32); - - self.background_color = color; - - // Scroll bars - self.vertical_scroll_bar - .set_full_size(self.full_dest.height()); // full dest - self.vertical_scroll_bar.set_viewport(self.dest.height()); - self.vertical_scroll_bar - .set_location(self.dest.width() as i32 - (scroll_width as i32 + scroll_margin)); - self.vertical_scroll_bar.update(ticks, context); - - self.horizontal_scroll_bar - .set_full_size(self.full_dest.width()); // full dest - self.horizontal_scroll_bar.set_viewport(self.dest.width()); - self.horizontal_scroll_bar - .set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin)); - self.horizontal_scroll_bar.update(ticks, context); - - // End - UR::NoOp +impl ConfigHolder for Settings { + fn config(&self) -> &ConfigAccess { + &self.config + } +} + +#[cfg(test)] +mod tests { + use crate::app::UpdateResult; + use crate::tests::*; + use crate::ui::{RenderContext, ScrollView, Settings, UpdateContext, Widget}; + use rider_derive::*; + use sdl2::rect::{Point, Rect}; + + #[test] + fn must_have_vertical_scrollbar() { + let config = build_config(); + let mut widget = Settings::new(config); + assert_eq!(widget.mut_vertical_scroll_handler().is_some(), true); + assert_eq!(widget.vertical_scroll_handler().is_some(), true); + } + + #[test] + fn must_have_horizontal_scrollbar() { + let config = build_config(); + let mut widget = Settings::new(config); + assert_eq!(widget.mut_horizontal_scroll_handler().is_some(), true); + assert_eq!(widget.horizontal_scroll_handler().is_some(), true); + } + + // Widget + + #[test] + fn assert_texture_path() { + let config = build_config(); + let widget = Settings::new(config); + let result = widget.texture_path(); + assert!(result.is_none()); + } + + #[test] + fn assert_dest() { + let config = build_config(); + let widget = Settings::new(config); + let result = widget.dest().clone(); + let expected = Rect::new(16, 24, 992, 812); + assert_eq!(result, expected); + } + + #[test] + fn assert_set_dest() { + let config = build_config(); + let mut widget = Settings::new(config); + widget.set_dest(&Rect::new(100, 200, 300, 400)); + assert_ne!(widget.dest(), &Rect::new(100, 200, 300, 400)); + } + #[test] + fn assert_source() { + let config = build_config(); + let widget = Settings::new(config); + let result = widget.source(); + assert_eq!(result, &Rect::new(16, 24, 992, 812)); + } + #[test] + fn assert_set_source() { + let config = build_config(); + let mut widget = Settings::new(config); + widget.set_source(&Rect::new(1, 2, 3, 4)); + assert_ne!(widget.source(), &Rect::new(1, 2, 3, 4)); + } + + #[test] + fn assert_update() { + let config = build_config(); + let mut widget = Settings::new(config); + let result = widget.update(0, &UpdateContext::Nothing); + assert_eq!(result, UpdateResult::NoOp); + } + + #[test] + fn assert_on_left_click() { + let config = build_config(); + let mut widget = Settings::new(config); + let result = widget.on_left_click(&Point::new(600, 800), &UpdateContext::Nothing); + assert_eq!(result, UpdateResult::NoOp); + } + + #[test] + fn assert_is_left_click_target() { + let config = build_config(); + let widget = Settings::new(config); + let result = widget.is_left_click_target(&Point::new(600, 800), &UpdateContext::Nothing); + assert_eq!(result, false); + } + + #[test] + fn assert_use_clipping() { + let config = build_config(); + let widget = Settings::new(config); + let result = widget.use_clipping(); + assert_eq!(result, false); + } + + #[test] + fn assert_render() { + build_test_renderer!(renderer); + let mut canvas = CanvasMock::new(); + let widget = Settings::new(config); + let result = widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + assert_eq!(result, ()); + } + #[test] + fn assert_prepare_ui() { + build_test_renderer!(renderer); + let mut widget = Settings::new(config); + let result = widget.prepare_ui(&mut renderer); + assert_eq!(result, ()); } } diff --git a/rider-editor/src/ui/project_tree/mod.rs b/rider-editor/src/ui/project_tree/mod.rs index 1f1378e..822f27a 100644 --- a/rider-editor/src/ui/project_tree/mod.rs +++ b/rider-editor/src/ui/project_tree/mod.rs @@ -4,7 +4,6 @@ use crate::ui::filesystem::directory::DirectoryView; use crate::ui::horizontal_scroll_bar::HorizontalScrollBar; use crate::ui::text_character::CharacterSizeManager; use crate::ui::vertical_scroll_bar::VerticalScrollBar; -use crate::ui::ClickHandler; use crate::ui::RenderContext; use crate::ui::UpdateContext; use crate::ui::{move_render_point, ScrollView}; @@ -33,31 +32,24 @@ pub struct ProjectTreeSidebar { horizontal_scroll_bar: HorizontalScrollBar, } -impl ProjectTreeSidebar { - pub fn new(root: String, config: Arc>) -> Self { - let (background_color, border_color, h): (Color, Color, u32) = { - let c = config.read().unwrap(); - ( - c.theme().background().into(), - c.theme().border_color().into(), - c.height(), - ) - }; - - Self { - dest: Rect::new(0, 0, 200, h), - full_dest: Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE), - dir_view: DirectoryView::new(root.clone(), config.clone()), - vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)), - horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)), - config, - root, - background_color, - border_color, - } +impl Widget for ProjectTreeSidebar { + fn texture_path(&self) -> Option { + None } - pub fn update(&mut self, ticks: i32) { + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, _rect: &Rect) {} + + fn source(&self) -> &Rect { + &self.dest + } + + fn set_source(&mut self, _rect: &Rect) {} + + fn update(&mut self, ticks: i32, _context: &UpdateContext) -> UpdateResult { let config = self.config.read().unwrap(); let height = config.height(); // let left_margin = config.editor_left_margin(); @@ -66,70 +58,9 @@ impl ProjectTreeSidebar { self.dest.set_y(top_margin); self.dest.set_height(height - top_margin as u32); self.dir_view.update(ticks, &UpdateContext::Nothing); + UpdateResult::NoOp } - pub fn prepare_ui(&mut self, renderer: &mut R) - where - R: Renderer + CharacterSizeManager, - { - let config = self.config.read().unwrap(); - let height = config.height(); - let left_margin = 0; - let top_margin = config.menu_height() as i32; - self.dest.set_x(left_margin); - self.dest.set_y(top_margin); - self.dest.set_height(height); - self.dir_view.prepare_ui(renderer); - self.dir_view.open_directory(self.root.clone(), renderer); - } - - pub fn render(&self, canvas: &mut C, renderer: &mut R) - where - R: Renderer + CharacterSizeManager, - C: CanvasAccess, - { - canvas.set_clipping(self.dest.clone()); - canvas - .render_rect(self.dest.clone(), self.background_color.clone()) - .unwrap(); - canvas - .render_border(self.dest.clone(), self.border_color.clone()) - .unwrap(); - - // dir view - let context = RenderContext::ParentPosition( - self.dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP), - ); - self.dir_view.render(canvas, renderer, &context); - } - - pub fn full_rect(&self) -> Rect { - self.dest.clone() - } - - pub fn root(&self) -> String { - self.root.clone() - } - - pub fn open_directory(&mut self, dir_path: String, renderer: &mut R) - where - R: Renderer + CharacterSizeManager, - { - self.dir_view.open_directory(dir_path, renderer); - { - let dest = self.dir_view.dest(); - let full_dest = Rect::new( - dest.x(), - dest.y(), - dest.width() + (2 * CONTENT_MARGIN_LEFT as u32), - dest.height() + (2 * CONTENT_MARGIN_TOP as u32), - ); - self.full_dest = full_dest; - } - } -} - -impl ClickHandler for ProjectTreeSidebar { fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult { let dest = match context { UpdateContext::ParentPosition(p) => move_render_point(*p, &self.dest), @@ -166,6 +97,95 @@ impl ClickHandler for ProjectTreeSidebar { Rect::new(p.x(), p.y(), dest.width(), dest.height()).contains_point(point.clone()) } } + + fn use_clipping(&self) -> bool { + true + } + + fn render(&self, canvas: &mut C, renderer: &mut R, _context: &RenderContext) + where + C: CanvasAccess, + R: Renderer + CharacterSizeManager + ConfigHolder, + { + if self.use_clipping() { + canvas.set_clipping(self.dest.clone()); + } + canvas + .render_rect(self.dest.clone(), self.background_color.clone()) + .unwrap(); + canvas + .render_border(self.dest.clone(), self.border_color.clone()) + .unwrap(); + + // dir view + let context = RenderContext::ParentPosition( + self.dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP), + ); + self.dir_view.render(canvas, renderer, &context); + } + + fn prepare_ui(&mut self, renderer: &mut R) + where + R: Renderer + CharacterSizeManager + ConfigHolder, + { + let config = self.config.read().unwrap(); + let height = config.height(); + let left_margin = 0; + let top_margin = config.menu_height() as i32; + self.dest.set_x(left_margin); + self.dest.set_y(top_margin); + self.dest.set_height(height); + self.dir_view.prepare_ui(renderer); + self.dir_view.open_directory(self.root.clone(), renderer); + } +} + +impl ProjectTreeSidebar { + pub fn new(root: String, config: Arc>) -> Self { + let (background_color, border_color, h): (Color, Color, u32) = { + let c = config.read().unwrap(); + ( + c.theme().background().into(), + c.theme().border_color().into(), + c.height(), + ) + }; + + Self { + dest: Rect::new(0, 0, 200, h), + full_dest: Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE), + dir_view: DirectoryView::new(root.clone(), config.clone()), + vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)), + horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)), + config, + root, + background_color, + border_color, + } + } + + pub fn full_rect(&self) -> Rect { + self.dest.clone() + } + + pub fn root(&self) -> String { + self.root.clone() + } + + pub fn open_directory(&mut self, dir_path: String, renderer: &mut R) + where + R: Renderer + CharacterSizeManager + ConfigHolder, + { + self.dir_view.open_directory(dir_path, renderer); + { + self.full_dest = Rect::new( + self.dir_view.dest().x(), + self.dir_view.dest().y(), + self.dir_view.dest().width() + (2 * CONTENT_MARGIN_LEFT as u32), + self.dir_view.dest().height() + (2 * CONTENT_MARGIN_TOP as u32), + ); + } + } } impl ConfigHolder for ProjectTreeSidebar { @@ -197,13 +217,12 @@ mod tests { use crate::renderer::managers::FontDetails; use crate::renderer::managers::TextDetails; use crate::renderer::renderer::Renderer; - use crate::tests::support::build_config; - use crate::tests::support::CanvasMock; + use crate::tests::*; use crate::ui::project_tree::ProjectTreeSidebar; use crate::ui::scroll_bar::ScrollView; use crate::ui::text_character::CharacterSizeManager; - use crate::ui::ClickHandler; - use crate::ui::UpdateContext; + use crate::ui::Widget; + use crate::ui::{RenderContext, UpdateContext}; use rider_config::ConfigAccess; use rider_config::ConfigHolder; use sdl2::rect::Point; @@ -270,7 +289,7 @@ mod tests { fn assert_update() { let config = build_config(); let mut widget = ProjectTreeSidebar::new("/tmp".to_owned(), config); - widget.update(0); + widget.update(0, &UpdateContext::Nothing); assert_eq!(widget.full_rect(), Rect::new(0, 40, 200, 820)); } @@ -289,7 +308,7 @@ mod tests { let mut renderer = RendererMock::new(config.clone()); let mut canvas = CanvasMock::new(); let widget = ProjectTreeSidebar::new("/tmp".to_owned(), config); - widget.render(&mut canvas, &mut renderer); + widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); } //####################################################################### diff --git a/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs b/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs index 4a2ee52..9e662f1 100644 --- a/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs +++ b/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs @@ -84,7 +84,7 @@ impl ScrollWidget for HorizontalScrollBar { #[cfg(test)] mod test_update { use super::*; - use crate::tests::support; + use crate::tests::*; use std::sync::*; impl HorizontalScrollBar { @@ -95,7 +95,7 @@ mod test_update { #[test] fn assert_do_nothing_when_small_content() { - let config = support::build_config(); + let config = build_config(); let mut widget = HorizontalScrollBar::new(Arc::clone(&config)); widget.set_viewport(100); widget.set_full_size(20); @@ -108,7 +108,7 @@ mod test_update { #[test] fn assert_update_when_huge_content() { - let config = support::build_config(); + let config = build_config(); let mut widget = HorizontalScrollBar::new(Arc::clone(&config)); widget.set_viewport(100); widget.set_full_size(200); @@ -123,12 +123,12 @@ mod test_update { #[cfg(test)] mod test_scrollable { use super::*; - use crate::tests::support; + use crate::tests::*; use std::sync::*; #[test] fn assert_scroll_to() { - let config = support::build_config(); + let config = build_config(); let mut widget = HorizontalScrollBar::new(Arc::clone(&config)); let old = widget.scroll_value(); widget.scroll_to(157); @@ -140,14 +140,14 @@ mod test_scrollable { #[test] fn assert_scroll_value() { - let config = support::build_config(); + let config = build_config(); let widget = HorizontalScrollBar::new(Arc::clone(&config)); assert_eq!(widget.scroll_value(), 0); } #[test] fn assert_set_viewport() { - let config = support::build_config(); + let config = build_config(); let mut widget = HorizontalScrollBar::new(Arc::clone(&config)); let old = widget.viewport(); widget.set_viewport(157); @@ -159,7 +159,7 @@ mod test_scrollable { #[test] fn assert_set_full_size() { - let config = support::build_config(); + let config = build_config(); let mut widget = HorizontalScrollBar::new(Arc::clone(&config)); let old = widget.full; widget.set_full_size(157); @@ -171,7 +171,7 @@ mod test_scrollable { #[test] fn assert_set_location() { - let config = support::build_config(); + let config = build_config(); let mut widget = HorizontalScrollBar::new(Arc::clone(&config)); let old = widget.rect().y(); widget.set_location(157); diff --git a/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs b/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs index ef1cfa5..8c10992 100644 --- a/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs +++ b/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs @@ -84,7 +84,7 @@ impl ScrollWidget for VerticalScrollBar { #[cfg(test)] mod test_update { use super::*; - use crate::tests::support; + use crate::tests::*; use std::sync::*; impl VerticalScrollBar { @@ -95,7 +95,7 @@ mod test_update { #[test] fn assert_do_nothing_when_small_content() { - let config = support::build_config(); + let config = build_config(); let mut widget = VerticalScrollBar::new(Arc::clone(&config)); widget.set_viewport(100); widget.set_full_size(20); @@ -108,7 +108,7 @@ mod test_update { #[test] fn assert_update_when_huge_content() { - let config = support::build_config(); + let config = build_config(); let mut widget = VerticalScrollBar::new(Arc::clone(&config)); widget.set_viewport(100); widget.set_full_size(200); @@ -123,12 +123,12 @@ mod test_update { #[cfg(test)] mod test_scrollable { use super::*; - use crate::tests::support; + use crate::tests::*; use std::sync::*; #[test] fn assert_scroll_to() { - let config = support::build_config(); + let config = build_config(); let mut widget = VerticalScrollBar::new(Arc::clone(&config)); let old = widget.scroll_value(); widget.scroll_to(157); @@ -140,14 +140,14 @@ mod test_scrollable { #[test] fn assert_scroll_value() { - let config = support::build_config(); + let config = build_config(); let widget = VerticalScrollBar::new(Arc::clone(&config)); assert_eq!(widget.scroll_value(), 0); } #[test] fn assert_set_viewport() { - let config = support::build_config(); + let config = build_config(); let mut widget = VerticalScrollBar::new(Arc::clone(&config)); let old = widget.viewport(); widget.set_viewport(157); @@ -159,7 +159,7 @@ mod test_scrollable { #[test] fn assert_set_full_size() { - let config = support::build_config(); + let config = build_config(); let mut widget = VerticalScrollBar::new(Arc::clone(&config)); let old = widget.full; widget.set_full_size(157); @@ -171,7 +171,7 @@ mod test_scrollable { #[test] fn assert_set_location() { - let config = support::build_config(); + let config = build_config(); let mut widget = VerticalScrollBar::new(Arc::clone(&config)); let old = widget.rect().x(); widget.set_location(157); diff --git a/rider-editor/src/ui/text_character.rs b/rider-editor/src/ui/text_character.rs index 1369b99..a6db3f1 100644 --- a/rider-editor/src/ui/text_character.rs +++ b/rider-editor/src/ui/text_character.rs @@ -51,18 +51,6 @@ impl TextCharacter { self.last_in_line } - pub fn source(&self) -> &Rect { - &self.source - } - - pub fn set_dest(&mut self, rect: &Rect) { - self.dest = rect.clone(); - } - - pub fn set_source(&mut self, rect: &Rect) { - self.source = rect.clone(); - } - pub fn color(&self) -> &Color { &self.color } @@ -101,15 +89,42 @@ impl TextCharacter { } } -impl TextCharacter { +impl Widget for TextCharacter { + fn texture_path(&self) -> Option { + None + } + + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone() + } + + fn source(&self) -> &Rect { + &self.source + } + + fn set_source(&mut self, rect: &Rect) { + self.source = rect.clone(); + } + + fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { + UR::MoveCaret( + self.dest.clone(), + CaretPosition::new(self.position(), self.line(), 0), + ) + } + /** * Must first create targets so even if new line appear renderer will know * where move render starting point */ - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where - R: Renderer + ConfigHolder, C: CanvasAccess, + R: Renderer + CharacterSizeManager + ConfigHolder, { let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into(); @@ -125,7 +140,7 @@ impl TextCharacter { }; let dest = match context { RenderContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest), - _ => self.dest(), + _ => self.dest().clone(), }; if let Ok(texture) = renderer.load_text_tex(&mut details, font_details) { @@ -135,9 +150,9 @@ impl TextCharacter { } } - pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T) + fn prepare_ui<'l, T>(&mut self, renderer: &mut T) where - T: ConfigHolder + CharacterSizeManager + Renderer, + T: Renderer + CharacterSizeManager + ConfigHolder, { let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into(); let rect = renderer.load_character_size(self.text_character); @@ -159,39 +174,6 @@ impl TextCharacter { } } -impl Update for TextCharacter { - fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR { - UR::NoOp - } -} - -impl ClickHandler for TextCharacter { - fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { - UR::MoveCaret( - self.dest().clone(), - CaretPosition::new(self.position(), self.line(), 0), - ) - } - - fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { - let rect = match context { - &UpdateContext::ParentPosition(p) => move_render_point(p, &self.dest), - _ => self.dest(), - }; - is_in_rect(point, &rect) - } -} - -impl RenderBox for TextCharacter { - fn render_start_point(&self) -> Point { - self.dest.top_left() - } - - fn dest(&self) -> Rect { - self.dest - } -} - impl PartialEq for TextCharacter { fn eq(&self, other: &Self) -> bool { self.line == other.line @@ -229,7 +211,7 @@ mod test_getters { #[test] fn must_return_valid_is_last_in_line() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new( '\n', 0, @@ -243,7 +225,7 @@ mod test_getters { #[test] fn must_return_true_for_is_new_line_if_new_line() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new( '\n', 0, @@ -257,7 +239,7 @@ mod test_getters { #[test] fn must_return_false_for_is_new_line_if_new_line() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new('W', 0, 0, true, Color::RGB(1, 12, 123), Arc::clone(&config)); assert_eq!(widget.is_new_line(), false); @@ -265,7 +247,7 @@ mod test_getters { #[test] fn must_return_valid_position() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new( '\n', 1, @@ -279,7 +261,7 @@ mod test_getters { #[test] fn must_return_valid_line() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new( '\n', 1, @@ -293,7 +275,7 @@ mod test_getters { #[test] fn must_return_valid_text_character() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new( '\n', 87, @@ -307,7 +289,7 @@ mod test_getters { #[test] fn must_return_valid_source() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new( '\n', 0, @@ -321,7 +303,7 @@ mod test_getters { #[test] fn must_return_valid_dest() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new( '\n', 0, @@ -330,12 +312,12 @@ mod test_getters { Color::RGB(1, 12, 123), Arc::clone(&config), ); - assert_eq!(widget.dest(), Rect::new(0, 0, 0, 0)); + assert_eq!(widget.dest(), &Rect::new(0, 0, 0, 0)); } #[test] fn must_return_valid_color() { - let config = support::build_config(); + let config = build_config(); let widget = TextCharacter::new( '\n', 0, @@ -357,7 +339,7 @@ mod test_own_methods { #[test] fn must_update_position_of_new_line() { - let config = support::build_config(); + let config = build_config(); let mut widget = TextCharacter::new( '\n', 0, @@ -371,13 +353,13 @@ mod test_own_methods { let mut current = Rect::new(10, 23, 0, 0); widget.update_position(&mut current); assert_eq!(current, Rect::new(0, 103, 1, 1)); - assert_eq!(widget.dest(), Rect::new(10, 23, 30, 40)); + assert_eq!(widget.dest(), &Rect::new(10, 23, 30, 40)); assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80)); } #[test] fn must_update_position_of_non_new_line() { - let config = support::build_config(); + let config = build_config(); let mut widget = TextCharacter::new( 'W', 0, @@ -391,7 +373,7 @@ mod test_own_methods { let mut current = Rect::new(10, 23, 0, 0); widget.update_position(&mut current); assert_eq!(current, Rect::new(80, 23, 1, 1)); - assert_eq!(widget.dest(), Rect::new(10, 23, 70, 80)); + assert_eq!(widget.dest(), &Rect::new(10, 23, 70, 80)); assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80)); } } @@ -406,7 +388,7 @@ mod test_click_handler { #[test] fn refute_when_not_click_target() { - let config = support::build_config(); + let config = build_config(); let mut widget = TextCharacter::new( '\n', 0, @@ -425,7 +407,7 @@ mod test_click_handler { #[test] fn assert_when_click_target() { - let config = support::build_config(); + let config = build_config(); let mut widget = TextCharacter::new( '\n', 0, @@ -444,7 +426,7 @@ mod test_click_handler { #[test] fn refute_when_not_click_target_because_parent() { - let config = support::build_config(); + let config = build_config(); let mut widget = TextCharacter::new( '\n', 0, @@ -463,7 +445,7 @@ mod test_click_handler { #[test] fn assert_when_click_target_because_parent() { - let config = support::build_config(); + let config = build_config(); let mut widget = TextCharacter::new( '\n', 0, @@ -482,7 +464,7 @@ mod test_click_handler { #[test] fn assert_on_click_return_move_caret() { - let config = support::build_config(); + let config = build_config(); let position = 1233; let line = 2893; let mut widget = TextCharacter::new( @@ -514,7 +496,7 @@ mod test_render_box { #[test] fn must_return_top_left_point() { - let config = support::build_config(); + let config = build_config(); let mut widget = TextCharacter::new( '\n', 0, @@ -541,7 +523,7 @@ mod test_update { #[test] fn assert_do_nothing() { - let config = support::build_config(); + let config = build_config(); let mut widget = TextCharacter::new( '\n', 0, diff --git a/rider-lexers/src/rust_lang.rs b/rider-lexers/src/rust_lang.rs index 4e7b5fd..ab8da7d 100644 --- a/rider-lexers/src/rust_lang.rs +++ b/rider-lexers/src/rust_lang.rs @@ -25,7 +25,7 @@ pub mod lexer { token: Token::new(text.to_string(), 0, 0, 0, 0) }, text), - r"(let|fn|type|struct|pub|impl|for|self|Self|mod|use|enum|(iu)(8|16|32)|usize|bool)" => (TokenType::Keyword { + r"(let|fn|type|struct|trait|pub|impl|for|self|Self|mod|use|enum|(iu)(8|16|32)|usize|bool)" => (TokenType::Keyword { token: Token::new(text.to_string(), 0, 0, 0, 0) }, text), diff --git a/rider-match-widget/Cargo.toml b/rider-match-widget/Cargo.toml new file mode 100644 index 0000000..88d8296 --- /dev/null +++ b/rider-match-widget/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rider-match-widget" +version = "0.1.0" +authors = ["Adrian Woźniak "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +darling = { version = "*" } + +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0" +features = ["full", "extra-traits"] diff --git a/rider-match-widget/src/lib.rs b/rider-match-widget/src/lib.rs new file mode 100644 index 0000000..c9fb4f9 --- /dev/null +++ b/rider-match-widget/src/lib.rs @@ -0,0 +1,176 @@ +use darling::{FromMeta, ToTokens}; +use proc_macro; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, AttributeArgs}; + +#[derive(Debug, Clone)] +struct AssertBuilder { + widget: String, + widget_variable: Option, + contains: String, + x: i32, + y: i32, + w: u32, + h: u32, + widget_dump_path: Option, + child_dump_path: Option, + background_from: Option, +} + +impl ToTokens for AssertBuilder { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let AssertBuilder { + widget, + contains, + x, + y, + w, + h, + widget_dump_path, + child_dump_path, + widget_variable, + background_from, + } = self.clone(); + + let var = format_ident!( + "{}", + widget_variable + .as_ref() + .cloned() + .unwrap_or_else(|| "widget".to_string()) + ); + let widget_name = format_ident!("{}", widget); + let child_name = format_ident!("{}", contains); + let background = match background_from { + Some(name) => { + let ident = format_ident!("{}", name); + quote! { + surface_canvas + .render_rect(size.clone(), widget. #ident .clone()) + .unwrap(); + } + } + _ => quote! {}, + }; + let widget_dump_path = match widget_dump_path.as_ref() { + Some(t) => { + quote! { Some( #t ) } + } + None => quote! { None }, + }; + let child_dump_path = match child_dump_path.as_ref() { + Some(t) => { + quote! { Some( #t ) } + } + None => quote! { None }, + }; + + let generated = quote! { + let mut character_sizes = std::collections::HashMap::new(); + let config = build_config(); + let mut surface = + sdl2::surface::Surface::new( #x as u32 + #w as u32 , #y as u32 + #h as u32, sdl2::pixels::PixelFormatEnum::RGBA8888).unwrap(); + let mut surface_canvas = sdl2::render::Canvas::from_surface(surface).unwrap(); + let mut texture_creator = surface_canvas.texture_creator(); + let mut texture_manager = crate::renderer::managers::TextureManager::new(&texture_creator); + let mut ttf_context = sdl2::ttf::Sdl2TtfContext {}; + let mut renderer = SimpleRendererMock { + config: config.clone(), + ttf: ttf_context, + character_sizes, + texture_manager, + }; + + let mut #var = #widget_name ::new(config); + #var .prepare_ui(); + let size = sdl2::rect::Rect::new( + 0i32, + 0i32, + #w as u32 + #x as u32, + #h as u32 + #y as u32, + ); + let expected_pixels = { + #background + #var . #child_name .render( + &mut surface_canvas, + &mut renderer, + &RenderContext::ParentPosition( size.top_left() .offset( #x , #y ) ), + ); + surface_canvas + .read_pixels(sdl2::rect::Rect::new( #x as i32, #y as i32 , #w as u32 , #h as u32 ), PixelFormatEnum::RGBA8888) + .unwrap() + }; + if let Some(child_dump_path) = #child_dump_path { + surface_canvas.dump_ui(child_dump_path); + } + + #var .render(&mut surface_canvas, &mut renderer, &RenderContext::Nothing); + if let Some(widget_dump_path) = #widget_dump_path { + surface_canvas.dump_ui(widget_dump_path); + } + let result_pixels = surface_canvas + .read_pixels(sdl2::rect::Rect::new( #x as i32, #y as i32, #w as u32, #h as u32 ), PixelFormatEnum::RGBA8888) + .unwrap(); + + if expected_pixels != result_pixels { + match ( #child_dump_path, #widget_dump_path ) { + (Some(c), Some(w)) => { + assert_eq!(c, w); + } + _ => { + assert_eq!(result_pixels, expected_pixels); + } + } + } + }; + + tokens.extend(generated); + } +} + +#[derive(Debug, Clone, FromMeta)] +struct AssertOptions { + fn_name: String, + widget: String, + contains: String, + x: i32, + y: i32, + w: u32, + h: u32, + widget_dump_path: Option, + child_dump_path: Option, + widget_variable: Option, + background_from: Option, +} + +#[proc_macro_attribute] +pub fn match_widgets(args: TokenStream, _input: TokenStream) -> TokenStream { + let attr_args = parse_macro_input!(args as AttributeArgs); + // let _input = parse_macro_input!(input as ItemFn); + + let opts = match AssertOptions::from_list(&attr_args) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(e.write_errors()); + } + }; + + let builder = AssertBuilder { + widget: opts.widget, + widget_variable: opts.widget_variable, + contains: opts.contains, + x: opts.x, + y: opts.y, + w: opts.w, + h: opts.h, + widget_dump_path: opts.widget_dump_path, + child_dump_path: opts.child_dump_path, + background_from: opts.background_from, + }; + + let name = format_ident!("{}", opts.fn_name); + + let output = quote! { fn #name () { #builder } }; + output.into() +} diff --git a/src/main.rs b/src/main.rs index 7f8ba6e..7bf3b5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ extern crate rider_config; use std::process::Command; +#[cfg_attr(tarpaulin, skip)] fn main() { let generator = rider_config::directories::get_binary_path("rider-generator").unwrap(); println!("generator will be {:?}", generator); diff --git a/test_files/test.rs b/test_files/test.rs index 5065550..954a655 100644 --- a/test_files/test.rs +++ b/test_files/test.rs @@ -1,154 +1,159 @@ -use std::ops::Deref; +#[cfg_attr(tarpaulin, skip)] +mod test_files { + use std::ops::Deref; -pub mod plain; -pub mod rust_lang; + pub mod plain; + pub mod rust_lang; -#[derive(Debug, Clone)] -pub enum Language { - PlainText, -} + #[cfg_attr(tarpaulin, skip)] +mod test_files { + #[derive(Debug, Clone)] + pub enum Language { + PlainText, + } -#[derive(Debug, Clone)] -pub enum TokenType { - Whitespace { token: Token }, - Keyword { token: Token }, - String { token: Token }, - Number { token: Token }, - Identifier { token: Token }, - Literal { token: Token }, - Comment { token: Token }, - Operator { token: Token }, - Separator { token: Token }, -} + #[derive(Debug, Clone)] + pub enum TokenType { + Whitespace { token: Token }, + Keyword { token: Token }, + String { token: Token }, + Number { token: Token }, + Identifier { token: Token }, + Literal { token: Token }, + Comment { token: Token }, + Operator { token: Token }, + Separator { token: Token }, + } -impl TokenType { - pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self { - match self { - TokenType::Whitespace { token } => TokenType::Whitespace { - token: token.move_to(line, character, start, end), - }, - TokenType::Keyword { token } => TokenType::Keyword { - token: token.move_to(line, character, start, end), - }, - TokenType::String { token } => TokenType::String { - token: token.move_to(line, character, start, end), - }, - TokenType::Number { token } => TokenType::Number { - token: token.move_to(line, character, start, end), - }, - TokenType::Identifier { token } => TokenType::Identifier { - token: token.move_to(line, character, start, end), - }, - TokenType::Literal { token } => TokenType::Literal { - token: token.move_to(line, character, start, end), - }, - TokenType::Comment { token } => TokenType::Comment { - token: token.move_to(line, character, start, end), - }, - TokenType::Operator { token } => TokenType::Operator { - token: token.move_to(line, character, start, end), - }, - TokenType::Separator { token } => TokenType::Separator { - token: token.move_to(line, character, start, end), - }, + impl TokenType { + pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self { + match self { + TokenType::Whitespace { token } => TokenType::Whitespace { + token: token.move_to(line, character, start, end), + }, + TokenType::Keyword { token } => TokenType::Keyword { + token: token.move_to(line, character, start, end), + }, + TokenType::String { token } => TokenType::String { + token: token.move_to(line, character, start, end), + }, + TokenType::Number { token } => TokenType::Number { + token: token.move_to(line, character, start, end), + }, + TokenType::Identifier { token } => TokenType::Identifier { + token: token.move_to(line, character, start, end), + }, + TokenType::Literal { token } => TokenType::Literal { + token: token.move_to(line, character, start, end), + }, + TokenType::Comment { token } => TokenType::Comment { + token: token.move_to(line, character, start, end), + }, + TokenType::Operator { token } => TokenType::Operator { + token: token.move_to(line, character, start, end), + }, + TokenType::Separator { token } => TokenType::Separator { + token: token.move_to(line, character, start, end), + }, + } + } + + pub fn is_new_line(&self) -> bool { + match self { + TokenType::Whitespace { token } => token.text().as_str() == "\n", + _ => false, + } + } + + pub fn is_space(&self) -> bool { + match self { + TokenType::Whitespace { token } => token.text().as_str() == " ", + _ => false, + } } } - pub fn is_new_line(&self) -> bool { - match self { - TokenType::Whitespace { token } => token.text().as_str() == "\n", - _ => false, + impl Deref for TokenType { + type Target = Token; + + fn deref(&self) -> &::Target { + match self { + TokenType::Whitespace { token } => token, + TokenType::Keyword { token } => token, + TokenType::String { token } => token, + TokenType::Number { token } => token, + TokenType::Identifier { token } => token, + TokenType::Literal { token } => token, + TokenType::Comment { token } => token, + TokenType::Operator { token } => token, + TokenType::Separator { token } => token, + } } } - pub fn is_space(&self) -> bool { - match self { - TokenType::Whitespace { token } => token.text().as_str() == " ", - _ => false, - } + #[derive(Debug, Clone)] + pub struct Token { + line: usize, + character: usize, + start: usize, + end: usize, + text: String, } -} -impl Deref for TokenType { - type Target = Token; - - fn deref(&self) -> &::Target { - match self { - TokenType::Whitespace { token } => token, - TokenType::Keyword { token } => token, - TokenType::String { token } => token, - TokenType::Number { token } => token, - TokenType::Identifier { token } => token, - TokenType::Literal { token } => token, - TokenType::Comment { token } => token, - TokenType::Operator { token } => token, - TokenType::Separator { token } => token, - } + #[derive(Debug, Clone, Copy)] + pub struct Span { + pub lo: usize, + pub hi: usize, } -} -#[derive(Debug, Clone)] -pub struct Token { - line: usize, - character: usize, - start: usize, - end: usize, - text: String, -} + impl Token { + pub fn new(text: String, line: usize, character: usize, start: usize, end: usize) -> Self { + Self { + text, + line, + character, + start, + end, + } + } -#[derive(Debug, Clone, Copy)] -pub struct Span { - pub lo: usize, - pub hi: usize, -} + pub fn text(&self) -> &String { + &self.text + } -impl Token { - pub fn new(text: String, line: usize, character: usize, start: usize, end: usize) -> Self { - Self { - text, - line, - character, - start, - end, + pub fn line(&self) -> usize { + self.line.clone() + } + + pub fn character(&self) -> usize { + self.character.clone() + } + + pub fn start(&self) -> usize { + self.start.clone() + } + + pub fn end(&self) -> usize { + self.end.clone() + } + + pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self { + Self { + text: self.text.clone(), + line, + character, + start, + end, + } } } - pub fn text(&self) -> &String { - &self.text - } - - pub fn line(&self) -> usize { - self.line.clone() - } - - pub fn character(&self) -> usize { - self.character.clone() - } - - pub fn start(&self) -> usize { - self.start.clone() - } - - pub fn end(&self) -> usize { - self.end.clone() - } - - pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self { - Self { - text: self.text.clone(), - line, - character, - start, - end, + pub fn parse(text: String, language: Language) -> Vec { + match language { + Language::PlainText => plain::lexer::Lexer::new(text.as_str()) + // .inspect(|tok| println!("tok: {:?}", tok)) + .map(|t| t.0) + .collect(), } } } - -pub fn parse(text: String, language: Language) -> Vec { - match language { - Language::PlainText => plain::lexer::Lexer::new(text.as_str()) -// .inspect(|tok| println!("tok: {:?}", tok)) - .map(|t| t.0) - .collect(), - } -}