diff --git a/Cargo.lock b/Cargo.lock index 69e27277ea..35315a04d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24606928a235e73cdef55a0c909719cadd72fce573e5713d58cb2952d8f5794c" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" + [[package]] name = "adler32" version = "1.2.0" @@ -27,15 +43,21 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "atomic_refcell" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" + [[package]] name = "audir-sles" version = "0.1.0" @@ -62,9 +84,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base-x" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74" [[package]] name = "bitflags" @@ -72,6 +94,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "bumpalo" version = "3.9.1" @@ -80,9 +108,23 @@ checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "562e382481975bc61d11275ac5e62a19abd00b0547d99516a415336f183dcd0e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -102,12 +144,36 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi", +] + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "copypasta" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + [[package]] name = "core-foundation" version = "0.6.4" @@ -164,6 +230,83 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "egui" +version = "0.18.1" +source = "git+https://github.com/aleokdev/egui#3c39b807317e640a5437b6e0280b4dccea0e431d" +dependencies = [ + "ahash 0.7.6", + "epaint", + "nohash-hasher", +] + +[[package]] +name = "egui-macroquad" +version = "0.9.0" +source = "git+https://github.com/aleokdev/egui-macroquad#1359301bdc2b043f00e166221a12f96eb034342c" +dependencies = [ + "egui", + "egui-miniquad", + "macroquad", +] + +[[package]] +name = "egui-miniquad" +version = "0.11.0" +source = "git+https://github.com/aleokdev/egui-miniquad#9ca4a5bd7153fe2fc7af03e2ee216bb85fd80b01" +dependencies = [ + "bytemuck", + "copypasta", + "egui", + "miniquad", + "quad-url", +] + +[[package]] +name = "egui_extras" +version = "0.18.0" +source = "git+https://github.com/aleokdev/egui#3c39b807317e640a5437b6e0280b4dccea0e431d" +dependencies = [ + "egui", +] + +[[package]] +name = "emath" +version = "0.18.0" +source = "git+https://github.com/aleokdev/egui#3c39b807317e640a5437b6e0280b4dccea0e431d" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "epaint" +version = "0.18.1" +source = "git+https://github.com/aleokdev/egui#3c39b807317e640a5437b6e0280b4dccea0e431d" +dependencies = [ + "ab_glyph", + "ahash 0.7.6", + "atomic_refcell", + "bytemuck", + "emath", + "nohash-hasher", + "parking_lot", +] + [[package]] name = "ff-particles" version = "0.1.2" @@ -178,6 +321,9 @@ dependencies = [ name = "fishfight" version = "0.4.2" dependencies = [ + "egui", + "egui-macroquad", + "egui_extras", "ff-particles", "fishfight-core", "fishsticks", @@ -206,8 +352,8 @@ dependencies = [ [[package]] name = "fishsticks" -version = "0.2.0" -source = "git+https://github.com/fishfight/fishsticks#95b45b717e427fb5fa1671f3bdf686d7270ad8a1" +version = "0.2.1" +source = "git+https://github.com/fishfight/fishsticks#ffd11a5b48abecadf8cd47033b1b26fff74b5557" dependencies = [ "cfg-if", "gilrs", @@ -226,14 +372,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75712fff1702bac51b7eaa5a5ca9f9853b8055ef5906088a32f4fe196595a1d" dependencies = [ "hashbrown 0.9.1", - "ttf-parser", + "ttf-parser 0.12.3", ] [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if", "js-sys", @@ -267,7 +413,7 @@ dependencies = [ "libc", "libudev-sys", "log", - "nix", + "nix 0.23.1", "rusty-xinput", "stdweb", "uuid", @@ -293,9 +439,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" dependencies = [ "ahash 0.7.6", ] @@ -306,7 +452,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d4e8eb4d5dd2ea100ffbb4eb6814f4294a7362fcafbc5c1c4c015b41d16f424" dependencies = [ - "hashbrown 0.12.0", + "hashbrown 0.12.1", "spin", ] @@ -349,13 +495,19 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -370,14 +522,24 @@ checksum = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0" dependencies = [ "byteorder", "ogg", - "smallvec", + "smallvec 0.6.14", ] [[package]] name = "libc" -version = "0.2.118" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" + +[[package]] +name = "libloading" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] [[package]] name = "libudev-sys" @@ -389,11 +551,21 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -409,9 +581,9 @@ dependencies = [ [[package]] name = "macroquad" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bbc44698249e98c7aedb8eef94b8806b5d68187c0c3d8c2a0912cb9efab1950" +checksum = "e69f39283065a2ebea3ae41294a44a9d61951d255917bdcc9ada0ec97612499f" dependencies = [ "bumpalo", "fontdue", @@ -438,12 +610,36 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5cecfede1e530599c8686f7f2d609489101d3d63741a6dc423afc997ce3fcc8" +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "maybe-uninit" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -453,11 +649,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniquad" -version = "0.3.0-alpha.42" +version = "0.3.0-alpha.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75bba73cbdaa1cb8f7889fcbb083941eae39ad4257d47e0d8e819d1de9963ba0" +checksum = "a5b39924083bf7da876e2d7125487eef77ea30f0c36a58fdfd04cb436eafaf5e" dependencies = [ "sapp-android", "sapp-darwin", @@ -483,6 +685,19 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nix" version = "0.23.1" @@ -496,11 +711,27 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -508,9 +739,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", @@ -530,13 +761,42 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "ogg" version = "0.7.1" @@ -548,15 +808,47 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "owned_ttf_parser" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1e509cfe7a12db2a90bfa057dfcdbc55a347f5da677c506b53dd099cfec9d" +dependencies = [ + "ttf-parser 0.15.0", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec 1.8.0", + "windows-sys", +] [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "png" @@ -572,9 +864,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" dependencies = [ "unicode-xid", ] @@ -607,15 +899,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "quad-url" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e674b7e6218e9d5d01ca431e2088b647cd75cdf483f8201533dcb1fecfa25f03" +dependencies = [ + "sapp-jsutils", + "webbrowser", +] + +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + [[package]] name = "quote" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + [[package]] name = "rustc_version" version = "0.2.3" @@ -644,9 +964,9 @@ checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "sapp-android" -version = "0.1.14" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c54dc8d8fc38874a0e5cca3a8fba35c7db8e46a3d65f47d0fc999aa48d6a9f" +checksum = "9c0d0e6f562c01c533f693ac9c045d69cbeab24d2e16caaaa0e67d06ae6e0940" dependencies = [ "libc", "ndk-sys", @@ -654,9 +974,9 @@ dependencies = [ [[package]] name = "sapp-darwin" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0310e2445f307468aa13f1cde94d6fba6b8fd329afbb642dedbe3faf1a145f31" +checksum = "f1282369f486bc334efa2d76fa4d54c20b37664d83da14fc60afbe941814e374" dependencies = [ "cc", ] @@ -679,11 +999,17 @@ dependencies = [ "cc", ] +[[package]] +name = "sapp-jsutils" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb8ababa867431fa6c0a178248bfe7e77b5d1de357c9848883ba8e3946bb21d4" + [[package]] name = "sapp-linux" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbdb2f8011955c62544d9e626a58333e788810d00bd7411d52b81611b92af142" +checksum = "dc71510a7781d8f31d58046a9117682c8e8546ad43f52c0e31275db214495c3d" dependencies = [ "libc", ] @@ -703,6 +1029,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "semver" version = "0.9.0" @@ -720,18 +1058,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -740,9 +1078,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "itoa", "ryu", @@ -773,11 +1111,45 @@ dependencies = [ "maybe-uninit", ] +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "smithay-client-toolkit" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" +dependencies = [ + "bitflags", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.22.3", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610b551bd25378bfd2b8e7a0fcbd83d427e8f2f6a40c47ae0f70688e9949dd55" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + [[package]] name = "spin" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" [[package]] name = "stdweb" @@ -832,9 +1204,9 @@ checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -843,9 +1215,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -856,11 +1228,17 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" +[[package]] +name = "ttf-parser" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c74c96594835e10fa545e2a51e8709f30b173a092bfd6036ef2cec53376244f3" + [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "uuid" @@ -888,9 +1266,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -898,9 +1276,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -913,9 +1291,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -923,9 +1301,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -936,20 +1314,110 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "wayland-client" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.22.3", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +dependencies = [ + "nix 0.22.3", + "once_cell", + "smallvec 1.8.0", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +dependencies = [ + "nix 0.22.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecad156490d6b620308ed411cfee90d280b3cbd13e189ea0d3fada8acc89158a" +dependencies = [ + "web-sys", + "widestring", + "winapi", +] + +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + [[package]] name = "winapi" version = "0.3.9" @@ -971,3 +1439,81 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "x11-clipboard" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" +dependencies = [ + "xcb", +] + +[[package]] +name = "xcb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" +dependencies = [ + "libc", + "log", + "quick-xml", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/Cargo.toml b/Cargo.toml index 9a8c0ad183..cec676e18f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,13 +22,22 @@ opt-level = 3 core = { path = "./core", package = "fishfight-core" } # ultimate = { path = "../FishFight-ultimate", package = "fishfight-ultimate", optional = true } ff-particles = { version = "0.1", features = ["serde"] } -fishsticks = { git = "https://github.com/fishfight/fishsticks", default-features = false, features = ["gilrs"] } +fishsticks = { git = "https://github.com/fishfight/fishsticks", default-features = false, features = [ + "gilrs", +] } macroquad = { version = "0.3.10" } macroquad-platformer = "0.1" hecs = "0.7.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +egui = "0.18.1" +egui_extras = { git = "https://github.com/aleokdev/egui" } +egui-macroquad = "0.9.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2.74" +[patch.crates-io] +# Temporal patch until https://github.com/optozorax/egui-macroquad/pull/17 gets merged +egui-macroquad = { git = "https://github.com/aleokdev/egui-macroquad" } +egui = { git = "https://github.com/aleokdev/egui" } diff --git a/assets/textures.json b/assets/textures.json index b84220fbc8..25c76546fb 100644 --- a/assets/textures.json +++ b/assets/textures.json @@ -83,6 +83,14 @@ "y": 49 } }, + { + "id": "object_error_icon", + "path": "textures/ui/editor_icons/error.png", + "sprite_size": { + "x": 32, + "y": 32 + } + }, { "id": "spawn_point_placement_tool_icon", "path": "textures/ui/editor_icons/Pointer and Map.png", @@ -631,4 +639,4 @@ "y": 6 } } -] +] \ No newline at end of file diff --git a/assets/textures/ui/editor_icons/error.png b/assets/textures/ui/editor_icons/error.png new file mode 100644 index 0000000000..4e676ddde8 Binary files /dev/null and b/assets/textures/ui/editor_icons/error.png differ diff --git a/core/src/input/mapping.rs b/core/src/input/mapping.rs index 582eb83f99..50200177e6 100644 --- a/core/src/input/mapping.rs +++ b/core/src/input/mapping.rs @@ -526,7 +526,7 @@ impl From for GamepadMapping { impl From<&GamepadId> for GamepadMapping { fn from(id: &GamepadId) -> Self { - let id: usize = id.into(); + let id: usize = (*id).into(); id.into() } } diff --git a/core/src/input/mod.rs b/core/src/input/mod.rs index 1eb22d3728..acc8eedf27 100644 --- a/core/src/input/mod.rs +++ b/core/src/input/mod.rs @@ -77,7 +77,7 @@ pub fn collect_local_input(input_scheme: GameInputScheme) -> PlayerInput { let config = storage::get::(); config .input - .get_gamepad_mapping(ix.into()) + .get_gamepad_mapping((*ix).into()) .unwrap_or_else(|| ix.into()) }; diff --git a/src/editor/RATIONALE.md b/src/editor/RATIONALE.md new file mode 100644 index 0000000000..cb09e18104 --- /dev/null +++ b/src/editor/RATIONALE.md @@ -0,0 +1,18 @@ +TODO: Delete this file before merging! + +- I changed the type of selected_tool from an option of type ID to an enum, because the available +tools are known at compiletime. +- The `actions` module file was too crowded, so I split all actions into their own files in `actions/`. +I think it's much more organized this way. +- Actions have been stripped from the `Action` suffix, as the parent module already tells us that +they are actions: `editor::actions::CreateLayerAction`. So instead of importing all the actions from +the editor and directly calling them with their names, I just import the actions module and refer to +them as `actions::CreateLayer` for instance. I think this is less redundant and also eliminates the +need to import all used actions, as their names are shorter. +- First, I tried to make the `ui` method in the editor state be immutable, sending *all* actions +through `UiAction`s being returned from the function. After a lot of work, I decided against making +it immutable. The reasoning behind this change is that passing all changes through the `UiAction` +enum is tiring and mechanical work that doesn't always have benefits, as there are some changes +that are *not* kept track of, such as opening windows, closing them, changing data within these +windows, etc. As of now, changes that do not require keeping track of are performed immediately, +while actions that do go through the history are sent through `apply_action` immediately. \ No newline at end of file diff --git a/src/editor/TODO.md b/src/editor/TODO.md new file mode 100644 index 0000000000..ed78ed60dc --- /dev/null +++ b/src/editor/TODO.md @@ -0,0 +1,13 @@ +- [X] Object list +- [X] Spawnpoint placement +- [X] Spawnpoint selection & movement +- [X] Object placement via rightclick context menu +- [X] Re-add object placement tool +- [X] Moving camera via middle click +- [X] Open map window +- [X] Import map window +- [X] New map window +- [X] Background edit window +- [X] Map zoom +- [ ] Theme support +- [ ] Gamepad support \ No newline at end of file diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 62553fb716..187a338315 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -1,47 +1,24 @@ -use std::any::TypeId; -use std::cmp::Ordering; - -use macroquad::experimental::collections::storage; use macroquad::prelude::*; -use core::error::{Error, ErrorKind, Result}; +use core::error::Result; + +use crate::map::{Map, MapLayerKind, MapTileset}; +use crate::map::{MapBackgroundLayer, MapObjectKind}; -use crate::editor::gui::windows::Window; -use crate::map::{MapBackgroundLayer, MapObject, MapObjectKind}; -use crate::{ - map::{Map, MapLayer, MapLayerKind, MapTile, MapTileset}, - Resources, -}; +use super::state::{EditorTool, SelectableEntity}; -/// These are all the actions available for the GUI and other sub-systems of the editor. +/// An enum describing a set of undoable actions done within the level editor. /// If you need to perform multiple actions in one call, use the `Batch` variant. #[derive(Debug, Clone)] -pub enum EditorAction { - Batch(Vec), +pub enum UiAction { + Batch(Vec), Undo, Redo, - SelectTool(Option), - OpenBackgroundPropertiesWindow, + SelectTool(EditorTool), UpdateBackground { color: Color, layers: Vec, }, - OpenCreateLayerWindow, - OpenCreateTilesetWindow, - OpenTilesetPropertiesWindow(String), - OpenCreateObjectWindow { - position: Vec2, - layer_id: String, - }, - OpenObjectPropertiesWindow { - layer_id: String, - index: usize, - }, - OpenTilePropertiesWindow { - layer_id: String, - index: usize, - }, - CloseWindow(TypeId), SelectTile { id: u32, tileset_id: String, @@ -68,7 +45,6 @@ pub enum EditorAction { is_visible: bool, }, SelectTileset(String), - OpenImportWindow(usize), Import { tilesets: Vec, background_color: Option, @@ -84,10 +60,6 @@ pub enum EditorAction { texture_id: String, autotile_mask: Vec, }, - SelectObject { - index: usize, - layer_id: String, - }, CreateObject { id: String, kind: MapObjectKind, @@ -103,6 +75,10 @@ pub enum EditorAction { index: usize, id: String, kind: MapObjectKind, + }, + MoveObject { + layer_id: String, + index: usize, position: Vec2, }, CreateSpawnPoint(Vec2), @@ -127,1170 +103,152 @@ pub enum EditorAction { tile_size: Vec2, grid_size: UVec2, }, - OpenCreateMapWindow, OpenMap(usize), - OpenLoadMapWindow, - SaveMap(Option), - OpenSaveMapWindow, + SaveMap { + name: Option, + }, DeleteMap(usize), - ExitToMainMenu, - QuitToDesktop, } -impl EditorAction { - pub fn batch(actions: &[EditorAction]) -> Self { +impl UiAction { + pub fn batch(actions: &[UiAction]) -> Self { Self::Batch(actions.to_vec()) } - pub fn close_window() -> Self { - let id = TypeId::of::(); - EditorAction::CloseWindow(id) - } - - pub fn then(self, action: EditorAction) -> Self { - Self::batch(&[self, action]) - } -} - -/// All actions that modify map data should implement this trait -pub trait UndoableAction { - fn apply(&mut self, _map: &mut Map) -> Result<()>; - - fn undo(&mut self, _map: &mut Map) -> Result<()>; - - fn redo(&mut self, map: &mut Map) -> Result<()> { - self.apply(map) - } - - /// Implement this for actions that can be redundant (ie. no change will take place if it is applied). - /// This is to avoid history being filled up with repeat actions if user is holding input down - /// for a long time, for example. - /// This should not be used to circumvent bugs and errors, however. It is meant to stop the same - /// action from firing several times in a row, for example from holding mouse down on the map - /// (placing multiple tiles, with the same id, to the same coords). - /// Edge cases, like an action wanting to delete a layer that does not exist, should be handled - /// with errors, in stead (basically, things like that shouldn't happen, as it should be prevented - /// at a higher level) - fn is_redundant(&self, _map: &Map) -> bool { - false - } -} - -#[derive(Debug)] -pub struct UpdateBackgroundAction { - color: Color, - old_color: Option, - layers: Vec, - old_layers: Option>, -} - -impl UpdateBackgroundAction { - pub fn new(color: Color, layers: Vec) -> Self { - UpdateBackgroundAction { - color, - old_color: None, - layers, - old_layers: None, - } - } -} - -impl UndoableAction for UpdateBackgroundAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - self.old_color = Some(map.background_color); - - map.background_color = self.color; - - self.old_layers = Some(map.background_layers.clone()); - - map.background_layers = self.layers.clone(); - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(color) = self.old_color.take() { - map.background_color = color; - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateBackgroundPropertiesAction (Undo): No old background color was found. Undo was probably called on an action that was never applied")); - } - - if let Some(layers) = self.old_layers.take() { - map.background_layers = layers; - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateBackgroundPropertiesAction (Undo): No old background layers was found. Undo was probably called on an action that was never applied")); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct SetLayerDrawOrderIndexAction { - id: String, - draw_order_index: usize, - old_draw_order_index: Option, -} - -impl SetLayerDrawOrderIndexAction { - pub fn new(id: String, draw_order_index: usize) -> Self { - SetLayerDrawOrderIndexAction { - id, - draw_order_index, - old_draw_order_index: None, - } - } -} - -impl UndoableAction for SetLayerDrawOrderIndexAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - for i in 0..map.draw_order.len() { - let id = map.draw_order.get(i).unwrap(); - if id == &self.id { - self.old_draw_order_index = Some(i); - map.draw_order.remove(i); - break; - } - } - - if self.old_draw_order_index.is_none() { - return Err(Error::new_const(ErrorKind::EditorAction, &"SetLayerDrawOrderIndexAction: Could not find the specified layer in the map draw order array")); - } - - if self.draw_order_index > map.draw_order.len() { - map.draw_order.push(self.id.clone()); - } else { - map.draw_order - .insert(self.draw_order_index, self.id.clone()); - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if map.draw_order.remove(self.draw_order_index) != self.id { - return Err(Error::new_const(ErrorKind::EditorAction, &"SetLayerDrawOrderIndexAction (Undo): There was a mismatch between the layer id found in at the specified draw order index and the id stored in the action")); - } - - if let Some(i) = self.old_draw_order_index { - if i > map.draw_order.len() { - map.draw_order.push(self.id.clone()); - } else { - map.draw_order.insert(i, self.id.clone()); - } - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"SetLayerDrawOrderIndexAction (Undo): No old draw order index was found. Undo was probably called on an action that was never applied")); - } - - Ok(()) - } - - fn is_redundant(&self, map: &Map) -> bool { - for i in 0..map.draw_order.len() { - let id = map.draw_order.get(i).unwrap(); - if id == &self.id && i == self.draw_order_index { - return true; - } - } - - false - } -} - -#[derive(Debug)] -pub struct UpdateTileAttributesAction { - index: usize, - layer_id: String, - attributes: Vec, - old_attributes: Option>, -} - -impl UpdateTileAttributesAction { - pub fn new(index: usize, layer_id: String, attributes: Vec) -> Self { - UpdateTileAttributesAction { - index, - layer_id, - attributes, - old_attributes: None, - } - } -} - -impl UndoableAction for UpdateTileAttributesAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - if let Some(Some(tile)) = layer.tiles.get_mut(self.index) { - self.old_attributes = Some(tile.attributes.clone()); - tile.attributes = self.attributes.clone(); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateTileAttributesAction: The specified tile does not exist", - )); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateTileAttributesAction: The specified layer does not exist", - )); - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - if let Some(Some(tile)) = layer.tiles.get_mut(self.index) { - if let Some(old_attributes) = self.old_attributes.take() { - tile.attributes = old_attributes; - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTileAttributesAction (Undo): No old attributes stored in action. Undo was probably called on an action that was never applied")); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateTileAttributesAction (Undo): The specified tile does not exist", - )); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateTileAttributesAction (Undo): The specified layer does not exist", - )); - } - - Ok(()) - } - - fn is_redundant(&self, map: &Map) -> bool { - if let Some(layer) = map.layers.get(&self.layer_id) { - if let Some(Some(tile)) = layer.tiles.get(self.index) { - let matching = tile - .attributes - .iter() - .zip(&self.attributes) - .filter(|&(a, b)| a == b) - .count(); - return matching == self.attributes.len(); - } - } - - false - } -} - -#[derive(Debug)] -pub struct CreateLayerAction { - id: String, - kind: MapLayerKind, - has_collision: bool, - index: Option, -} - -impl CreateLayerAction { - pub fn new(id: String, kind: MapLayerKind, has_collision: bool, index: Option) -> Self { - CreateLayerAction { - id, - kind, - has_collision, - index, - } - } -} - -impl UndoableAction for CreateLayerAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if map.layers.contains_key(&self.id) { - let len = map.draw_order.len(); - for i in 0..len { - let id = map.draw_order.get(i).unwrap(); - if id == &self.id { - map.draw_order.remove(i); - break; - } - } - } - - let layer = MapLayer::new(&self.id, self.kind, self.has_collision, map.grid_size); - map.layers.insert(self.id.clone(), layer); - - if let Some(i) = self.index { - if i <= map.draw_order.len() { - map.draw_order.insert(i, self.id.clone()); - - return Ok(()); - } - } - - map.draw_order.push(self.id.clone()); - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if map.layers.remove(&self.id).is_none() { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"CreateLayerAction (Undo): The specified layer does not exist", - )); - } - - if let Some(i) = self.index { - let id = map.draw_order.remove(i); - if id != self.id { - return Err(Error::new_const(ErrorKind::EditorAction, &"CreateLayerAction (Undo): There is a mismatch between the actions layer id and the one found at the actions draw order index")); - } - } else { - map.draw_order.retain(|id| id != &self.id); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct DeleteLayerAction { - id: String, - layer: Option, - draw_order_index: Option, -} - -impl DeleteLayerAction { - pub fn new(id: String) -> Self { - DeleteLayerAction { - id, - layer: None, - draw_order_index: None, - } - } -} - -impl UndoableAction for DeleteLayerAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.remove(&self.id) { - self.layer = Some(layer); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"DeleteLayerAction: The specified layer does not exist", - )); - } - - let len = map.draw_order.len(); - for i in 0..len { - let layer_id = map.draw_order.get(i).unwrap(); - if layer_id == &self.id { - map.draw_order.remove(i); - self.draw_order_index = Some(i); - break; - } - } - - if self.draw_order_index.is_none() { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"DeleteLayerAction: The specified layer was not found in the draw order array", - )); - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = self.layer.take() { - map.layers.insert(self.id.clone(), layer); - if let Some(i) = self.draw_order_index { - if i >= map.draw_order.len() { - map.draw_order.push(self.id.clone()); - } else { - map.draw_order.insert(i, self.id.clone()); - } - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteLayerAction (Undo): No draw order index stored in action. Undo was probably called on an action that was never applied")); - } - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteLayerAction (Undo): No layer stored in action. Undo was probably called on an action that was never applied")); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct UpdateLayerAction { - id: String, - is_visible: bool, - old_is_visible: Option, -} - -impl UpdateLayerAction { - pub fn new(id: String, is_visible: bool) -> Self { - UpdateLayerAction { - id, - is_visible, - old_is_visible: None, - } - } -} - -impl UndoableAction for UpdateLayerAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.id) { - self.old_is_visible = Some(layer.is_visible); - layer.is_visible = self.is_visible; - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateLayerAction: The specified layer does not exist", - )); - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.id) { - if let Some(old_is_visible) = self.old_is_visible.take() { - layer.is_visible = old_is_visible; - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateLayerAction (Undo): No `old_is_visible` on action. Undo was probably called on an action that was never applied")); + pub fn then(mut self, action: UiAction) -> Self { + match &mut self { + UiAction::Batch(batch) => { + batch.push(action); + self } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateLayerAction (Undo): The specified layer does not exist", - )); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct ImportAction { - tilesets: Vec, - background_color: Option, - old_background_color: Option, - background_layers: Vec, - old_background_layers: Vec, -} - -impl ImportAction { - pub fn new( - tilesets: Vec, - background_color: Option, - background_layers: Vec, - ) -> Self { - ImportAction { - tilesets, - background_color, - old_background_color: None, - background_layers, - old_background_layers: Vec::new(), + _ => Self::batch(&[self, action]), } } -} -impl UndoableAction for ImportAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - for tileset in &self.tilesets { - let mut first_tile_id = 1; - for tileset in map.tilesets.values() { - let next_tile_id = tileset.first_tile_id + tileset.tile_cnt; - if next_tile_id > first_tile_id { - first_tile_id = next_tile_id; - } + pub fn then_do(&mut self, action: UiAction) { + match self { + UiAction::Batch(batch) => { + batch.push(action); } - - let tileset = MapTileset { - id: tileset.id.clone(), - texture_id: tileset.texture_id.clone(), - texture_size: tileset.texture_size, - tile_size: tileset.tile_size, - grid_size: tileset.grid_size, - first_tile_id, - tile_cnt: tileset.tile_cnt, - tile_subdivisions: tileset.tile_subdivisions, - autotile_mask: tileset.autotile_mask.clone(), - tile_attributes: tileset.tile_attributes.clone(), - properties: tileset.properties.clone(), - bitmasks: None, - }; - - map.tilesets.insert(tileset.id.clone(), tileset); - } - - if let Some(background_color) = self.background_color { - self.old_background_color = Some(map.background_color); - map.background_color = background_color; - } - - self.old_background_layers = map.background_layers.clone(); - - map.background_layers - .append(&mut self.background_layers.clone()); - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - for tileset in &self.tilesets { - if map.tilesets.remove(&tileset.id).is_none() { - return Err(Error::new_const(ErrorKind::EditorAction, &"ImportTilesetsAction (Undo): One of the imported tilesets could not be found in the map")); - } - } - - if let Some(background_color) = self.old_background_color.take() { - map.background_color = background_color; - } - - map.background_layers = self.old_background_layers.drain(..).collect(); - - Ok(()) - } -} - -#[derive(Debug)] -pub struct CreateTilesetAction { - id: String, - texture_id: String, -} - -impl CreateTilesetAction { - pub fn new(id: String, texture_id: String) -> Self { - CreateTilesetAction { id, texture_id } - } -} - -impl UndoableAction for CreateTilesetAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - let resources = storage::get::(); - if let Some(texture_entry) = resources.textures.get(&self.texture_id).cloned() { - let texture_size = uvec2( - texture_entry.texture.width() as u32, - texture_entry.texture.height() as u32, - ); - - let mut first_tile_id = 1; - for tileset in map.tilesets.values() { - let next_tile_id = tileset.first_tile_id + tileset.tile_cnt; - if next_tile_id > first_tile_id { - first_tile_id = next_tile_id; - } - } - - let tileset = MapTileset::new( - &self.id, - &self.texture_id, - texture_size, - map.tile_size, - first_tile_id, - ); - - map.tilesets.insert(self.id.clone(), tileset); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"CreateTilesetAction: The specified texture does not exist", - )); - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if map.tilesets.remove(&self.id).is_none() { - return Err(Error::new_const(ErrorKind::EditorAction, &"CreateTilesetAction (Undo): The specified tileset does not exist. Undo was probably called on an action that was never applied")); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct DeleteTilesetAction { - id: String, - tileset: Option, -} - -impl DeleteTilesetAction { - pub fn new(id: String) -> Self { - DeleteTilesetAction { id, tileset: None } - } -} - -impl UndoableAction for DeleteTilesetAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(tileset) = map.tilesets.remove(&self.id) { - self.tileset = Some(tileset); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"DeleteTilesetAction: The specified tileset does not exist", - )); - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(tileset) = self.tileset.take() { - map.tilesets.insert(self.id.clone(), tileset); - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteTilesetAction (Undo): No tileset stored in action. Undo was probably called on an action that was never applied")); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct UpdateTilesetAction { - id: String, - texture_id: String, - old_texture_id: Option, - autotile_mask: Vec, - old_autotile_mask: Option>, -} - -impl UpdateTilesetAction { - pub fn new(id: String, texture_id: String, autotile_mask: Vec) -> Self { - UpdateTilesetAction { - id, - texture_id, - old_texture_id: None, - autotile_mask, - old_autotile_mask: None, - } - } -} - -impl UndoableAction for UpdateTilesetAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(tileset) = map.tilesets.get_mut(&self.id) { - self.old_texture_id = Some(tileset.texture_id.clone()); - tileset.texture_id = self.texture_id.clone(); - - if self.autotile_mask.len() != tileset.autotile_mask.len() { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTilesetAction: There is a size mismatch between the actions autotile mask vector and the one on the tileset")); - } - - self.old_autotile_mask = Some(tileset.autotile_mask.clone()); - tileset.autotile_mask = self.autotile_mask.clone(); - - tileset.bitmasks = tileset.get_bitmasks(); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateTilesetAction: The specified tileset does not exist", - )); - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(tileset) = map.tilesets.get_mut(&self.id) { - if let Some(old_texture_id) = &self.old_texture_id { - tileset.texture_id = old_texture_id.clone(); - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTilesetAction (Undo): No old texture id stored in action. Undo was probably called on an action that was never applied")); - } - - if let Some(old_autotile_mask) = &self.old_autotile_mask { - if old_autotile_mask.len() != tileset.autotile_mask.len() { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTilesetAction (Undo): There is a size mismatch between the actions autotile mask vector and the one on the tileset")); - } - - tileset.autotile_mask = old_autotile_mask.clone(); - self.old_autotile_mask = None; - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTilesetAction (Undo): No old autotile mask stored in action. Undo was probably called on an action that was never applied")); + _ => { + let mut temp = Self::Batch(Vec::with_capacity(2)); + std::mem::swap(&mut temp, self); + self.then_do(temp); + self.then_do(action); } - - tileset.bitmasks = tileset.get_bitmasks(); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateTilesetAction (Undo): The specified tileset does not exist", - )); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct CreateObjectAction { - id: String, - kind: MapObjectKind, - position: Vec2, - layer_id: String, -} - -impl CreateObjectAction { - pub fn new(id: String, kind: MapObjectKind, position: Vec2, layer_id: String) -> Self { - CreateObjectAction { - id, - kind, - position, - layer_id, - } - } -} - -impl UndoableAction for CreateObjectAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - let object = MapObject::new(&self.id, self.kind, self.position); - - layer.objects.insert(0, object); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"CreateObjectAction: The specified layer does not exist", - )); } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - layer.objects.remove(0); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"CreateObjectAction (Undo): The specified layer does not exist", - )); - } - - Ok(()) } } -#[derive(Debug)] -pub struct DeleteObjectAction { - index: usize, - layer_id: String, - object: Option, +pub trait UiActionExt { + fn then(self, action: Option) -> Option; + fn then_some(self, action: UiAction) -> Option; + fn then_do(&mut self, action: Option); + fn then_do_some(&mut self, action: UiAction); } -impl DeleteObjectAction { - pub fn new(index: usize, layer_id: String) -> Self { - DeleteObjectAction { - index, - layer_id, - object: None, +impl UiActionExt for Option { + fn then(self, action: Option) -> Option { + match action { + Some(action) => self.then_some(action), + None => self, } } -} -impl UndoableAction for DeleteObjectAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - let object = layer.objects.remove(self.index); - self.object = Some(object); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"DeleteObjectAction: The specified layer does not exist", - )); + fn then_some(self, action: UiAction) -> Option { + match self { + Some(self_action) => Some(self_action.then(action)), + None => Some(action), } - - Ok(()) } - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(object) = self.object.take() { - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - layer.objects.insert(self.index, object); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"DeleteObjectAction (Undo): The specified layer does not exist", - )); - } - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteObjectAction (Undo): No object stored in action. Undo was probably called on an action that was never applied")); + fn then_do(&mut self, action: Option) { + if let Some(action) = action { + self.then_do_some(action); } - - Ok(()) } -} - -#[derive(Debug)] -pub struct UpdateObjectAction { - layer_id: String, - index: usize, - id: String, - kind: MapObjectKind, - position: Vec2, - object: Option, -} -impl UpdateObjectAction { - pub fn new( - layer_id: String, - index: usize, - id: String, - kind: MapObjectKind, - position: Vec2, - ) -> Self { - UpdateObjectAction { - layer_id, - index, - id, - kind, - position, - object: None, + fn then_do_some(&mut self, action: UiAction) { + match self { + Some(self_action) => self_action.then_do(action), + None => *self = Some(action), } } } -impl UndoableAction for UpdateObjectAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - if let Some(object) = layer.objects.get_mut(self.index) { - self.object = Some(object.clone()); - - object.id = self.id.clone(); - object.kind = self.kind; - object.position = self.position; - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateObjectAction: The specified object index does not exist", - )); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateObjectAction: The specified layer does not exist", - )); - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - if let Some(object) = self.object.take() { - layer.objects[self.index] = object; - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateObjectAction: No object found on action. Undo was probably called on an action that was never applied")); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"UpdateObjectAction (Undo): The specified layer does not exist", - )); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct CreateSpawnPointAction { - position: Vec2, -} - -impl CreateSpawnPointAction { - pub fn new(position: Vec2) -> Self { - CreateSpawnPointAction { position } - } -} - -impl UndoableAction for CreateSpawnPointAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - map.spawn_points.push(self.position); - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - map.spawn_points.pop(); - - Ok(()) - } -} - -#[derive(Debug)] -pub struct DeleteSpawnPointAction { - index: usize, - spawn_point: Option, -} - -impl DeleteSpawnPointAction { - pub fn new(index: usize) -> Self { - DeleteSpawnPointAction { - index, - spawn_point: None, - } - } -} - -impl UndoableAction for DeleteSpawnPointAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - let spawn_point = map.spawn_points.remove(self.index); - self.spawn_point = Some(spawn_point); - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(spawn_point) = self.spawn_point.take() { - if self.index >= map.spawn_points.len() { - map.spawn_points.push(spawn_point); - } else { - map.spawn_points.insert(self.index, spawn_point); - } - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteSpawnPointAction (Undo): No spawn point saved in action. Undo was probably called on an action that was never applied")); - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct MoveSpawnPointAction { - index: usize, - position: Vec2, - old_position: Option, -} - -impl MoveSpawnPointAction { - pub fn new(index: usize, position: Vec2) -> Self { - MoveSpawnPointAction { - index, - position, - old_position: None, - } - } -} - -impl UndoableAction for MoveSpawnPointAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - let old_position = map.spawn_points.remove(self.index); - self.old_position = Some(old_position); - - match map.spawn_points.len().cmp(&self.index) { - Ordering::Equal => map.spawn_points.push(self.position), - Ordering::Greater => map.spawn_points.insert(self.index, self.position), - _ => { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"MoveSpawnPointAction: Index out of bounds", - )) - } - } - - Ok(()) - } - - fn undo(&mut self, map: &mut Map) -> Result<()> { - if let Some(old_position) = self.old_position { - map.spawn_points[self.index] = old_position; - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"MoveSpawnPointAction (Undo): No old position saved in action. Undo was probably called on an action that was never applied")); - } - - Ok(()) - } -} +/// All actions that modify map data should implement this trait +pub trait UndoableAction { + fn apply_to(&mut self, _map: &mut Map) -> Result<()>; -#[derive(Debug)] -pub struct PlaceTileAction { - id: u32, - layer_id: String, - tileset_id: String, - coords: UVec2, - replaced_tile: Option, -} + fn undo(&mut self, _map: &mut Map) -> Result<()>; -impl PlaceTileAction { - pub fn new(id: u32, layer_id: String, tileset_id: String, coords: UVec2) -> Self { - PlaceTileAction { - id, - layer_id, - tileset_id, - coords, - replaced_tile: None, - } + /// Implement this for actions that can be redundant (ie. no change will take place if it is applied). + /// This is to avoid history being filled up with repeat actions if user is holding input down + /// for a long time, for example. + /// This should not be used to circumvent bugs and errors, however. It is meant to stop the same + /// action from firing several times in a row, for example from holding mouse down on the map + /// (placing multiple tiles, with the same id, to the same coords). + /// Edge cases, like an action wanting to delete a layer that does not exist, should be handled + /// with errors, in stead (basically, things like that shouldn't happen, as it should be prevented + /// at a higher level) + fn is_redundant(&self, _map: &Map) -> bool { + false } } -impl UndoableAction for PlaceTileAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - if let Some(tileset) = map.tilesets.get(&self.tileset_id) { - let texture_id = tileset.texture_id.clone(); - let texture_coords = tileset.get_texture_coords(self.id); +mod update_background; +pub use update_background::*; - let i = map.to_index(self.coords); +mod set_layer_draw_order_index; +pub use set_layer_draw_order_index::*; - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - if let MapLayerKind::TileLayer = layer.kind { - self.replaced_tile = layer.tiles.remove(i as usize); +mod update_tile_attributes; +pub use update_tile_attributes::*; - let tile = MapTile { - tile_id: self.id, - tileset_id: self.tileset_id.clone(), - texture_id, - texture_coords, - attributes: vec![], - }; +mod create_layer; +pub use create_layer::*; - layer.tiles.insert(i as usize, Some(tile)); - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"PlaceTileAction: The specified layer is not a tile layer", - )); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"PlaceTileAction: The specified layer does not exist", - )); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"PlaceTileAction: The specified tileset does not exist", - )); - } +mod delete_layer; +pub use delete_layer::*; - Ok(()) - } +mod update_layer; +pub use update_layer::*; - fn undo(&mut self, map: &mut Map) -> Result<()> { - let i = map.to_index(self.coords); +mod import; +pub use import::*; - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - if let MapLayerKind::TileLayer = layer.kind { - if layer.tiles.get(i as usize).is_none() { - return Err(Error::new_const(ErrorKind::EditorAction, &"PlaceTileAction (Undo): No tile at the specified coords, in the specified layer. Undo was probably called on an action that was never applied")); - } +mod create_tileset; +pub use create_tileset::*; - if let Some(tile) = self.replaced_tile.take() { - layer.tiles[i as usize] = Some(tile); - } else { - layer.tiles[i as usize] = None; - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"PlaceTileAction (Undo): The specified layer is not a tile layer", - )); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"PlaceTileAction (Undo): The specified layer does not exist", - )); - } +mod delete_tileset; +pub use delete_tileset::*; - Ok(()) - } +mod update_tileset; +pub use update_tileset::*; - fn is_redundant(&self, map: &Map) -> bool { - if let Some(layer) = map.layers.get(&self.layer_id) { - let i = map.to_index(self.coords); - if let Some(Some(tile)) = layer.tiles.get(i) { - return tile.tileset_id == self.tileset_id && tile.tile_id == self.id; - } - } - - false - } -} +mod create_object; +pub use create_object::*; -#[derive(Debug)] -pub struct RemoveTileAction { - layer_id: String, - coords: UVec2, - tile: Option, -} +mod delete_object; +pub use delete_object::*; -impl RemoveTileAction { - pub fn new(layer_id: String, coords: UVec2) -> Self { - RemoveTileAction { - layer_id, - coords, - tile: None, - } - } -} +mod update_object; +pub use update_object::*; -impl UndoableAction for RemoveTileAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - let i = map.to_index(self.coords); +mod move_object; +pub use move_object::*; - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - if let MapLayerKind::TileLayer = layer.kind { - if let Some(tile) = layer.tiles.remove(i as usize) { - self.tile = Some(tile); +mod create_spawn_point; +pub use create_spawn_point::*; - layer.tiles.insert(i, None); - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"RemoveTileAction: No tile at the specified coords, in the specified layer. Undo was probably called on an action that was never applied")); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"RemoveTileAction: The specified layer is not a tile layer", - )); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"RemoveTileAction: The specified layer does not exist", - )); - } +mod delete_spawn_point; +pub use delete_spawn_point::*; - Ok(()) - } +mod move_spawn_point; +pub use move_spawn_point::*; - fn undo(&mut self, map: &mut Map) -> Result<()> { - let i = map.to_index(self.coords); +mod place_tile; +pub use place_tile::*; - if let Some(layer) = map.layers.get_mut(&self.layer_id) { - if let MapLayerKind::TileLayer = layer.kind { - if let Some(old_tile) = self.tile.take() { - if let Some(tile) = layer.tiles.get_mut(i) { - *tile = Some(old_tile); - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"RemoveTileAction (Undo): No tile found vec entry in map at the index stored in action (this should not be possible, as the entry should be a `None` if the tile was empty)")); - } - } else { - return Err(Error::new_const(ErrorKind::EditorAction, &"RemoveTileAction (Undo): No tile stored in action. Undo was probably called on an action that was never applied")); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"RemoveTileAction (Undo): The specified layer is not a tile layer", - )); - } - } else { - return Err(Error::new_const( - ErrorKind::EditorAction, - &"RemoveTileAction (Undo): The specified layer does not exist", - )); - } - - Ok(()) - } - - fn is_redundant(&self, map: &Map) -> bool { - let i = map.to_index(self.coords); - if let Some(layer) = map.layers.get(&self.layer_id) { - if let Some(tile) = layer.tiles.get(i) { - return tile.is_none(); - } - } - - false - } -} +mod remove_tile; +pub use remove_tile::*; diff --git a/src/editor/actions/create_layer.rs b/src/editor/actions/create_layer.rs new file mode 100644 index 0000000000..149324ef7b --- /dev/null +++ b/src/editor/actions/create_layer.rs @@ -0,0 +1,82 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use crate::map::MapLayer; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapLayerKind; + +#[derive(Debug)] +pub struct CreateLayer { + id: String, + kind: MapLayerKind, + has_collision: bool, + index: Option, +} + +impl CreateLayer { + pub fn new(id: String, kind: MapLayerKind, has_collision: bool, index: Option) -> Self { + CreateLayer { + id, + kind, + has_collision, + index, + } + } +} + +impl UndoableAction for CreateLayer { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if map.layers.contains_key(&self.id) { + let len = map.draw_order.len(); + for i in 0..len { + let id = map.draw_order.get(i).unwrap(); + if id == &self.id { + map.draw_order.remove(i); + break; + } + } + } + + let layer = MapLayer::new(&self.id, self.kind, self.has_collision, map.grid_size); + map.layers.insert(self.id.clone(), layer); + + if let Some(i) = self.index { + if i <= map.draw_order.len() { + map.draw_order.insert(i, self.id.clone()); + + return Ok(()); + } + } + + map.draw_order.push(self.id.clone()); + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if map.layers.remove(&self.id).is_none() { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"CreateLayer (Undo): The specified layer does not exist", + )); + } + + if let Some(i) = self.index { + let id = map.draw_order.remove(i); + if id != self.id { + return Err(Error::new_const(ErrorKind::EditorAction, &"CreateLayer (Undo): There is a mismatch between the actions layer id and the one found at the actions draw order index")); + } + } else { + map.draw_order.retain(|id| id != &self.id); + } + + Ok(()) + } +} diff --git a/src/editor/actions/create_object.rs b/src/editor/actions/create_object.rs new file mode 100644 index 0000000000..2c8f88abfe --- /dev/null +++ b/src/editor/actions/create_object.rs @@ -0,0 +1,64 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use macroquad::prelude::Vec2; + +use crate::map::MapObject; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapObjectKind; + +#[derive(Debug)] +pub struct CreateObject { + id: String, + kind: MapObjectKind, + position: Vec2, + layer_id: String, +} + +impl CreateObject { + pub fn new(id: String, kind: MapObjectKind, position: Vec2, layer_id: String) -> Self { + CreateObject { + id, + kind, + position, + layer_id, + } + } +} + +impl UndoableAction for CreateObject { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + let object = MapObject::new(&self.id, self.kind, self.position); + + layer.objects.insert(0, object); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"CreateObject: The specified layer does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + layer.objects.remove(0); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"CreateObject (Undo): The specified layer does not exist", + )); + } + + Ok(()) + } +} diff --git a/src/editor/actions/create_spawn_point.rs b/src/editor/actions/create_spawn_point.rs new file mode 100644 index 0000000000..18f3948d8f --- /dev/null +++ b/src/editor/actions/create_spawn_point.rs @@ -0,0 +1,32 @@ +use core::error::Result; + +use macroquad::prelude::Vec2; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct CreateSpawnPoint { + position: Vec2, +} + +impl CreateSpawnPoint { + pub fn new(position: Vec2) -> Self { + CreateSpawnPoint { position } + } +} + +impl UndoableAction for CreateSpawnPoint { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + map.spawn_points.push(self.position); + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + map.spawn_points.pop(); + + Ok(()) + } +} diff --git a/src/editor/actions/create_tileset.rs b/src/editor/actions/create_tileset.rs new file mode 100644 index 0000000000..905b548e0d --- /dev/null +++ b/src/editor/actions/create_tileset.rs @@ -0,0 +1,73 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use crate::map::MapTileset; + +use crate::Resources; + +use macroquad::experimental::collections::storage; +use macroquad::prelude::uvec2; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct CreateTileset { + id: String, + texture_id: String, +} + +impl CreateTileset { + pub fn new(id: String, texture_id: String) -> Self { + CreateTileset { id, texture_id } + } +} + +impl UndoableAction for CreateTileset { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + let resources = storage::get::(); + if let Some(texture_entry) = resources.textures.get(&self.texture_id).cloned() { + let texture_size = uvec2( + texture_entry.texture.width() as u32, + texture_entry.texture.height() as u32, + ); + + let mut first_tile_id = 1; + for tileset in map.tilesets.values() { + let next_tile_id = tileset.first_tile_id + tileset.tile_cnt; + if next_tile_id > first_tile_id { + first_tile_id = next_tile_id; + } + } + + let tileset = MapTileset::new( + &self.id, + &self.texture_id, + texture_size, + map.tile_size, + first_tile_id, + ); + + map.tilesets.insert(self.id.clone(), tileset); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"CreateTileset: The specified texture does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if map.tilesets.remove(&self.id).is_none() { + return Err(Error::new_const(ErrorKind::EditorAction, &"CreateTileset (Undo): The specified tileset does not exist. Undo was probably called on an action that was never applied")); + } + + Ok(()) + } +} diff --git a/src/editor/actions/delete_layer.rs b/src/editor/actions/delete_layer.rs new file mode 100644 index 0000000000..97dfee6802 --- /dev/null +++ b/src/editor/actions/delete_layer.rs @@ -0,0 +1,79 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapLayer; + +#[derive(Debug)] +pub struct DeleteLayer { + id: String, + layer: Option, + draw_order_index: Option, +} + +impl DeleteLayer { + pub fn new(id: String) -> Self { + DeleteLayer { + id, + layer: None, + draw_order_index: None, + } + } +} + +impl UndoableAction for DeleteLayer { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.remove(&self.id) { + self.layer = Some(layer); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"DeleteLayer: The specified layer does not exist", + )); + } + + let len = map.draw_order.len(); + for i in 0..len { + let layer_id = map.draw_order.get(i).unwrap(); + if layer_id == &self.id { + map.draw_order.remove(i); + self.draw_order_index = Some(i); + break; + } + } + + if self.draw_order_index.is_none() { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"DeleteLayer: The specified layer was not found in the draw order array", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = self.layer.take() { + map.layers.insert(self.id.clone(), layer); + if let Some(i) = self.draw_order_index { + if i >= map.draw_order.len() { + map.draw_order.push(self.id.clone()); + } else { + map.draw_order.insert(i, self.id.clone()); + } + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteLayer (Undo): No draw order index stored in action. Undo was probably called on an action that was never applied")); + } + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteLayer (Undo): No layer stored in action. Undo was probably called on an action that was never applied")); + } + + Ok(()) + } +} diff --git a/src/editor/actions/delete_object.rs b/src/editor/actions/delete_object.rs new file mode 100644 index 0000000000..06023506c5 --- /dev/null +++ b/src/editor/actions/delete_object.rs @@ -0,0 +1,61 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapObject; + +#[derive(Debug)] +pub struct DeleteObject { + index: usize, + layer_id: String, + object: Option, +} + +impl DeleteObject { + pub fn new(index: usize, layer_id: String) -> Self { + DeleteObject { + index, + layer_id, + object: None, + } + } +} + +impl UndoableAction for DeleteObject { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + let object = layer.objects.remove(self.index); + self.object = Some(object); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"DeleteObject: The specified layer does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(object) = self.object.take() { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + layer.objects.insert(self.index, object); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"DeleteObject (Undo): The specified layer does not exist", + )); + } + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteObject (Undo): No object stored in action. Undo was probably called on an action that was never applied")); + } + + Ok(()) + } +} diff --git a/src/editor/actions/delete_spawn_point.rs b/src/editor/actions/delete_spawn_point.rs new file mode 100644 index 0000000000..d0c696733c --- /dev/null +++ b/src/editor/actions/delete_spawn_point.rs @@ -0,0 +1,49 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use macroquad::prelude::Vec2; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct DeleteSpawnPoint { + index: usize, + spawn_point: Option, +} + +impl DeleteSpawnPoint { + pub fn new(index: usize) -> Self { + DeleteSpawnPoint { + index, + spawn_point: None, + } + } +} + +impl UndoableAction for DeleteSpawnPoint { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + let spawn_point = map.spawn_points.remove(self.index); + self.spawn_point = Some(spawn_point); + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(spawn_point) = self.spawn_point.take() { + if self.index >= map.spawn_points.len() { + map.spawn_points.push(spawn_point); + } else { + map.spawn_points.insert(self.index, spawn_point); + } + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteSpawnPoint (Undo): No spawn point saved in action. Undo was probably called on an action that was never applied")); + } + + Ok(()) + } +} diff --git a/src/editor/actions/delete_tileset.rs b/src/editor/actions/delete_tileset.rs new file mode 100644 index 0000000000..bc15d9cc96 --- /dev/null +++ b/src/editor/actions/delete_tileset.rs @@ -0,0 +1,48 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapTileset; + +#[derive(Debug)] +pub struct DeleteTileset { + id: String, + tileset: Option, +} + +impl DeleteTileset { + pub fn new(id: String) -> Self { + DeleteTileset { id, tileset: None } + } +} + +impl UndoableAction for DeleteTileset { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(tileset) = map.tilesets.remove(&self.id) { + self.tileset = Some(tileset); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"DeleteTileset: The specified tileset does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(tileset) = self.tileset.take() { + map.tilesets.insert(self.id.clone(), tileset); + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"DeleteTileset (Undo): No tileset stored in action. Undo was probably called on an action that was never applied")); + } + + Ok(()) + } +} diff --git a/src/editor/actions/import.rs b/src/editor/actions/import.rs new file mode 100644 index 0000000000..cf65878d98 --- /dev/null +++ b/src/editor/actions/import.rs @@ -0,0 +1,99 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use macroquad::prelude::Color; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapBackgroundLayer; + +use crate::map::MapTileset; + +#[derive(Debug)] +pub struct Import { + tilesets: Vec, + background_color: Option, + old_background_color: Option, + background_layers: Vec, + old_background_layers: Vec, +} + +impl Import { + pub fn new( + tilesets: Vec, + background_color: Option, + background_layers: Vec, + ) -> Self { + Import { + tilesets, + background_color, + old_background_color: None, + background_layers, + old_background_layers: Vec::new(), + } + } +} + +impl UndoableAction for Import { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + for tileset in &self.tilesets { + let mut first_tile_id = 1; + for tileset in map.tilesets.values() { + let next_tile_id = tileset.first_tile_id + tileset.tile_cnt; + if next_tile_id > first_tile_id { + first_tile_id = next_tile_id; + } + } + + let tileset = MapTileset { + id: tileset.id.clone(), + texture_id: tileset.texture_id.clone(), + texture_size: tileset.texture_size, + tile_size: tileset.tile_size, + grid_size: tileset.grid_size, + first_tile_id, + tile_cnt: tileset.tile_cnt, + tile_subdivisions: tileset.tile_subdivisions, + autotile_mask: tileset.autotile_mask.clone(), + tile_attributes: tileset.tile_attributes.clone(), + properties: tileset.properties.clone(), + bitmasks: None, + }; + + map.tilesets.insert(tileset.id.clone(), tileset); + } + + if let Some(background_color) = self.background_color { + self.old_background_color = Some(map.background_color); + map.background_color = background_color; + } + + self.old_background_layers = map.background_layers.clone(); + + map.background_layers + .append(&mut self.background_layers.clone()); + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + for tileset in &self.tilesets { + if map.tilesets.remove(&tileset.id).is_none() { + return Err(Error::new_const(ErrorKind::EditorAction, &"ImportTilesets (Undo): One of the imported tilesets could not be found in the map")); + } + } + + if let Some(background_color) = self.old_background_color.take() { + map.background_color = background_color; + } + + map.background_layers = self.old_background_layers.drain(..).collect(); + + Ok(()) + } +} diff --git a/src/editor/actions/move_object.rs b/src/editor/actions/move_object.rs new file mode 100644 index 0000000000..308f85f342 --- /dev/null +++ b/src/editor/actions/move_object.rs @@ -0,0 +1,71 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use macroquad::prelude::Vec2; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct MoveObject { + layer_id: String, + index: usize, + position: Vec2, + old_position: Option, +} + +impl MoveObject { + pub fn new(layer_id: String, index: usize, position: Vec2) -> Self { + MoveObject { + layer_id, + index, + position, + old_position: None, + } + } +} + +impl UndoableAction for MoveObject { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let Some(object) = layer.objects.get_mut(self.index) { + self.old_position = Some(object.position); + + object.position = self.position; + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"MoveObject: The specified object index does not exist", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"MoveObject: The specified layer does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let Some(object) = self.old_position.take() { + layer.objects[self.index].position = object; + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"MoveObject: No old position found on action. Undo was probably called on an action that was never applied")); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"MoveObject (Undo): The specified layer does not exist", + )); + } + + Ok(()) + } +} diff --git a/src/editor/actions/move_spawn_point.rs b/src/editor/actions/move_spawn_point.rs new file mode 100644 index 0000000000..1a7ca96c1e --- /dev/null +++ b/src/editor/actions/move_spawn_point.rs @@ -0,0 +1,48 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use macroquad::prelude::Vec2; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct MoveSpawnPoint { + index: usize, + position: Vec2, + old_position: Option, +} + +impl MoveSpawnPoint { + pub fn new(index: usize, position: Vec2) -> Self { + MoveSpawnPoint { + index, + position, + old_position: None, + } + } +} + +impl UndoableAction for MoveSpawnPoint { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + self.old_position = Some(map.spawn_points[self.index]); + + map.spawn_points[self.index] = self.position; + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(old_position) = self.old_position { + map.spawn_points[self.index] = old_position; + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"MoveSpawnPoint (Undo): No old position saved in action. Undo was probably called on an action that was never applied")); + } + + Ok(()) + } +} diff --git a/src/editor/actions/place_tile.rs b/src/editor/actions/place_tile.rs new file mode 100644 index 0000000000..566d87db1b --- /dev/null +++ b/src/editor/actions/place_tile.rs @@ -0,0 +1,121 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use macroquad::prelude::UVec2; + +use crate::map::MapLayerKind; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapTile; + +#[derive(Debug)] +pub struct PlaceTile { + id: u32, + layer_id: String, + tileset_id: String, + coords: UVec2, + replaced_tile: Option, +} + +impl PlaceTile { + pub fn new(id: u32, layer_id: String, tileset_id: String, coords: UVec2) -> Self { + PlaceTile { + id, + layer_id, + tileset_id, + coords, + replaced_tile: None, + } + } +} + +impl UndoableAction for PlaceTile { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(tileset) = map.tilesets.get(&self.tileset_id) { + let texture_id = tileset.texture_id.clone(); + let texture_coords = tileset.get_texture_coords(self.id); + + let i = map.to_index(self.coords); + + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let MapLayerKind::TileLayer = layer.kind { + self.replaced_tile = layer.tiles.remove(i as usize); + + let tile = MapTile { + tile_id: self.id, + tileset_id: self.tileset_id.clone(), + texture_id, + texture_coords, + attributes: vec![], + }; + + layer.tiles.insert(i as usize, Some(tile)); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"PlaceTile: The specified layer is not a tile layer", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"PlaceTile: The specified layer does not exist", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"PlaceTile: The specified tileset does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + let i = map.to_index(self.coords); + + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let MapLayerKind::TileLayer = layer.kind { + if layer.tiles.get(i as usize).is_none() { + return Err(Error::new_const(ErrorKind::EditorAction, &"PlaceTile (Undo): No tile at the specified coords, in the specified layer. Undo was probably called on an action that was never applied")); + } + + if let Some(tile) = self.replaced_tile.take() { + layer.tiles[i as usize] = Some(tile); + } else { + layer.tiles[i as usize] = None; + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"PlaceTile (Undo): The specified layer is not a tile layer", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"PlaceTile (Undo): The specified layer does not exist", + )); + } + + Ok(()) + } + + fn is_redundant(&self, map: &Map) -> bool { + if let Some(layer) = map.layers.get(&self.layer_id) { + let i = map.to_index(self.coords); + if let Some(Some(tile)) = layer.tiles.get(i) { + return tile.tileset_id == self.tileset_id && tile.tile_id == self.id; + } + } + + false + } +} diff --git a/src/editor/actions/remove_tile.rs b/src/editor/actions/remove_tile.rs new file mode 100644 index 0000000000..1a8bd7ff2e --- /dev/null +++ b/src/editor/actions/remove_tile.rs @@ -0,0 +1,103 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use macroquad::prelude::UVec2; + +use crate::map::MapLayerKind; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapTile; + +#[derive(Debug)] +pub struct RemoveTile { + layer_id: String, + coords: UVec2, + tile: Option, +} + +impl RemoveTile { + pub fn new(layer_id: String, coords: UVec2) -> Self { + RemoveTile { + layer_id, + coords, + tile: None, + } + } +} + +impl UndoableAction for RemoveTile { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + let i = map.to_index(self.coords); + + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let MapLayerKind::TileLayer = layer.kind { + if let Some(tile) = layer.tiles.remove(i as usize) { + self.tile = Some(tile); + + layer.tiles.insert(i, None); + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"RemoveTile: No tile at the specified coords, in the specified layer. Undo was probably called on an action that was never applied")); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"RemoveTile: The specified layer is not a tile layer", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"RemoveTile: The specified layer does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + let i = map.to_index(self.coords); + + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let MapLayerKind::TileLayer = layer.kind { + if let Some(old_tile) = self.tile.take() { + if let Some(tile) = layer.tiles.get_mut(i) { + *tile = Some(old_tile); + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"RemoveTile (Undo): No tile found vec entry in map at the index stored in action (this should not be possible, as the entry should be a `None` if the tile was empty)")); + } + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"RemoveTile (Undo): No tile stored in action. Undo was probably called on an action that was never applied")); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"RemoveTile (Undo): The specified layer is not a tile layer", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"RemoveTile (Undo): The specified layer does not exist", + )); + } + + Ok(()) + } + + fn is_redundant(&self, map: &Map) -> bool { + let i = map.to_index(self.coords); + if let Some(layer) = map.layers.get(&self.layer_id) { + if let Some(tile) = layer.tiles.get(i) { + return tile.is_none(); + } + } + + false + } +} diff --git a/src/editor/actions/set_layer_draw_order_index.rs b/src/editor/actions/set_layer_draw_order_index.rs new file mode 100644 index 0000000000..e246a49ae6 --- /dev/null +++ b/src/editor/actions/set_layer_draw_order_index.rs @@ -0,0 +1,81 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct SetLayerDrawOrderIndex { + pub(crate) id: String, + pub(crate) draw_order_index: usize, + pub(crate) old_draw_order_index: Option, +} + +impl SetLayerDrawOrderIndex { + pub fn new(id: String, draw_order_index: usize) -> Self { + SetLayerDrawOrderIndex { + id, + draw_order_index, + old_draw_order_index: None, + } + } +} + +impl UndoableAction for SetLayerDrawOrderIndex { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + for i in 0..map.draw_order.len() { + let id = map.draw_order.get(i).unwrap(); + if id == &self.id { + self.old_draw_order_index = Some(i); + map.draw_order.remove(i); + break; + } + } + + if self.old_draw_order_index.is_none() { + return Err(Error::new_const(ErrorKind::EditorAction, &"SetLayerDrawOrderIndex: Could not find the specified layer in the map draw order array")); + } + + if self.draw_order_index > map.draw_order.len() { + map.draw_order.push(self.id.clone()); + } else { + map.draw_order + .insert(self.draw_order_index, self.id.clone()); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if map.draw_order.remove(self.draw_order_index) != self.id { + return Err(Error::new_const(ErrorKind::EditorAction, &"SetLayerDrawOrderIndex (Undo): There was a mismatch between the layer id found in at the specified draw order index and the id stored in the action")); + } + + if let Some(i) = self.old_draw_order_index { + if i > map.draw_order.len() { + map.draw_order.push(self.id.clone()); + } else { + map.draw_order.insert(i, self.id.clone()); + } + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"SetLayerDrawOrderIndex (Undo): No old draw order index was found. Undo was probably called on an action that was never applied")); + } + + Ok(()) + } + + fn is_redundant(&self, map: &Map) -> bool { + for i in 0..map.draw_order.len() { + let id = map.draw_order.get(i).unwrap(); + if id == &self.id && i == self.draw_order_index { + return true; + } + } + + false + } +} diff --git a/src/editor/actions/update_background.rs b/src/editor/actions/update_background.rs new file mode 100644 index 0000000000..e5625d19af --- /dev/null +++ b/src/editor/actions/update_background.rs @@ -0,0 +1,62 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use macroquad::prelude::Color; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapBackgroundLayer; + +#[derive(Debug)] +pub struct UpdateBackground { + pub(crate) color: Color, + pub(crate) old_color: Option, + pub(crate) layers: Vec, + pub(crate) old_layers: Option>, +} + +impl UpdateBackground { + pub fn new(color: Color, layers: Vec) -> Self { + UpdateBackground { + color, + old_color: None, + layers, + old_layers: None, + } + } +} + +impl UndoableAction for UpdateBackground { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + self.old_color = Some(map.background_color); + + map.background_color = self.color; + + self.old_layers = Some(map.background_layers.clone()); + + map.background_layers = self.layers.clone(); + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(color) = self.old_color.take() { + map.background_color = color; + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateBackgroundProperties (Undo): No old background color was found. Undo was probably called on an action that was never applied")); + } + + if let Some(layers) = self.old_layers.take() { + map.background_layers = layers; + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateBackgroundProperties (Undo): No old background layers was found. Undo was probably called on an action that was never applied")); + } + + Ok(()) + } +} diff --git a/src/editor/actions/update_layer.rs b/src/editor/actions/update_layer.rs new file mode 100644 index 0000000000..e8f2da1476 --- /dev/null +++ b/src/editor/actions/update_layer.rs @@ -0,0 +1,59 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct UpdateLayer { + id: String, + is_visible: bool, + old_is_visible: Option, +} + +impl UpdateLayer { + pub fn new(id: String, is_visible: bool) -> Self { + UpdateLayer { + id, + is_visible, + old_is_visible: None, + } + } +} + +impl UndoableAction for UpdateLayer { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.id) { + self.old_is_visible = Some(layer.is_visible); + layer.is_visible = self.is_visible; + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateLayer: The specified layer does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.id) { + if let Some(old_is_visible) = self.old_is_visible.take() { + layer.is_visible = old_is_visible; + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateLayer (Undo): No `old_is_visible` on action. Undo was probably called on an action that was never applied")); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateLayer (Undo): The specified layer does not exist", + )); + } + + Ok(()) + } +} diff --git a/src/editor/actions/update_object.rs b/src/editor/actions/update_object.rs new file mode 100644 index 0000000000..db791dff51 --- /dev/null +++ b/src/editor/actions/update_object.rs @@ -0,0 +1,76 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapObject; + +use crate::map::MapObjectKind; + +#[derive(Debug)] +pub struct UpdateObject { + layer_id: String, + index: usize, + id: String, + kind: MapObjectKind, + object: Option, +} + +impl UpdateObject { + pub fn new(layer_id: String, index: usize, id: String, kind: MapObjectKind) -> Self { + UpdateObject { + layer_id, + index, + id, + kind, + object: None, + } + } +} + +impl UndoableAction for UpdateObject { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let Some(object) = layer.objects.get_mut(self.index) { + self.object = Some(object.clone()); + + object.id = self.id.clone(); + object.kind = self.kind; + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateObject: The specified object index does not exist", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateObject: The specified layer does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let Some(object) = self.object.take() { + layer.objects[self.index] = object; + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateObject: No object found on action. Undo was probably called on an action that was never applied")); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateObject (Undo): The specified layer does not exist", + )); + } + + Ok(()) + } +} diff --git a/src/editor/actions/update_tile_attributes.rs b/src/editor/actions/update_tile_attributes.rs new file mode 100644 index 0000000000..014fb97ae5 --- /dev/null +++ b/src/editor/actions/update_tile_attributes.rs @@ -0,0 +1,91 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct UpdateTileAttributes { + index: usize, + layer_id: String, + attributes: Vec, + old_attributes: Option>, +} + +impl UpdateTileAttributes { + pub fn new(index: usize, layer_id: String, attributes: Vec) -> Self { + UpdateTileAttributes { + index, + layer_id, + attributes, + old_attributes: None, + } + } +} + +impl UndoableAction for UpdateTileAttributes { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let Some(Some(tile)) = layer.tiles.get_mut(self.index) { + self.old_attributes = Some(tile.attributes.clone()); + tile.attributes = self.attributes.clone(); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateTileAttributes: The specified tile does not exist", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateTileAttributes: The specified layer does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(layer) = map.layers.get_mut(&self.layer_id) { + if let Some(Some(tile)) = layer.tiles.get_mut(self.index) { + if let Some(old_attributes) = self.old_attributes.take() { + tile.attributes = old_attributes; + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTileAttributes (Undo): No old attributes stored in action. Undo was probably called on an action that was never applied")); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateTileAttributes (Undo): The specified tile does not exist", + )); + } + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateTileAttributes (Undo): The specified layer does not exist", + )); + } + + Ok(()) + } + + fn is_redundant(&self, map: &Map) -> bool { + if let Some(layer) = map.layers.get(&self.layer_id) { + if let Some(Some(tile)) = layer.tiles.get(self.index) { + let matching = tile + .attributes + .iter() + .zip(&self.attributes) + .filter(|&(a, b)| a == b) + .count(); + return matching == self.attributes.len(); + } + } + + false + } +} diff --git a/src/editor/actions/update_tileset.rs b/src/editor/actions/update_tileset.rs new file mode 100644 index 0000000000..609217f258 --- /dev/null +++ b/src/editor/actions/update_tileset.rs @@ -0,0 +1,85 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use crate::map::Map; + +use super::UndoableAction; + +#[derive(Debug)] +pub struct UpdateTileset { + id: String, + texture_id: String, + old_texture_id: Option, + autotile_mask: Vec, + old_autotile_mask: Option>, +} + +impl UpdateTileset { + pub fn new(id: String, texture_id: String, autotile_mask: Vec) -> Self { + UpdateTileset { + id, + texture_id, + old_texture_id: None, + autotile_mask, + old_autotile_mask: None, + } + } +} + +impl UndoableAction for UpdateTileset { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { + if let Some(tileset) = map.tilesets.get_mut(&self.id) { + self.old_texture_id = Some(tileset.texture_id.clone()); + tileset.texture_id = self.texture_id.clone(); + + if self.autotile_mask.len() != tileset.autotile_mask.len() { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTileset: There is a size mismatch between the actions autotile mask vector and the one on the tileset")); + } + + self.old_autotile_mask = Some(tileset.autotile_mask.clone()); + tileset.autotile_mask = self.autotile_mask.clone(); + + tileset.bitmasks = tileset.get_bitmasks(); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateTileset: The specified tileset does not exist", + )); + } + + Ok(()) + } + + fn undo(&mut self, map: &mut Map) -> Result<()> { + if let Some(tileset) = map.tilesets.get_mut(&self.id) { + if let Some(old_texture_id) = &self.old_texture_id { + tileset.texture_id = old_texture_id.clone(); + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTileset (Undo): No old texture id stored in action. Undo was probably called on an action that was never applied")); + } + + if let Some(old_autotile_mask) = &self.old_autotile_mask { + if old_autotile_mask.len() != tileset.autotile_mask.len() { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTileset (Undo): There is a size mismatch between the actions autotile mask vector and the one on the tileset")); + } + + tileset.autotile_mask = old_autotile_mask.clone(); + self.old_autotile_mask = None; + } else { + return Err(Error::new_const(ErrorKind::EditorAction, &"UpdateTileset (Undo): No old autotile mask stored in action. Undo was probably called on an action that was never applied")); + } + + tileset.bitmasks = tileset.get_bitmasks(); + } else { + return Err(Error::new_const( + ErrorKind::EditorAction, + &"UpdateTileset (Undo): The specified tileset does not exist", + )); + } + + Ok(()) + } +} diff --git a/src/editor/camera.rs b/src/editor/camera.rs deleted file mode 100644 index 6cc95bd998..0000000000 --- a/src/editor/camera.rs +++ /dev/null @@ -1,57 +0,0 @@ -use macroquad::{experimental::scene::RefMut, prelude::*}; - -pub struct EditorCamera { - pub position: Vec2, - pub scale: f32, -} - -impl EditorCamera { - const FRUSTUM_PADDING: f32 = 64.0; - const DEFAULT_SCALE: f32 = 1.0; - - pub fn new(position: Vec2) -> Self { - EditorCamera { - position, - scale: Self::DEFAULT_SCALE, - } - } - - pub fn get_view_rect(&self) -> Rect { - let size = vec2(screen_width() / self.scale, screen_height() / self.scale); - let position = self.position - size / 2.0; - - Rect::new(position.x, position.y, size.x, size.y) - } - - // This can be used for culling when drawing the map. Not strictly necessary with the small maps in FF - pub fn get_padded_frustum(&self) -> Rect { - let mut res = self.get_view_rect(); - res.move_to(res.point() - vec2(Self::FRUSTUM_PADDING, Self::FRUSTUM_PADDING)); - res.w += Self::FRUSTUM_PADDING * 2.0; - res.h += Self::FRUSTUM_PADDING * 2.0; - res - } - - pub fn to_world_space(&self, position: Vec2) -> Vec2 { - let rect = self.get_view_rect(); - position / self.scale + rect.point() - } - - pub fn to_screen_space(&self, position: Vec2) -> Vec2 { - let rect = self.get_view_rect(); - (position - rect.point()) * self.scale - } -} - -impl scene::Node for EditorCamera { - fn fixed_update(node: RefMut) { - let camera = Some(Camera2D { - offset: vec2(0.0, 0.0), - target: vec2(node.position.x.round(), node.position.y.round()), - zoom: vec2(node.scale / screen_width(), -node.scale / screen_height()) * 2.0, - ..Camera2D::default() - }); - - scene::set_camera(0, camera); - } -} diff --git a/src/editor/gui/combobox.rs b/src/editor/gui/combobox.rs deleted file mode 100644 index cba5cd005a..0000000000 --- a/src/editor/gui/combobox.rs +++ /dev/null @@ -1,127 +0,0 @@ -use macroquad::{ - prelude::*, - ui::{widgets, Id, Ui}, -}; - -pub trait ComboBoxValue { - fn get_index(&self) -> usize; - - fn get_options(&self) -> Vec; - - fn set_index(&mut self, index: usize); - - fn get_value(&self) -> String { - self.get_options() - .get(self.get_index()) - .unwrap() - .to_string() - } -} - -pub struct ComboBoxVec { - index: usize, - options: Vec, -} - -impl ComboBoxVec { - pub fn new(index: usize, options: &[&str]) -> Self { - let options = options.iter().map(|s| s.to_string()).collect(); - - ComboBoxVec { index, options } - } - - pub fn set_value(&mut self, value: &str) { - for (i, v) in self.options.iter().enumerate() { - if *v == *value { - self.index = i; - } - } - } -} - -impl ComboBoxValue for ComboBoxVec { - fn set_index(&mut self, index: usize) { - self.index = index; - } - - fn get_index(&self) -> usize { - self.index - } - - fn get_options(&self) -> Vec { - self.options.clone() - } -} - -impl From<&[&str]> for ComboBoxVec { - fn from(slice: &[&str]) -> Self { - ComboBoxVec::new(0, slice) - } -} - -impl From<&[String]> for ComboBoxVec { - fn from(slice: &[String]) -> Self { - let slice = slice.iter().map(|s| s.as_str()).collect::>(); - - ComboBoxVec::new(0, &slice) - } -} - -impl From<&ComboBoxVec> for usize { - fn from(v: &ComboBoxVec) -> Self { - v.get_index() - } -} - -pub struct ComboBoxBuilder { - id: Id, - label: Option, - ratio: Option, -} - -impl ComboBoxBuilder { - pub fn new(id: Id) -> Self { - ComboBoxBuilder { - id, - label: None, - ratio: None, - } - } - - #[must_use] - pub fn with_label(self, label: &str) -> Self { - ComboBoxBuilder { - label: Some(label.to_string()), - ..self - } - } - - #[must_use] - pub fn with_ratio(self, ratio: f32) -> Self { - ComboBoxBuilder { - ratio: Some(ratio), - ..self - } - } - - pub fn build(&self, ui: &mut Ui, value: &mut V) { - let mut index = value.get_index(); - - let owned = value.get_options(); - let options = owned.iter().map(String::as_str).collect::>(); - - let mut combobox = widgets::ComboBox::new(self.id, &options); - - if let Some(ratio) = self.ratio { - combobox = combobox.ratio(ratio); - } - - if let Some(label) = &self.label { - combobox = combobox.label(label); - } - - combobox.ui(ui, &mut index); - - value.set_index(index); - } -} diff --git a/src/editor/gui/context_menu.rs b/src/editor/gui/context_menu.rs deleted file mode 100644 index 3e12041709..0000000000 --- a/src/editor/gui/context_menu.rs +++ /dev/null @@ -1,244 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use super::EditorAction; - -use crate::gui::{GuiResources, ELEMENT_MARGIN}; - -#[derive(Debug, Clone)] -pub enum ContextMenuEntry { - Separator, - Action { - label: String, - action: EditorAction, - }, - SubMenu { - label: String, - entries: Vec, - is_open: bool, - }, -} - -impl ContextMenuEntry { - const WIDTH: f32 = 150.0; - const ENTRY_HEIGHT: f32 = 25.0; - - pub fn separator() -> Self { - ContextMenuEntry::Separator - } - - pub fn action(label: &str, action: EditorAction) -> Self { - ContextMenuEntry::Action { - label: label.to_string(), - action, - } - } - - pub fn sub_menu(label: &str, entries: &[ContextMenuEntry]) -> Self { - ContextMenuEntry::SubMenu { - label: label.to_string(), - entries: entries.to_vec(), - is_open: false, - } - } - - fn get_height(&self) -> f32 { - if let Self::Separator = self { - return ELEMENT_MARGIN; - } - - Self::ENTRY_HEIGHT - } - - fn contains(&self, position: Vec2, point: Vec2) -> bool { - let rect = Rect::new(position.x, position.y, Self::WIDTH, self.get_height()); - if rect.contains(point) { - return true; - } - - if let Self::SubMenu { - entries, is_open, .. - } = self - { - if *is_open { - let mut position = vec2(position.x + Self::WIDTH, position.y); - position = get_corrected_position(position, entries, false); - - for entry in entries { - if entry.contains(position, point) { - return true; - } - - position.y += entry.get_height(); - } - } - } - - false - } -} - -pub struct ContextMenu { - entries: Vec, - position: Vec2, -} - -impl ContextMenu { - pub fn new(position: Vec2, entries: &[ContextMenuEntry]) -> Self { - let entries = entries.to_vec(); - let position = get_corrected_position(position, &entries, true); - - ContextMenu { entries, position } - } - - pub fn get_size(&self) -> Vec2 { - let mut height = 0.0; - for entry in &self.entries { - height += entry.get_height(); - } - - vec2(ContextMenuEntry::WIDTH, height) - } - - pub fn contains(&self, point: Vec2) -> bool { - let mut position = self.position; - for entry in &self.entries { - if entry.contains(position, point) { - return true; - } - - position.y += entry.get_height(); - } - - false - } - - pub fn draw(&mut self, ui: &mut Ui) -> Option { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.context_menu); - - let res = draw_entries(ui, self.position, &mut self.entries); - - ui.pop_skin(); - - res - } -} - -fn get_corrected_position(position: Vec2, entries: &[ContextMenuEntry], is_root: bool) -> Vec2 { - let mut height = 0.0; - - for entry in entries { - height += entry.get_height(); - } - - let screen_width = screen_width(); - let screen_height = screen_height(); - - let x = if is_root { - position - .x - .clamp(0.0, screen_width - ContextMenuEntry::WIDTH) - } else if position.x + ContextMenuEntry::WIDTH > screen_width { - position.x - ContextMenuEntry::WIDTH * 2.0 - } else { - position.x - }; - - let y = position.y.clamp(0.0, screen_height - height); - - vec2(x, y) -} - -fn draw_entries( - ui: &mut Ui, - position: Vec2, - entries: &mut [ContextMenuEntry], -) -> Option { - let mut res = None; - - let mut size = vec2(ContextMenuEntry::WIDTH, 0.0); - for entry in &mut *entries { - size.y += entry.get_height(); - } - - let mut sub_menus = Vec::new(); - - widgets::Group::new(hash!(), size) - .position(position) - .ui(ui, |ui| { - let mut y_offset = 0.0; - - for entry in entries { - let size = vec2(ContextMenuEntry::WIDTH, entry.get_height()); - let entry_position = vec2(0.0, y_offset); - - match entry { - ContextMenuEntry::Separator => { - widgets::Group::new(hash!(), size) - .position(entry_position) - .ui(ui, |_ui| { - // TODO: Draw texture - }); - } - ContextMenuEntry::Action { label, action } => { - let button = widgets::Button::new("") - .position(entry_position) - .size(size) - .ui(ui); - - ui.label(entry_position, label); - - if button { - res = Some(action.clone()); - } - } - ContextMenuEntry::SubMenu { - label, - entries, - is_open, - } => { - let button = widgets::Button::new("") - .position(entry_position) - .size(size) - .ui(ui); - - ui.label(entry_position, label); - - { - let suffix = if *is_open { "<" } else { ">" }; - let suffix_width = ui.calc_size(suffix).x; - let position = - vec2(entry_position.x + size.x - suffix_width, entry_position.y); - ui.label(position, suffix); - } - - if button { - *is_open = !*is_open; - } - - if *is_open { - let position = position - + vec2( - entry_position.x + ContextMenuEntry::WIDTH, - entry_position.y, - ); - sub_menus.push((position, entries)); - } - } - } - - y_offset += size.y; - } - }); - - for (position, entries) in sub_menus { - let position = get_corrected_position(position, entries, false); - res = draw_entries(ui, position, entries); - } - - res -} diff --git a/src/editor/gui/editor_menu.rs b/src/editor/gui/editor_menu.rs deleted file mode 100644 index fba7fc397a..0000000000 --- a/src/editor/gui/editor_menu.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::editor::EditorContext; -use macroquad::{ - prelude::*, - ui::{hash, Ui}, -}; - -use crate::gui::{Menu, MenuEntry, MenuResult}; - -const MENU_WIDTH: f32 = 300.0; - -pub const EDITOR_MENU_RESULT_NEW: usize = 0; -pub const EDITOR_MENU_RESULT_OPEN_IMPORT: usize = 1; -pub const EDITOR_MENU_RESULT_SAVE: usize = 2; -pub const EDITOR_MENU_RESULT_SAVE_AS: usize = 3; -pub const EDITOR_MENU_RESULT_MAIN_MENU: usize = 4; -pub const EDITOR_MENU_RESULT_QUIT: usize = 5; - -static mut EDITOR_MENU_INSTANCE: Option = None; - -pub fn open_editor_menu(ctx: &EditorContext) { - unsafe { - if EDITOR_MENU_INSTANCE.is_none() { - let menu = Menu::new( - hash!(), - MENU_WIDTH, - &[ - MenuEntry { - index: EDITOR_MENU_RESULT_NEW, - title: "New".to_string(), - ..Default::default() - }, - MenuEntry { - index: EDITOR_MENU_RESULT_OPEN_IMPORT, - title: "Open/Import".to_string(), - ..Default::default() - }, - MenuEntry { - index: EDITOR_MENU_RESULT_SAVE, - title: "Save".to_string(), - is_disabled: !ctx.is_user_map, - ..Default::default() - }, - MenuEntry { - index: EDITOR_MENU_RESULT_SAVE_AS, - title: "Save As".to_string(), - ..Default::default() - }, - MenuEntry { - index: EDITOR_MENU_RESULT_MAIN_MENU, - title: "Main Menu".to_string(), - ..Default::default() - }, - MenuEntry { - index: EDITOR_MENU_RESULT_QUIT, - title: "Quit".to_string(), - ..Default::default() - }, - ], - ); - - EDITOR_MENU_INSTANCE = Some(menu); - } - } -} - -pub fn close_editor_menu() { - unsafe { EDITOR_MENU_INSTANCE = None }; -} - -pub fn draw_editor_menu(ui: &mut Ui, ctx: &EditorContext) -> Option { - let menu = unsafe { - if EDITOR_MENU_INSTANCE.is_none() { - open_editor_menu(ctx); - } - - EDITOR_MENU_INSTANCE.as_mut().unwrap() - }; - - let res = menu.ui(ui); - - if res.is_some() { - close_editor_menu(); - } - - res -} - -pub fn is_editor_menu_open() -> bool { - unsafe { EDITOR_MENU_INSTANCE.is_some() } -} - -/// Toggle editor menu and return state after toggle -pub fn toggle_editor_menu(ctx: &EditorContext) -> bool { - if is_editor_menu_open() { - close_editor_menu(); - false - } else { - open_editor_menu(ctx); - true - } -} diff --git a/src/editor/gui/mod.rs b/src/editor/gui/mod.rs deleted file mode 100644 index 04a53fa4cd..0000000000 --- a/src/editor/gui/mod.rs +++ /dev/null @@ -1,346 +0,0 @@ -use std::{any::TypeId, collections::HashMap}; - -pub mod context_menu; -pub mod toolbars; -pub mod windows; - -pub mod combobox; - -mod editor_menu; - -pub use combobox::{ComboBoxBuilder, ComboBoxValue}; - -pub use editor_menu::{ - close_editor_menu, draw_editor_menu, is_editor_menu_open, open_editor_menu, toggle_editor_menu, - EDITOR_MENU_RESULT_MAIN_MENU, EDITOR_MENU_RESULT_NEW, EDITOR_MENU_RESULT_OPEN_IMPORT, - EDITOR_MENU_RESULT_QUIT, EDITOR_MENU_RESULT_SAVE, EDITOR_MENU_RESULT_SAVE_AS, -}; - -use macroquad::{ - experimental::collections::storage, - hash, - prelude::*, - ui::{root_ui, widgets}, -}; - -use super::{EditorAction, EditorContext}; - -use crate::{ - gui::{GuiResources, ELEMENT_MARGIN}, - map::Map, -}; - -pub use toolbars::{ - LayerListElement, ObjectListElement, TilesetDetailsElement, TilesetListElement, - ToolSelectorElement, Toolbar, ToolbarElement, ToolbarElementParams, ToolbarPosition, -}; - -pub use windows::{ - ConfirmDialog, CreateLayerWindow, CreateObjectWindow, CreateTilesetWindow, - TilesetPropertiesWindow, Window, WINDOW_BUTTON_MAX_WIDTH, WINDOW_BUTTON_MIN_WIDTH, -}; - -use crate::gui::{BUTTON_FONT_SIZE, BUTTON_MARGIN_V, WINDOW_MARGIN_H, WINDOW_MARGIN_V}; -use crate::map::MapLayerKind; -use context_menu::{ContextMenu, ContextMenuEntry}; - -#[derive(Debug, Default, Clone)] -pub struct ButtonParams { - pub label: &'static str, - // This should be an absolute width for window and a width factor for toolbar elements. - // Permitted width factors for toolbar element buttons are 0.25 and 0.5. - pub width_override: Option, - // This holds the action that will be applied on click. - // Setting this to `None` will disable the button. - pub action: Option, -} - -pub struct EditorGui { - left_toolbar: Option, - right_toolbar: Option, - open_windows: HashMap>, - context_menu: Option, -} - -impl EditorGui { - pub const LEFT_TOOLBAR_WIDTH: f32 = 82.0; - pub const RIGHT_TOOLBAR_WIDTH: f32 = 250.0; - - pub const TOOL_SELECTOR_HEIGHT_FACTOR: f32 = 0.5; - pub const LAYER_LIST_HEIGHT_FACTOR: f32 = 0.3; - pub const TILESET_LIST_HEIGHT_FACTOR: f32 = 0.2; - pub const TILESET_DETAILS_HEIGHT_FACTOR: f32 = 0.5; - pub const OBJECT_LIST_HEIGHT_FACTOR: f32 = 0.7; - - pub fn new() -> Self { - EditorGui { - left_toolbar: None, - right_toolbar: None, - open_windows: HashMap::new(), - context_menu: None, - } - } - - pub fn add_toolbar(&mut self, toolbar: Toolbar) { - match toolbar.position { - ToolbarPosition::Left => { - self.left_toolbar = Some(toolbar); - } - ToolbarPosition::Right => { - self.right_toolbar = Some(toolbar); - } - } - } - - #[must_use] - pub fn with_toolbar(self, toolbar: Toolbar) -> Self { - let mut gui = self; - gui.add_toolbar(toolbar); - gui - } - - pub fn context_menu_contains(&self, position: Vec2) -> bool { - if let Some(context_menu) = &self.context_menu { - if context_menu.contains(position) { - return true; - } - } - - false - } - - pub fn contains(&self, position: Vec2) -> bool { - if self.context_menu_contains(position) { - return true; - } - - if let Some(left_toolbar) = &self.left_toolbar { - if left_toolbar.contains(position) { - return true; - } - } - - if let Some(right_toolbar) = &self.right_toolbar { - if right_toolbar.contains(position) { - return true; - } - } - - for window in self.open_windows.values() { - if window.contains(position) { - return true; - } - } - - false - } - - pub fn open_context_menu(&mut self, position: Vec2, map: &Map, ctx: EditorContext) { - let mut entries = vec![ - ContextMenuEntry::action("Undo", EditorAction::Undo), - ContextMenuEntry::action("Redo", EditorAction::Redo), - ]; - - if let Some(layer_id) = &ctx.selected_layer { - let layer = &map.layers.get(layer_id).unwrap(); - if layer.kind == MapLayerKind::ObjectLayer { - entries.push(ContextMenuEntry::action( - "Create Object", - EditorAction::OpenCreateObjectWindow { - position, - layer_id: layer_id.clone(), - }, - )); - } - } - - entries.append(&mut vec![ - ContextMenuEntry::action("Add Layer", EditorAction::OpenCreateLayerWindow), - ContextMenuEntry::action("Background", EditorAction::OpenBackgroundPropertiesWindow), - ]); - - self.context_menu = Some(ContextMenu::new(position, &entries)); - } - - pub fn close_context_menu(&mut self) { - self.context_menu = None; - } - - pub fn add_window(&mut self, window: W) { - let key = TypeId::of::(); - self.open_windows - .entry(key) - .or_insert_with(|| Box::new(window)); - } - - pub fn remove_window(&mut self) { - let key = TypeId::of::(); - self.open_windows.remove(&key).unwrap(); - } - - pub fn remove_window_id(&mut self, id: TypeId) { - self.open_windows.remove(&id).unwrap(); - } - - pub fn draw(&mut self, map: &Map, ctx: EditorContext) -> Option { - let mut res = None; - - let ui = &mut root_ui(); - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.default); - } - - if let Some(left_toolbar) = &mut self.left_toolbar { - if let Some(action) = left_toolbar.draw(ui, map, &ctx) { - res = Some(action); - } - } - - if let Some(right_toolbar) = &mut self.right_toolbar { - if let Some(action) = right_toolbar.draw(ui, map, &ctx) { - res = Some(action); - } - } - - for (id, window) in &mut self.open_windows { - let params = window.get_params().clone(); - - let position = params.get_absolute_position(); - let size = params.size; - - widgets::Window::new(hash!(id), position, size) - .titlebar(false) - .movable(!params.is_static) - .ui(ui, |ui| { - let mut content_size = - size - vec2(WINDOW_MARGIN_H * 2.0, WINDOW_MARGIN_V * 2.0); - - let mut content_position = Vec2::ZERO; - - if let Some(title) = ¶ms.title { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.window_header); - - ui.label(content_position, title); - - let label_size = ui.calc_size(title); - - content_size.y -= label_size.y; - content_position.y += label_size.y; - - ui.pop_skin(); - } - - let button_height = BUTTON_FONT_SIZE + (BUTTON_MARGIN_V * 2.0); - - if params.has_buttons { - content_size.y -= button_height + ELEMENT_MARGIN; - } - - widgets::Group::new(hash!(id, "content"), content_size) - .position(content_position) - .ui(ui, |ui| { - if let Some(action) = window.draw(ui, content_size, map, &ctx) { - res = Some(action); - } - }); - - if params.has_buttons { - let button_area_size = vec2(content_size.x, button_height); - let button_area_position = - vec2(content_position.x, content_position.y + content_size.y); - - widgets::Group::new(hash!(id, "buttons"), button_area_size) - .position(button_area_position) - .ui(ui, |ui| { - let mut button_position = vec2(ELEMENT_MARGIN, 0.0); - - let buttons = window.get_buttons(map, &ctx); - - let button_cnt = buttons.len(); - let margins = button_cnt as f32 * ELEMENT_MARGIN; - let width = ((size.x - margins) / button_cnt as f32) - .clamp(WINDOW_BUTTON_MIN_WIDTH, WINDOW_BUTTON_MAX_WIDTH); - - let button_size = vec2(width, button_height); - - for button in buttons { - if button.action.is_none() { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.button_disabled); - } - - let was_clicked = widgets::Button::new(button.label) - .position(button_position) - .size(button_size) - .ui(ui); - - if button.action.is_some() { - if was_clicked { - res = button.action; - } - } else { - ui.pop_skin(); - } - - button_position.x += button_size.x + ELEMENT_MARGIN; - } - }); - } - }); - } - - if let Some(context_menu) = &mut self.context_menu { - if let Some(action) = context_menu.draw(ui) { - self.context_menu = None; - res = Some(action); - } - } - - if is_editor_menu_open() { - if let Some(menu_res) = draw_editor_menu(ui, &ctx) { - close_editor_menu(); - - match menu_res.into_usize() { - EDITOR_MENU_RESULT_NEW => { - let action = EditorAction::OpenCreateMapWindow; - res = Some(action); - } - EDITOR_MENU_RESULT_OPEN_IMPORT => { - let action = EditorAction::OpenLoadMapWindow; - res = Some(action); - } - EDITOR_MENU_RESULT_SAVE => { - let action = EditorAction::SaveMap(None); - res = Some(action); - } - EDITOR_MENU_RESULT_SAVE_AS => { - let action = EditorAction::OpenSaveMapWindow; - res = Some(action); - } - EDITOR_MENU_RESULT_MAIN_MENU => { - let action = EditorAction::ExitToMainMenu; - res = Some(action); - } - EDITOR_MENU_RESULT_QUIT => { - let action = EditorAction::QuitToDesktop; - res = Some(action); - } - _ => {} - } - } - } - - ui.pop_skin(); - - res - } -} - -impl Default for EditorGui { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/toolbars/layer_list.rs b/src/editor/gui/toolbars/layer_list.rs deleted file mode 100644 index f7b68b0815..0000000000 --- a/src/editor/gui/toolbars/layer_list.rs +++ /dev/null @@ -1,220 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{widgets, Ui}, -}; - -use crate::{ - gui::GuiResources, - map::{Map, MapLayerKind}, - Resources, -}; - -use super::{ - ButtonParams, EditorAction, EditorContext, Toolbar, ToolbarElement, ToolbarElementParams, -}; -use crate::gui::ELEMENT_MARGIN; - -pub struct LayerListElement { - params: ToolbarElementParams, -} - -impl LayerListElement { - pub fn new() -> Self { - let params = ToolbarElementParams { - header: Some("Layers".to_string()), - has_buttons: true, - has_margins: false, - }; - - LayerListElement { params } - } -} - -impl ToolbarElement for LayerListElement { - fn get_params(&self) -> &ToolbarElementParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - map: &Map, - ctx: &EditorContext, - ) -> Option { - let mut res = None; - - let entry_size = vec2(size.x, Toolbar::LIST_ENTRY_HEIGHT); - let mut position = Vec2::ZERO; - - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box); - - for layer_id in &map.draw_order { - let layer = map.layers.get(layer_id).unwrap(); - - let is_selected = if let Some(selected_id) = &ctx.selected_layer { - layer_id == selected_id - } else { - false - }; - - if is_selected { - ui.push_skin(&gui_resources.skins.list_box_selected); - } - - let layer_btn = widgets::Button::new("") - .size(entry_size) - .position(position) - .ui(ui); - - let label = if layer.kind == MapLayerKind::ObjectLayer { - format!("(O) {}", layer_id) - } else { - format!("(T) {}", layer_id) - }; - - ui.label(position, &label); - - if layer_btn { - res = Some(EditorAction::SelectLayer(layer_id.clone())); - } - - if is_selected { - ui.pop_skin(); - } - - ui.push_skin(&gui_resources.skins.list_box_no_bg); - - { - let texture = { - let resources = storage::get::(); - if layer.is_visible { - resources.textures.get("visibility_icon").unwrap().texture - } else { - resources - .textures - .get("visibility_off_icon") - .unwrap() - .texture - } - }; - - let btn_size = vec2(entry_size.y, entry_size.y); - - let btn_position = vec2(position.x + entry_size.x - btn_size.x, position.y); - - let visibility_btn = widgets::Button::new("") - .size(btn_size) - .position(btn_position) - .ui(ui); - - { - let texture_size = { - let height = entry_size.y - ELEMENT_MARGIN; - vec2(height, height) - }; - - let half_margin = ELEMENT_MARGIN / 2.0; - - let texture_position = - vec2(btn_position.x + half_margin, btn_position.y + half_margin); - - widgets::Texture::new(texture) - .size(texture_size.x, texture_size.y) - .position(texture_position) - .ui(ui); - } - - if visibility_btn { - let action = EditorAction::UpdateLayer { - id: layer_id.clone(), - is_visible: !layer.is_visible, - }; - - res = Some(action); - } - } - - ui.pop_skin(); - - position.y += entry_size.y; - } - - ui.pop_skin(); - - res - } - - fn get_buttons(&self, map: &Map, ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let mut delete_action = None; - let mut move_up_action = None; - let mut move_down_action = None; - - if let Some(layer_id) = &ctx.selected_layer { - let mut index = None; - - { - for (i, id) in map.draw_order.iter().enumerate() { - if id == layer_id { - index = Some(i); - break; - } - } - } - - delete_action = Some(EditorAction::DeleteLayer(layer_id.clone())); - - if let Some(index) = index { - if index > 0 { - move_up_action = Some(EditorAction::SetLayerDrawOrderIndex { - id: layer_id.clone(), - index: index - 1, - }); - } - - if index + 1 < map.draw_order.len() { - move_down_action = Some(EditorAction::SetLayerDrawOrderIndex { - id: layer_id.clone(), - index: index + 1, - }); - } - } - } - - res.push(ButtonParams { - label: "+", - action: Some(EditorAction::OpenCreateLayerWindow), - ..Default::default() - }); - - res.push(ButtonParams { - label: "-", - action: delete_action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Up", - action: move_up_action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Down", - action: move_down_action, - ..Default::default() - }); - - res - } -} - -impl Default for LayerListElement { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/toolbars/mod.rs b/src/editor/gui/toolbars/mod.rs deleted file mode 100644 index b0714e9bc4..0000000000 --- a/src/editor/gui/toolbars/mod.rs +++ /dev/null @@ -1,333 +0,0 @@ -use std::{any::TypeId, collections::HashMap}; - -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::{ - gui::{GuiResources, ELEMENT_MARGIN}, - map::Map, -}; - -use super::{ButtonParams, EditorAction, EditorContext}; - -mod tool_selector; - -pub use tool_selector::ToolSelectorElement; - -mod layer_list; - -pub use layer_list::LayerListElement; - -mod tileset_list; - -pub use tileset_list::TilesetListElement; - -mod tileset_details; - -pub use tileset_details::TilesetDetailsElement; - -mod object_list; - -pub use object_list::ObjectListElement; - -#[derive(Debug, Default, Clone)] -pub struct ToolbarElementParams { - header: Option, - has_margins: bool, - has_buttons: bool, -} - -pub trait ToolbarElement { - fn get_params(&self) -> &ToolbarElementParams; - - // The `size` parameter will be the size of the group containing the element - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - map: &Map, - ctx: &EditorContext, - ) -> Option; - - // Implement this and set `has_buttons` to true in the `ToolbarElementParams` returned by - // `get_params` to add a button bar to the bottom of the element. - // There can be no more than four buttons, each represented by a `ButtonParam` struct, in - // the `Vec` returned by this method. - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - Vec::new() - } - - // This controls whether the element is drawn or not - fn is_drawn(&self, _map: &Map, _ctx: &EditorContext) -> bool { - true - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum ToolbarPosition { - Left, - Right, -} - -pub struct Toolbar { - pub width: f32, - pub position: ToolbarPosition, - draw_order: Vec, - elements: HashMap)>, -} - -impl Toolbar { - pub const LIST_ENTRY_HEIGHT: f32 = 25.0; - - pub const BUTTON_HEIGHT: f32 = 25.0; - - pub fn new(position: ToolbarPosition, width: f32) -> Self { - Toolbar { - position, - width, - draw_order: Vec::new(), - elements: HashMap::new(), - } - } - - #[must_use] - pub fn with_element(self, height_factor: f32, element: E) -> Self { - let mut res = self; - res.add_element(height_factor, element); - res - } - - pub fn add_element(&mut self, height_factor: f32, element: E) { - let id = TypeId::of::(); - self.elements.insert(id, (height_factor, Box::new(element))); - self.draw_order.push(id); - } - - pub fn remove_element( - &mut self, - ) -> Option> { - let id = TypeId::of::(); - self.draw_order.retain(|other_id| *other_id != id); - self.elements.remove(&id).map(|(_, id)| id) - } - - pub fn get_rect(&self) -> Rect { - let mut offset = 0.0; - if self.position == ToolbarPosition::Right { - offset += screen_width() - self.width; - } - - Rect::new(offset, 0.0, self.width, screen_height()) - } - - pub fn contains(&self, point: Vec2) -> bool { - let rect = self.get_rect(); - rect.contains(point) - } - - pub fn draw(&mut self, ui: &mut Ui, map: &Map, ctx: &EditorContext) -> Option { - let mut res = None; - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.toolbar); - } - - let mut position = Vec2::ZERO; - if self.position == ToolbarPosition::Right { - position.x += screen_width() - self.width; - } - - let toolbar_id = hash!(self.position); - let toolbar_size = vec2(self.width, screen_height()); - - widgets::Group::new(toolbar_id, toolbar_size) - .position(position) - .ui(ui, |ui| { - let mut position = Vec2::ZERO; - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.toolbar_bg); - widgets::Button::new("") - .position(position) - .size(toolbar_size) - .ui(ui); - ui.pop_skin(); - } - - for element_id in &self.draw_order { - let (height_factor, element) = self - .elements - .get_mut(element_id) - .map(|(height_factor, element)| (*height_factor, element)) - .unwrap(); - - if element.is_drawn(map, ctx) { - let params = element.get_params().clone(); - - let element_id = hash!(toolbar_id, element_id); - - let element_size = { - let height = screen_height() * height_factor; - vec2(self.width, height) - }; - - let element_position = position; - let mut content_position = element_position; - let mut content_size = element_size; - - if let Some(header) = ¶ms.header { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.toolbar_header_bg); - - let header_height = ui.calc_size(header).y; - - { - let size = vec2(toolbar_size.x, header_height); - - widgets::Button::new("") - .position(element_position) - .size(size) - .ui(ui); - ui.label(element_position, header); - } - - content_size.y -= header_height; - content_position.y += header_height; - - ui.pop_skin(); - } - - if params.has_buttons { - content_size.y -= Toolbar::BUTTON_HEIGHT + (ELEMENT_MARGIN * 2.0); - } - - let margins = vec2(ELEMENT_MARGIN, ELEMENT_MARGIN); - if params.has_margins { - content_size -= vec2(margins.x, margins.y * 2.0); - content_position += margins; - } - - widgets::Group::new(hash!(element_id, "content"), content_size) - .position(content_position) - .ui(ui, |ui| { - content_size.x -= margins.x; - if let Some(action) = element.draw(ui, content_size, map, ctx) { - res = Some(action); - } - }); - - if params.has_buttons { - let mut menubar_position = - vec2(element_position.x, content_position.y) + margins; - menubar_position.y += content_size.y; - - let mut menubar_size = vec2(element_size.x, Toolbar::BUTTON_HEIGHT); - menubar_size.x -= margins.x * 2.0; - - widgets::Group::new(hash!(element_id, "menubar"), menubar_size) - .position(menubar_position) - .ui(ui, |ui| { - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.toolbar_button); - } - - let buttons = element.get_buttons(map, ctx); - - let button_cnt = buttons.len().clamp(0, 4); - - if button_cnt > 0 { - let mut button_position = Vec2::ZERO; - - let total_width = - menubar_size.x - (margins.x * (button_cnt - 1) as f32); - - let mut overrides = 0; - let mut total_override_factor = 0.0; - for i in 0..button_cnt { - let button = buttons.get(i).unwrap(); - if let Some(width_factor) = button.width_override { - total_override_factor += - to_corrected_button_width_factor(width_factor); - overrides += 1; - } - } - - let auto_width = { - let remaining_buttons = button_cnt - overrides; - let remaining_width_factor = - 1.0 - total_override_factor; - let width_factor = - remaining_width_factor / remaining_buttons as f32; - total_width - * to_corrected_button_width_factor(width_factor) - }; - - let mut i = 0; - for button in buttons { - let mut button_size = - vec2(auto_width, Self::BUTTON_HEIGHT); - if let Some(width_factor) = button.width_override { - button_size.x = total_width - * to_corrected_button_width_factor( - width_factor, - ); - } - - if button.action.is_none() { - let gui_resources = storage::get::(); - ui.push_skin( - &gui_resources.skins.toolbar_button_disabled, - ); - } - - let was_clicked = widgets::Button::new(button.label) - .size(button_size) - .position(button_position) - .ui(ui); - - if button.action.is_some() { - if was_clicked { - res = button.action; - } - } else { - ui.pop_skin(); - } - - button_position.x += button_size.x + margins.x; - - i += 1; - - if i >= button_cnt { - break; - } - } - } - - ui.pop_skin(); - }); - } - - position.y += element_size.y; - } - } - }); - - ui.pop_skin(); - - res - } -} - -fn to_corrected_button_width_factor(width_factor: f32) -> f32 { - if width_factor <= 0.25 { - 0.25 - } else { - 0.5 - } -} diff --git a/src/editor/gui/toolbars/object_list.rs b/src/editor/gui/toolbars/object_list.rs deleted file mode 100644 index b5ea971906..0000000000 --- a/src/editor/gui/toolbars/object_list.rs +++ /dev/null @@ -1,156 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{widgets, Ui}, -}; - -use super::{ - EditorAction, EditorContext, GuiResources, Map, Toolbar, ToolbarElement, ToolbarElementParams, -}; - -use crate::{ - editor::{gui::ButtonParams, EditorCamera}, - map::MapLayerKind, -}; - -pub struct ObjectListElement { - params: ToolbarElementParams, -} - -impl ObjectListElement { - pub fn new() -> Self { - let params = ToolbarElementParams { - header: Some("Objects".to_string()), - has_buttons: true, - has_margins: false, - }; - - ObjectListElement { params } - } -} - -impl ToolbarElement for ObjectListElement { - fn get_params(&self) -> &ToolbarElementParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - map: &Map, - ctx: &EditorContext, - ) -> Option { - let mut res = None; - - let entry_size = vec2(size.x, Toolbar::LIST_ENTRY_HEIGHT); - let mut position = Vec2::ZERO; - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box); - } - - let layer_id = ctx.selected_layer.as_ref().unwrap(); - let layer = map.layers.get(layer_id).unwrap(); - - for (i, object) in layer.objects.iter().enumerate() { - let is_selected = if let Some(selected_index) = ctx.selected_object { - selected_index == i - } else { - false - }; - - if is_selected { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box_selected); - } - - let was_clicked = widgets::Button::new("") - .size(entry_size) - .position(position) - .ui(ui); - - ui.label(position, &object.id); - - if was_clicked { - res = Some(EditorAction::SelectObject { - index: i, - layer_id: layer_id.clone(), - }); - } - - if is_selected { - ui.pop_skin(); - } - - position.y += entry_size.y; - } - - ui.pop_skin(); - - res - } - - fn get_buttons(&self, map: &Map, ctx: &EditorContext) -> Vec { - let layer_id = ctx.selected_layer.clone().unwrap(); - - let position = { - let camera = scene::find_node_by_type::().unwrap(); - let view_rect = camera.get_view_rect(); - let offset = vec2(view_rect.w, view_rect.h) / 2.0; - (view_rect.point() + offset) - map.world_offset - }; - - let create_action = Some(EditorAction::OpenCreateObjectWindow { - layer_id: layer_id.clone(), - position, - }); - - let mut delete_action = None; - let mut properties_action = None; - - if let Some(index) = ctx.selected_object { - delete_action = Some(EditorAction::DeleteObject { - index, - layer_id: layer_id.clone(), - }); - - properties_action = Some(EditorAction::OpenObjectPropertiesWindow { layer_id, index }); - } - - vec![ - ButtonParams { - label: "+", - width_override: Some(0.25), - action: create_action, - }, - ButtonParams { - label: "-", - width_override: Some(0.25), - action: delete_action, - }, - ButtonParams { - label: "Edit", - width_override: Some(0.5), - action: properties_action, - }, - ] - } - - fn is_drawn(&self, map: &Map, ctx: &EditorContext) -> bool { - if let Some(layer_id) = &ctx.selected_layer { - if let Some(layer) = map.layers.get(layer_id) { - return layer.kind == MapLayerKind::ObjectLayer; - } - } - - false - } -} - -impl Default for ObjectListElement { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/toolbars/tileset_details.rs b/src/editor/gui/toolbars/tileset_details.rs deleted file mode 100644 index 99a72c947e..0000000000 --- a/src/editor/gui/toolbars/tileset_details.rs +++ /dev/null @@ -1,125 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{widgets, Ui}, -}; - -use super::{EditorAction, EditorContext, GuiResources, Map, ToolbarElement, ToolbarElementParams}; - -use crate::{gui::ELEMENT_MARGIN, Resources}; - -pub struct TilesetDetailsElement { - params: ToolbarElementParams, -} - -impl TilesetDetailsElement { - pub fn new() -> Self { - let params = ToolbarElementParams { - header: None, - has_margins: true, - ..Default::default() - }; - - TilesetDetailsElement { params } - } -} - -impl ToolbarElement for TilesetDetailsElement { - fn get_params(&self) -> &ToolbarElementParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - map: &Map, - ctx: &EditorContext, - ) -> Option { - let mut res = None; - - let mut position = Vec2::ZERO; - - if let Some(tileset_id) = &ctx.selected_tileset { - let tileset = map.tilesets.get(tileset_id).unwrap(); - - let texture_entry = { - let resources = storage::get::(); - resources - .textures - .get(&tileset.texture_id) - .cloned() - .unwrap() - }; - - let grid_size = vec2(tileset.grid_size.x as f32, tileset.grid_size.y as f32); - - let scaled_width = size.x; - let scaled_height = - (scaled_width / texture_entry.texture.width()) * texture_entry.texture.height(); - - let scaled_tile_size = vec2(scaled_width / grid_size.x, scaled_height / grid_size.y); - - widgets::Texture::new(texture_entry.texture) - .position(position) - .size(scaled_width, scaled_height) - .ui(ui); - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.tileset_grid); - } - - for y in 0..tileset.grid_size.y { - for x in 0..tileset.grid_size.x { - let tile_id = y * tileset.grid_size.x + x; - - let is_selected = if let Some(selected) = ctx.selected_tile { - selected == tile_id - } else { - false - }; - - if is_selected { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.tileset_grid_selected); - } - - let position = vec2(x as f32, y as f32) * scaled_tile_size; - - let button = widgets::Button::new("") - .size(scaled_tile_size) - .position(position) - .ui(ui); - - if button { - res = Some(EditorAction::SelectTile { - id: tile_id, - tileset_id: tileset.id.clone(), - }); - } - - if is_selected { - ui.pop_skin(); - } - } - } - - ui.pop_skin(); - - position.y += scaled_height + ELEMENT_MARGIN; - } - - res - } - - fn is_drawn(&self, _map: &Map, ctx: &EditorContext) -> bool { - ctx.selected_tileset.is_some() - } -} - -impl Default for TilesetDetailsElement { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/toolbars/tileset_list.rs b/src/editor/gui/toolbars/tileset_list.rs deleted file mode 100644 index 3d4e840565..0000000000 --- a/src/editor/gui/toolbars/tileset_list.rs +++ /dev/null @@ -1,133 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{widgets, Ui}, -}; - -use super::{ - EditorAction, EditorContext, GuiResources, Map, Toolbar, ToolbarElement, ToolbarElementParams, -}; - -use crate::{editor::gui::ButtonParams, map::MapLayerKind}; - -pub struct TilesetListElement { - params: ToolbarElementParams, -} - -impl TilesetListElement { - pub fn new() -> Self { - let params = ToolbarElementParams { - header: Some("Tilesets".to_string()), - has_buttons: true, - has_margins: false, - }; - - TilesetListElement { params } - } -} - -impl ToolbarElement for TilesetListElement { - fn get_params(&self) -> &ToolbarElementParams { - &self.params - } - - fn get_buttons(&self, _map: &Map, ctx: &EditorContext) -> Vec { - // Properties are disabled until autotile is up and running - - let mut delete_action = None; - //let mut properties_action = None; - - if let Some(tileset_id) = &ctx.selected_tileset { - delete_action = Some(EditorAction::DeleteTileset(tileset_id.clone())); - /* - properties_action = Some(EditorAction::OpenTilesetPropertiesWindow( - tileset_id.clone(), - )); - */ - } - - vec![ - ButtonParams { - label: "+", - width_override: Some(0.25), - action: Some(EditorAction::OpenCreateTilesetWindow), - }, - ButtonParams { - label: "-", - width_override: Some(0.25), - action: delete_action, - }, - ButtonParams { - label: "Edit", - width_override: Some(0.5), - //action: properties_action, - action: None, - }, - ] - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - map: &Map, - ctx: &EditorContext, - ) -> Option { - let mut res = None; - - let entry_size = vec2(size.x, Toolbar::LIST_ENTRY_HEIGHT); - let mut position = Vec2::ZERO; - - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box); - - for tileset_id in map.tilesets.keys() { - let is_selected = if let Some(selected_id) = &ctx.selected_tileset { - tileset_id == selected_id - } else { - false - }; - - if is_selected { - ui.push_skin(&gui_resources.skins.list_box_selected); - } - - let was_clicked = widgets::Button::new("") - .size(entry_size) - .position(position) - .ui(ui); - - ui.label(position, tileset_id); - - if was_clicked { - res = Some(EditorAction::SelectTileset(tileset_id.clone())); - } - - if is_selected { - ui.pop_skin(); - } - - position.y += entry_size.y; - } - - ui.pop_skin(); - - res - } - - fn is_drawn(&self, map: &Map, ctx: &EditorContext) -> bool { - if let Some(layer_id) = &ctx.selected_layer { - if let Some(layer) = map.layers.get(layer_id) { - return layer.kind == MapLayerKind::TileLayer; - } - } - - false - } -} - -impl Default for TilesetListElement { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/toolbars/tool_selector.rs b/src/editor/gui/toolbars/tool_selector.rs deleted file mode 100644 index 25291c1b4c..0000000000 --- a/src/editor/gui/toolbars/tool_selector.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::any::TypeId; - -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{widgets, Ui}, -}; - -use super::{ - EditorAction, EditorContext, Map, ToolbarElement, ToolbarElementParams, ELEMENT_MARGIN, -}; - -use crate::editor::tools::EditorTool; -use crate::{ - editor::tools::{get_tool_instance_of_id, EditorToolParams}, - gui::GuiResources, - Resources, -}; - -pub struct ToolSelectorElement { - params: ToolbarElementParams, - tools: Vec, -} - -impl ToolSelectorElement { - pub fn new() -> Self { - let params = ToolbarElementParams { - header: None, - has_margins: true, - has_buttons: false, - }; - - ToolSelectorElement { - params, - tools: Vec::new(), - } - } - - #[must_use] - pub fn with_tool(self) -> Self { - let id = TypeId::of::(); - let mut tools = self.tools; - tools.push(id); - - ToolSelectorElement { tools, ..self } - } - - pub fn add_tool(&mut self) { - let id = TypeId::of::(); - self.tools.push(id); - } -} - -impl ToolbarElement for ToolSelectorElement { - fn get_params(&self) -> &ToolbarElementParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - map: &Map, - ctx: &EditorContext, - ) -> Option { - let mut res = None; - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.tool_selector); - } - - let size = vec2(size.x, size.x); - let mut position = Vec2::ZERO; - - // TODO: Grey out inactive tools, in stead of removing them altogether - let mut available_tools = self - .tools - .iter() - .filter_map(|id| { - let tool = get_tool_instance_of_id(id); - if tool.is_available(map, ctx) { - return Some((Some(*id), tool.get_params().clone())); - } - - None - }) - .collect::, EditorToolParams)>>(); - - available_tools.insert( - 0, - ( - None, - EditorToolParams { - name: "Cursor".to_string(), - icon_texture_id: "cursor_tool_icon".to_string(), - ..Default::default() - }, - ), - ); - - let resources = storage::get::(); - for (id, params) in available_tools { - let mut is_selected = false; - if let Some(id) = id { - if let Some(selected_id) = ctx.selected_tool { - is_selected = id == selected_id; - } - } else { - is_selected = ctx.selected_tool.is_none(); - } - - if is_selected { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.tool_selector_selected); - } - - let was_clicked = widgets::Button::new("") - .position(position) - .size(size) - .ui(ui); - - let texture_entry = resources - .textures - .get(¶ms.icon_texture_id) - .cloned() - .unwrap(); - - widgets::Texture::new(texture_entry.texture) - .position(position) - .size(size.x, size.y) - .ui(ui); - - /*{ - let label_size = ui.calc_size(¶ms.name); - if label_size.x + (ELEMENT_MARGIN * 2.0) > size.x { - let words: Vec<_> = params.name.split(' ').collect(); - - let x_center = position.x + size.x / 2.0; - let mut y_offset = ELEMENT_MARGIN; - - for word in words { - let label_size = ui.calc_size(word); - - if y_offset + label_size.y > size.y { - break; - } - - let position = vec2(x_center - label_size.x / 2.0, position.y + y_offset); - widgets::Label::new(word).position(position).ui(ui); - - y_offset += label_size.y; - } - } - }*/ - - if was_clicked { - res = Some(EditorAction::SelectTool(id)); - } - - position.y += size.y + ELEMENT_MARGIN; - - if is_selected { - ui.pop_skin(); - } - } - - ui.pop_skin(); - - res - } -} - -impl Default for ToolSelectorElement { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/windows/background_properties.rs b/src/editor/gui/windows/background_properties.rs deleted file mode 100644 index 624332fac2..0000000000 --- a/src/editor/gui/windows/background_properties.rs +++ /dev/null @@ -1,306 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::map::MapBackgroundLayer; -use crate::{map::Map, Resources}; - -use crate::gui::{GuiResources, ELEMENT_MARGIN, LIST_BOX_ENTRY_HEIGHT}; -use crate::resources::TextureKind; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; - -pub struct BackgroundPropertiesWindow { - params: WindowParams, - color: Color, - layers: Vec, - layer_texture_id: Option, - layer_depth: f32, - selected_layer: Option, -} - -impl BackgroundPropertiesWindow { - pub fn new(color: Color, layers: Vec) -> Self { - let params = WindowParams { - title: Some("Background Properties".to_string()), - size: vec2(360.0, 500.0), - ..Default::default() - }; - - BackgroundPropertiesWindow { - params, - color, - layers, - layer_texture_id: None, - layer_depth: 0.0, - selected_layer: None, - } - } -} - -impl Window for BackgroundPropertiesWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let action = self - .get_close_action() - .then(EditorAction::UpdateBackground { - color: self.color, - layers: self.layers.clone(), - }); - - res.push(ButtonParams { - label: "Save", - action: Some(action), - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("background_properties_window"); - - widgets::Group::new(hash!(id, "color_group"), vec2(size.x * 0.4, size.y * 0.5)) - .position(vec2(0.0, 0.0)) - .ui(ui, |ui| { - let mut r_str = format!("{:.1}", self.color.r); - let mut g_str = format!("{:.1}", self.color.g); - let mut b_str = format!("{:.1}", self.color.b); - let mut a_str = format!("{:.1}", self.color.a); - - widgets::InputText::new(hash!(id, "color_r_input")) - .ratio(1.0) - .label("r") - .ui(ui, &mut r_str); - - widgets::InputText::new(hash!(id, "color_g_input")) - .ratio(1.0) - .label("g") - .ui(ui, &mut g_str); - - widgets::InputText::new(hash!(id, "color_b_input")) - .ratio(1.0) - .label("b") - .ui(ui, &mut b_str); - - widgets::InputText::new(hash!(id, "color_a_input")) - .ratio(1.0) - .label("a") - .ui(ui, &mut a_str); - - if let Ok(r) = r_str.parse::() { - self.color.r = r; - } - - if let Ok(g) = g_str.parse::() { - self.color.g = g; - } - - if let Ok(b) = b_str.parse::() { - self.color.b = b; - } - - if let Ok(a) = a_str.parse::() { - self.color.a = a; - } - }); - - let layer_list_size = vec2((size.x * 0.6) - ELEMENT_MARGIN, size.y * 0.5); - let layer_list_entry_size = vec2(layer_list_size.x, LIST_BOX_ENTRY_HEIGHT); - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box_no_bg); - } - - widgets::Group::new(hash!(id, "layer_list"), layer_list_size) - .position(vec2((size.x * 0.4) + ELEMENT_MARGIN, 0.0)) - .ui(ui, |ui| { - let layers = self.layers.clone(); - for (i, layer) in layers.iter().enumerate() { - widgets::Group::new(hash!(id, "layer_list_entry", i), layer_list_entry_size) - .position(vec2(0.0, i as f32 * LIST_BOX_ENTRY_HEIGHT)) - .ui(ui, |ui| { - let mut is_selected = false; - if let Some(index) = self.selected_layer { - is_selected = index == i; - } - - if is_selected { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box_selected); - } - - let entry_btn = widgets::Button::new("") - .size(layer_list_entry_size) - .position(vec2(0.0, 0.0)); - - if entry_btn.ui(ui) { - if is_selected { - self.selected_layer = None; - self.layer_texture_id = None; - self.layer_depth = 0.0; - } else { - self.selected_layer = Some(i); - self.layer_texture_id = Some(layer.texture_id.clone()); - self.layer_depth = layer.depth; - } - } - - ui.label(vec2(0.0, 0.0), &layer.texture_id); - - if is_selected { - ui.pop_skin(); - } - }); - } - }); - - ui.pop_skin(); - - widgets::Group::new( - hash!(id, "layer_attributes"), - vec2(size.x, (size.y * 0.5) - ELEMENT_MARGIN), - ) - .position(vec2(0.0, (size.y * 0.5) + ELEMENT_MARGIN)) - .ui(ui, |ui| { - let resources = storage::get::(); - let mut texture_ids = resources - .textures - .values() - .filter_map(|texture_res| { - let mut res = None; - - if let Some(kind) = texture_res.meta.kind { - if kind == TextureKind::Background { - res = Some(texture_res.meta.id.as_str()); - } - } - - res - }) - .collect::>(); - - texture_ids.sort_unstable(); - - let mut texture_index = texture_ids - .iter() - .enumerate() - .find_map(|(i, id)| { - if let Some(texture_id) = &self.layer_texture_id { - if *id == texture_id { - return Some(i); - } - } - - None - }) - .unwrap_or(0); - - widgets::ComboBox::new(hash!(id, "layer_texture_input"), &texture_ids) - .ratio(0.8) - .label("Texture") - .ui(ui, &mut texture_index); - - self.layer_texture_id = texture_ids.get(texture_index).map(|str| str.to_string()); - - let mut depth_str = format!("{:.1}", self.layer_depth); - - widgets::InputText::new(hash!(id, "layer_depth_input")) - .ratio(0.4) - .label("Depth") - .ui(ui, &mut depth_str); - - if let Ok(depth) = depth_str.parse::() { - self.layer_depth = depth; - } - - ui.same_line(0.0); - - if let Some(mut index) = self.selected_layer { - { - let layer = self.layers.get_mut(index).unwrap(); - layer.texture_id = self.layer_texture_id.clone().unwrap(); - layer.depth = self.layer_depth; - } - - let delete_btn = widgets::Button::new("Delete"); - - if delete_btn.ui(ui) { - self.layers.remove(index); - - self.selected_layer = None; - self.layer_texture_id = None; - self.layer_depth = 0.0; - } - - ui.same_line(0.0); - - let up_btn = widgets::Button::new("Up"); - - if up_btn.ui(ui) && index > 0 { - let layer = self.layers.remove(index); - - index -= 1; - self.layers.insert(index, layer); - - self.selected_layer = Some(index); - } - - ui.same_line(0.0); - - let down_btn = widgets::Button::new("Down"); - - if down_btn.ui(ui) && index < self.layers.len() { - let layer = self.layers.remove(index); - - if index < self.layers.len() { - index += 1; - self.layers.insert(index, layer); - } else { - index = self.layers.len(); - self.layers.push(layer); - } - - self.selected_layer = Some(index); - } - } else { - let add_btn = widgets::Button::new("Add"); - - if add_btn.ui(ui) && self.layer_texture_id.is_some() { - let texture_id = self.layer_texture_id.take().unwrap(); - let depth = self.layer_depth; - - self.layer_depth = 0.0; - - self.layers.push(MapBackgroundLayer { - texture_id, - depth, - offset: Vec2::ZERO, - }); - } - } - }); - - None - } -} diff --git a/src/editor/gui/windows/confirm_dialog.rs b/src/editor/gui/windows/confirm_dialog.rs deleted file mode 100644 index 13d6f26eff..0000000000 --- a/src/editor/gui/windows/confirm_dialog.rs +++ /dev/null @@ -1,78 +0,0 @@ -use macroquad::{prelude::*, ui::Ui}; - -use super::{EditorAction, EditorContext, Map, Window, WindowParams}; -use crate::editor::gui::windows::ButtonParams; - -pub struct ConfirmDialog { - params: WindowParams, - body: Vec, - confirm_action: EditorAction, -} - -impl ConfirmDialog { - const WINDOW_TITLE: &'static str = "Please Confirm"; - const CONFIRM_LABEL: &'static str = "Ok"; - const CANCEL_LABEL: &'static str = "Cancel"; - - pub fn new(size: Vec2, body: &[&str], confirm_action: EditorAction) -> Self { - let params = WindowParams { - title: Some(Self::WINDOW_TITLE.to_string()), - size, - is_static: true, - ..Default::default() - }; - - let body = body.iter().map(|line| line.to_string()).collect(); - - ConfirmDialog { - params, - body, - confirm_action, - } - } -} - -impl Window for ConfirmDialog { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let action = self.get_close_action().then(self.confirm_action.clone()); - - res.push(ButtonParams { - label: Self::CONFIRM_LABEL, - action: Some(action), - ..Default::default() - }); - - res.push(ButtonParams { - label: Self::CANCEL_LABEL, - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - for line in &self.body { - ui.label(None, line); - } - - ui.separator(); - ui.separator(); - ui.separator(); - ui.separator(); - - None - } -} diff --git a/src/editor/gui/windows/create_layer.rs b/src/editor/gui/windows/create_layer.rs deleted file mode 100644 index b0c9f3f933..0000000000 --- a/src/editor/gui/windows/create_layer.rs +++ /dev/null @@ -1,114 +0,0 @@ -use macroquad::{ - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::map::{Map, MapLayerKind}; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; -use crate::editor::gui::ComboBoxBuilder; -use crate::gui::Checkbox; - -pub struct CreateLayerWindow { - params: WindowParams, - id: String, - kind: MapLayerKind, - has_collision: bool, -} - -impl CreateLayerWindow { - pub fn new() -> Self { - let params = WindowParams { - title: Some("Create Layer".to_string()), - size: vec2(275.0, 275.0), - ..Default::default() - }; - - CreateLayerWindow { - params, - id: "Unnamed Layer".to_string(), - kind: MapLayerKind::TileLayer, - has_collision: false, - } - } -} - -impl Window for CreateLayerWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("create_layer_window"); - - { - let size = vec2(173.0, 25.0); - - widgets::InputText::new(hash!(id, "name_input")) - .size(size) - .ratio(1.0) - .label("Name") - .ui(ui, &mut self.id); - } - - ui.separator(); - - ComboBoxBuilder::new(hash!(id, "type_input")) - .with_label("Type") - .with_ratio(1.0) - .build(ui, &mut self.kind); - - if self.kind == MapLayerKind::TileLayer { - ui.separator(); - - Checkbox::new(hash!(id, "collision_input"), None, "Collision") - .ui(ui, &mut self.has_collision); - } - - None - } - - fn get_buttons(&self, map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let is_existing_id = map.draw_order.iter().any(|id| id == &self.id); - - let mut action = None; - if !is_existing_id { - let batch = self.get_close_action().then(EditorAction::CreateLayer { - id: self.id.clone(), - kind: self.kind, - has_collision: self.has_collision, - index: None, - }); - - action = Some(batch); - } - - res.push(ButtonParams { - label: "Create", - action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } -} - -impl Default for CreateLayerWindow { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/windows/create_map.rs b/src/editor/gui/windows/create_map.rs deleted file mode 100644 index b898258330..0000000000 --- a/src/editor/gui/windows/create_map.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::path::Path; - -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use core::text::ToStringHelper; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; - -use crate::map::Map; -use crate::resources::{map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION}; -use crate::Resources; - -pub struct CreateMapWindow { - params: WindowParams, - name: String, - description: String, - grid_size: UVec2, - tile_size: Vec2, - map_export_path: String, -} - -impl CreateMapWindow { - pub fn new() -> Self { - let params = WindowParams { - title: Some("Create Map".to_string()), - size: vec2(350.0, 425.0), - ..Default::default() - }; - - let map_export_path = { - let resources = storage::get::(); - Path::new(&resources.assets_dir).join(MAP_EXPORTS_DEFAULT_DIR) - }; - - CreateMapWindow { - params, - name: "unnamed_map".to_string(), - description: "".to_string(), - grid_size: uvec2(100, 75), - tile_size: vec2(16.0, 16.0), - map_export_path: map_export_path.to_string_helper(), - } - } -} - -impl Window for CreateMapWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("create_map_window"); - - ui.separator(); - - { - let size = vec2(275.0, 25.0); - - widgets::InputText::new(hash!(id, "name_input")) - .size(size) - .ratio(1.0) - .ui(ui, &mut self.name); - } - - ui.separator(); - - { - let path_label = Path::new(&self.map_export_path) - .join(map_name_to_filename(&self.name)) - .with_extension(MAP_EXPORTS_EXTENSION); - - widgets::Label::new(path_label.to_string_lossy().as_ref()).ui(ui); - } - - ui.separator(); - - { - let size = vec2(275.0, 75.0); - - widgets::InputText::new(hash!(id, "description_input")) - .size(size) - .ratio(1.0) - .ui(ui, &mut self.description); - } - - ui.separator(); - - { - let mut grid_width = self.grid_size.x.to_string(); - let mut grid_height = self.grid_size.y.to_string(); - - let mut tile_width = self.tile_size.x.to_string(); - let mut tile_height = self.tile_size.y.to_string(); - - let size = vec2(75.0, 25.0); - - widgets::InputText::new(hash!(id, "tile_width_input")) - .size(size) - .ratio(1.0) - .label("x") - .ui(ui, &mut tile_width); - - ui.same_line(size.x + 25.0); - - widgets::InputText::new(hash!(id, "tile_height_input")) - .size(size) - .ratio(1.0) - .label("Tile size") - .ui(ui, &mut tile_height); - - widgets::InputText::new(hash!(id, "grid_width_input")) - .size(size) - .ratio(1.0) - .label("x") - .ui(ui, &mut grid_width); - - ui.same_line(size.x + 25.0); - - widgets::InputText::new(hash!(id, "grid_height_input")) - .size(size) - .ratio(1.0) - .label("Grid size") - .ui(ui, &mut grid_height); - - self.grid_size = uvec2( - grid_width.parse::().unwrap(), - grid_height.parse::().unwrap(), - ); - - self.tile_size = vec2( - tile_width.parse::().unwrap(), - tile_height.parse::().unwrap(), - ); - } - - ui.separator(); - - None - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let mut action = None; - - if self.grid_size > UVec2::ZERO && self.tile_size > Vec2::ZERO { - let mut description = None; - if !self.description.is_empty() { - description = Some(self.description.clone()); - } - - let batch = self.get_close_action().then(EditorAction::CreateMap { - name: self.name.clone(), - description, - tile_size: self.tile_size, - grid_size: self.grid_size, - }); - - action = Some(batch); - } - - res.push(ButtonParams { - label: "Create", - action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } -} - -impl Default for CreateMapWindow { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/windows/create_object.rs b/src/editor/gui/windows/create_object.rs deleted file mode 100644 index 92320eb02d..0000000000 --- a/src/editor/gui/windows/create_object.rs +++ /dev/null @@ -1,164 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::editor::gui::combobox::ComboBoxVec; -use crate::{ - editor::gui::{ComboBoxBuilder, ComboBoxValue}, - map::{Map, MapObjectKind}, - Resources, -}; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; - -pub struct CreateObjectWindow { - params: WindowParams, - id: Option, - kind: MapObjectKind, - position: Vec2, - layer_id: String, -} - -impl CreateObjectWindow { - pub fn new(position: Vec2, layer_id: String) -> Self { - let params = WindowParams { - title: Some("Create Object".to_string()), - size: vec2(300.0, 300.0), - ..Default::default() - }; - - CreateObjectWindow { - params, - id: None, - kind: MapObjectKind::Item, - position, - layer_id, - } - } -} - -impl Window for CreateObjectWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - if let Some(id) = self.id.clone() { - let action = self.get_close_action().then(EditorAction::CreateObject { - id, - kind: self.kind, - position: self.position, - layer_id: self.layer_id.clone(), - }); - - res.push(ButtonParams { - label: "Create", - action: Some(action), - ..Default::default() - }); - } - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("create_object_window"); - - { - let size = vec2(72.0, 28.0); - - let mut x_str = format!("{:.1}", self.position.x); - let mut y_str = format!("{:.1}", self.position.y); - - widgets::InputText::new(hash!(id, "position_x_input")) - .size(size) - .ui(ui, &mut x_str); - - ui.same_line(0.0); - - ui.label(None, "x"); - - ui.same_line(0.0); - - widgets::InputText::new(hash!(id, "position_y_input")) - .size(size) - .ui(ui, &mut y_str); - - ui.separator(); - ui.separator(); - ui.separator(); - ui.separator(); - - let x = if let Ok(x) = x_str.parse::() { - (x * 100.0).round() / 100.0 - } else { - 0.0 - }; - - let y = if let Ok(y) = y_str.parse::() { - (y * 100.0).round() / 100.0 - } else { - 0.0 - }; - - self.position = vec2(x, y); - } - - ComboBoxBuilder::new(hash!(id, "type_input")) - .with_ratio(0.8) - .with_label("Type") - .build(ui, &mut self.kind); - - let resources = storage::get::(); - let item_ids = match self.kind { - MapObjectKind::Item => resources - .items - .keys() - .map(|k| k.as_str()) - .collect::>(), - MapObjectKind::Environment => vec!["sproinger", "crab", "fish_school"], - MapObjectKind::Decoration => resources - .decoration - .keys() - .map(|k| k.as_str()) - .collect::>(), - }; - - let mut item_id_value = if let Some(current_id) = &self.id { - let index = item_ids - .iter() - .enumerate() - .find_map(|(i, id)| if id == current_id { Some(i) } else { None }) - .unwrap_or_default(); - - ComboBoxVec::new(index, &item_ids) - } else { - ComboBoxVec::new(0, &item_ids) - }; - - ComboBoxBuilder::new(hash!("id_input")) - .with_ratio(0.8) - .with_label("Variant") - .build(ui, &mut item_id_value); - - self.id = Some(item_id_value.get_value()); - - None - } -} diff --git a/src/editor/gui/windows/create_tileset.rs b/src/editor/gui/windows/create_tileset.rs deleted file mode 100644 index 39c8870287..0000000000 --- a/src/editor/gui/windows/create_tileset.rs +++ /dev/null @@ -1,117 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::editor::gui::combobox::ComboBoxVec; -use crate::editor::gui::{ComboBoxBuilder, ComboBoxValue}; -use crate::{map::Map, Resources}; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; -use crate::resources::TextureKind; - -pub struct CreateTilesetWindow { - params: WindowParams, - tileset_id: String, - texture: ComboBoxVec, -} - -impl CreateTilesetWindow { - pub fn new() -> Self { - let params = WindowParams { - title: Some("Create Tileset".to_string()), - size: vec2(320.0, 250.0), - ..Default::default() - }; - - let resources = storage::get::(); - let mut textures = resources - .textures - .iter() - .filter_map(|(k, v)| { - if let Some(kind) = v.meta.kind { - if kind == TextureKind::Tileset { - return Some(k.clone()); - } - } - - None - }) - .collect::>(); - - textures.sort_unstable(); - - CreateTilesetWindow { - params, - tileset_id: "Unnamed Tileset".to_string(), - texture: textures.as_slice().into(), - } - } -} - -impl Window for CreateTilesetWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn get_buttons(&self, map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let is_existing_id = map.tilesets.iter().any(|(id, _)| id == &self.tileset_id); - - let mut action = None; - if !is_existing_id { - let batch = self.get_close_action().then(EditorAction::CreateTileset { - id: self.tileset_id.clone(), - texture_id: self.texture.get_value(), - }); - - action = Some(batch); - } - - res.push(ButtonParams { - label: "Create", - action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("create_tileset_window"); - - widgets::InputText::new(hash!(id, "name_input")) - .ratio(0.8) - .label("Name") - .ui(ui, &mut self.tileset_id); - - ui.separator(); - - ComboBoxBuilder::new(hash!(id, "texture_input")) - .with_ratio(0.8) - .with_label("Texture") - .build(ui, &mut self.texture); - - None - } -} - -impl Default for CreateTilesetWindow { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/windows/import.rs b/src/editor/gui/windows/import.rs deleted file mode 100644 index f509a86f35..0000000000 --- a/src/editor/gui/windows/import.rs +++ /dev/null @@ -1,174 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::gui::{Checkbox, GuiResources, ELEMENT_MARGIN, LIST_BOX_ENTRY_HEIGHT}; - -use crate::map::{Map, MapBackgroundLayer, MapTileset}; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; -use crate::Resources; - -pub struct ImportWindow { - params: WindowParams, - map_index: usize, - tilesets: Vec, - selected_tilesets: Vec, - should_import_background: bool, - background_color: Option, - background_layers: Vec, - is_loaded: bool, -} - -impl ImportWindow { - pub fn new(map_index: usize) -> Self { - let params = WindowParams { - title: Some("Import".to_string()), - size: vec2(350.0, 350.0), - ..Default::default() - }; - - ImportWindow { - params, - map_index, - tilesets: Vec::new(), - selected_tilesets: Vec::new(), - should_import_background: false, - background_color: None, - background_layers: Vec::new(), - is_loaded: false, - } - } -} - -impl Window for ImportWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("import_window"); - - if !self.is_loaded { - let resources = storage::get::(); - let map_resource = resources.maps.get(self.map_index).unwrap(); - self.tilesets = map_resource.map.tilesets.values().cloned().collect(); - - self.background_color = Some(map_resource.map.background_color); - self.background_layers = map_resource.map.background_layers.clone(); - - self.is_loaded = true; - } - - widgets::Group::new(hash!(id, "list_box"), vec2(size.x, size.y * 0.8)) - .position(vec2(0.0, 0.0)) - .ui(ui, |ui| { - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box_no_bg); - } - - let entry_size = vec2(size.x, LIST_BOX_ENTRY_HEIGHT); - - for (i, tileset) in self.tilesets.iter().enumerate() { - let is_selected = self.selected_tilesets.contains(&i); - - if is_selected { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box_selected); - } - - let entry_position = vec2(0.0, i as f32 * entry_size.y); - - let entry_btn = widgets::Button::new("") - .size(entry_size) - .position(entry_position); - - if entry_btn.ui(ui) { - if is_selected { - self.selected_tilesets.retain(|selected| *selected != i); - } else { - self.selected_tilesets.push(i); - } - } - - ui.label(entry_position, &tileset.id); - - if is_selected { - ui.pop_skin(); - } - } - - ui.pop_skin(); - }); - - { - let position = vec2(0.0, (size.y * 0.8) + ELEMENT_MARGIN); - - let checkbox = Checkbox::new( - hash!(id, "background_checkbox"), - position, - "Import Background", - ); - - checkbox - .with_margin(ELEMENT_MARGIN) - .ui(ui, &mut self.should_import_background); - } - - None - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let tilesets = self - .tilesets - .iter() - .enumerate() - .filter_map(|(i, tileset)| { - if self.selected_tilesets.contains(&i) { - Some(tileset.clone()) - } else { - None - } - }) - .collect(); - - let mut background_color = None; - let mut background_layers = Vec::new(); - - if self.should_import_background { - background_color = self.background_color; - background_layers = self.background_layers.clone(); - } - - let batch = self.get_close_action().then(EditorAction::Import { - tilesets, - background_color, - background_layers, - }); - - res.push(ButtonParams { - label: "Import", - action: Some(batch), - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } -} diff --git a/src/editor/gui/windows/load_map.rs b/src/editor/gui/windows/load_map.rs deleted file mode 100644 index 85685d0a34..0000000000 --- a/src/editor/gui/windows/load_map.rs +++ /dev/null @@ -1,177 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::gui::{GuiResources, ELEMENT_MARGIN, LIST_BOX_ENTRY_HEIGHT}; - -use crate::map::Map; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; -use crate::Resources; - -pub struct LoadMapWindow { - params: WindowParams, - index: Option, -} - -impl LoadMapWindow { - pub fn new() -> Self { - let params = WindowParams { - title: Some("Open Map".to_string()), - size: vec2(350.0, 350.0), - ..Default::default() - }; - - LoadMapWindow { - params, - index: None, - } - } -} - -impl Window for LoadMapWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("load_map_window"); - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box_no_bg); - } - - if let Some(index) = self.index { - let btn_size = vec2(size.x, LIST_BOX_ENTRY_HEIGHT); - let btn_position = vec2(0.0, 0.0); - - let back_btn = widgets::Button::new("") - .size(btn_size) - .position(btn_position); - - if back_btn.ui(ui) { - self.index = None; - } - - ui.label(btn_position, "< Back"); - - ui.pop_skin(); - - { - let resources = storage::get::(); - let map_resource = resources.maps.get(index).unwrap(); - - let mut width = size.x; - let mut height = - (width / map_resource.preview.width()) * map_resource.preview.height(); - - let max_height = size.y - LIST_BOX_ENTRY_HEIGHT - (ELEMENT_MARGIN * 2.0); - - if height > max_height { - height = max_height; - width = (height / map_resource.preview.height()) * map_resource.preview.width(); - } - - let preview_position = vec2((size.x - width) / 2.0, btn_size.y + ELEMENT_MARGIN); - - widgets::Texture::new(map_resource.preview) - .size(width, height) - .position(preview_position) - .ui(ui); - } - } else { - let size = vec2(size.x, size.y - ELEMENT_MARGIN); - widgets::Group::new(hash!(id, "list_box"), size) - .position(Vec2::ZERO) - .ui(ui, |ui| { - let resources = storage::get::(); - - let entry_size = vec2(size.x, LIST_BOX_ENTRY_HEIGHT); - - for (i, map_resource) in resources.maps.iter().enumerate() { - let mut is_selected = false; - if let Some(index) = self.index { - is_selected = index == i; - } - - if is_selected { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.list_box_selected); - } - - let entry_position = vec2(0.0, i as f32 * entry_size.y); - - let entry_btn = widgets::Button::new("") - .size(entry_size) - .position(entry_position); - - if entry_btn.ui(ui) { - self.index = Some(i); - } - - ui.label(entry_position, &map_resource.meta.path); - - if is_selected { - ui.pop_skin(); - } - } - }); - - ui.pop_skin(); - } - - None - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let mut open_action = None; - let mut import_action = None; - - if let Some(index) = self.index { - let open_batch = self.get_close_action().then(EditorAction::OpenMap(index)); - open_action = Some(open_batch); - - let import_batch = self - .get_close_action() - .then(EditorAction::OpenImportWindow(index)); - import_action = Some(import_batch); - } - - res.push(ButtonParams { - label: "Open", - action: open_action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Import", - action: import_action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } -} - -impl Default for LoadMapWindow { - fn default() -> Self { - Self::new() - } -} diff --git a/src/editor/gui/windows/mod.rs b/src/editor/gui/windows/mod.rs deleted file mode 100644 index 1e930dd839..0000000000 --- a/src/editor/gui/windows/mod.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::any::TypeId; - -use macroquad::{prelude::*, ui::Ui}; - -mod confirm_dialog; -mod create_tileset; - -mod background_properties; -mod create_layer; -mod create_map; -mod create_object; -mod import; -mod load_map; -mod object_properties; -mod save_map; -mod tile_properties; -mod tileset_properties; - -pub use background_properties::BackgroundPropertiesWindow; -pub use confirm_dialog::ConfirmDialog; -pub use create_layer::CreateLayerWindow; -pub use create_map::CreateMapWindow; -pub use create_object::CreateObjectWindow; -pub use create_tileset::CreateTilesetWindow; -pub use import::ImportWindow; -pub use load_map::LoadMapWindow; -pub use object_properties::ObjectPropertiesWindow; -pub use save_map::SaveMapWindow; -pub use tile_properties::TilePropertiesWindow; -pub use tileset_properties::TilesetPropertiesWindow; - -use super::{ButtonParams, EditorAction, EditorContext, Map}; - -pub const WINDOW_BUTTON_MIN_WIDTH: f32 = 64.0; -pub const WINDOW_BUTTON_MAX_WIDTH: f32 = 96.0; - -#[derive(Debug, Copy, Clone)] -pub enum WindowPosition { - Centered, - Absolute(Vec2), -} - -impl WindowPosition { - pub fn to_absolute(&self, size: Vec2) -> Vec2 { - match self { - WindowPosition::Centered => { - let screen_size = vec2(screen_width(), screen_height()); - (screen_size - size) / 2.0 - } - WindowPosition::Absolute(position) => *position, - } - } -} - -#[derive(Debug, Clone)] -pub struct WindowParams { - pub title: Option, - pub size: Vec2, - pub position: WindowPosition, - pub has_buttons: bool, - pub is_static: bool, -} - -impl WindowParams { - pub fn get_absolute_position(&self) -> Vec2 { - self.position.to_absolute(self.size) - } -} - -impl Default for WindowParams { - fn default() -> Self { - WindowParams { - title: None, - size: vec2(250.0, 350.0), - position: WindowPosition::Centered, - has_buttons: true, - is_static: false, - } - } -} - -// This should be implemented for any windows opened by the gui. -// It provides a uniform standardization of window, boot visually and in regards to how control -// flow is handled, by returning actions, in stead of performing the logic in the window draw. -pub trait Window { - fn get_params(&self) -> &WindowParams; - - // Implement this and set `has_buttons` to true in the `WindowParams` returned by - // `get_params` to add buttons to the bottom of the window. - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec - where - Self: 'static, - { - vec![ButtonParams { - label: "Close", - action: Some(self.get_close_action()), - ..Default::default() - }] - } - - fn draw( - &mut self, - ui: &mut Ui, - size: Vec2, - map: &Map, - ctx: &EditorContext, - ) -> Option; - - fn get_absolute_position(&self) -> Vec2 { - let params = self.get_params(); - params.position.to_absolute(params.size) - } - - fn get_rect(&self) -> Rect { - let params = self.get_params(); - let position = params.position.to_absolute(params.size); - Rect::new(position.x, position.y, params.size.x, params.size.y) - } - - fn contains(&self, point: Vec2) -> bool { - let rect = self.get_rect(); - rect.contains(point) - } - - fn get_close_action(&self) -> EditorAction - where - Self: 'static, - { - let id = TypeId::of::(); - EditorAction::CloseWindow(id) - } -} diff --git a/src/editor/gui/windows/object_properties.rs b/src/editor/gui/windows/object_properties.rs deleted file mode 100644 index 15a3d08ef3..0000000000 --- a/src/editor/gui/windows/object_properties.rs +++ /dev/null @@ -1,178 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::editor::gui::combobox::ComboBoxVec; -use crate::map::MapObject; -use crate::{ - editor::gui::{ComboBoxBuilder, ComboBoxValue}, - map::{Map, MapObjectKind}, - Resources, -}; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; - -pub struct ObjectPropertiesWindow { - params: WindowParams, - layer_id: String, - index: usize, - object: Option, -} - -impl ObjectPropertiesWindow { - pub fn new(layer_id: String, index: usize) -> Self { - let params = WindowParams { - title: Some("Object Properties".to_string()), - size: vec2(300.0, 300.0), - ..Default::default() - }; - - ObjectPropertiesWindow { - params, - layer_id, - index, - object: None, - } - } -} - -impl Window for ObjectPropertiesWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let mut action = None; - - if let Some(object) = &self.object { - let batch = self.get_close_action().then(EditorAction::UpdateObject { - layer_id: self.layer_id.clone(), - index: self.index, - id: object.id.clone(), - kind: object.kind, - position: object.position, - }); - - action = Some(batch); - } - - res.push(ButtonParams { - label: "Save", - action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("update_object_window"); - - let mut object = self.object.clone().unwrap_or({ - let mut res = None; - - if let Some(layer) = map.layers.get(&self.layer_id) { - res = layer.objects.get(self.index); - } - - res.cloned().unwrap() - }); - - { - let size = vec2(72.0, 28.0); - - let mut x_str = format!("{:.1}", object.position.x); - let mut y_str = format!("{:.1}", object.position.y); - - widgets::InputText::new(hash!(id, "position_x_input")) - .size(size) - .ui(ui, &mut x_str); - - ui.same_line(0.0); - - ui.label(None, "x"); - - ui.same_line(0.0); - - widgets::InputText::new(hash!(id, "position_y_input")) - .size(size) - .ui(ui, &mut y_str); - - ui.separator(); - ui.separator(); - ui.separator(); - ui.separator(); - - let x = if let Ok(x) = x_str.parse::() { - (x * 100.0).round() / 100.0 - } else { - 0.0 - }; - - let y = if let Ok(y) = y_str.parse::() { - (y * 100.0).round() / 100.0 - } else { - 0.0 - }; - - object.position = vec2(x, y); - } - - ComboBoxBuilder::new(hash!(id, "type_input")) - .with_ratio(0.8) - .with_label("Type") - .build(ui, &mut object.kind); - - let resources = storage::get::(); - let item_ids = match object.kind { - MapObjectKind::Item => resources - .items - .keys() - .map(|k| k.as_str()) - .collect::>(), - MapObjectKind::Environment => vec!["sproinger"], - MapObjectKind::Decoration => resources - .decoration - .keys() - .map(|k| k.as_str()) - .collect::>(), - }; - - let mut item_id_value = { - let index = item_ids - .iter() - .enumerate() - .find_map(|(i, id)| if *id == object.id { Some(i) } else { None }) - .unwrap_or_default(); - - ComboBoxVec::new(index, &item_ids) - }; - - ComboBoxBuilder::new(hash!("id_input")) - .with_ratio(0.8) - .with_label("Variant") - .build(ui, &mut item_id_value); - - object.id = item_id_value.get_value(); - - self.object = Some(object); - - None - } -} diff --git a/src/editor/gui/windows/save_map.rs b/src/editor/gui/windows/save_map.rs deleted file mode 100644 index 97565f23ba..0000000000 --- a/src/editor/gui/windows/save_map.rs +++ /dev/null @@ -1,113 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; -use std::path::Path; - -use crate::map::Map; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; -use crate::resources::{ - is_valid_map_export_path, map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION, -}; -use crate::Resources; - -pub struct SaveMapWindow { - params: WindowParams, - name: String, - should_overwrite: bool, -} - -impl SaveMapWindow { - pub fn new(current_name: &str) -> Self { - let params = WindowParams { - title: Some("Save Map".to_string()), - size: vec2(350.0, 350.0), - ..Default::default() - }; - - SaveMapWindow { - params, - name: current_name.to_string(), - should_overwrite: false, - } - } -} - -impl Window for SaveMapWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - _map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("save_map_window"); - - { - let size = vec2(173.0, 25.0); - - widgets::InputText::new(hash!(id, "name_input")) - .size(size) - .ratio(1.0) - .label("Name") - .ui(ui, &mut self.name); - - { - let resources = storage::get::(); - let path = Path::new(&resources.assets_dir) - .join(MAP_EXPORTS_DEFAULT_DIR) - .join(map_name_to_filename(&self.name)) - .with_extension(MAP_EXPORTS_EXTENSION); - - widgets::Label::new(path.to_string_lossy().as_ref()).ui(ui); - } - } - - ui.separator(); - ui.separator(); - ui.separator(); - ui.separator(); - - widgets::Checkbox::new(hash!(id, "overwrite_input")) - .label("Overwrite Existing") - .ui(ui, &mut self.should_overwrite); - - None - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let path = Path::new(MAP_EXPORTS_DEFAULT_DIR) - .join(map_name_to_filename(&self.name)) - .with_extension(MAP_EXPORTS_EXTENSION); - - let mut action = None; - if is_valid_map_export_path(&path, self.should_overwrite) { - let save_action = EditorAction::SaveMap(Some(self.name.clone())); - let batch = self.get_close_action().then(save_action); - - action = Some(batch); - } - - res.push(ButtonParams { - label: "Save", - action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } -} diff --git a/src/editor/gui/windows/tile_properties.rs b/src/editor/gui/windows/tile_properties.rs deleted file mode 100644 index 08ef603a0a..0000000000 --- a/src/editor/gui/windows/tile_properties.rs +++ /dev/null @@ -1,110 +0,0 @@ -use macroquad::{ - prelude::*, - ui::{hash, Ui}, -}; - -use crate::map::Map; - -use crate::gui::Checkbox; - -use super::{ButtonParams, EditorAction, EditorContext, Window, WindowParams}; - -const JUMPTHROUGH_ATTRIBUTE: &str = "jumpthrough"; - -pub struct TilePropertiesWindow { - params: WindowParams, - layer_id: String, - index: usize, - attributes: Option>, -} - -impl TilePropertiesWindow { - pub fn new(layer_id: String, index: usize) -> Self { - let params = WindowParams { - title: Some("Tile Properties".to_string()), - size: vec2(300.0, 200.0), - ..Default::default() - }; - - TilePropertiesWindow { - params, - layer_id, - index, - attributes: None, - } - } -} - -impl Window for TilePropertiesWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let mut action = None; - - if let Some(attributes) = self.attributes.clone() { - let batch = self - .get_close_action() - .then(EditorAction::UpdateTileAttributes { - layer_id: self.layer_id.clone(), - index: self.index, - attributes, - }); - - action = Some(batch); - } - - res.push(ButtonParams { - label: "Save", - action, - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - map: &Map, - _ctx: &EditorContext, - ) -> Option { - let id = hash!("update_tile_window"); - - if self.attributes.is_none() { - if let Some(layer) = map.layers.get(&self.layer_id) { - if let Some(Some(tile)) = layer.tiles.get(self.index) { - self.attributes = Some(tile.attributes.clone()); - } - } - } - - let mut is_jumpthrough; - - if let Some(attributes) = &mut self.attributes { - let was_jumpthrough = attributes.contains(&(JUMPTHROUGH_ATTRIBUTE.to_string())); - is_jumpthrough = was_jumpthrough; - - Checkbox::new(hash!(id, "jumpthrough_input"), None, "Platform") - .ui(ui, &mut is_jumpthrough); - - if is_jumpthrough && !was_jumpthrough { - attributes.push(JUMPTHROUGH_ATTRIBUTE.to_string()); - } else if !is_jumpthrough && was_jumpthrough { - attributes.retain(|s| s != JUMPTHROUGH_ATTRIBUTE); - } - } - - None - } -} diff --git a/src/editor/gui/windows/tileset_properties.rs b/src/editor/gui/windows/tileset_properties.rs deleted file mode 100644 index 88d3f3fda1..0000000000 --- a/src/editor/gui/windows/tileset_properties.rs +++ /dev/null @@ -1,228 +0,0 @@ -use macroquad::{ - experimental::collections::storage, - prelude::*, - ui::{hash, widgets, Ui}, -}; - -use crate::editor::gui::combobox::ComboBoxVec; -use crate::editor::gui::{ComboBoxBuilder, ComboBoxValue}; -use crate::{gui::GuiResources, Resources}; - -use super::{ButtonParams, EditorAction, EditorContext, Map, Window, WindowParams}; -use crate::map::MapTileset; -use crate::resources::TextureKind; - -pub struct TilesetPropertiesWindow { - params: WindowParams, - tileset_id: String, - autotile_mask: Vec, - texture: ComboBoxVec, - has_data: bool, -} - -impl TilesetPropertiesWindow { - pub fn new(tileset_id: &str) -> Self { - let params = WindowParams { - size: vec2(600.0, 500.0), - is_static: true, - ..Default::default() - }; - - let resources = storage::get::(); - let texture_ids = resources - .textures - .iter() - .filter_map(|(k, v)| { - if let Some(kind) = v.meta.kind { - if kind == TextureKind::Tileset { - return Some(k.as_str()); - } - } - - None - }) - .collect::>(); - - let texture = ComboBoxVec::new(0, &texture_ids); - - TilesetPropertiesWindow { - params, - tileset_id: tileset_id.to_string(), - autotile_mask: Vec::new(), - texture, - has_data: false, - } - } - - pub fn read_from_tileset(&mut self, map: &Map) { - if let Some(tileset) = map.tilesets.get(&self.tileset_id) { - let subgrid_size = tileset.grid_size * tileset.tile_subdivisions; - let subtile_cnt = (subgrid_size.x * subgrid_size.y) as usize; - - self.texture.set_value(&tileset.texture_id); - - self.autotile_mask = Vec::with_capacity(subtile_cnt); - for i in 0..subtile_cnt { - if let Some(subtile) = tileset.autotile_mask.get(i) { - self.autotile_mask.push(*subtile); - } else { - self.autotile_mask.push(false); - } - } - - self.has_data = true; - } - } - - #[allow(dead_code)] - fn draw_autotile_settings( - &mut self, - ui: &mut Ui, - position: Vec2, - size: Vec2, - tileset: &MapTileset, - ) -> Option { - let texture_entry = { - let resources = storage::get::(); - resources - .textures - .get(&tileset.texture_id) - .cloned() - .unwrap() - }; - - let tileset_texture_size = vec2( - texture_entry.texture.width(), - texture_entry.texture.height(), - ); - - let mut scaled_width = size.x; - let mut scaled_height = (scaled_width / tileset_texture_size.x) * tileset_texture_size.y; - - if scaled_height > size.y { - scaled_height = size.y; - scaled_width = (scaled_height / tileset_texture_size.y) * tileset_texture_size.x; - } - - let subgrid_size = tileset.grid_size * tileset.tile_subdivisions; - - let scaled_subtile_size = vec2( - scaled_width / subgrid_size.x as f32, - scaled_height / subgrid_size.y as f32, - ); - - widgets::Texture::new(texture_entry.texture) - .size(scaled_width, scaled_height) - .position(position) - .ui(ui); - - { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.tileset_subtile_grid); - } - - for y in 0..subgrid_size.y { - for x in 0..subgrid_size.x { - let i = (y * subgrid_size.x + x) as usize; - - let is_selected = self.autotile_mask[i]; - - if is_selected { - let gui_resources = storage::get::(); - ui.push_skin(&gui_resources.skins.tileset_subtile_grid_selected); - } - - let subtile_position = position + vec2(x as f32, y as f32) * scaled_subtile_size; - - let was_clicked = widgets::Button::new("") - .size(scaled_subtile_size) - .position(subtile_position) - .ui(ui); - - if is_selected { - ui.pop_skin(); - } - - if was_clicked { - self.autotile_mask[i] = !is_selected; - } - } - } - - ui.pop_skin(); - - None - } -} - -impl Window for TilesetPropertiesWindow { - fn get_params(&self) -> &WindowParams { - &self.params - } - - fn draw( - &mut self, - ui: &mut Ui, - _size: Vec2, - map: &Map, - _ctx: &EditorContext, - ) -> Option { - if let Some(_tileset) = map.tilesets.get(&self.tileset_id) { - let id = hash!("tileset_properties_window"); - - if !self.has_data { - self.read_from_tileset(map); - } - - if self.has_data { - /* - { - let size = size; - let position = Vec2::ZERO; - - if let Some(action) = self.draw_autotile_settings(ui, position, size, tileset) { - return Some(action); - } - } - */ - - widgets::Label::new(&self.tileset_id).ui(ui); - - ui.separator(); - - ComboBoxBuilder::new(hash!(id, "texture_input")) - .with_ratio(0.8) - .with_label("Texture") - .build(ui, &mut self.texture); - } - } - - None - } - - fn get_buttons(&self, _map: &Map, _ctx: &EditorContext) -> Vec { - let mut res = Vec::new(); - - let autotile_mask = self.autotile_mask.clone(); - - let action = self.get_close_action().then(EditorAction::UpdateTileset { - id: self.tileset_id.clone(), - texture_id: self.texture.get_value(), - autotile_mask, - }); - - res.push(ButtonParams { - label: "Save", - action: Some(action), - ..Default::default() - }); - - res.push(ButtonParams { - label: "Cancel", - action: Some(self.get_close_action()), - ..Default::default() - }); - - res - } -} diff --git a/src/editor/history.rs b/src/editor/history.rs index 5bd33f33cd..748d8c9813 100644 --- a/src/editor/history.rs +++ b/src/editor/history.rs @@ -2,25 +2,29 @@ use core::Result; use crate::Map; -use super::UndoableAction; +use super::actions::UndoableAction; -pub struct EditorHistory { +pub struct ActionHistory { undo_stack: Vec>, redo_stack: Vec>, } -impl EditorHistory { +impl ActionHistory { pub fn new() -> Self { - EditorHistory { + ActionHistory { undo_stack: Vec::new(), redo_stack: Vec::new(), } } - pub fn apply(&mut self, mut action: Box, map: &mut Map) -> Result<()> { + pub fn apply( + &mut self, + mut action: impl UndoableAction + 'static, + map: &mut Map, + ) -> Result<()> { if !action.is_redundant(map) { - action.apply(map)?; - self.undo_stack.push(action); + action.apply_to(map)?; + self.undo_stack.push(Box::new(action)); self.redo_stack.clear(); } @@ -38,15 +42,22 @@ impl EditorHistory { pub fn redo(&mut self, map: &mut Map) -> Result<()> { if let Some(mut action) = self.redo_stack.pop() { - action.redo(map)?; + action.apply_to(map)?; self.undo_stack.push(action); } Ok(()) } + #[allow(dead_code)] pub fn clear(&mut self) { self.undo_stack.clear(); self.redo_stack.clear(); } } + +impl Default for ActionHistory { + fn default() -> Self { + Self::new() + } +} diff --git a/src/editor/input.rs b/src/editor/input.rs index 8f986a16ef..46137bd055 100644 --- a/src/editor/input.rs +++ b/src/editor/input.rs @@ -2,7 +2,7 @@ use macroquad::{experimental::collections::storage, prelude::*}; use fishsticks::{Axis, Button}; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum EditorInputScheme { Mouse, Gamepad(fishsticks::GamepadId), @@ -29,98 +29,104 @@ pub struct EditorInput { pub delete: bool, } -pub fn collect_editor_input(scheme: EditorInputScheme) -> EditorInput { - let mut input = EditorInput::default(); - - match scheme { - EditorInputScheme::Mouse => { - input.action = is_mouse_button_down(MouseButton::Left); - input.camera_mouse_move = is_mouse_button_down(MouseButton::Middle); - input.context_menu = is_mouse_button_pressed(MouseButton::Right); - - let (_, zoom) = mouse_wheel(); - if zoom < 0.0 { - input.camera_zoom = -1.0; - } else if zoom > 0.0 { - input.camera_zoom = 1.0; - } - - if is_key_down(KeyCode::LeftControl) { - if is_key_pressed(KeyCode::Z) { - if is_key_down(KeyCode::LeftShift) { - input.redo = true; - } else { - input.undo = true; +impl EditorInputScheme { + pub fn collect_input(&self, keyboard_enabled: bool, mouse_enabled: bool) -> EditorInput { + let mut input = EditorInput::default(); + + match self { + EditorInputScheme::Mouse => { + if mouse_enabled { + input.action = is_mouse_button_down(MouseButton::Left); + input.camera_mouse_move = is_mouse_button_down(MouseButton::Middle); + input.context_menu = is_mouse_button_pressed(MouseButton::Right); + + let zoom = mouse_wheel().1; + if zoom < 0.0 { + input.camera_zoom = -1.0; + } else if zoom > 0.0 { + input.camera_zoom = 1.0; } } - input.toggle_snap_to_grid = is_key_pressed(KeyCode::G); - - if is_key_pressed(KeyCode::S) { - if is_key_down(KeyCode::LeftShift) { - input.save_as = true; + if keyboard_enabled { + if is_key_down(KeyCode::LeftControl) { + if is_key_pressed(KeyCode::Z) { + if is_key_down(KeyCode::LeftShift) { + input.redo = true; + } else { + input.undo = true; + } + } + + input.toggle_snap_to_grid = is_key_pressed(KeyCode::G); + + if is_key_pressed(KeyCode::S) { + if is_key_down(KeyCode::LeftShift) { + input.save_as = true; + } else { + input.save = true; + } + } + + if is_key_pressed(KeyCode::L) { + input.load = true; + } } else { - input.save = true; - } - } + if is_key_pressed(KeyCode::Escape) { + input.toggle_menu = true; + input.back = true; + } - if is_key_pressed(KeyCode::L) { - input.load = true; - } - } else { - if is_key_pressed(KeyCode::Escape) { - input.toggle_menu = true; - input.back = true; - } + if is_key_down(KeyCode::Left) || is_key_down(KeyCode::A) { + input.camera_move_direction.x = -1.0; + } else if is_key_down(KeyCode::Right) || is_key_down(KeyCode::D) { + input.camera_move_direction.x = 1.0; + } - if is_key_down(KeyCode::Left) || is_key_down(KeyCode::A) { - input.camera_move_direction.x = -1.0; - } else if is_key_down(KeyCode::Right) || is_key_down(KeyCode::D) { - input.camera_move_direction.x = 1.0; - } - - if is_key_down(KeyCode::Up) || is_key_down(KeyCode::W) { - input.camera_move_direction.y = -1.0; - } else if is_key_down(KeyCode::Down) || is_key_down(KeyCode::S) { - input.camera_move_direction.y = 1.0; - } + if is_key_down(KeyCode::Up) || is_key_down(KeyCode::W) { + input.camera_move_direction.y = -1.0; + } else if is_key_down(KeyCode::Down) || is_key_down(KeyCode::S) { + input.camera_move_direction.y = 1.0; + } - input.toggle_draw_grid = is_key_pressed(KeyCode::G); + input.toggle_draw_grid = is_key_pressed(KeyCode::G); - input.toggle_disable_parallax = is_key_pressed(KeyCode::P); + input.toggle_disable_parallax = is_key_pressed(KeyCode::P); - input.delete = is_key_pressed(KeyCode::Delete); + input.delete = is_key_pressed(KeyCode::Delete); + } + } } - } - EditorInputScheme::Gamepad(ix) => { - let gamepad_system = storage::get_mut::(); - let gamepad = gamepad_system.gamepad(ix); + EditorInputScheme::Gamepad(ix) => { + let gamepad_system = storage::get_mut::(); + let gamepad = gamepad_system.gamepad(*ix); - if let Some(gamepad) = gamepad { - input.action = gamepad.digital_inputs.activated(Button::East); - input.back = gamepad.digital_inputs.activated(Button::South); - input.context_menu = gamepad.digital_inputs.activated(Button::West); + if let Some(gamepad) = gamepad { + input.action = gamepad.digital_inputs.activated(Button::East); + input.back = gamepad.digital_inputs.activated(Button::South); + input.context_menu = gamepad.digital_inputs.activated(Button::West); - input.camera_move_direction = { - let direction_x = gamepad.analog_inputs.value(Axis::LeftStickX); - let direction_y = gamepad.analog_inputs.value(Axis::LeftStickY); + input.camera_move_direction = { + let direction_x = gamepad.analog_inputs.value(Axis::LeftStickX); + let direction_y = gamepad.analog_inputs.value(Axis::LeftStickY); - let direction = vec2(direction_x, direction_y); + let direction = vec2(direction_x, direction_y); - direction.normalize_or_zero() - }; + direction.normalize_or_zero() + }; - input.cursor_move_direction = { - let direction_x = gamepad.analog_inputs.value(Axis::RightStickX); - let direction_y = gamepad.analog_inputs.value(Axis::RightStickY); + input.cursor_move_direction = { + let direction_x = gamepad.analog_inputs.value(Axis::RightStickX); + let direction_y = gamepad.analog_inputs.value(Axis::RightStickY); - let direction = vec2(direction_x, direction_y); + let direction = vec2(direction_x, direction_y); - direction.normalize_or_zero() - }; + direction.normalize_or_zero() + }; + } } } - } - input + input + } } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 291730597f..08ae6c0ac1 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,1654 +1,15 @@ -use std::any::TypeId; -use std::path::Path; - -use crate::{ - exit_to_main_menu, - map::{CRAB_TEXTURE_ID, FISH_SCHOOL_ICON_TEXTURE_ID}, - quit_to_desktop, Resources, -}; - -mod camera; - -pub use camera::EditorCamera; - -pub mod gui; - -use gui::{ - toggle_editor_menu, - toolbars::{ - LayerListElement, ObjectListElement, TilesetDetailsElement, TilesetListElement, - ToolSelectorElement, Toolbar, ToolbarPosition, - }, - CreateLayerWindow, CreateObjectWindow, CreateTilesetWindow, EditorGui, TilesetPropertiesWindow, -}; +mod state; +mod util; +mod view; +mod windows; mod actions; -use actions::{ - CreateLayerAction, CreateObjectAction, CreateTilesetAction, DeleteLayerAction, - DeleteObjectAction, DeleteTilesetAction, EditorAction, PlaceTileAction, RemoveTileAction, - SetLayerDrawOrderIndexAction, UndoableAction, UpdateTilesetAction, -}; - mod input; mod history; -mod tools; +mod node; +pub use node::EditorNode; -pub use tools::{ - add_tool_instance, get_tool_instance, get_tool_instance_of_id, EraserTool, ObjectPlacementTool, - TilePlacementTool, DEFAULT_TOOL_ICON_TEXTURE_ID, -}; - -use history::EditorHistory; pub use input::EditorInputScheme; - -use crate::editor::actions::{ - CreateSpawnPointAction, DeleteSpawnPointAction, ImportAction, MoveSpawnPointAction, - UpdateBackgroundAction, UpdateLayerAction, UpdateObjectAction, UpdateTileAttributesAction, -}; -use crate::editor::gui::windows::{ - BackgroundPropertiesWindow, CreateMapWindow, ImportWindow, LoadMapWindow, - ObjectPropertiesWindow, SaveMapWindow, TilePropertiesWindow, -}; -use crate::editor::input::{collect_editor_input, EditorInput}; -use crate::editor::tools::SpawnPointPlacementTool; -use crate::gui::SELECTION_HIGHLIGHT_COLOR; -use crate::map::{MapObject, MapObjectKind}; -use crate::player::IDLE_ANIMATION_ID; -use macroquad::{ - color, - experimental::{ - collections::storage, - scene::{Node, RefMut}, - }, - prelude::*, -}; - -use core::text::{draw_aligned_text, HorizontalAlignment, VerticalAlignment}; - -use super::map::{Map, MapLayerKind}; -use crate::resources::{ - map_name_to_filename, MapResource, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION, -}; - -#[derive(Debug, Clone)] -pub struct EditorContext { - pub selected_tool: Option, - pub selected_layer: Option, - pub selected_tileset: Option, - pub selected_tile: Option, - pub selected_object: Option, - pub input_scheme: EditorInputScheme, - pub cursor_position: Vec2, - pub is_user_map: bool, - pub is_tiled_map: bool, - pub should_snap_to_grid: bool, -} - -impl Default for EditorContext { - fn default() -> Self { - EditorContext { - selected_tool: None, - selected_layer: None, - selected_tileset: None, - selected_tile: None, - selected_object: None, - input_scheme: EditorInputScheme::Mouse, - cursor_position: Vec2::ZERO, - is_user_map: false, - is_tiled_map: false, - should_snap_to_grid: false, - } - } -} - -#[derive(Debug, Clone)] -enum DraggedObject { - MapObject { - id: String, - kind: MapObjectKind, - index: usize, - layer_id: String, - click_offset: Vec2, - }, - SpawnPoint { - index: usize, - click_offset: Vec2, - }, -} - -const SPAWN_POINT_COLLIDER_WIDTH: f32 = 38.0; -const SPAWN_POINT_COLLIDER_HEIGHT: f32 = 49.0; - -pub struct Editor { - map_resource: MapResource, - - selected_tool: Option, - selected_layer: Option, - selected_tileset: Option, - // Selected tile in tileset - selected_tile: Option, - selected_object: Option, - selected_spawn_point: Option, - - // Selected tile in map - selected_map_tile_index: Option, - - input_scheme: EditorInputScheme, - previous_cursor_position: Vec2, - cursor_position: Vec2, - history: EditorHistory, - - previous_input: EditorInput, - input: EditorInput, - mouse_movement: Vec2, - - info_message: Option, - - dragged_object: Option, - - info_message_timer: f32, - double_click_timer: f32, - - should_draw_grid: bool, - should_snap_to_grid: bool, - is_parallax_disabled: bool, -} - -impl Editor { - const CAMERA_PAN_THRESHOLD: f32 = 0.025; - - const CAMERA_PAN_SPEED: f32 = 5.0; - const CAMERA_ZOOM_STEP: f32 = 0.1; - const CAMERA_ZOOM_MIN: f32 = 0.1; - const CAMERA_ZOOM_MAX: f32 = 2.5; - - const CURSOR_MOVE_SPEED: f32 = 5.0; - - const OBJECT_SELECTION_RECT_SIZE: f32 = 75.0; - const OBJECT_SELECTION_RECT_PADDING: f32 = 8.0; - - const GRID_LINE_WIDTH: f32 = 1.0; - const GRID_COLOR: Color = Color { - r: 1.0, - g: 1.0, - b: 1.0, - a: 0.25, - }; - - const DOUBLE_CLICK_THRESHOLD: f32 = 0.25; - - const MESSAGE_TIMEOUT: f32 = 2.5; - - pub fn new(input_scheme: EditorInputScheme, map_resource: MapResource) -> Self { - add_tool_instance(TilePlacementTool::new()); - add_tool_instance(ObjectPlacementTool::new()); - add_tool_instance(SpawnPointPlacementTool::new()); - add_tool_instance(EraserTool::new()); - - let selected_tool = None; - - let selected_layer = map_resource.map.draw_order.first().cloned(); - - let cursor_position = match input_scheme { - EditorInputScheme::Mouse => mouse_position().into(), - EditorInputScheme::Gamepad(..) => vec2(screen_width() / 2.0, screen_height() / 2.0), - }; - - let tool_selector_element = ToolSelectorElement::new() - .with_tool::() - .with_tool::() - .with_tool::() - .with_tool::(); - - let left_toolbar = Toolbar::new(ToolbarPosition::Left, EditorGui::LEFT_TOOLBAR_WIDTH) - .with_element( - EditorGui::TOOL_SELECTOR_HEIGHT_FACTOR, - tool_selector_element, - ); - - let right_toolbar = Toolbar::new(ToolbarPosition::Right, EditorGui::RIGHT_TOOLBAR_WIDTH) - .with_element(EditorGui::LAYER_LIST_HEIGHT_FACTOR, LayerListElement::new()) - .with_element( - EditorGui::TILESET_LIST_HEIGHT_FACTOR, - TilesetListElement::new(), - ) - .with_element( - EditorGui::TILESET_DETAILS_HEIGHT_FACTOR, - TilesetDetailsElement::new(), - ) - .with_element( - EditorGui::OBJECT_LIST_HEIGHT_FACTOR, - ObjectListElement::new(), - ); - - let gui = EditorGui::new() - .with_toolbar(left_toolbar) - .with_toolbar(right_toolbar); - - storage::store(gui); - - Editor { - map_resource, - selected_tool, - selected_layer, - selected_tileset: None, - selected_tile: None, - selected_object: None, - selected_spawn_point: None, - - selected_map_tile_index: None, - - input_scheme, - previous_cursor_position: cursor_position, - cursor_position, - history: EditorHistory::new(), - - previous_input: EditorInput::default(), - input: EditorInput::default(), - mouse_movement: Vec2::ZERO, - - info_message: None, - - dragged_object: None, - - info_message_timer: 0.0, - double_click_timer: Self::DOUBLE_CLICK_THRESHOLD, - - should_draw_grid: true, - should_snap_to_grid: false, - is_parallax_disabled: false, - } - } - - #[allow(dead_code)] - fn get_selected_tile(&self) -> Option<(String, u32)> { - if let Some(tileset_id) = self.selected_tileset.clone() { - if let Some(tile_id) = self.selected_tile { - let selected = (tileset_id, tile_id); - return Some(selected); - } - } - - None - } - - fn get_map(&self) -> &Map { - &self.map_resource.map - } - - #[allow(dead_code)] - fn get_map_mut(&mut self) -> &mut Map { - &mut self.map_resource.map - } - - fn get_context(&self) -> EditorContext { - EditorContext { - selected_tool: self.selected_tool, - selected_layer: self.selected_layer.clone(), - selected_tileset: self.selected_tileset.clone(), - selected_tile: self.selected_tile, - selected_object: self.selected_object, - input_scheme: self.input_scheme, - cursor_position: self.cursor_position, - is_user_map: self.map_resource.meta.is_user_map, - is_tiled_map: self.map_resource.meta.is_tiled_map, - should_snap_to_grid: self.should_snap_to_grid, - } - } - - fn update_context(&mut self) { - if let Some(layer_id) = &self.selected_layer { - if !self.get_map().draw_order.contains(layer_id) { - self.selected_layer = None; - } - } else if let Some(layer_id) = self.get_map().draw_order.first() { - self.selected_layer = Some(layer_id.clone()); - } - - if let Some(layer_id) = &self.selected_layer { - let layer = self.get_map().layers.get(layer_id).unwrap(); - - match layer.kind { - MapLayerKind::TileLayer => { - self.selected_object = None; - } - MapLayerKind::ObjectLayer => { - self.selected_tileset = None; - self.selected_tile = None; - } - } - } - - if let Some(tileset_id) = &self.selected_tileset { - if let Some(tileset) = self.get_map().tilesets.get(tileset_id) { - if let Some(tile_id) = self.selected_tile { - if tile_id >= tileset.tile_cnt { - self.selected_tile = None; - } - } - } else { - self.selected_tileset = None; - self.selected_tile = None; - } - } - - if let Some(tool_id) = &self.selected_tool { - let tool = get_tool_instance_of_id(tool_id); - let ctx = self.get_context(); - if !tool.is_available(self.get_map(), &ctx) { - self.selected_tool = None; - } - } - } - - fn clear_context(&mut self) { - self.selected_tool = None; - self.selected_layer = None; - self.selected_tileset = None; - self.selected_tile = None; - self.selected_object = None; - } - - fn select_tileset(&mut self, tileset_id: &str, tile_id: Option) { - if let Some(tileset) = self.map_resource.map.tilesets.get(tileset_id) { - self.selected_tileset = Some(tileset_id.to_string()); - - if let Some(tile_id) = tile_id { - if tile_id < tileset.first_tile_id + tileset.tile_cnt { - self.selected_tile = Some(tile_id); - return; - } - } - - self.selected_tile = Some(tileset.first_tile_id); - } - } - - // This applies an `EditorAction`. This is to be used, exclusively, in stead of, for example, - // applying `UndoableActions` directly on the `History` of `Editor`. - fn apply_action(&mut self, action: EditorAction) { - //println!("Action: {:?}", action); - - let mut res = Ok(()); - - match action { - EditorAction::Batch(actions) => { - for action in actions { - self.apply_action(action) - } - } - EditorAction::Undo => { - res = self.history.undo(&mut self.map_resource.map); - } - EditorAction::Redo => { - res = self.history.redo(&mut self.map_resource.map); - } - EditorAction::SelectTool(id) => { - self.selected_tool = id; - } - EditorAction::UpdateBackground { color, layers } => { - let action = UpdateBackgroundAction::new(color, layers); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::OpenBackgroundPropertiesWindow => { - let map = &self.map_resource.map; - - let mut gui = storage::get_mut::(); - gui.add_window(BackgroundPropertiesWindow::new( - map.background_color, - map.background_layers.clone(), - )); - } - EditorAction::OpenCreateLayerWindow => { - let mut gui = storage::get_mut::(); - gui.add_window(CreateLayerWindow::new()); - } - EditorAction::OpenCreateTilesetWindow => { - let mut gui = storage::get_mut::(); - gui.add_window(CreateTilesetWindow::new()); - } - EditorAction::OpenTilesetPropertiesWindow(tileset_id) => { - let mut gui = storage::get_mut::(); - gui.add_window(TilesetPropertiesWindow::new(&tileset_id)); - } - EditorAction::OpenCreateObjectWindow { position, layer_id } => { - let mut gui = storage::get_mut::(); - gui.add_window(CreateObjectWindow::new(position, layer_id)) - } - EditorAction::OpenObjectPropertiesWindow { layer_id, index } => { - let mut gui = storage::get_mut::(); - gui.add_window(ObjectPropertiesWindow::new(layer_id, index)) - } - EditorAction::OpenTilePropertiesWindow { layer_id, index } => { - let mut gui = storage::get_mut::(); - gui.add_window(TilePropertiesWindow::new(layer_id, index)) - } - EditorAction::CloseWindow(id) => { - let mut gui = storage::get_mut::(); - gui.remove_window_id(id); - } - EditorAction::SelectTile { id, tileset_id } => { - self.select_tileset(&tileset_id, Some(id)); - } - EditorAction::UpdateTileAttributes { - index, - layer_id, - attributes, - } => { - let action = UpdateTileAttributesAction::new(index, layer_id, attributes); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::SelectLayer(id) => { - if self.get_map().layers.contains_key(&id) { - self.selected_layer = Some(id); - } - } - EditorAction::SetLayerDrawOrderIndex { id, index } => { - let action = SetLayerDrawOrderIndexAction::new(id, index); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::CreateLayer { - id, - kind, - has_collision, - index, - } => { - let action = CreateLayerAction::new(id, kind, has_collision, index); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::DeleteLayer(id) => { - let action = DeleteLayerAction::new(id); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::UpdateLayer { id, is_visible } => { - let action = UpdateLayerAction::new(id, is_visible); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::SelectTileset(id) => { - self.select_tileset(&id, None); - } - EditorAction::CreateTileset { id, texture_id } => { - let action = CreateTilesetAction::new(id, texture_id); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::DeleteTileset(id) => { - let action = DeleteTilesetAction::new(id); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::UpdateTileset { - id, - texture_id, - autotile_mask, - } => { - let action = UpdateTilesetAction::new(id, texture_id, autotile_mask); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::SelectObject { index, layer_id } => { - self.selected_layer = Some(layer_id); - self.selected_object = Some(index); - } - EditorAction::CreateObject { - id, - kind, - position, - layer_id, - } => { - let action = CreateObjectAction::new(id, kind, position, layer_id); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::DeleteObject { index, layer_id } => { - let action = DeleteObjectAction::new(index, layer_id); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::UpdateObject { - layer_id, - index, - id, - kind, - position, - } => { - let action = UpdateObjectAction::new(layer_id, index, id, kind, position); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::CreateSpawnPoint(position) => { - let action = CreateSpawnPointAction::new(position); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::DeleteSpawnPoint(index) => { - let action = DeleteSpawnPointAction::new(index); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::MoveSpawnPoint { index, position } => { - let action = MoveSpawnPointAction::new(index, position); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::PlaceTile { - id, - layer_id, - tileset_id, - coords, - } => { - let action = PlaceTileAction::new(id, layer_id, tileset_id, coords); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::RemoveTile { layer_id, coords } => { - let action = RemoveTileAction::new(layer_id, coords); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::OpenImportWindow(map_index) => { - let mut gui = storage::get_mut::(); - gui.add_window(ImportWindow::new(map_index)); - } - EditorAction::Import { - tilesets, - background_color, - background_layers, - } => { - let action = ImportAction::new(tilesets, background_color, background_layers); - res = self - .history - .apply(Box::new(action), &mut self.map_resource.map); - } - EditorAction::CreateMap { - name, - description, - grid_size, - tile_size, - } => { - let resources = storage::get::(); - let res = resources.create_map(&name, description.as_deref(), tile_size, grid_size); - match res { - Err(err) => println!("Create Map: {}", err), - Ok(map_resource) => { - self.map_resource = map_resource; - self.history.clear(); - self.clear_context(); - } - } - } - EditorAction::OpenCreateMapWindow => { - let mut gui = storage::get_mut::(); - gui.add_window(CreateMapWindow::new()); - } - EditorAction::OpenMap(index) => { - let resources = storage::get::(); - let map_resource = resources.maps.get(index).cloned().unwrap(); - - self.map_resource = map_resource; - self.history.clear(); - self.clear_context(); - } - EditorAction::OpenLoadMapWindow => { - let mut gui = storage::get_mut::(); - gui.add_window(LoadMapWindow::new()); - } - EditorAction::SaveMap(name) => { - let mut map_resource = self.map_resource.clone(); - - if let Some(name) = name { - let path = Path::new(MAP_EXPORTS_DEFAULT_DIR) - .join(map_name_to_filename(&name)) - .with_extension(MAP_EXPORTS_EXTENSION); - - map_resource.meta.name = name; - map_resource.meta.path = path.to_string_lossy().to_string(); - } - - map_resource.meta.is_user_map = true; - map_resource.meta.is_tiled_map = false; - - let mut resources = storage::get_mut::(); - if resources.save_map(&map_resource).is_ok() { - self.map_resource = map_resource; - } - } - EditorAction::OpenSaveMapWindow => { - let mut gui = storage::get_mut::(); - gui.add_window(SaveMapWindow::new(&self.map_resource.meta.name)); - } - EditorAction::DeleteMap(index) => { - let mut resources = storage::get_mut::(); - resources.delete_map(index).unwrap(); - } - EditorAction::ExitToMainMenu => { - exit_to_main_menu(); - } - EditorAction::QuitToDesktop => { - quit_to_desktop(); - } - } - - if let Err(err) = res { - panic!("Error: {}", err); - } - - self.update_context(); - } -} - -impl Node for Editor { - fn update(mut node: RefMut) { - node.update_context(); - - if node.input_scheme == EditorInputScheme::Mouse { - node.previous_cursor_position = node.cursor_position; - node.cursor_position = mouse_position().into(); - } - - let dt = get_frame_time(); - - node.previous_input = node.input; - node.input = collect_editor_input(node.input_scheme); - - { - let movement = node.cursor_position - node.previous_cursor_position; - node.mouse_movement += movement; - } - - if node.info_message.is_some() { - node.info_message_timer += dt; - - if node.info_message_timer >= Self::MESSAGE_TIMEOUT { - node.info_message = None; - node.info_message_timer = 0.0; - } - } - - if node.input.save { - let action = if node.map_resource.meta.is_user_map { - EditorAction::SaveMap(None) - } else { - EditorAction::OpenSaveMapWindow - }; - - node.apply_action(action); - } - - if node.input.save_as { - let action = EditorAction::OpenSaveMapWindow; - node.apply_action(action); - } - - if node.input.load { - let action = EditorAction::OpenLoadMapWindow; - node.apply_action(action); - } - - if !node.input.action && node.double_click_timer < Self::DOUBLE_CLICK_THRESHOLD { - node.double_click_timer = - (node.double_click_timer + dt).clamp(0.0, Self::DOUBLE_CLICK_THRESHOLD); - } - - if node.input.toggle_menu { - toggle_editor_menu(&node.get_context()); - } - - if node.input.toggle_draw_grid { - node.should_draw_grid = !node.should_draw_grid; - - node.info_message = { - let state = if node.should_draw_grid { "ON" } else { "OFF" }; - - Some(format!("Draw grid: {}", state)) - } - } - - if node.input.toggle_snap_to_grid { - node.should_snap_to_grid = !node.should_snap_to_grid; - - node.info_message = { - let state = if node.should_snap_to_grid { - "ON" - } else { - "OFF" - }; - - Some(format!("Snap to grid: {}", state)) - } - } - - if node.input.toggle_disable_parallax { - node.is_parallax_disabled = !node.is_parallax_disabled; - - node.info_message = { - let state = if node.is_parallax_disabled { - "OFF" - } else { - "ON" - }; - - Some(format!("Parallax: {}", state)) - } - } - - if node.input.undo { - node.apply_action(EditorAction::Undo); - } else if node.input.redo { - node.apply_action(EditorAction::Redo); - } - - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(node.cursor_position); - - let (is_cursor_over_gui, is_cursor_over_context_menu) = { - let gui = storage::get::(); - let is_over_gui = gui.contains(node.cursor_position); - let mut is_over_context_menu = false; - if is_over_gui && gui.context_menu_contains(node.cursor_position) { - is_over_context_menu = true; - } - - (is_over_gui, is_over_context_menu) - }; - - if let Some(id) = &node.selected_tool { - let res = { - let tool = get_tool_instance_of_id(id); - let map = node.get_map(); - let ctx = node.get_context(); - - tool.update(map, &ctx) - }; - - if let Some(action) = res { - node.apply_action(action); - } - } - - if node.input.action { - if !is_cursor_over_context_menu { - let mut gui = storage::get_mut::(); - gui.close_context_menu(); - } - - if !is_cursor_over_gui { - if let Some(id) = &node.selected_tool { - let ctx = node.get_context(); - let tool = get_tool_instance_of_id(id); - let params = tool.get_params(); - if !node.previous_input.action || params.is_continuous { - if let Some(action) = tool.get_action(node.get_map(), &ctx) { - node.apply_action(action); - } - } - } else if node.previous_input.action { - if node.cursor_position == node.previous_cursor_position - && node.dragged_object.is_none() - { - if let Some(index) = node.selected_object { - let layer_id = node.selected_layer.clone().unwrap(); - let layer = node.get_map().layers.get(&layer_id).unwrap(); - - let object = layer.objects.get(index).unwrap(); - let position = scene::find_node_by_type::() - .unwrap() - .to_screen_space(object.position); - - let size = get_object_size(object); - let rect = Rect::new(position.x, position.y, size.x, size.y); - - if rect.contains(node.cursor_position) { - let click_offset = node.cursor_position - position; - - node.dragged_object = Some(DraggedObject::MapObject { - id: object.id.clone(), - kind: object.kind, - index, - layer_id, - click_offset, - }) - } - } else if let Some(index) = node.selected_spawn_point { - let spawn_point = node.get_map().spawn_points[index]; - - let position = scene::find_node_by_type::() - .unwrap() - .to_screen_space(spawn_point); - - let rect = Rect::new( - position.x, - position.y, - SPAWN_POINT_COLLIDER_WIDTH, - SPAWN_POINT_COLLIDER_HEIGHT, - ); - - if rect.contains(node.cursor_position) { - let click_offset = node.cursor_position - position; - - node.dragged_object = Some(DraggedObject::SpawnPoint { - index, - click_offset, - }) - } - } - } - } else { - let mut is_double_click = false; - let mut is_selecting_object = false; - let mut is_selecting_spawn_point = false; - let mut is_selecting_tile = false; - - if node.double_click_timer < Self::DOUBLE_CLICK_THRESHOLD { - node.double_click_timer = Self::DOUBLE_CLICK_THRESHOLD; - is_double_click = true; - } else { - node.double_click_timer = 0.0; - } - - let mut layer_ids = node - .map_resource - .map - .layers - .keys() - .cloned() - .collect::>(); - - if let Some(selected_layer_id) = &node.selected_layer { - let res = layer_ids.iter().enumerate().find_map(|(i, layer_id)| { - if layer_id == selected_layer_id { - Some(i) - } else { - None - } - }); - - if let Some(i) = res { - layer_ids.remove(i); - layer_ids.insert(0, selected_layer_id.clone()); - } - } - - let mut object_index = None; - let mut layer_id = None; - - 'layers: for id in &layer_ids { - let layer = node.map_resource.map.layers.get(id).unwrap(); - if layer.kind == MapLayerKind::ObjectLayer { - for (i, object) in layer.objects.iter().enumerate() { - let size = get_object_size(object); - let position = object.position + node.map_resource.map.world_offset; - - let rect = Rect::new(position.x, position.y, size.x, size.y); - - if rect.contains(cursor_world_position) { - object_index = Some(i); - layer_id = Some(id.clone()); - - break 'layers; - } - } - } - } - - if let Some(i) = object_index { - let mut should_select = true; - - if let Some(current_index) = node.selected_object { - if current_index == i { - should_select = false; - - if is_double_click { - let layer_id = layer_id.clone().unwrap(); - - let action = EditorAction::OpenObjectPropertiesWindow { - layer_id, - index: i, - }; - - node.apply_action(action); - } else { - node.selected_object = None; - } - } - } - - if should_select { - is_selecting_object = true; - - let layer_id = layer_id.unwrap(); - - let action = EditorAction::SelectObject { index: i, layer_id }; - - node.apply_action(action); - } - } else { - for (i, spawn_point) in node.get_map().spawn_points.iter().enumerate() { - let position = scene::find_node_by_type::() - .unwrap() - .to_screen_space(*spawn_point); - - let rect = Rect::new( - position.x, - position.y, - SPAWN_POINT_COLLIDER_WIDTH, - SPAWN_POINT_COLLIDER_HEIGHT, - ); - - if rect.contains(node.cursor_position) { - is_selecting_spawn_point = true; - - let mut should_select = true; - - if let Some(index) = node.selected_spawn_point { - if index == i { - node.selected_spawn_point = None; - should_select = false; - } - } - - if should_select { - node.selected_spawn_point = Some(i); - } - - break; - } - } - - if !is_selecting_spawn_point { - let mut tile_index = None; - - 'tile_layers: for id in &layer_ids { - let layer = node.get_map().layers.get(id).unwrap(); - if layer.kind == MapLayerKind::TileLayer { - let world_offset = node.get_map().world_offset; - let tile_size = node.get_map().tile_size; - - for (x, y, tile) in node.map_resource.map.get_tiles(id, None) { - if tile.is_some() { - let rect = Rect::new( - world_offset.x + (x as f32 * tile_size.x), - world_offset.y + (y as f32 * tile_size.y), - tile_size.x, - tile_size.y, - ); - if rect.contains(cursor_world_position) { - let i = node.get_map().to_index(uvec2(x, y)); - tile_index = Some(i); - layer_id = Some(id.clone()); - - break 'tile_layers; - } - } - } - } - } - - if let Some(tile_index) = tile_index { - let mut should_select = true; - - if let Some(selected_tile_index) = node.selected_map_tile_index { - if selected_tile_index == tile_index - && layer_id.as_ref().unwrap() - == node.selected_layer.as_ref().unwrap() - { - should_select = false; - - if is_double_click { - let layer_id = layer_id.clone().unwrap(); - - let action = EditorAction::OpenTilePropertiesWindow { - layer_id, - index: tile_index, - }; - - node.apply_action(action); - } else { - node.selected_map_tile_index = None; - } - } - } - - if should_select { - is_selecting_tile = true; - node.selected_map_tile_index = Some(tile_index); - node.selected_layer = layer_id; - } - } - } - } - - if !is_selecting_tile && !is_selecting_object && !is_selecting_spawn_point { - node.selected_map_tile_index = None; - node.selected_object = None; - node.selected_spawn_point = None; - } - } - } - } else if let Some(dragged_object) = node.dragged_object.take() { - let map = node.get_map(); - - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(node.cursor_position); - - let mut position = (cursor_world_position).clamp( - map.world_offset, - map.world_offset + (map.grid_size.as_f32() * map.tile_size), - ); - - if node.should_snap_to_grid { - let coords = map.to_coords(position); - position = map.to_position(coords); - } - - match dragged_object { - DraggedObject::MapObject { - id, - kind, - index, - layer_id, - click_offset, - } => { - let position = position - click_offset; - - let action = EditorAction::UpdateObject { - id, - kind, - index, - layer_id, - position, - }; - - node.apply_action(action); - } - DraggedObject::SpawnPoint { - index, - click_offset, - } => { - let position = position - click_offset; - - let action = EditorAction::MoveSpawnPoint { index, position }; - - node.apply_action(action); - } - } - } - - if node.input.delete { - if let Some(index) = node.selected_object.take() { - let layer_id = node.selected_layer.clone().unwrap(); - - let action = EditorAction::DeleteObject { index, layer_id }; - - node.apply_action(action); - } else if let Some(index) = node.selected_map_tile_index.take() { - let layer_id = node.selected_layer.clone().unwrap(); - let coords = { - let grid_size = node.get_map().grid_size; - uvec2(index as u32 % grid_size.x, index as u32 / grid_size.x) - }; - - let action = EditorAction::RemoveTile { coords, layer_id }; - - node.apply_action(action); - } else if let Some(index) = node.selected_spawn_point.take() { - let action = EditorAction::DeleteSpawnPoint(index); - node.apply_action(action); - } - } - - if node.input.context_menu { - let mut gui = storage::get_mut::(); - gui.open_context_menu( - node.cursor_position, - &node.map_resource.map, - node.get_context(), - ); - } - } - - fn fixed_update(mut node: RefMut) { - if let EditorInputScheme::Gamepad { .. } = node.input_scheme { - node.previous_cursor_position = node.cursor_position; - let movement = node.input.cursor_move_direction * Self::CURSOR_MOVE_SPEED; - node.cursor_position += movement; - } - - let is_cursor_over_map = { - let gui = storage::get::(); - !gui.contains(node.cursor_position) - }; - - let screen_size = vec2(screen_width(), screen_height()); - - let threshold = screen_size * Self::CAMERA_PAN_THRESHOLD; - - let mut pan_direction = node.input.camera_move_direction; - - if node.cursor_position.x <= threshold.x { - pan_direction.x = -1.0; - } else if node.cursor_position.x >= screen_size.x - threshold.x { - pan_direction.x = 1.0; - } - - if node.cursor_position.y <= threshold.y { - pan_direction.y = -1.0; - } else if node.cursor_position.y >= screen_size.y - threshold.y { - pan_direction.y = 1.0; - } - - let mut movement = pan_direction * Self::CAMERA_PAN_SPEED; - - let mut camera = scene::find_node_by_type::().unwrap(); - - if movement == Vec2::ZERO && node.input.camera_mouse_move { - movement = -node.mouse_movement / camera.scale; - } - - node.mouse_movement = Vec2::ZERO; - - camera.position = (camera.position + movement).clamp(Vec2::ZERO, node.get_map().get_size()); - - if is_cursor_over_map { - camera.scale = (camera.scale + node.input.camera_zoom * Self::CAMERA_ZOOM_STEP) - .clamp(Self::CAMERA_ZOOM_MIN, Self::CAMERA_ZOOM_MAX); - } - } - - fn draw(mut node: RefMut) { - { - let map = node.get_map(); - map.draw_background(None, node.is_parallax_disabled); - map.draw(None, false); - } - - if node.should_draw_grid { - let map = node.get_map(); - let map_size = map.grid_size.as_f32() * map.tile_size; - - draw_rectangle_lines( - map.world_offset.x, - map.world_offset.y, - map_size.x, - map_size.y, - Self::GRID_LINE_WIDTH, - Self::GRID_COLOR, - ); - - for x in 0..map.grid_size.x { - let begin = vec2( - map.world_offset.x + (x as f32 * map.tile_size.x), - map.world_offset.y, - ); - - let end = vec2( - begin.x, - begin.y + (map.grid_size.y as f32 * map.tile_size.y), - ); - - draw_line( - begin.x, - begin.y, - end.x, - end.y, - Self::GRID_LINE_WIDTH, - Self::GRID_COLOR, - ) - } - - for y in 0..map.grid_size.y { - let begin = vec2( - map.world_offset.x, - map.world_offset.y + (y as f32 * map.tile_size.y), - ); - - let end = vec2( - begin.x + (map.grid_size.x as f32 * map.tile_size.x), - begin.y, - ); - - draw_line( - begin.x, - begin.y, - end.x, - end.y, - Self::GRID_LINE_WIDTH, - Self::GRID_COLOR, - ) - } - } - - { - let resources = storage::get::(); - - for (i, spawn_point) in node.get_map().spawn_points.iter().enumerate() { - let mut is_selected = false; - - let mut position = *spawn_point; - - if let Some(DraggedObject::SpawnPoint { - index, - click_offset, - }) = node.dragged_object.clone() - { - if index == i { - let map = node.get_map(); - - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(node.cursor_position - click_offset); - - position = (cursor_world_position).clamp( - map.world_offset, - map.world_offset + (map.grid_size.as_f32() * map.tile_size), - ); - - if node.should_snap_to_grid { - let coords = map.to_coords(position); - position = map.to_position(coords); - } - } - } - - if let Some(index) = node.selected_spawn_point { - is_selected = index == i; - } - - let texture_res = resources.textures.get("spawn_point_icon").unwrap(); - - let frame_size = texture_res.meta.frame_size.unwrap_or_else(|| { - vec2(texture_res.texture.width(), texture_res.texture.height()) - }); - - let source_rect = Rect::new(0.0, 0.0, frame_size.x, frame_size.y); - - draw_texture_ex( - texture_res.texture, - position.x, - position.y, - color::WHITE, - DrawTextureParams { - dest_size: Some(frame_size), - source: Some(source_rect), - ..Default::default() - }, - ); - - if is_selected { - draw_rectangle_lines( - position.x, - position.y, - SPAWN_POINT_COLLIDER_WIDTH, - SPAWN_POINT_COLLIDER_HEIGHT, - 4.0, - SELECTION_HIGHLIGHT_COLOR, - ) - } - } - - let len = node.get_map().draw_order.len(); - for i in 0..len { - let i = len as i32 - i as i32 - 1; - if i >= 0 { - let layer_id = node.get_map().draw_order.get(i as usize).unwrap(); - let layer = node.get_map().layers.get(layer_id).unwrap(); - - if layer.is_visible && layer.kind == MapLayerKind::ObjectLayer { - for (i, object) in layer.objects.iter().enumerate() { - let mut label = None; - - let mut is_selected = false; - if let Some(layer_id) = &node.selected_layer { - if let Some(index) = node.selected_object { - is_selected = *layer_id == layer.id && index == i; - } - } - - let mut object_position = - node.map_resource.map.world_offset + object.position; - - if let Some(DraggedObject::MapObject { - layer_id, - index, - click_offset, - .. - }) = node.dragged_object.clone() - { - if layer.id == layer_id && index == i { - let map = node.get_map(); - - let cursor_world_position = - scene::find_node_by_type::() - .unwrap() - .to_world_space(node.cursor_position - click_offset); - - object_position = (cursor_world_position).clamp( - map.world_offset, - map.world_offset + (map.grid_size.as_f32() * map.tile_size), - ); - - if node.should_snap_to_grid { - let coords = map.to_coords(object_position); - object_position = map.to_position(coords); - } - } - } - - match object.kind { - MapObjectKind::Item => { - if let Some(meta) = resources.items.get(&object.id) { - if let Some(texture_res) = - resources.textures.get(&meta.sprite.texture_id) - { - let (texture, frame_size) = - (texture_res.texture, texture_res.frame_size()); - - let row = meta - .sprite - .animations - .iter() - .find(|&a| a.id == *IDLE_ANIMATION_ID) - .map(|a| a.row) - .unwrap_or_default(); - - let position = object_position + meta.sprite.offset; - - let tint = meta.sprite.tint.unwrap_or(color::WHITE); - - let dest_size = - meta.sprite.scale.map(|s| s * frame_size); - - let source = Some(Rect::new( - 0.0, - row as f32 * frame_size.y, - frame_size.x, - frame_size.y, - )); - - draw_texture_ex( - texture, - position.x, - position.y, - tint, - DrawTextureParams { - dest_size, - source, - ..Default::default() - }, - ); - } else { - label = Some("INVALID TEXTURE ID".to_string()); - } - } else { - label = Some("INVALID OBJECT ID".to_string()); - } - } - MapObjectKind::Decoration => { - if let Some(params) = resources.decoration.get(&object.id) { - if let Some(texture_res) = - resources.textures.get(¶ms.sprite.texture_id) - { - let position = object_position + params.sprite.offset; - - let tint = params.sprite.tint.unwrap_or(color::WHITE); - - let (texture, frame_size) = - (texture_res.texture, texture_res.frame_size()); - - let dest_size = - params.sprite.scale.map(|s| s * frame_size); - - let source = - params.sprite.animations.first().map(|a| { - Rect::new( - 0.0, - a.row as f32 * frame_size.y, - frame_size.x, - frame_size.y, - ) - }); - - draw_texture_ex( - texture, - position.x, - position.y, - tint, - DrawTextureParams { - dest_size, - source, - ..Default::default() - }, - ); - } else { - label = Some("INVALID TEXTURE ID".to_string()); - } - } else { - label = Some("INVALID OBJECT ID".to_string()); - } - } - MapObjectKind::Environment => match object.id.as_str() { - "sproinger" | "crab" | "fish_school" => { - let texture_id = match object.id.as_str() { - "sproinger" => "sproinger", - "crab" => CRAB_TEXTURE_ID, - "fish_school" => FISH_SCHOOL_ICON_TEXTURE_ID, - _ => unreachable!(), - }; - let texture_res = - resources.textures.get(texture_id).unwrap(); - - let frame_size = - texture_res.meta.frame_size.unwrap_or_else(|| { - vec2( - texture_res.texture.width(), - texture_res.texture.height(), - ) - }); - - let source_rect = - Rect::new(0.0, 0.0, frame_size.x, frame_size.y); - - draw_texture_ex( - texture_res.texture, - object_position.x, - object_position.y, - color::WHITE, - DrawTextureParams { - dest_size: Some(frame_size), - source: Some(source_rect), - ..Default::default() - }, - ); - } - _ => { - label = Some("INVALID OBJECT ID".to_string()); - } - }, - } - - let size = get_object_size(object); - - if let Some(label) = &label { - let params = TextParams::default(); - - draw_text_ex( - label, - object_position.x, - object_position.y + (size.y / 2.0) - - Self::OBJECT_SELECTION_RECT_PADDING, - params, - ); - } - - if is_selected { - draw_rectangle_lines( - object_position.x - Self::OBJECT_SELECTION_RECT_PADDING, - object_position.y - Self::OBJECT_SELECTION_RECT_PADDING, - size.x, - size.y, - 4.0, - SELECTION_HIGHLIGHT_COLOR, - ); - } - } - } - } - } - } - - if let Some(tile_index) = node.selected_map_tile_index { - let grid_size = node.get_map().grid_size; - let tile_size = node.get_map().tile_size; - - let coords = uvec2( - tile_index as u32 % grid_size.x, - tile_index as u32 / grid_size.x, - ); - let position = node.get_map().to_position(coords); - - draw_rectangle_lines( - position.x, - position.y, - tile_size.x, - tile_size.y, - 5.0, - SELECTION_HIGHLIGHT_COLOR, - ) - } - - if let Some(label) = &node.info_message { - push_camera_state(); - set_default_camera(); - - let label_position = vec2(screen_width() / 2.0, 16.0); - - draw_aligned_text( - label, - label_position, - HorizontalAlignment::Center, - VerticalAlignment::Bottom, - TextParams { - ..Default::default() - }, - ); - - pop_camera_state(); - } - - let mut res = None; - - if let Some(tool_id) = &node.selected_tool { - let tool = get_tool_instance_of_id(tool_id); - let ctx = node.get_context(); - res = tool.draw_cursor(node.get_map(), &ctx); - } - - { - let ctx = node.get_context(); - let mut gui = storage::get_mut::(); - if let Some(action) = gui.draw(node.get_map(), ctx) { - res = Some(action); - } - } - - if let Some(action) = res { - node.apply_action(action); - } - } -} - -fn get_object_size(object: &MapObject) -> Vec2 { - let mut res = None; - - let mut label = None; - - let resources = storage::get::(); - - match object.kind { - MapObjectKind::Item => { - if let Some(meta) = resources.items.get(&object.id) { - if resources.textures.get(&meta.sprite.texture_id).is_some() { - res = Some(meta.collider_size); - } else { - label = Some("INVALID TEXTURE ID".to_string()); - } - } else { - label = Some("INVALID OBJECT ID".to_string()) - } - } - MapObjectKind::Decoration => { - if let Some(meta) = resources.decoration.get(&object.id) { - if let Some(texture_res) = resources.textures.get(&meta.sprite.texture_id) { - res = Some(texture_res.frame_size()); - } else { - label = Some("INVALID TEXTURE ID".to_string()); - } - } else { - label = Some("INVALID OBJECT ID".to_string()) - } - } - MapObjectKind::Environment => match object.id.as_str() { - "sproinger" => { - let texture_res = resources.textures.get("sproinger").unwrap(); - res = texture_res.meta.frame_size; - } - "crab" => { - let texture_res = resources.textures.get(CRAB_TEXTURE_ID).unwrap(); - res = texture_res.meta.frame_size; - } - "fish_school" => { - let texture_res = resources.textures.get(FISH_SCHOOL_ICON_TEXTURE_ID).unwrap(); - res = texture_res.meta.frame_size; - } - _ => label = Some("INVALID OBJECT ID".to_string()), - }, - } - - if let Some(label) = &label { - let params = TextParams::default(); - let measure = measure_text( - label, - Some(params.font), - params.font_size, - params.font_scale, - ); - res = Some(vec2(measure.width, measure.height)); - } - - res.unwrap_or_else(|| { - vec2( - Editor::OBJECT_SELECTION_RECT_SIZE, - Editor::OBJECT_SELECTION_RECT_SIZE, - ) - }) + (vec2( - Editor::OBJECT_SELECTION_RECT_PADDING, - Editor::OBJECT_SELECTION_RECT_PADDING, - ) * 2.0) -} +pub use state::Editor; diff --git a/src/editor/node.rs b/src/editor/node.rs new file mode 100644 index 0000000000..590975da3b --- /dev/null +++ b/src/editor/node.rs @@ -0,0 +1,49 @@ +use macroquad::experimental::scene::{Node, RefMut}; + +use super::{Editor, EditorInputScheme}; + +/// Used to interface with macroquad. Necessary because using `node: RefMut` really limits +/// what can be done in regards to borrowing. +pub struct EditorNode { + editor: Editor, + + accept_mouse_input: bool, + accept_kb_input: bool, + input_scheme: EditorInputScheme, +} + +impl EditorNode { + pub fn new(editor: Editor, input_scheme: EditorInputScheme) -> Self { + Self { + editor, + accept_mouse_input: true, + accept_kb_input: true, + input_scheme, + } + } +} + +impl Node for EditorNode { + fn fixed_update(mut node: RefMut) { + let input = node + .input_scheme + .collect_input(node.accept_kb_input, node.accept_mouse_input); + + node.editor.process_input(&input); + } + + fn draw(mut node: RefMut) + where + Self: Sized, + { + node.editor.draw_level(); + + egui_macroquad::ui(|egui_ctx| { + node.editor.ui(egui_ctx); + //node.accept_mouse_input = !egui_ctx.wants_pointer_input(); + node.accept_kb_input = !egui_ctx.wants_keyboard_input(); + }); + + egui_macroquad::draw(); + } +} diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs new file mode 100644 index 0000000000..bea1cc854d --- /dev/null +++ b/src/editor/state/mod.rs @@ -0,0 +1,493 @@ +mod ui; + +use std::path::Path; + +use macroquad::prelude::{collections::storage, RenderTarget}; + +use crate::{ + editor::actions, + map::{Map, MapLayerKind, MapObjectKind}, + resources::{ + map_name_to_filename, MapResource, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION, + }, + Resources, +}; + +use super::{ + actions::UiAction, history::ActionHistory, input::EditorInput, view::LevelView, windows, +}; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum EditorTool { + Cursor, + TilePlacer, + SpawnPointPlacer, + Eraser, + ObjectPlacer, +} + +pub struct TileSelection { + pub tileset: String, + pub tile_id: u32, +} + +#[derive(Debug, Clone)] +pub enum SelectableEntityKind { + Object { layer_id: String, index: usize }, + SpawnPoint { index: usize }, +} + +#[derive(Debug, Clone, Copy)] +pub struct DragData { + pub cursor_offset: egui::Vec2, + pub new_pos: egui::Pos2, +} + +#[derive(Debug, Clone)] +pub struct SelectableEntity { + pub kind: SelectableEntityKind, + pub drag_data: Option, +} + +impl SelectableEntityKind { + pub fn is_object(&self, layer_id: &str, index: usize) -> bool { + match self { + SelectableEntityKind::Object { + layer_id: l, + index: i, + } => l == layer_id && i == &index, + SelectableEntityKind::SpawnPoint { .. } => false, + } + } +} + +#[derive(Debug, Clone)] +pub struct ObjectSettings { + pub position: egui::Pos2, + pub kind: MapObjectKind, + pub id: Option, +} + +pub struct Editor { + map_resource: MapResource, + + selected_tool: EditorTool, + + is_parallax_enabled: bool, + should_draw_grid: bool, + + selected_layer: Option, + tile_palette: Option, + object_being_placed: Option, + + create_map_window: Option, + create_layer_window: Option, + background_properties_window: Option, + create_tileset_window: Option, + menu_window: Option, + save_map_window: Option, + open_map_window: Option, + import_window: Option, + + selection: Option, + + history: ActionHistory, + + level_view: LevelView, + + level_render_target: RenderTarget, +} + +impl Editor { + const CAMERA_ZOOM_STEP: f32 = 0.1; + const CAMERA_ZOOM_MIN: f32 = 0.1; + const CAMERA_ZOOM_MAX: f32 = 2.5; + + pub fn new(map_resource: MapResource) -> Self { + Self { + map_resource, + + selected_tool: EditorTool::Cursor, + + is_parallax_enabled: true, + should_draw_grid: true, + + selected_layer: None, + tile_palette: None, + object_being_placed: None, + + create_map_window: None, + background_properties_window: None, + create_layer_window: None, + create_tileset_window: None, + menu_window: None, + save_map_window: None, + open_map_window: None, + import_window: None, + + selection: None, + + history: Default::default(), + + level_view: Default::default(), + + level_render_target: macroquad::prelude::render_target(1, 1), + } + } + + pub fn selected_layer_type(&self) -> Option { + self.selected_layer + .as_ref() + .and_then(|id| self.map_resource.map.layers.get(id)) + .map(|layer| layer.kind) + } + + pub fn apply_action(&mut self, action: UiAction) { + dbg!(&action); + + match action { + UiAction::Batch(batch) => batch + .into_iter() + .for_each(|action| self.apply_action(action)), + UiAction::Undo => self.history.undo(&mut self.map_resource.map).unwrap(), + UiAction::Redo => self.history.redo(&mut self.map_resource.map).unwrap(), + UiAction::SelectTool(tool) => self.selected_tool = tool, + UiAction::CreateLayer { + id, + kind, + has_collision, + index, + } => { + let action = actions::CreateLayer::new(id, kind, has_collision, index); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::CreateTileset { id, texture_id } => { + let action = actions::CreateTileset::new(id, texture_id); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::DeleteLayer(id) => { + let action = actions::DeleteLayer::new(id); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + self.selected_layer = None; + } + UiAction::DeleteTileset(id) => { + let action = actions::DeleteTileset::new(id); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + self.tile_palette = None; + } + UiAction::UpdateLayer { id, is_visible } => { + let action = actions::UpdateLayer::new(id, is_visible); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::SetLayerDrawOrderIndex { id, index } => { + let action = actions::SetLayerDrawOrderIndex::new(id, index); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::SelectLayer(id) => { + if self.map_resource.map.layers[&id].kind == MapLayerKind::ObjectLayer + && self.selected_tool == EditorTool::TilePlacer + { + self.selected_tool = EditorTool::Cursor; + } + self.selected_layer = Some(id); + } + UiAction::SelectTileset(id) => { + self.tile_palette = Some(TileSelection { + tileset: id, + tile_id: 0, + }); + } + UiAction::SelectTile { id, tileset_id } => { + self.tile_palette = Some(TileSelection { + tileset: tileset_id, + tile_id: id, + }); + self.selected_tool = EditorTool::TilePlacer; + } + UiAction::PlaceTile { + id, + coords, + layer_id, + tileset_id, + } => { + let action = actions::PlaceTile::new(id, layer_id, tileset_id, coords); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::RemoveTile { coords, layer_id } => { + let action = actions::RemoveTile::new(layer_id, coords); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::MoveObject { + index, + layer_id, + position, + } => { + let action = actions::MoveObject::new(layer_id, index, position); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::SaveMap { name } => { + let mut map_resource = self.map_resource.clone(); + + if let Some(name) = name { + let path = Path::new(MAP_EXPORTS_DEFAULT_DIR) + .join(map_name_to_filename(&name)) + .with_extension(MAP_EXPORTS_EXTENSION); + + map_resource.meta.name = name; + map_resource.meta.path = path.to_string_lossy().to_string(); + } + + map_resource.meta.is_user_map = true; + map_resource.meta.is_tiled_map = false; + + let mut resources = storage::get_mut::(); + if resources.save_map(&map_resource).is_ok() { + self.map_resource = map_resource; + } + } + UiAction::CreateObject { + id, + kind, + layer_id, + position, + } => { + let action = actions::CreateObject::new(id, kind, position, layer_id); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::DeleteObject { index, layer_id } => { + let action = actions::DeleteObject::new(index, layer_id); + + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::CreateSpawnPoint(pos) => { + let action = actions::CreateSpawnPoint::new(pos); + + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::MoveSpawnPoint { index, position } => { + let action = actions::MoveSpawnPoint::new(index, position); + + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::DeleteSpawnPoint(index) => { + let action = actions::DeleteSpawnPoint::new(index); + + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + UiAction::CreateMap { + name, + description, + grid_size, + tile_size, + } => { + let resources = storage::get::(); + let res = resources.create_map(&name, description.as_deref(), tile_size, grid_size); + match res { + Err(err) => println!("Create Map: {}", err), + Ok(map_resource) => { + self.map_resource = map_resource; + self.clear_context(); + } + } + } + UiAction::OpenMap(index) => { + let resources = storage::get::(); + let map_resource = resources.maps.get(index).cloned().unwrap(); + + self.map_resource = map_resource; + self.clear_context(); + } + + UiAction::Import { + tilesets, + background_color, + background_layers, + } => { + let action = actions::Import::new(tilesets, background_color, background_layers); + + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + + UiAction::UpdateBackground { color, layers } => { + let action = actions::UpdateBackground::new(color, layers); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + + _ => todo!(), + } + } + + fn clear_context(&mut self) { + self.selected_tool = EditorTool::Cursor; + self.selected_layer = None; + self.tile_palette = None; + self.object_being_placed = None; + self.selection = None; + self.history.clear(); + self.level_view = Default::default(); + } + + pub fn draw_level(&self) { + let camera = { + use macroquad::prelude::*; + let target_size = vec2( + self.level_render_target.texture.width(), + self.level_render_target.texture.height(), + ); + let zoom = vec2( + self.level_view.scale / target_size.x, + self.level_view.scale / target_size.y, + ) * 2.; + + Camera2D { + offset: vec2(-1., -1.), + target: self.level_view.position, + zoom, + render_target: Some(self.level_render_target), + ..Camera2D::default() + } + }; + + macroquad::experimental::scene::set_camera(0, Some(camera)); + + let map = &self.map_resource.map; + { + map.draw_background(None, !self.is_parallax_enabled); + map.draw(None, false); + } + + if self.should_draw_grid { + self::draw_grid(map); + } + } + + pub fn process_input(&mut self, input: &EditorInput) { + const CAMERA_PAN_SPEED: f32 = 5.0; + + // Move camera + self.level_view.position += input.camera_move_direction * CAMERA_PAN_SPEED; + + // Scale camera + self.level_view.scale = (self.level_view.scale + + input.camera_zoom * Self::CAMERA_ZOOM_STEP) + .clamp(Self::CAMERA_ZOOM_MIN, Self::CAMERA_ZOOM_MAX); + + // Undo/redo + if input.undo { + self.apply_action(UiAction::Undo); + } + if input.redo { + self.apply_action(UiAction::Redo); + } + + // Toggle menu + if input.toggle_menu { + self.menu_window = if self.menu_window.is_some() { + None + } else { + Some(Default::default()) + }; + } + + // Delete selected object + if input.delete { + if let Some(entity) = &self.selection { + match &entity.kind { + SelectableEntityKind::Object { layer_id, index } => { + let layer_id = layer_id.clone(); + let index = *index; + self.apply_action(UiAction::DeleteObject { layer_id, index }); + } + SelectableEntityKind::SpawnPoint { index } => { + let index = *index; + self.apply_action(UiAction::DeleteSpawnPoint(index)); + } + } + self.selection = None; + } + } + } +} + +fn draw_grid(map: &Map) { + const GRID_LINE_WIDTH: f32 = 1.0; + const GRID_COLOR: macroquad::prelude::Color = macroquad::prelude::Color { + r: 1.0, + g: 1.0, + b: 1.0, + a: 0.25, + }; + + use macroquad::prelude::*; + + let map_size = map.grid_size.as_f32() * map.tile_size; + + draw_rectangle_lines( + map.world_offset.x, + map.world_offset.y, + map_size.x, + map_size.y, + GRID_LINE_WIDTH, + GRID_COLOR, + ); + + for x in 0..map.grid_size.x { + let begin = vec2( + map.world_offset.x + (x as f32 * map.tile_size.x), + map.world_offset.y, + ); + + let end = vec2( + begin.x, + begin.y + (map.grid_size.y as f32 * map.tile_size.y), + ); + + draw_line(begin.x, begin.y, end.x, end.y, GRID_LINE_WIDTH, GRID_COLOR) + } + + for y in 0..map.grid_size.y { + let begin = vec2( + map.world_offset.x, + map.world_offset.y + (y as f32 * map.tile_size.y), + ); + + let end = vec2( + begin.x + (map.grid_size.x as f32 * map.tile_size.x), + begin.y, + ); + + draw_line(begin.x, begin.y, end.x, end.y, GRID_LINE_WIDTH, GRID_COLOR) + } +} diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs new file mode 100644 index 0000000000..7c8edfcb5a --- /dev/null +++ b/src/editor/state/ui.rs @@ -0,0 +1,22 @@ +mod level; +mod side_panel; +mod toolbar; +mod windows; + +impl super::Editor { + pub fn ui(&mut self, egui_ctx: &egui::Context) { + // +---------+----------------------------+------------+ + // | | | | + // | toolbar | level_view | side_panel | + // | | | | + // | | +--------+ | | + // | | | window + | | + // | | +--------+ | | + // +---------+----------------------------+------------+ + + self.draw_toolbar(egui_ctx); + self.draw_side_panel(egui_ctx); + self.handle_level_view(egui_ctx); + self.draw_windows(egui_ctx); + } +} diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs new file mode 100644 index 0000000000..f514fdb284 --- /dev/null +++ b/src/editor/state/ui/level/mod.rs @@ -0,0 +1,425 @@ +mod objects; +mod tools; + +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::UiAction, + state::{DragData, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind}, + util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec, Resizable}, + view::UiLevelView, windows::BackgroundPropertiesWindow, + }, + map::{MapLayerKind, MapObjectKind}, + Resources, +}; + +use self::objects::draw_object; + +use super::super::Editor; + +impl Editor { + pub(super) fn handle_level_view(&mut self, egui_ctx: &egui::Context) { + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(egui_ctx, |ui| { + let mut view = self.draw_level_tiles(ui); + let mut clicked_add_object = false; + + view.response = view.response.context_menu(|ui| { + if let Some(MapLayerKind::ObjectLayer) = self.selected_layer_type() { + if ui.button("Add object").clicked() { + clicked_add_object = true; + ui.close_menu() + } + } + if ui.button("Edit background").clicked() { + self.background_properties_window = Some(Default::default()); + ui.close_menu(); + } + }); + + if view.response.dragged_by(egui::PointerButton::Middle) { + let drag_delta = egui_ctx.input().pointer.delta(); + + self.level_view.position -= drag_delta.into_macroquad() / view.view.scale; + } + + if clicked_add_object + || (view.response.clicked() && self.selected_tool == EditorTool::ObjectPlacer) + { + let position = view + .screen_to_world_pos(view.ctx().input().pointer.interact_pos().unwrap()); + + self.object_being_placed = + if let Some(settings) = self.object_being_placed.take() { + Some(ObjectSettings { + position, + ..settings + }) + } else { + Some(ObjectSettings { + position, + kind: MapObjectKind::Item, + id: None, + }) + }; + } + + let mut to_select = None; + self.handle_objects(ui, &view, &mut to_select); + self.handle_spawnpoints(&view, &mut to_select); + self.draw_level_overlays(ui, &view); + // Draw selection last (Special case) + self.handle_selection(&view, to_select); + + let (width, height) = ( + view.response.rect.width() as u32, + view.response.rect.height() as u32, + ); + self.level_render_target.resize_if_appropiate(width, height); + }); + } + + fn handle_selection(&mut self, view: &UiLevelView, to_select: Option) { + let mut selection_dest = None; + + let selected_anything = to_select.is_some(); + + if let Some(to_select) = to_select { + if let SelectableEntity { + kind: SelectableEntityKind::Object { layer_id, .. }, + .. + } = &to_select + { + self.selected_layer = Some(layer_id.clone()); + self.selected_tool = EditorTool::Cursor; + } + + self.selection = Some(to_select); + dbg!("Selection set: ", &self.selection); + } + + match self.selection.take() { + Some(SelectableEntity { + kind: SelectableEntityKind::Object { index, layer_id }, + mut drag_data, + }) => { + let resources = storage::get::(); + let object = &self.map_resource.map.layers[&layer_id].objects[index]; + let is_being_dragged; + let mut action_to_apply = None; + + if let Some(DragData { + new_pos, + cursor_offset, + }) = drag_data.take() + { + draw_object(object, new_pos, &view, &resources, 1.); + let response = &view.response; + + if response.dragged_by(egui::PointerButton::Primary) { + let cursor_level_pos = view.screen_to_world_pos( + view.ctx().input().pointer.interact_pos().unwrap() + cursor_offset, + ); + + drag_data = Some(DragData { + new_pos: cursor_level_pos, + cursor_offset, + }); + } + if response.drag_released() { + action_to_apply = Some(UiAction::MoveObject { + index, + layer_id: layer_id.clone(), + position: macroquad::math::vec2(new_pos.x, new_pos.y), + }); + } + + is_being_dragged = true; + } else { + is_being_dragged = false; + } + + self.selection = Some(SelectableEntity { + drag_data, + kind: SelectableEntityKind::Object { index, layer_id }, + }); + + let (dest, _is_valid) = draw_object( + object, + object.position.into_egui().to_pos2(), + &view, + &resources, + if is_being_dragged { 0.5 } else { 1.0 }, + ); + selection_dest = Some(dest); + + view.painter().add(egui::Shape::rect_stroke( + dest, + egui::Rounding::none(), + egui::Stroke::new(1., egui::Color32::WHITE), + )); + + if let Some(action) = action_to_apply { + self.apply_action(action); + } + } + + Some(SelectableEntity { + kind: SelectableEntityKind::SpawnPoint { index }, + mut drag_data, + }) => { + let resources = storage::get::(); + let texture = &storage::get::().textures["spawn_point_icon"]; + let texture_id = texture.texture.egui_id(); + let texture_size = texture.meta.size.into_egui(); + let position = self.map_resource.map.spawn_points[index]; + let is_being_dragged; + let mut action_to_apply = None; + + if let Some(DragData { + new_pos, + cursor_offset, + }) = drag_data.take() + { + let dest = egui::Rect::from_min_size( + view.world_to_screen_pos( + new_pos - egui::vec2(texture_size.x / 2., texture_size.y), + ), + texture_size, + ); + let mut mesh = egui::Mesh::with_texture(texture_id); + mesh.add_rect_with_uv( + dest, + egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)), + egui::Color32::WHITE, + ); + view.painter().add(egui::Shape::mesh(mesh)); + let response = &view.response; + + if response.dragged_by(egui::PointerButton::Primary) { + let cursor_level_pos = view.screen_to_world_pos( + view.ctx().input().pointer.interact_pos().unwrap() + cursor_offset, + ); + + drag_data = Some(DragData { + new_pos: cursor_level_pos, + cursor_offset, + }); + } + if response.drag_released() { + action_to_apply = Some(UiAction::MoveSpawnPoint { + index, + position: macroquad::math::vec2(new_pos.x, new_pos.y), + }); + } + + is_being_dragged = true; + } else { + is_being_dragged = false; + } + + self.selection = Some(SelectableEntity { + drag_data, + kind: SelectableEntityKind::SpawnPoint { index }, + }); + + let dest = egui::Rect::from_min_size( + view.world_to_screen_pos( + position.into_egui().to_pos2() + - egui::vec2(texture_size.x / 2., texture_size.y), + ), + texture_size, + ); + let mut mesh = egui::Mesh::with_texture(texture_id); + mesh.add_rect_with_uv( + dest, + egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)), + egui::Color32::WHITE.linear_multiply(if is_being_dragged { 0.5 } else { 1.0 }), + ); + view.painter().add(egui::Shape::mesh(mesh)); + selection_dest = Some(dest); + + view.painter().add(egui::Shape::rect_stroke( + dest, + egui::Rounding::none(), + egui::Stroke::new(1., egui::Color32::WHITE), + )); + + if let Some(action) = action_to_apply { + self.apply_action(action); + } + } + + _ => (), + } + + if !selected_anything + && view.response.clicked() + && !selection_dest.map_or(false, |d| { + d.contains(view.ctx().input().pointer.interact_pos().unwrap()) + }) + { + self.selection = None; + dbg!("Selection unset"); + } + } + + fn handle_spawnpoints(&mut self, view: &UiLevelView, to_select: &mut Option) { + let texture = &storage::get::().textures["spawn_point_icon"]; + let texture_id = texture.texture.egui_id(); + let texture_size = texture.meta.size.into_egui(); + let is_selection_being_dragged = matches!( + &self.selection, + Some(SelectableEntity { + drag_data: Some(_), + .. + }) + ); + + for (idx, spawnpoint) in self + .map_resource + .map + .spawn_points + .iter() + .enumerate() + .filter(|(idx, _)| { + let is_selection = matches!( + &self.selection, + Some(SelectableEntity { + kind: SelectableEntityKind::SpawnPoint { + index + }, + .. + }) if index == idx + ); + + !(is_selection && is_selection_being_dragged) + }) + { + // This position is the bottom midpoint of the destination rect + let pos = view.world_to_screen_pos(spawnpoint.into_egui().to_pos2()); + + let dest = egui::Rect::from_min_size( + pos - egui::vec2(texture_size.x / 2., texture_size.y), + texture_size, + ); + + let mut mesh = egui::Mesh::with_texture(texture_id); + mesh.add_rect_with_uv( + dest, + egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)), + egui::Color32::WHITE, + ); + view.painter().add(egui::Shape::mesh(mesh)); + + let is_hovered = view.response.hovered() + && view + .ctx() + .input() + .pointer + .hover_pos() + .map_or(false, |hover_pos| dest.contains(hover_pos)) + && self.selected_tool == EditorTool::Cursor; + + if is_hovered && !is_selection_being_dragged { + view.painter().add(egui::Shape::rect_stroke( + dest, + egui::Rounding::none(), + egui::Stroke::new(1., egui::Color32::GRAY), + )); + + if view.response.drag_started() || view.response.clicked() { + let click_pos = view.ctx().input().pointer.interact_pos().unwrap(); + + *to_select = Some(SelectableEntity { + kind: SelectableEntityKind::SpawnPoint { index: idx }, + drag_data: view.response.drag_started().then(|| DragData { + cursor_offset: pos - click_pos, + new_pos: spawnpoint.into_egui().to_pos2(), + }), + }); + } + } + } + + if self.selected_tool == EditorTool::SpawnPointPlacer && view.response.clicked() { + let pos = view + .screen_to_world_pos(view.ctx().input().pointer.interact_pos().unwrap()) + .to_vec2() + .into_macroquad(); + self.apply_action(UiAction::CreateSpawnPoint(pos)); + } + } + + fn draw_level_tiles(&self, ui: &mut egui::Ui) -> UiLevelView { + let texture_id = self.level_render_target.texture.egui_id(); + + let (response, painter) = + ui.allocate_painter(ui.available_size(), egui::Sense::click_and_drag()); + let mut level_mesh = egui::Mesh::with_texture(texture_id); + level_mesh.add_rect_with_uv( + response.rect, + egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)), + egui::Color32::WHITE, + ); + painter.add(egui::Shape::mesh(level_mesh)); + + UiLevelView::new(self.level_view, response, painter) + } + + fn draw_level_overlays(&mut self, ui: &mut egui::Ui, view: &UiLevelView) { + let level_contains_cursor = ui + .input() + .pointer + .hover_pos() + .map(|pos| view.response.rect.contains(pos)) + .unwrap_or(false); + + if level_contains_cursor { + let tile_size = self.map_resource.map.tile_size.into_egui(); + + let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); + let cursor_px_pos = view.screen_to_world_pos(cursor_screen_pos); + let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); + + // TODO: Move outside + self.handle_tool(view, cursor_tile_pos); + + self.draw_level_pointer_pos_overlay(ui, view, cursor_px_pos, cursor_tile_pos); + + self.draw_level_object_placement_overlay(view); + } + } + + fn draw_level_pointer_pos_overlay( + &self, + ui: &mut egui::Ui, + view: &UiLevelView, + cursor_px_pos: egui::Pos2, + cursor_tile_pos: egui::Pos2, + ) { + egui::containers::Area::new("pointer pos overlay") + .order(egui::Order::Tooltip) + .fixed_pos( + view.level_top_left() + + egui::vec2( + ui.spacing().window_margin.left, + ui.spacing().window_margin.top, + ), + ) + .interactable(false) + .drag_bounds(egui::Rect::EVERYTHING) // disable clip rect + .show(ui.ctx(), |ui| { + egui::Frame::popup(&ui.ctx().style()) + .show(ui, |ui| { + ui.label(format!( + "Cursor position: ({}, {}) in pixels: ({:.2}, {:.2})", + cursor_tile_pos.x, cursor_tile_pos.y, cursor_px_pos.x, cursor_px_pos.y, + )) + }) + .inner + }); + } +} diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs new file mode 100644 index 0000000000..9e473f9dfb --- /dev/null +++ b/src/editor/state/ui/level/objects.rs @@ -0,0 +1,423 @@ +use std::ops::Deref; + +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::UiAction, + state::{ + DragData, Editor, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind, + }, + util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec}, + view::UiLevelView, + }, + map::{MapLayer, MapLayerKind, MapObject, MapObjectKind}, + AnimatedSpriteMetadata, Resources, +}; + +impl Editor { + pub(super) fn draw_level_object_placement_overlay(&mut self, view: &UiLevelView) { + if let Some(settings) = &mut self.object_being_placed { + let position = view.world_to_screen_pos(settings.position); + + enum PlaceObjectResult { + Create, + Close, + Noop, + } + + let response = egui::Window::new("Placing object") + .current_pos(position) + .collapsible(false) + .resizable(false) + .show(view.ctx(), |ui| { + let target_layer = self + .selected_layer + .as_ref() + .and_then(|id| self.map_resource.map.layers.get(id)) + .filter(|layer| layer.kind == MapLayerKind::ObjectLayer); + + if let Some(target) = target_layer { + ui.label(format!("Within layer selection: {}", target.id)); + } else { + ui.label(format!("Select an object layer")); + } + + egui::ComboBox::new("object_kind", "Kind") + .selected_text(format!("{}", settings.kind)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut settings.kind, + MapObjectKind::Decoration, + "Decoration", + ) + .clicked() + .then(|| settings.id = None); + ui.selectable_value( + &mut settings.kind, + MapObjectKind::Environment, + "Environment", + ) + .clicked() + .then(|| settings.id = None); + ui.selectable_value(&mut settings.kind, MapObjectKind::Item, "Item") + .clicked() + .then(|| settings.id = None); + }); + egui::ComboBox::new("object_id", "Id") + .selected_text(settings.id.as_deref().unwrap_or("Pick one")) + .show_ui(ui, |ui| { + let resources = storage::get::(); + + match settings.kind { + MapObjectKind::Item => resources.items.keys().for_each(|id| { + ui.selectable_value(&mut settings.id, Some(id.clone()), id); + }), + MapObjectKind::Environment => { + ["sproinger", "crab"].into_iter().for_each(|id| { + ui.selectable_value( + &mut settings.id, + Some(id.to_owned()), + id, + ); + }) + } + MapObjectKind::Decoration => { + resources.decoration.keys().for_each(|id| { + ui.selectable_value(&mut settings.id, Some(id.clone()), id); + }) + } + } + }); + + ui.horizontal(|ui| { + let create_button = ui.add_enabled( + settings.id.is_some() && target_layer.is_some(), + egui::Button::new("Create"), + ); + if create_button.clicked() { + return PlaceObjectResult::Create; + } + if ui.button("Cancel").clicked() { + return PlaceObjectResult::Close; + } + + PlaceObjectResult::Noop + }) + }); + + let result = response + .and_then(|r| { + settings.position = view.screen_to_world_pos(r.response.rect.min); + + r.inner.map(|r| r.inner) + }) + .unwrap_or(PlaceObjectResult::Noop); + + match result { + PlaceObjectResult::Create => { + let id = settings.id.as_ref().unwrap().clone(); + let kind = settings.kind; + let position = settings.position.into_macroquad(); + let layer_id = self.selected_layer.as_ref().unwrap().clone(); + self.apply_action(UiAction::CreateObject { + id, + kind, + position, + layer_id, + }); + self.object_being_placed = None; + } + PlaceObjectResult::Close => { + self.object_being_placed = None; + } + PlaceObjectResult::Noop => (), + } + } + } + + pub(super) fn handle_objects( + &mut self, + ui: &mut egui::Ui, + view: &UiLevelView, + to_select: &mut Option, + ) { + for layer in self + .map_resource + .map + .draw_order + .iter() + .filter_map(|layer_id| self.map_resource.map.layers.get(layer_id)) + { + if layer.is_visible { + self.handle_object_layer(layer, view, ui, to_select); + } + } + } + + fn handle_object_layer( + &self, + layer: &MapLayer, + view: &UiLevelView, + ui: &mut egui::Ui, + to_select: &mut Option, + ) { + let resources = storage::get::(); + + let is_selection_being_dragged = matches!( + &self.selection, + Some(SelectableEntity { + drag_data: Some(_), + .. + }) + ); + + for (object_idx, object) in layer.objects.iter().enumerate().filter(|(idx, _)| { + let is_selection = matches!( + &self.selection, + Some(SelectableEntity { + kind: SelectableEntityKind::Object { + index, + layer_id + }, + .. + }) if index == idx && layer_id == &layer.id + ); + + !(is_selection && is_selection_being_dragged) + }) { + let (dest, is_valid) = draw_object( + object, + object.position.into_egui().to_pos2(), + view, + &resources, + 1., + ); + + let response = &view.response; + let is_hovered = response.hovered() + && ui + .input() + .pointer + .hover_pos() + .map(|hover_pos| dest.contains(hover_pos)) + .unwrap_or(false) + && self.selected_tool == EditorTool::Cursor; + + if is_hovered && !is_selection_being_dragged { + self.show_object_info_tooltip(ui.ctx(), object, is_valid); + + if response.drag_started() || response.clicked() { + let click_pos = ui.input().pointer.interact_pos().unwrap(); + + *to_select = Some(SelectableEntity { + kind: SelectableEntityKind::Object { + index: object_idx, + layer_id: layer.id.clone(), + }, + drag_data: response.drag_started().then(|| DragData { + cursor_offset: dest.min - click_pos, + new_pos: object.position.into_egui().to_pos2(), + }), + }); + dbg!("Selected object"); + } + view.painter().add(egui::Shape::rect_stroke( + dest, + egui::Rounding::none(), + egui::Stroke::new(1., egui::Color32::GRAY), + )); + } + } + } + + fn show_object_info_tooltip( + &self, + egui_ctx: &egui::Context, + object: &MapObject, + is_valid: bool, + ) { + egui::containers::show_tooltip_at_pointer(egui_ctx, egui::Id::new("object info"), |ui| { + ui.set_max_width(200.); + ui.vertical_centered(|ui| { + ui.heading(&object.id); + ui.label(egui::RichText::new(format!("{}", object.kind)).small()); + }); + ui.separator(); + ui.horizontal_top(|ui| { + ui.label(egui::RichText::new("Position: ").weak()); + ui.label( + egui::RichText::new(format!("({}, {})", object.position.x, object.position.y)) + .monospace(), + ); + }); + if !is_valid { + ui.label( + egui::RichText::new( + "Object is not valid (i.e. has no valid object or texture ID)", + ) + .color(egui::Color32::RED), + ); + } + }); + } +} + +pub fn draw_object( + object: &crate::map::MapObject, + position: egui::Pos2, + view: &UiLevelView, + resources: &impl Deref, + opacity: f32, +) -> (egui::Rect, bool) { + const FULL_UV: egui::Rect = egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)); + + let draw_object = |texture_id: egui::TextureId, + offset: macroquad::math::Vec2, + dest_size: egui::Vec2, + uv: egui::Rect, + tint: egui::Color32| + -> egui::Rect { + let position_in_lvl = position + offset.into_egui(); + + let dest = egui::Rect::from_min_size( + view.world_to_screen_pos(position_in_lvl), + dest_size * view.view.scale, + ); + + let mut mesh = egui::Mesh::with_texture(texture_id); + mesh.add_rect_with_uv(dest, uv, tint); + view.painter().add(egui::Shape::mesh(mesh)); + dest + }; + + let draw_invalid_object = || -> egui::Rect { + let texture_id = resources + .textures + .get("object_error_icon") + .unwrap() + .texture + .egui_id(); + let dest_size = egui::vec2(32., 32.); + let uv = egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)); + + draw_object( + texture_id, + (0., 0.).into(), + dest_size, + uv, + egui::Color32::WHITE.linear_multiply(opacity), + ) + }; + + let draw_animated_sprite = |sprite: &AnimatedSpriteMetadata, row: Option| -> egui::Rect { + if let Some(texture_res) = resources.textures.get(&sprite.texture_id) { + let tint = sprite + .tint + .map(|color| { + let [r, g, b, a]: [u8; 4] = color.into(); + egui::Color32::from_rgba_unmultiplied(r, g, b, a) + }) + .unwrap_or(egui::Color32::WHITE); + + let texture_id = texture_res.texture.egui_id(); + let texture_size = + egui::vec2(texture_res.texture.width(), texture_res.texture.height()); + let frame_size = texture_res.frame_size().into_egui(); + + let dest_size = sprite.scale.map(|s| s * frame_size).unwrap_or(frame_size); + + let uv = row + .map(|row| { + egui::Rect::from_min_size( + (egui::vec2(0.0, row as f32 * frame_size.y) / texture_size).to_pos2(), + frame_size / texture_size, + ) + }) + .unwrap_or(FULL_UV); + + draw_object( + texture_id, + sprite.offset, + dest_size, + uv, + tint.linear_multiply(opacity), + ) + } else { + // Invalid texture ID + draw_invalid_object() + } + }; + + let dest; + let is_valid; + match object.kind { + MapObjectKind::Decoration => { + if let Some(meta) = resources.decoration.get(&object.id) { + dest = draw_animated_sprite( + &meta.sprite, + meta.sprite.animations.first().map(|a| a.row), + ); + is_valid = true; + } else { + // Invalid object ID + dest = draw_invalid_object(); + is_valid = false; + } + } + + MapObjectKind::Item => { + if let Some(meta) = resources.items.get(&object.id) { + dest = draw_animated_sprite( + &meta.sprite, + Some( + meta.sprite + .animations + .iter() + .find(|&a| a.id == *crate::player::IDLE_ANIMATION_ID) + .map(|a| a.row) + .unwrap_or_default(), + ), + ); + is_valid = true; + } else { + // Invalid object ID + dest = draw_invalid_object(); + is_valid = false; + } + } + MapObjectKind::Environment => { + if &object.id == "sproinger" { + let texture_res = resources.textures.get("sproinger").unwrap(); + + let texture_id = texture_res.texture.egui_id(); + let texture_size = + egui::vec2(texture_res.texture.width(), texture_res.texture.height()); + let dest_size = texture_res + .meta + .frame_size + .map(macroquad::math::Vec2::into_egui) + .unwrap_or_else(|| texture_size); + let uv = egui::Rect::from_min_size( + egui::Vec2::ZERO.to_pos2(), + texture_res.frame_size().into_egui() / texture_size, + ); + + dest = draw_object( + texture_id, + (0., 0.).into(), + dest_size, + uv, + egui::Color32::WHITE.linear_multiply(opacity), + ); + is_valid = true; + } else { + // Invalid object ID + dest = draw_invalid_object(); + is_valid = false; + } + } + }; + + (dest, is_valid) +} diff --git a/src/editor/state/ui/level/spawnpoints.rs b/src/editor/state/ui/level/spawnpoints.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/editor/state/ui/level/tools.rs b/src/editor/state/ui/level/tools.rs new file mode 100644 index 0000000000..c983cdcde7 --- /dev/null +++ b/src/editor/state/ui/level/tools.rs @@ -0,0 +1,90 @@ +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::UiAction, + state::EditorTool, + util::{EguiCompatibleVec, EguiTextureHandler}, + view::UiLevelView, + Editor, + }, + Resources, +}; + +impl Editor { + pub(super) fn handle_tool(&mut self, view: &UiLevelView, cursor_tile_pos: egui::Pos2) { + match self.selected_tool { + EditorTool::TilePlacer => self.handle_tile_placement(view, cursor_tile_pos), + // TODO: Spawnpoint placement overlay + // TODO: Object placement overlay + _ => (), + }; + } + + fn handle_tile_placement(&mut self, view: &UiLevelView, cursor_tile_pos: egui::Pos2) { + let tile_size = self.map_resource.map.tile_size.into_egui(); + + if cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { + if let (Some(selected_tile), Some(selected_layer)) = + (&self.tile_palette, &self.selected_layer) + { + let tileset = &self.map_resource.map.tilesets[&selected_tile.tileset]; + let texture_id = storage::get::().textures[&tileset.texture_id] + .texture + .egui_id(); + let tileset_uv_tile_size = egui::Vec2::splat(1.) + / egui::vec2(tileset.grid_size.x as f32, tileset.grid_size.y as f32); + let tileset_x = + (selected_tile.tile_id % tileset.grid_size.x) as f32 * tileset_uv_tile_size.x; + let tileset_y = + (selected_tile.tile_id / tileset.grid_size.x) as f32 * tileset_uv_tile_size.y; + let uv = egui::Rect::from_min_size( + egui::Pos2 { + x: tileset_x, + y: tileset_y, + }, + tileset_uv_tile_size, + ); + + let mut tile_mesh = egui::Mesh::with_texture(texture_id); + tile_mesh.add_rect_with_uv( + egui::Rect::from_min_size( + view.world_to_screen_pos((cursor_tile_pos.to_vec2() * tile_size).to_pos2()), + tile_size * view.view.scale, + ), + uv, + egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), + ); + + view.painter().add(egui::Shape::mesh(tile_mesh)); + + if view.response.clicked() || view.response.dragged() { + let input = view.ctx().input(); + if input.pointer.button_down(egui::PointerButton::Primary) { + let id = selected_tile.tile_id; + let layer_id = selected_layer.clone(); + let tileset_id = selected_tile.tileset.clone(); + self.apply_action(UiAction::PlaceTile { + id, + layer_id, + tileset_id, + coords: macroquad::math::UVec2::new( + cursor_tile_pos.x as u32, + cursor_tile_pos.y as u32, + ), + }); + } else if input.pointer.button_down(egui::PointerButton::Secondary) { + let layer_id = selected_layer.clone(); + self.apply_action(UiAction::RemoveTile { + layer_id, + coords: macroquad::math::UVec2::new( + cursor_tile_pos.x as u32, + cursor_tile_pos.y as u32, + ), + }); + } + } + } + } + } +} diff --git a/src/editor/state/ui/side_panel/layers.rs b/src/editor/state/ui/side_panel/layers.rs new file mode 100644 index 0000000000..3a6e0d7bef --- /dev/null +++ b/src/editor/state/ui/side_panel/layers.rs @@ -0,0 +1,160 @@ +use egui_extras::Size; + +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::Editor, + }, + map::MapLayerKind, +}; + +use super::TABLE_ROW_HEIGHT; + +impl Editor { + pub(super) fn draw_layer_info(&mut self, ui: &mut egui::Ui) { + ui.heading("Layers"); + self.draw_layer_list(ui); // Draw layer list + self.draw_layer_utils(ui); // Draw layer util buttons (+ - Up Down) + } + + fn draw_layer_list(&mut self, ui: &mut egui::Ui) { + let mut action = None; + + ui.group(|ui| { + egui_extras::TableBuilder::new(ui) + .column(Size::exact(40.0)) + .column(Size::remainder().at_least(100.0)) + .column(Size::exact(70.0)) + .striped(true) + .sense(egui::Sense::click()) + .header(20.0, |mut header| { + header.col(|ui| { + ui.heading("Type"); + }); + header.col(|ui| { + ui.heading("Name"); + }); + header.col(|ui| { + ui.heading("Visibility"); + }); + }) + .body(|body| { + body.rows( + TABLE_ROW_HEIGHT, + self.map_resource.map.draw_order.len(), + |row_index, mut row| { + let layer_name = &self.map_resource.map.draw_order[row_index]; + let layer = &self.map_resource.map.layers[layer_name]; + let mut clicked = false; + let is_selected = self.selected_layer.as_ref() == Some(layer_name); + + clicked |= row + .col(|ui| { + let mut label = egui::RichText::new(match layer.kind { + MapLayerKind::TileLayer => "T", + MapLayerKind::ObjectLayer => "O", + }); + + if is_selected { + label = label.strong(); + } + + ui.label(label); + }) + .clicked(); + clicked |= row + .col(|ui| { + let mut label = egui::RichText::new(layer_name); + + if is_selected { + label = label.strong(); + } + + ui.label(label); + }) + .clicked(); + let mut is_visible = layer.is_visible; + clicked |= row + .col(|ui| { + let mut label = egui::RichText::new("Visible"); + + if is_selected { + label = label.strong(); + } + + if ui.checkbox(&mut is_visible, label).clicked() { + action.then_do_some(UiAction::UpdateLayer { + id: layer_name.clone(), + is_visible, + }); + } + }) + .clicked(); + + if clicked { + let layer_name = layer_name.clone(); + self.apply_action(UiAction::SelectLayer(layer_name)); + } + }, + ); + }); + }); + + if let Some(action) = action { + self.apply_action(action); + } + } + + fn draw_layer_utils(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + if ui.button("+").clicked() { + self.create_layer_window = Some(Default::default()); + } + + ui.add_enabled_ui(self.selected_layer.is_some(), |ui| { + if ui.button("-").clicked() { + let layer_id = self.selected_layer.as_ref().unwrap().clone(); + self.apply_action(UiAction::DeleteLayer(layer_id)); + } + let selected_layer_idx = self + .selected_layer + .as_ref() + .and_then(|layer| { + self.map_resource + .map + .draw_order + .iter() + .enumerate() + .find(|(_idx, id)| &layer == id) + .map(|(idx, _)| idx) + }) + .unwrap_or(usize::MAX - 1); + + if ui + .add_enabled(selected_layer_idx > 0, egui::Button::new("Up")) + .clicked() + { + let id = self.selected_layer.as_ref().unwrap().clone(); + self.apply_action(UiAction::SetLayerDrawOrderIndex { + id, + index: selected_layer_idx - 1, + }); + } + + if ui + .add_enabled( + selected_layer_idx + 1 < self.map_resource.map.draw_order.len(), + egui::Button::new("Down"), + ) + .clicked() + { + let id = self.selected_layer.as_ref().unwrap().clone(); + self.apply_action(UiAction::SetLayerDrawOrderIndex { + id, + index: selected_layer_idx + 1, + }); + } + }); + }); + } +} diff --git a/src/editor/state/ui/side_panel/mod.rs b/src/editor/state/ui/side_panel/mod.rs new file mode 100644 index 0000000000..842088d3d2 --- /dev/null +++ b/src/editor/state/ui/side_panel/mod.rs @@ -0,0 +1,35 @@ +mod layers; +mod objects; +mod tileset; + +use crate::map::MapLayerKind; + +use super::super::Editor; + +pub const TABLE_ROW_HEIGHT: f32 = 18.0; + +impl Editor { + pub(super) fn draw_side_panel(&mut self, egui_ctx: &egui::Context) { + egui::SidePanel::new(egui::containers::panel::Side::Right, "Side panel").show( + egui_ctx, + |ui| { + self.draw_layer_info(ui); + + ui.separator(); + + if let Some(selected_layer_id) = self.selected_layer.as_ref() { + if let Some(layer) = self.map_resource.map.layers.get(selected_layer_id) { + match layer.kind { + MapLayerKind::TileLayer => { + self.draw_tileset_info(ui); + } + MapLayerKind::ObjectLayer => { + self.draw_object_info(ui, layer); + } + } + } + } + }, + ); + } +} diff --git a/src/editor/state/ui/side_panel/objects.rs b/src/editor/state/ui/side_panel/objects.rs new file mode 100644 index 0000000000..9b78441b3c --- /dev/null +++ b/src/editor/state/ui/side_panel/objects.rs @@ -0,0 +1,56 @@ +use egui::RichText; +use egui_extras::TableBuilder; +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::{EditorTool, SelectableEntity, SelectableEntityKind}, + util::EguiTextureHandler, + }, + map::MapLayer, + resources::TextureResource, + Resources, +}; + +use super::{Editor, TABLE_ROW_HEIGHT}; + +impl Editor { + pub(super) fn draw_object_info(&self, ui: &mut egui::Ui, layer: &MapLayer) { + egui_extras::TableBuilder::new(ui) + .column(egui_extras::Size::remainder()) + .striped(true) + .header(20.0, |mut row| { + row.col(|ui| { + ui.heading("Objects"); + }); + }) + .body(|body| { + body.rows( + TABLE_ROW_HEIGHT, + layer.objects.len(), + |row_index, mut row| { + let object = &layer.objects[row_index]; + let is_selected = matches!( + self.selection.as_ref(), + Some(SelectableEntity { + kind: SelectableEntityKind::Object { + layer_id, + index + }, + .. + }) if layer_id == &layer.id && index == &row_index + ); + + row.col(|ui| { + if is_selected { + ui.label(RichText::new(&object.id).strong()); + } else { + ui.label(&object.id); + } + }); + }, + ) + }) + } +} diff --git a/src/editor/state/ui/side_panel/tileset.rs b/src/editor/state/ui/side_panel/tileset.rs new file mode 100644 index 0000000000..2abf77bc97 --- /dev/null +++ b/src/editor/state/ui/side_panel/tileset.rs @@ -0,0 +1,122 @@ +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::EditorTool, + util::EguiTextureHandler, + }, + resources::TextureResource, + Resources, +}; + +use super::Editor; + +impl Editor { + pub(super) fn draw_tileset_info(&mut self, ui: &mut egui::Ui) { + ui.heading("Tilesets"); + self.draw_tileset_list(ui); // Draw tileset list + self.draw_tileset_utils(ui); // Draw tileset utils + self.draw_tileset_image(ui); // Draw tileset image + } + + fn draw_tileset_list(&mut self, ui: &mut egui::Ui) { + let mut action = None; + + ui.group(|ui| { + for (tileset_name, _tileset) in self.map_resource.map.tilesets.iter() { + let is_selected = self + .tile_palette + .as_ref() + .map(|s| &s.tileset == tileset_name) + .unwrap_or(false); + + if ui.selectable_label(is_selected, tileset_name).clicked() { + action.then_do_some(UiAction::SelectTileset(tileset_name.clone())); + } + } + }); + + if let Some(action) = action { + self.apply_action(action); + } + } + + fn draw_tileset_utils(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + if ui.button("+").clicked() { + self.create_tileset_window = Some(Default::default()); + } + ui.add_enabled_ui(self.tile_palette.is_some(), |ui| { + if ui.button("-").clicked() { + let selected_tileset = self.tile_palette.as_ref().unwrap().tileset.clone(); + self.apply_action(UiAction::DeleteTileset(selected_tileset)); + } + ui.button("Edit").on_hover_text("Does not do anything yet"); + }) + }); + } + + fn draw_tileset_image(&mut self, ui: &mut egui::Ui) { + if let Some(selection) = &self.tile_palette { + if let Some(tileset) = self.map_resource.map.tilesets.get(&selection.tileset) { + let tileset_texture: &TextureResource = + &storage::get::().textures[&tileset.texture_id]; + let texture_id = tileset_texture.texture.egui_id(); + let texture_size = tileset_texture.meta.size; + let tile_size = egui::Vec2 { + x: tileset.tile_size.x, + y: tileset.tile_size.y, + }; + let tileset_size = egui::Vec2 { + x: tileset.grid_size.x as f32, + y: tileset.grid_size.y as f32, + }; + + let image = + egui::Image::new(texture_id, egui::Vec2::new(texture_size.x, texture_size.y)) + .sense(egui::Sense::click()); + let image_response = ui.add(image); + let image_bounds = image_response.rect; + + if let EditorTool::TilePlacer = self.selected_tool { + let painter = ui.painter_at(image_bounds); + let tile_rect = egui::Rect::from_min_size( + image_bounds.min + + tile_size + * egui::Vec2::new( + (selection.tile_id % tileset_size.x as u32) as f32, + (selection.tile_id / tileset_size.x as u32) as f32, + ), + tile_size, + ); + painter.rect_filled( + tile_rect, + egui::Rounding::none(), + egui::Color32::BLUE.linear_multiply(0.3), + ); + } + + if image_response.clicked() { + let mouse_pos = ui.input().pointer.interact_pos().unwrap() - image_bounds.min; + let tile_pos = (mouse_pos / tile_size).floor(); + dbg!(&tile_pos); + if tile_pos.x < tileset_size.x + && tile_pos.y < tileset_size.y + && tile_pos.x >= 0. + && tile_pos.y >= 0. + { + let tile_id = tile_pos.x as u32 + tile_pos.y as u32 * tileset_size.x as u32; + + let selection_tileset = selection.tileset.clone(); + + self.apply_action(UiAction::SelectTile { + id: tile_id, + tileset_id: selection_tileset, + }); + } + } + } + } + } +} diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs new file mode 100644 index 0000000000..66e16e2c87 --- /dev/null +++ b/src/editor/state/ui/toolbar.rs @@ -0,0 +1,39 @@ +use crate::{ + editor::{actions::UiAction, state::EditorTool}, + map::MapLayerKind, +}; + +use super::super::Editor; + +impl Editor { + /// Draw the sidebar placed on the left showcasing all available [`EditorTool`]s. + pub(super) fn draw_toolbar(&mut self, egui_ctx: &egui::Context) { + egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show(egui_ctx, |ui| { + let selected_layer_type = self.selected_layer_type(); + let mut add_tool = |tool_name, tool_variant| { + if ui + .add(egui::SelectableLabel::new( + self.selected_tool == tool_variant, + tool_name, + )) + .clicked() + { + self.apply_action(UiAction::SelectTool(tool_variant)); + } + }; + + add_tool("Cursor", EditorTool::Cursor); + add_tool("Spawn Point Placer", EditorTool::SpawnPointPlacer); + match selected_layer_type { + Some(MapLayerKind::TileLayer) => { + add_tool("Tiles", EditorTool::TilePlacer); + add_tool("Eraser", EditorTool::Eraser); + } + Some(MapLayerKind::ObjectLayer) => { + add_tool("Object Placer", EditorTool::ObjectPlacer); + } + _ => (), + } + }); + } +} diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs new file mode 100644 index 0000000000..2f11f11adf --- /dev/null +++ b/src/editor/state/ui/windows.rs @@ -0,0 +1,165 @@ +use std::ops::ControlFlow; + +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::UiAction, + state::Editor, + windows::{ + CreateLayerResult, CreateMapResult, CreateTilesetResult, ImportResult, ImportWindow, + MenuResult, OpenMapResult, SaveMapResult, + }, + }, + Resources, +}; + +impl Editor { + pub(super) fn draw_windows(&mut self, egui_ctx: &egui::Context) { + if let Some(window) = &mut self.background_properties_window { + if let Some(action) = window.ui(egui_ctx, &self.map_resource.map) { + self.apply_action(action); + } + } + + if let Some(window) = &mut self.create_layer_window { + match window.ui(egui_ctx, &self.map_resource.map) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(CreateLayerResult::Create { + has_collision, + layer_kind, + layer_name, + }) => { + self.apply_action(UiAction::CreateLayer { + has_collision, + kind: layer_kind, + index: None, + id: layer_name, + }); + self.create_layer_window = None; + } + ControlFlow::Break(CreateLayerResult::Close) => { + self.create_layer_window = None; + } + } + } + + if let Some(window) = &mut self.create_tileset_window { + match window.ui(egui_ctx, &self.map_resource.map) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(CreateTilesetResult::Create { + tileset_name, + texture, + }) => { + self.apply_action(UiAction::CreateTileset { + id: tileset_name, + texture_id: texture, + }); + self.create_tileset_window = None; + } + ControlFlow::Break(CreateTilesetResult::Close) => { + self.create_tileset_window = None; + } + } + } + + if let Some(window) = &mut self.save_map_window { + match window.ui(egui_ctx) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(SaveMapResult::Save { name }) => { + self.apply_action(UiAction::SaveMap { name: Some(name) }); + self.save_map_window = None; + } + ControlFlow::Break(SaveMapResult::Close) => { + self.save_map_window = None; + } + } + } else if let Some(window) = &mut self.create_map_window { + match window.ui(egui_ctx) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(CreateMapResult::Create { + name, + description, + tile_size, + grid_size, + }) => { + self.apply_action(UiAction::CreateMap { + name, + description: if description.is_empty() { + None + } else { + Some(description) + }, + tile_size: tile_size.as_f32(), + grid_size, + }); + self.create_map_window = None; + } + ControlFlow::Break(CreateMapResult::Close) => { + self.create_map_window = None; + } + } + } else if let Some(window) = &mut self.open_map_window { + match window.ui(egui_ctx) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(OpenMapResult::Open { map_index }) => { + self.apply_action(UiAction::OpenMap(map_index)); + self.open_map_window = None; + } + ControlFlow::Break(OpenMapResult::Import { map_index }) => { + self.import_window = Some(ImportWindow::new( + storage::get::().maps[map_index].map.clone(), + )); + self.open_map_window = None; + } + ControlFlow::Break(OpenMapResult::Close) => { + self.open_map_window = None; + } + } + } else if let Some(window) = &mut self.import_window { + match window.ui(egui_ctx) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(ImportResult::Import { + background_color, + background_layers, + tilesets, + }) => { + self.apply_action(UiAction::Import { + background_color, + background_layers, + tilesets, + }); + self.import_window = None; + } + ControlFlow::Break(ImportResult::Close) => { + self.import_window = None; + } + } + } else if let Some(window) = &mut self.menu_window { + match window.ui(egui_ctx, self.map_resource.meta.is_user_map) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(MenuResult::OpenCreateMapWindow) => { + self.create_map_window = Some(Default::default()); + self.menu_window = None; + } + ControlFlow::Break(MenuResult::OpenLoadMapWindow) => { + self.open_map_window = Some(Default::default()); + self.menu_window = None; + } + ControlFlow::Break(MenuResult::OpenSaveMapWindow) => { + self.save_map_window = Some(Default::default()); + self.menu_window = None; + } + ControlFlow::Break(MenuResult::SaveMap) => { + self.apply_action(UiAction::SaveMap { name: None }); + } + ControlFlow::Break(MenuResult::ExitToMainMenu) => { + crate::exit_to_main_menu(); + } + ControlFlow::Break(MenuResult::QuitToDesktop) => { + crate::quit_to_desktop(); + } + } + } + } +} diff --git a/src/editor/tools/eraser.rs b/src/editor/tools/eraser.rs deleted file mode 100644 index e8e5faab7a..0000000000 --- a/src/editor/tools/eraser.rs +++ /dev/null @@ -1,104 +0,0 @@ -use macroquad::{color, prelude::*}; - -use super::{EditorAction, EditorContext, EditorTool, EditorToolParams}; - -use crate::{ - editor::EditorCamera, - map::{Map, MapLayerKind}, -}; - -#[derive(Default)] -pub struct EraserTool { - params: EditorToolParams, -} - -impl EraserTool { - pub fn new() -> Self { - let params = EditorToolParams { - name: "Erase Tiles".to_string(), - icon_texture_id: "eraser_tool_icon".to_string(), - is_continuous: true, - }; - - EraserTool { params } - } -} - -impl EditorTool for EraserTool { - fn get_params(&self) -> &EditorToolParams { - &self.params - } - - fn get_action(&mut self, map: &Map, ctx: &EditorContext) -> Option { - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - - if map.contains(cursor_world_position) { - if let Some(layer_id) = &ctx.selected_layer { - let layer = map.layers.get(layer_id).unwrap(); - let camera = scene::find_node_by_type::().unwrap(); - let world_position = camera.to_world_space(ctx.cursor_position); - - match layer.kind { - MapLayerKind::TileLayer => { - let coords = map.to_coords(world_position); - - return Some(EditorAction::RemoveTile { - layer_id: layer_id.clone(), - coords, - }); - } - MapLayerKind::ObjectLayer => { - // TODO: Implement object layers - } - } - } - } - - None - } - - fn is_available(&self, map: &Map, ctx: &EditorContext) -> bool { - if let Some(layer_id) = &ctx.selected_layer { - let layer = map.layers.get(layer_id).unwrap(); - return layer.kind == MapLayerKind::TileLayer; - } - - false - } - - fn draw_cursor(&mut self, map: &Map, ctx: &EditorContext) -> Option { - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - - if map.contains(cursor_world_position) { - if let Some(layer_id) = &ctx.selected_layer { - let layer = map.layers.get(layer_id).unwrap(); - - if layer.kind == MapLayerKind::TileLayer { - let coords = map.to_coords(cursor_world_position); - let position = map.to_position(coords); - - let outline_color = if layer.tiles[map.to_index(coords)].is_some() { - color::YELLOW - } else { - color::RED - }; - - draw_rectangle_lines( - position.x, - position.y, - map.tile_size.x, - map.tile_size.y, - 2.0, - outline_color, - ); - } - } - } - - None - } -} diff --git a/src/editor/tools/mod.rs b/src/editor/tools/mod.rs deleted file mode 100644 index fb93cfb74a..0000000000 --- a/src/editor/tools/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::{any::TypeId, collections::HashMap}; - -mod eraser; -mod placement; - -pub use eraser::EraserTool; -pub use placement::{ObjectPlacementTool, SpawnPointPlacementTool, TilePlacementTool}; - -use macroquad::prelude::*; - -use super::{EditorAction, EditorContext, Map}; - -static mut TOOL_INSTANCES: Option>> = None; - -unsafe fn get_tool_instance_directory() -> &'static mut HashMap> { - if TOOL_INSTANCES.is_none() { - TOOL_INSTANCES = Some(HashMap::new()); - } - - TOOL_INSTANCES.as_mut().unwrap() -} - -pub fn add_tool_instance(tool: T) -> TypeId { - let id = TypeId::of::(); - unsafe { get_tool_instance_directory() }.insert(id, Box::new(tool)); - id -} - -pub fn get_tool_instance_of_id(id: &TypeId) -> &'static mut dyn EditorTool { - unsafe { get_tool_instance_directory() } - .get_mut(id) - .unwrap() - .as_mut() -} - -// TODO: Cast to T -pub fn get_tool_instance() -> &'static mut dyn EditorTool { - let id = TypeId::of::(); - get_tool_instance_of_id(&id) -} - -#[allow(dead_code)] -pub fn get_tool_params_of_id(id: &TypeId) -> &'static EditorToolParams { - get_tool_instance_of_id(id).get_params() -} - -#[allow(dead_code)] -pub fn get_tool_params() -> &'static EditorToolParams { - let id = TypeId::of::(); - get_tool_params_of_id(&id) -} - -pub const DEFAULT_TOOL_ICON_TEXTURE_ID: &str = "default_tool_icon"; - -#[derive(Debug, Clone)] -pub struct EditorToolParams { - pub name: String, - pub icon_texture_id: String, - /// Set this to `true` if the tool should be activated every update when action button is held - /// or `false` if it should only activate once per click - pub is_continuous: bool, -} - -impl Default for EditorToolParams { - fn default() -> Self { - EditorToolParams { - name: "Unnamed Tool".to_string(), - icon_texture_id: DEFAULT_TOOL_ICON_TEXTURE_ID.to_string(), - is_continuous: false, - } - } -} - -pub trait EditorTool { - fn get_params(&self) -> &EditorToolParams; - - fn get_action(&mut self, map: &Map, ctx: &EditorContext) -> Option; - - fn update(&mut self, _map: &Map, _ctx: &EditorContext) -> Option { - None - } - - fn is_available(&self, _map: &Map, _ctx: &EditorContext) -> bool { - true - } - - fn draw_cursor(&mut self, _map: &Map, _ctx: &EditorContext) -> Option { - None - } -} diff --git a/src/editor/tools/placement.rs b/src/editor/tools/placement.rs deleted file mode 100644 index c397527dd8..0000000000 --- a/src/editor/tools/placement.rs +++ /dev/null @@ -1,308 +0,0 @@ -use macroquad::{color, experimental::collections::storage, prelude::*}; - -use super::{EditorAction, EditorContext, EditorTool, EditorToolParams}; - -use crate::{ - editor::EditorCamera, - map::{Map, MapLayerKind}, - rand::ChooseRandom, - Resources, -}; - -#[derive(Default)] -pub struct TilePlacementTool { - params: EditorToolParams, - coords: Option, -} - -impl TilePlacementTool { - pub fn new() -> Self { - let params = EditorToolParams { - name: "Place Tiles".to_string(), - icon_texture_id: "tile_placement_tool_icon".to_string(), - is_continuous: true, - }; - - TilePlacementTool { - params, - coords: None, - } - } -} - -impl EditorTool for TilePlacementTool { - fn get_params(&self) -> &EditorToolParams { - &self.params - } - - fn get_action(&mut self, map: &Map, ctx: &EditorContext) -> Option { - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - - if map.contains(cursor_world_position) { - if let Some(layer_id) = &ctx.selected_layer { - let camera = scene::find_node_by_type::().unwrap(); - let world_position = camera.to_world_space(ctx.cursor_position); - - if let Some(tileset_id) = &ctx.selected_tileset { - if let Some(tile_id) = ctx.selected_tile { - let coords = map.to_coords(world_position); - - return Some(EditorAction::PlaceTile { - id: tile_id, - layer_id: layer_id.clone(), - tileset_id: tileset_id.clone(), - coords, - }); - } - } - } - } - - None - } - - fn update(&mut self, map: &Map, ctx: &EditorContext) -> Option { - #[allow(unused_mut)] - let mut res = None; - - if self.is_available(map, ctx) { - if let Some(tileset_id) = &ctx.selected_tileset { - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - let coords = map.to_coords(cursor_world_position); - - if self.coords != Some(coords) { - let tileset = map.tilesets.get(tileset_id).unwrap(); - - // Do autotile resolution here and set `res` to an `EditorAction::SelectTile` if - // selected tile should be changed according to context. - - //Get self surrounding tiles - let mut surrounding_tiles: Vec = vec![]; - for y in 0..3 { - for x in 0..3 { - if let Some(layer) = &ctx.selected_layer { - let is_some = map - .get_tile( - layer, - (coords.x + x).saturating_sub(1), - (coords.y + y).saturating_sub(1), - ) - .is_some(); - surrounding_tiles.push(is_some); - } - } - } - - //Get bitmask value from self surrounding tiles - let mut bitmask = 0; - for (i, b) in surrounding_tiles.iter().enumerate() { - if *b && i < 4 { - bitmask += 2_u32.pow(i as u32); - } else if *b && i > 4 { - bitmask += 2_u32.pow(i as u32 - 1); - } - } - - let mut tile_ids = Vec::new(); - if let Some(bitmasks) = &tileset.bitmasks { - for (i, tileset_bitmask) in bitmasks.iter().enumerate() { - if *tileset_bitmask == bitmask && bitmask != 0 { - tile_ids.push(i as u32); - } - } - } - - if let Some(id) = tile_ids.choose() { - res = Some(EditorAction::SelectTile { - tileset_id: tileset_id.to_owned(), - id: *id, - }); - } - } - - self.coords = Some(coords); - } - } - - res - } - - fn is_available(&self, map: &Map, ctx: &EditorContext) -> bool { - if let Some(layer_id) = &ctx.selected_layer { - let layer = map.layers.get(layer_id).unwrap(); - return layer.kind == MapLayerKind::TileLayer; - } - - false - } - - fn draw_cursor(&mut self, map: &Map, ctx: &EditorContext) -> Option { - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - - if map.contains(cursor_world_position) { - if let Some(layer_id) = &ctx.selected_layer { - let layer = map.layers.get(layer_id).unwrap(); - - if layer.kind == MapLayerKind::TileLayer { - if let Some(tileset_id) = &ctx.selected_tileset { - if let Some(tile_id) = ctx.selected_tile { - let tileset = map.tilesets.get(tileset_id).unwrap(); - - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - - let coords = map.to_coords(cursor_world_position); - let position = map.to_position(coords); - - let texture_coords = tileset.get_texture_coords(tile_id); - let texture = { - let resources = storage::get::(); - let res = resources.textures.get(&tileset.texture_id).unwrap(); - res.texture - }; - - let source_rect = Rect::new( - texture_coords.x, - texture_coords.y, - map.tile_size.x, - map.tile_size.y, - ); - - draw_texture_ex( - texture, - position.x, - position.y, - color::WHITE, - DrawTextureParams { - dest_size: Some(map.tile_size), - source: Some(source_rect), - ..Default::default() - }, - ) - } - } - } - } - } - - None - } -} - -#[derive(Default)] -pub struct ObjectPlacementTool { - params: EditorToolParams, -} - -impl ObjectPlacementTool { - pub fn new() -> Self { - let params = EditorToolParams { - name: "Place Objects".to_string(), - icon_texture_id: "object_placement_tool_icon".to_string(), - ..Default::default() - }; - - ObjectPlacementTool { params } - } -} - -impl EditorTool for ObjectPlacementTool { - fn get_params(&self) -> &EditorToolParams { - &self.params - } - - fn get_action(&mut self, map: &Map, ctx: &EditorContext) -> Option { - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - - if map.contains(cursor_world_position) { - if let Some(layer_id) = ctx.selected_layer.clone() { - let layer = map.layers.get(&layer_id).unwrap(); - - if layer.kind == MapLayerKind::ObjectLayer { - let mut position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - - let rect = Rect::new( - map.world_offset.x, - map.world_offset.y, - map.grid_size.x as f32 * map.tile_size.x, - map.grid_size.y as f32 * map.tile_size.y, - ); - - if ctx.should_snap_to_grid { - let coords = map.to_coords(position); - position = map.to_position(coords); - } - - if rect.contains(position) { - let action = EditorAction::OpenCreateObjectWindow { position, layer_id }; - - return Some(action); - } - } - } - } - - None - } - - fn is_available(&self, map: &Map, ctx: &EditorContext) -> bool { - if let Some(layer_id) = &ctx.selected_layer { - let layer = map.layers.get(layer_id).unwrap(); - return layer.kind == MapLayerKind::ObjectLayer; - } - - false - } -} - -pub struct SpawnPointPlacementTool { - params: EditorToolParams, -} - -impl SpawnPointPlacementTool { - pub fn new() -> Self { - let params = EditorToolParams { - name: "Place Spawn Point".to_string(), - icon_texture_id: "spawn_point_placement_tool_icon".to_string(), - ..Default::default() - }; - - SpawnPointPlacementTool { params } - } -} - -impl EditorTool for SpawnPointPlacementTool { - fn get_params(&self) -> &EditorToolParams { - &self.params - } - - fn get_action(&mut self, _map: &Map, ctx: &EditorContext) -> Option { - // TODO: Snap to grid - - let cursor_world_position = scene::find_node_by_type::() - .unwrap() - .to_world_space(ctx.cursor_position); - - let resources = storage::get::(); - let texture_res = resources.textures.get("spawn_point_icon").unwrap(); - let offset = vec2( - texture_res.texture.width() / 2.0, - texture_res.texture.height(), - ); - - let action = EditorAction::CreateSpawnPoint(cursor_world_position - offset); - - Some(action) - } -} diff --git a/src/editor/util.rs b/src/editor/util.rs new file mode 100644 index 0000000000..07a3164280 --- /dev/null +++ b/src/editor/util.rs @@ -0,0 +1,50 @@ +pub trait EguiCompatibleVec { + fn into_egui(self) -> egui::Vec2; +} + +impl EguiCompatibleVec for macroquad::math::Vec2 { + fn into_egui(self) -> egui::Vec2 { + egui::vec2(self.x, self.y) + } +} + +pub trait MqCompatibleVec { + fn into_macroquad(self) -> macroquad::math::Vec2; +} + +impl MqCompatibleVec for egui::Vec2 { + fn into_macroquad(self) -> macroquad::math::Vec2 { + macroquad::math::vec2(self.x, self.y) + } +} + +impl MqCompatibleVec for egui::Pos2 { + fn into_macroquad(self) -> macroquad::math::Vec2 { + macroquad::math::vec2(self.x, self.y) + } +} + +pub trait EguiTextureHandler { + fn egui_id(&self) -> egui::TextureId; +} + +impl EguiTextureHandler for macroquad::prelude::Texture2D { + fn egui_id(&self) -> egui::TextureId { + egui::TextureId::User(self.raw_miniquad_texture_handle().gl_internal_id() as u64) + } +} + +pub trait Resizable { + /// Resizes by recreating the render target if the current width & height don't match the + /// parameters given. + fn resize_if_appropiate(&mut self, width: u32, height: u32); +} + +impl Resizable for macroquad::prelude::RenderTarget { + fn resize_if_appropiate(&mut self, width: u32, height: u32) { + if width != self.texture.width() as u32 || height != self.texture.height() as u32 { + self.delete(); + *self = macroquad::prelude::render_target(width, height); + } + } +} diff --git a/src/editor/view.rs b/src/editor/view.rs new file mode 100644 index 0000000000..0901076da0 --- /dev/null +++ b/src/editor/view.rs @@ -0,0 +1,70 @@ +use super::util::EguiCompatibleVec; + +#[derive(Clone, Copy)] +pub struct LevelView { + /// The view offset in pixels. + pub position: macroquad::prelude::Vec2, + /// The scale the level is viewed with. 1.0 == 1:1, bigger numbers mean bigger tiles. + pub scale: f32, +} + +impl Default for LevelView { + fn default() -> Self { + Self { + position: Default::default(), + scale: 1., + } + } +} + +impl LevelView { + // TODO: Factor in level view scale + pub fn screen_to_world_pos(&self, p: egui::Pos2) -> egui::Pos2 { + (p.to_vec2() / self.scale + self.position.into_egui()).to_pos2() + } + + pub fn world_to_screen_pos(&self, p: egui::Pos2) -> egui::Pos2 { + ((p.to_vec2() - self.position.into_egui()) * self.scale).to_pos2() + } +} + +#[derive(Clone)] +pub struct UiLevelView { + pub view: LevelView, + pub response: egui::Response, + painter: egui::Painter, +} + +impl UiLevelView { + pub fn new(view: LevelView, response: egui::Response, painter: egui::Painter) -> Self { + Self { + view, + response, + painter, + } + } + + /// Get a reference to the ui level view's painter. + pub fn painter(&self) -> &egui::Painter { + &self.painter + } + + pub fn ctx(&self) -> &egui::Context { + self.painter.ctx() + } + + /// Returns the top left pixel of the level rect in screen coordinates. + pub fn level_top_left(&self) -> egui::Pos2 { + self.response.rect.min + } + + // TODO: Factor in level view scale + pub fn screen_to_world_pos(&self, p: egui::Pos2) -> egui::Pos2 { + self.view + .screen_to_world_pos(p - self.level_top_left().to_vec2()) + } + + pub fn world_to_screen_pos(&self, p: egui::Pos2) -> egui::Pos2 { + self.view.world_to_screen_pos(p) + self.level_top_left().to_vec2() + } +} diff --git a/src/editor/windows/background_properties.rs b/src/editor/windows/background_properties.rs new file mode 100644 index 0000000000..9f5457085a --- /dev/null +++ b/src/editor/windows/background_properties.rs @@ -0,0 +1,151 @@ +use std::ops::ControlFlow; + +use macroquad::prelude::{collections::storage, Color}; + +use crate::{ + editor::actions::{UiAction, UiActionExt}, + map::{Map, MapBackgroundLayer, MapLayerKind}, + resources::TextureKind, + Resources, +}; + +pub struct BackgroundPropertiesWindow { + valid_layer_texture_ids: Vec, + layer_depth: f32, + selected_layer: Option, +} + +impl Default for BackgroundPropertiesWindow { + fn default() -> Self { + let resources = storage::get::(); + Self { + valid_layer_texture_ids: resources + .textures + .values() + .filter_map(|texture_res| { + let mut res = None; + + if let Some(kind) = texture_res.meta.kind { + if kind == TextureKind::Background { + res = Some(texture_res.meta.id.clone()); + } + } + + res + }) + .collect(), + layer_depth: Default::default(), + selected_layer: Default::default(), + } + } +} + +impl BackgroundPropertiesWindow { + pub fn ui(&mut self, egui_ctx: &egui::Context, map: &Map) -> Option { + let mut action = None; + + egui::Window::new("Background Properties").show(egui_ctx, |ui| { + let color = map.background_color; + + let mut color = egui::Rgba::from_rgba_premultiplied(color.r, color.g, color.b, color.a); + + ui.horizontal(|ui| { + ui.label("Color: "); + egui::color_picker::color_edit_button_rgba( + ui, + &mut color, + egui::color_picker::Alpha::OnlyBlend, + ); + }); + + let color = Color::new(color.r(), color.g(), color.b(), color.a()); + + if color != map.background_color { + action = Some(UiAction::UpdateBackground { + color, + layers: map.background_layers.clone(), + }); + } + + ui.separator(); + + ui.horizontal(|ui| { + egui_extras::TableBuilder::new(ui) + .striped(true) + .column(egui_extras::Size::exact(175.)) + .body(|body| { + body.rows(16., map.background_layers.len(), |idx, mut row| { + row.col(|ui| { + let response = ui.selectable_label( + self.selected_layer == Some(idx), + format!( + "Layer {} ({})", + idx, map.background_layers[idx].texture_id + ), + ); + + if response.clicked() { + self.selected_layer = Some(idx); + } + }); + }) + }); + + if let Some(selected_layer_idx) = self.selected_layer { + let selected_layer = &map.background_layers[selected_layer_idx]; + ui.vertical(|ui| { + let mut idx_selected = self + .valid_layer_texture_ids + .iter() + .enumerate() + .find(|(_, x)| x == &&selected_layer.texture_id) + .map(|(i, _)| i) + .unwrap(); + + if egui::ComboBox::new("texture", "Texture") + .show_index( + ui, + &mut idx_selected, + self.valid_layer_texture_ids.len(), + |i| self.valid_layer_texture_ids[i].to_owned(), + ) + .changed() + { + let mut layers = map.background_layers.clone(); + layers[selected_layer_idx].texture_id = + self.valid_layer_texture_ids[idx_selected].clone(); + action = Some(UiAction::UpdateBackground { + color: map.background_color, + layers, + }); + } + + let mut depth = selected_layer.depth; + if ui + .horizontal(|ui| { + ui.label("Depth: "); + ui.add(egui::DragValue::new(&mut depth)) + }) + .response + .changed() + { + // FIXME This does not set the depth (maybe doesn't get run) + let mut layers = map.background_layers.clone(); + layers[selected_layer_idx].depth = depth; + action = Some(UiAction::UpdateBackground { + color: map.background_color, + layers, + }); + } + + // TODO: Delete/Up/Down + }); + } else { + ui.label("Select a layer on the left"); + } + }); + }); + + action + } +} diff --git a/src/editor/windows/create_layer.rs b/src/editor/windows/create_layer.rs new file mode 100644 index 0000000000..3588409d25 --- /dev/null +++ b/src/editor/windows/create_layer.rs @@ -0,0 +1,72 @@ +use std::ops::ControlFlow; + +use crate::map::{Map, MapLayerKind}; + +pub struct CreateLayerWindow { + layer_name: String, + layer_kind: MapLayerKind, + has_collision: bool, +} +pub enum CreateLayerResult { + Create { + layer_name: String, + layer_kind: MapLayerKind, + has_collision: bool, + }, + Close, +} + +impl Default for CreateLayerWindow { + fn default() -> Self { + Self { + layer_name: "Unnamed Layer".to_owned(), + layer_kind: MapLayerKind::TileLayer, + has_collision: false, + } + } +} + +impl CreateLayerWindow { + pub fn ui(&mut self, egui_ctx: &egui::Context, map: &Map) -> ControlFlow { + let mut action = ControlFlow::Continue(()); + + egui::Window::new("Create Layer").show(egui_ctx, |ui| { + ui.text_edit_singleline(&mut self.layer_name).changed(); + egui::ComboBox::new("layer type", "Type") + .selected_text(match self.layer_kind { + MapLayerKind::TileLayer => "Tiles", + MapLayerKind::ObjectLayer => "Objects", + }) + .show_ui(ui, |ui| { + ui.selectable_value(&mut self.layer_kind, MapLayerKind::TileLayer, "Tiles"); + ui.selectable_value(&mut self.layer_kind, MapLayerKind::ObjectLayer, "Objects"); + }); + if self.layer_kind == MapLayerKind::TileLayer { + ui.checkbox(&mut self.has_collision, "Collision"); + } + let can_create_map = !map.layers.contains_key(&self.layer_name); + if !can_create_map { + ui.label( + egui::RichText::new("Layer names must be unique").color(egui::Color32::RED), + ); + } + ui.horizontal(|ui| { + if ui + .add_enabled(can_create_map, egui::Button::new("Create")) + .clicked() + { + action = ControlFlow::Break(CreateLayerResult::Create { + layer_name: self.layer_name.clone(), + has_collision: self.has_collision, + layer_kind: self.layer_kind, + }); + } + if ui.button("Cancel").clicked() { + action = ControlFlow::Break(CreateLayerResult::Close) + } + }) + }); + + action + } +} diff --git a/src/editor/windows/create_map.rs b/src/editor/windows/create_map.rs new file mode 100644 index 0000000000..bd47148a7a --- /dev/null +++ b/src/editor/windows/create_map.rs @@ -0,0 +1,92 @@ +use std::{ops::ControlFlow, path::Path}; + +use macroquad::prelude::{collections::storage, ivec2, uvec2, IVec2, UVec2}; + +use crate::{ + resources::{map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION}, + Resources, +}; + +pub struct CreateMapWindow { + name: String, + description: String, + tile_size: UVec2, + grid_size: UVec2, +} +pub enum CreateMapResult { + Create { + name: String, + description: String, + tile_size: UVec2, + grid_size: UVec2, + }, + Close, +} + +impl Default for CreateMapWindow { + fn default() -> Self { + Self { + name: "Unnamed Map".to_owned(), + description: String::new(), + tile_size: uvec2(32, 32), + grid_size: uvec2(100, 75), + } + } +} + +impl CreateMapWindow { + pub fn ui(&mut self, egui_ctx: &egui::Context) -> ControlFlow { + let mut action = ControlFlow::Continue(()); + + egui::Window::new("Create Map") + .resizable(false) + .collapsible(false) + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .show(egui_ctx, |ui| { + ui.text_edit_singleline(&mut self.name); + + { + let map_export_path = { + let resources = storage::get::(); + Path::new(&resources.assets_dir).join(MAP_EXPORTS_DEFAULT_DIR) + }; + + let path = map_export_path + .join(map_name_to_filename(&self.name)) + .with_extension(MAP_EXPORTS_EXTENSION); + + ui.label(path.to_string_lossy().as_ref()); + } + + ui.add(egui::TextEdit::multiline(&mut self.description).hint_text("Description")); + ui.horizontal(|ui| { + ui.label("Tile size: "); + ui.add(egui::DragValue::new(&mut self.tile_size.x).clamp_range(1..=512u32)); + ui.label("*"); + ui.add(egui::DragValue::new(&mut self.tile_size.y).clamp_range(1..=512u32)); + }); + ui.horizontal(|ui| { + ui.label("Grid size: "); + ui.add(egui::DragValue::new(&mut self.grid_size.x).clamp_range(1..=512u32)); + ui.label("*"); + ui.add(egui::DragValue::new(&mut self.grid_size.y).clamp_range(1..=512u32)); + }); + + ui.horizontal(|ui| { + if ui.button("Create").clicked() { + action = ControlFlow::Break(CreateMapResult::Create { + name: self.name.clone(), + description: self.description.clone(), + grid_size: self.grid_size, + tile_size: self.tile_size, + }); + } + if ui.button("Cancel").clicked() { + action = ControlFlow::Break(CreateMapResult::Close); + } + }) + }); + + action + } +} diff --git a/src/editor/windows/create_tileset.rs b/src/editor/windows/create_tileset.rs new file mode 100644 index 0000000000..30ef225bc7 --- /dev/null +++ b/src/editor/windows/create_tileset.rs @@ -0,0 +1,74 @@ +use std::ops::ControlFlow; + +use macroquad::prelude::collections::storage; + +use crate::{map::Map, resources::TextureKind, Resources}; + +pub struct CreateTilesetWindow { + tileset_name: String, + texture_options: Vec, + texture_idx: usize, +} + +impl Default for CreateTilesetWindow { + fn default() -> Self { + Self { + tileset_name: "Unnamed Tileset".to_owned(), + texture_options: storage::get::() + .textures + .iter() + .filter_map(|(id, resource)| { + matches!(resource.meta.kind, Some(TextureKind::Tileset)).then(|| id.clone()) + }) + .collect(), + texture_idx: 0, + } + } +} +pub enum CreateTilesetResult { + Create { + tileset_name: String, + texture: String, + }, + Close, +} + +impl CreateTilesetWindow { + pub fn ui(&mut self, egui_ctx: &egui::Context, map: &Map) -> ControlFlow { + let mut action = ControlFlow::Continue(()); + + egui::Window::new("Create Tileset").show(egui_ctx, |ui| { + ui.text_edit_singleline(&mut self.tileset_name).changed(); + egui::ComboBox::new("tileset texture", "Texture") + .selected_text(&self.texture_options[self.texture_idx]) + .show_ui(ui, |ui| { + for (texture_idx, texture) in self.texture_options.iter().enumerate() { + ui.selectable_value(&mut self.texture_idx, texture_idx, texture); + } + }); + + let can_create_tileset = !map.tilesets.contains_key(&self.tileset_name); + if !can_create_tileset { + ui.label( + egui::RichText::new("Tileset names must be unique").color(egui::Color32::RED), + ); + } + ui.horizontal(|ui| { + if ui + .add_enabled(can_create_tileset, egui::Button::new("Create")) + .clicked() + { + action = ControlFlow::Break(CreateTilesetResult::Create { + tileset_name: self.tileset_name.clone(), + texture: self.texture_options[self.texture_idx].clone(), + }); + } + if ui.button("Cancel").clicked() { + action = ControlFlow::Break(CreateTilesetResult::Close) + } + }) + }); + + action + } +} diff --git a/src/editor/windows/import.rs b/src/editor/windows/import.rs new file mode 100644 index 0000000000..890893ccde --- /dev/null +++ b/src/editor/windows/import.rs @@ -0,0 +1,105 @@ +use std::{collections::HashSet, ops::ControlFlow, path::Path}; + +use macroquad::prelude::{collections::storage, ivec2, uvec2, Color, IVec2, UVec2}; + +use crate::{ + editor::util::EguiTextureHandler, + map::{Map, MapBackgroundLayer, MapTileset}, + resources::{map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION}, + Resources, +}; + +pub struct ImportWindow { + map_importing_from: Map, + selected_tilesets: HashSet, + should_import_background: bool, +} +pub enum ImportResult { + Import { + tilesets: Vec, + background_color: Option, + background_layers: Vec, + }, + Close, +} + +impl ImportWindow { + pub fn new(map: Map) -> Self { + Self { + map_importing_from: map, + selected_tilesets: HashSet::new(), + should_import_background: false, + } + } + + pub fn ui(&mut self, egui_ctx: &egui::Context) -> ControlFlow { + let mut action = ControlFlow::Continue(()); + + egui::Window::new("Import To Map") + .resizable(false) + .collapsible(false) + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .show(egui_ctx, |ui| { + egui_extras::TableBuilder::new(ui) + .column(egui_extras::Size::remainder()) + .header(20.0, |mut row| { + row.col(|ui| { + ui.label("Tilesets to import:"); + }); + }) + .body(|body| { + body.rows( + 18.0, + self.map_importing_from.tilesets.len(), + |idx, mut row| { + let key = self.map_importing_from.tilesets.keys().nth(idx).unwrap(); + let is_selected = self.selected_tilesets.contains(key); + let mut clicked = false; + + row.col(|ui| { + clicked = + ui.toggle_value(&mut (is_selected.clone()), key).clicked(); + }); + + if clicked { + if is_selected { + self.selected_tilesets.remove(key); + } else { + self.selected_tilesets.insert(key.clone()); + } + } + }, + ); + }); + ui.separator(); + ui.checkbox(&mut self.should_import_background, "Import Background"); + + ui.horizontal(|ui| { + if ui.button(egui::RichText::new("Import").heading()).clicked() { + action = ControlFlow::Break(ImportResult::Import { + tilesets: self + .map_importing_from + .tilesets + .iter() + .filter(|(k, _)| self.selected_tilesets.contains(*k)) + .map(|(_, x)| x) + .cloned() + .collect(), + background_color: Some(self.map_importing_from.background_color), + background_layers: if self.should_import_background { + self.map_importing_from.background_layers.clone() + } else { + Vec::new() + }, + }) + } + + if ui.button(egui::RichText::new("Cancel").heading()).clicked() { + action = ControlFlow::Break(ImportResult::Close); + } + }); + }); + + action + } +} diff --git a/src/editor/windows/menu.rs b/src/editor/windows/menu.rs new file mode 100644 index 0000000000..3107c7e8af --- /dev/null +++ b/src/editor/windows/menu.rs @@ -0,0 +1,52 @@ +use std::ops::ControlFlow; + +#[derive(Default)] +pub struct MenuWindow; + +pub enum MenuResult { + OpenCreateMapWindow, + OpenLoadMapWindow, + SaveMap, + OpenSaveMapWindow, + ExitToMainMenu, + QuitToDesktop, +} + +impl MenuWindow { + pub fn ui(&mut self, egui_ctx: &egui::Context, is_user_map: bool) -> ControlFlow { + let mut action = ControlFlow::Continue(()); + + egui::Window::new("Pause") + .title_bar(false) + .resizable(false) + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .show(egui_ctx, |ui| { + ui.vertical_centered(|ui| { + ui.style_mut().override_text_style = Some(egui::TextStyle::Heading); + ui.spacing_mut().item_spacing.y = ui.spacing().window_margin.sum().y; + if ui.button("New").clicked() { + action = ControlFlow::Break(MenuResult::OpenCreateMapWindow); + } + if ui.button("Open/Import").clicked() { + action = ControlFlow::Break(MenuResult::OpenLoadMapWindow); + } + ui.add_enabled_ui(is_user_map, |ui| { + if ui.button("Save").clicked() { + action = ControlFlow::Break(MenuResult::SaveMap); + } + }); + if ui.button("Save As").clicked() { + action = ControlFlow::Break(MenuResult::OpenSaveMapWindow); + } + if ui.button("Exit To Main Menu").clicked() { + action = ControlFlow::Break(MenuResult::ExitToMainMenu); + } + if ui.button("Exit To Desktop").clicked() { + action = ControlFlow::Break(MenuResult::QuitToDesktop); + } + }); + }); + + action + } +} diff --git a/src/editor/windows/mod.rs b/src/editor/windows/mod.rs new file mode 100644 index 0000000000..e958e464a4 --- /dev/null +++ b/src/editor/windows/mod.rs @@ -0,0 +1,23 @@ +mod background_properties; +pub use background_properties::*; + +mod create_map; +pub use create_map::*; + +mod open_map; +pub use open_map::*; + +mod import; +pub use import::*; + +mod create_layer; +pub use create_layer::*; + +mod create_tileset; +pub use create_tileset::*; + +mod menu; +pub use menu::*; + +mod save_map; +pub use save_map::*; diff --git a/src/editor/windows/open_map.rs b/src/editor/windows/open_map.rs new file mode 100644 index 0000000000..05ed1217d5 --- /dev/null +++ b/src/editor/windows/open_map.rs @@ -0,0 +1,91 @@ +use std::{ops::ControlFlow, path::Path}; + +use macroquad::prelude::{collections::storage, ivec2, uvec2, IVec2, UVec2}; + +use crate::{ + editor::util::EguiTextureHandler, + resources::{map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION}, + Resources, +}; + +#[derive(Default)] +pub struct OpenMapWindow { + selected_idx: Option, +} +pub enum OpenMapResult { + Open { map_index: usize }, + Import { map_index: usize }, + Close, +} + +impl OpenMapWindow { + pub fn ui(&mut self, egui_ctx: &egui::Context) -> ControlFlow { + let mut action = ControlFlow::Continue(()); + + egui::Window::new("Open/Import Map") + .resizable(false) + .collapsible(false) + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .show(egui_ctx, |ui| { + let resources = storage::get::(); + if let Some(idx) = self.selected_idx { + if ui.button("< Back").clicked() { + self.selected_idx = None; + } + let map = &resources.maps[idx]; + let texture = &map.preview; + let texture_id = texture.egui_id(); + let preview_width = 300.; + ui.vertical_centered(|ui| { + ui.image( + texture_id, + egui::vec2( + preview_width, + preview_width / texture.width() * texture.height(), + ), + ); + }); + + ui.horizontal(|ui| { + if ui.button(egui::RichText::new("Open").heading()).clicked() { + action = ControlFlow::Break(OpenMapResult::Open { map_index: idx }); + } + if ui.button(egui::RichText::new("Import").heading()).clicked() { + action = ControlFlow::Break(OpenMapResult::Import { map_index: idx }); + } + }); + } else { + egui_extras::TableBuilder::new(ui) + .striped(true) + .sense(egui::Sense::click()) + .column(egui_extras::Size::remainder()) + .body(|body| { + body.rows(18., resources.maps.len(), |idx, mut row| { + let col_response = row.col(|ui| { + let map = &resources.maps[idx]; + let map_name = &map.meta.name; + let map_path = &map.meta.path; + + ui.horizontal(|ui| { + ui.label(egui::RichText::new(map_name).strong()); + ui.label( + egui::RichText::new(format!("({map_path})")).weak(), + ); + }); + }); + + if col_response.clicked() { + self.selected_idx = Some(idx); + } + }); + }); + + if ui.button(egui::RichText::new("Cancel").heading()).clicked() { + action = ControlFlow::Break(OpenMapResult::Close); + } + } + }); + + action + } +} diff --git a/src/editor/windows/save_map.rs b/src/editor/windows/save_map.rs new file mode 100644 index 0000000000..3b2f919686 --- /dev/null +++ b/src/editor/windows/save_map.rs @@ -0,0 +1,73 @@ +use std::{ops::ControlFlow, path::Path}; + +use macroquad::prelude::collections::storage; + +use crate::{ + resources::{ + is_valid_map_export_path, map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, + MAP_EXPORTS_EXTENSION, + }, + Resources, +}; + +#[derive(Default)] +pub struct SaveMapWindow { + map_name: String, + overwrite_existing: bool, +} +pub enum SaveMapResult { + Save { name: String }, + Close, +} + +impl SaveMapWindow { + pub fn ui(&mut self, egui_ctx: &egui::Context) -> ControlFlow { + let mut action = ControlFlow::Continue(()); + let resources = storage::get::(); + + egui::Window::new("Save Map") + .resizable(false) + .collapsible(false) + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .show(egui_ctx, |ui| { + ui.text_edit_singleline(&mut self.map_name).changed(); + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = 0.; + ui.label( + egui::RichText::new(format!( + "{}/{}/", + &resources.assets_dir.trim_end_matches('/'), + MAP_EXPORTS_DEFAULT_DIR.trim_end_matches('/') + )) + .weak(), + ); + ui.label(format!( + "{}.{}", + map_name_to_filename(&self.map_name), + MAP_EXPORTS_EXTENSION + )); + }); + ui.checkbox(&mut self.overwrite_existing, "Overwrite Existing"); + ui.horizontal(|ui| { + let path = Path::new(MAP_EXPORTS_DEFAULT_DIR) + .join(map_name_to_filename(&self.map_name)) + .with_extension(MAP_EXPORTS_EXTENSION); + + let save_button = ui.add_enabled( + is_valid_map_export_path(path, self.overwrite_existing), + egui::Button::new("Save"), + ); + if save_button.clicked() { + action = ControlFlow::Break(SaveMapResult::Save { + name: self.map_name.clone(), + }); + } + if ui.button("Cancel").clicked() { + action = ControlFlow::Break(SaveMapResult::Close); + } + }); + }); + + action + } +} diff --git a/src/main.rs b/src/main.rs index 3d0687d7d4..bd38b1d36b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ pub mod drawables; pub use drawables::*; pub use physics::*; -use editor::{Editor, EditorCamera, EditorInputScheme}; +use editor::EditorInputScheme; use map::{Map, MapLayerKind, MapObjectKind}; @@ -108,6 +108,8 @@ fn window_conf() -> Conf { async fn init_game() -> Result { use gui::MainMenuResult; + use crate::editor::{Editor, EditorNode}; + match gui::show_main_menu().await { MainMenuResult::LocalGame { map, players } => { let game = Game::new(GameMode::Local, *map, &players)?; @@ -130,10 +132,7 @@ async fn init_game() -> Result { gui::show_select_map_menu().await }; - let position = map_resource.map.get_size() * 0.5; - - scene::add_node(EditorCamera::new(position)); - scene::add_node(Editor::new(input_scheme, map_resource)); + scene::add_node(EditorNode::new(Editor::new(map_resource), input_scheme)); } MainMenuResult::ReloadResources => { reload_resources(); diff --git a/src/map/mod.rs b/src/map/mod.rs index 687d20339c..eee1cb5e92 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -19,7 +19,6 @@ use core::text::ToStringHelper; use core::Result; use crate::{ - editor::gui::combobox::ComboBoxValue, json::{self, TiledMap}, Resources, }; @@ -448,27 +447,6 @@ impl MapLayerKind { } } -impl ComboBoxValue for MapLayerKind { - fn get_index(&self) -> usize { - match self { - Self::TileLayer => 0, - Self::ObjectLayer => 1, - } - } - - fn set_index(&mut self, index: usize) { - *self = match index { - 0 => Self::TileLayer, - 1 => Self::ObjectLayer, - _ => unreachable!(), - } - } - - fn get_options(&self) -> Vec { - Self::options().iter().map(|s| s.to_string()).collect() - } -} - impl Default for MapLayerKind { fn default() -> Self { MapLayerKind::TileLayer @@ -549,6 +527,16 @@ pub enum MapObjectKind { Decoration, } +impl std::fmt::Display for MapObjectKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MapObjectKind::Item => f.write_str("Item"), + MapObjectKind::Environment => f.write_str("Environment"), + MapObjectKind::Decoration => f.write_str("Decoration"), + } + } +} + impl MapObjectKind { const ITEM: &'static str = "item"; const ENVIRONMENT: &'static str = "environment"; @@ -589,29 +577,6 @@ impl From for String { } } -impl ComboBoxValue for MapObjectKind { - fn get_index(&self) -> usize { - match self { - Self::Item => 0, - Self::Environment => 1, - Self::Decoration => 2, - } - } - - fn set_index(&mut self, index: usize) { - *self = match index { - 0 => Self::Item, - 1 => Self::Environment, - 2 => Self::Decoration, - _ => unreachable!(), - } - } - - fn get_options(&self) -> Vec { - Self::options().iter().map(|s| s.to_string()).collect() - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MapObject { pub id: String,