From ff9c920ca463dda8474f569653a005f5f1956561 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 2 Apr 2022 13:19:34 +0200 Subject: [PATCH 01/81] Get egui working in the editor --- Cargo.lock | 472 ++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 + src/editor/mod.rs | 12 +- 3 files changed, 470 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69e27277ea..d83ba85bc6 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" @@ -36,6 +52,12 @@ dependencies = [ "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" @@ -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" @@ -83,6 +111,20 @@ name = "bytemuck" version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +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,79 @@ 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.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a3cd1d47e12f7a17912595241622e373aa652a4e0fa90b3f9278f90a64aedf7" +dependencies = [ + "ahash 0.7.6", + "epaint", + "nohash-hasher", +] + +[[package]] +name = "egui-macroquad" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a600e32edf2775b3ff8017eac144f954c2db04a2d8c0eeda703f58f147c4380a" +dependencies = [ + "egui", + "egui-miniquad", + "macroquad", +] + +[[package]] +name = "egui-miniquad" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f1c9544e632b65e1adb64cf01e8844c8d228d1179d18dc5525dba6718990e1" +dependencies = [ + "bytemuck", + "copypasta", + "egui", + "miniquad", + "quad-url", +] + +[[package]] +name = "emath" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a977a80456be58a2c2d48e69c1d0baadef46cecef5a0c98df141c468da006f12" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "epaint" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033292846059f08e03a71e1b5db2ee6ab7c9622c3b48da21f4bd13258ebee2db" +dependencies = [ + "ab_glyph", + "ahash 0.7.6", + "atomic_refcell", + "bytemuck", + "emath", + "nohash-hasher", +] + [[package]] name = "ff-particles" version = "0.1.2" @@ -178,6 +317,8 @@ dependencies = [ name = "fishfight" version = "0.4.2" dependencies = [ + "egui", + "egui-macroquad", "ff-particles", "fishfight-core", "fishsticks", @@ -226,7 +367,7 @@ 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]] @@ -267,7 +408,7 @@ dependencies = [ "libc", "libudev-sys", "log", - "nix", + "nix 0.23.1", "rusty-xinput", "stdweb", "uuid", @@ -356,6 +497,12 @@ 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,7 +517,7 @@ checksum = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0" dependencies = [ "byteorder", "ogg", - "smallvec", + "smallvec 0.6.14", ] [[package]] @@ -379,6 +526,16 @@ version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libudev-sys" version = "0.1.4" @@ -409,9 +566,9 @@ dependencies = [ [[package]] name = "macroquad" -version = "0.3.13" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bbc44698249e98c7aedb8eef94b8806b5d68187c0c3d8c2a0912cb9efab1950" +checksum = "d54a31d3996831b27b429f8e9d7b948aa589d3571305eba39f57b4600bd2cd01" dependencies = [ "bumpalo", "fontdue", @@ -438,12 +595,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.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[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 +634,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.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75bba73cbdaa1cb8f7889fcbb083941eae39ad4257d47e0d8e819d1de9963ba0" +checksum = "4b1edf0ba64f267a4cc0ba453d95da072bfa078d755917e1411e4dc20b729da8" dependencies = [ "sapp-android", "sapp-darwin", @@ -483,6 +670,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,6 +696,22 @@ 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" @@ -537,6 +753,35 @@ 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" @@ -552,6 +797,15 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +[[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 = "pkg-config" version = "0.3.24" @@ -607,6 +861,25 @@ 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" @@ -644,9 +917,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 +927,9 @@ dependencies = [ [[package]] name = "sapp-darwin" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0310e2445f307468aa13f1cde94d6fba6b8fd329afbb642dedbe3faf1a145f31" +checksum = "fd874867dc35d9a42bba830cee0210702438082c7d6a39132ab420e6c20a5809" dependencies = [ "cc", ] @@ -679,6 +952,12 @@ 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" @@ -703,6 +982,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "semver" version = "0.9.0" @@ -773,6 +1058,40 @@ 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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a" +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" @@ -856,6 +1175,12 @@ 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" @@ -940,6 +1265,79 @@ version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +[[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" @@ -950,6 +1348,23 @@ dependencies = [ "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 +1386,38 @@ 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 = "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..9ee8ae8f23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,8 @@ macroquad-platformer = "0.1" hecs = "0.7.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +egui = "0.17.0" +egui-macroquad = "0.9.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2.74" diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 421b8bc6c9..7b358e298f 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1560,11 +1560,13 @@ impl Node for Editor { } { - let ctx = node.get_context(); - let mut gui = storage::get_mut::(); - if let Some(action) = gui.draw(node.get_map(), ctx) { - res = Some(action); - } + egui_macroquad::ui(|egui_ctx| { + egui::Window::new("Test").show(egui_ctx, |ui| { + ui.label("Hello World!"); + }); + }); + + egui_macroquad::draw(); } if let Some(action) = res { From 5f1828b2e044aaf8bd13cb5a546edcd0b0febf64 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 2 Apr 2022 18:33:55 +0200 Subject: [PATCH 02/81] Partial commit --- src/editor/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 7b358e298f..5bb2ad3256 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1561,7 +1561,7 @@ impl Node for Editor { { egui_macroquad::ui(|egui_ctx| { - egui::Window::new("Test").show(egui_ctx, |ui| { + egui::SidePanel::new(egui::containers::panel::Side::Right, "Test").show(egui_ctx, |ui| { ui.label("Hello World!"); }); }); From 1a1707f168bab38fcd1c0d90411a381757c6ad31 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 2 Apr 2022 18:55:50 +0200 Subject: [PATCH 03/81] Barebones layout --- src/editor/mod.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 5bb2ad3256..132132d1ae 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1561,11 +1561,56 @@ impl Node for Editor { { egui_macroquad::ui(|egui_ctx| { - egui::SidePanel::new(egui::containers::panel::Side::Right, "Test").show(egui_ctx, |ui| { - ui.label("Hello World!"); + egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show( + egui_ctx, + |ui| { + ui.selectable_value(&mut 0, 0, "Cursor"); + ui.selectable_value(&mut 0, 1, "Tiles"); + ui.selectable_value(&mut 0, 2, "Spawnpoints"); + ui.selectable_value(&mut 0, 3, "Eraser"); + }, + ); + egui::SidePanel::new(egui::containers::panel::Side::Right, "Layers").show( + egui_ctx, + |ui| { + ui.heading("Layers"); + for layer in ["Unnamed Layer"] { + ui.horizontal(|ui| { + ui.label(layer); + ui.button("Visible"); + }); + } + ui.horizontal(|ui| { + ui.button("+"); + ui.button("-"); + ui.button("Up"); + ui.button("Down"); + }); + ui.separator(); + ui.heading("Tilesets"); + ui.horizontal(|ui| { + ui.button("+"); + ui.button("-"); + ui.button("Edit"); + }); + }, + ); + + let mut selected = 0; + egui::Window::new("Create Layer").show(egui_ctx, |ui| { + ui.text_edit_singleline(&mut "Unnamed Layer"); + egui::ComboBox::new("layer type", "Type").show_ui(ui, |ui| { + ui.selectable_value(&mut selected, 0, "Tiles"); + ui.selectable_value(&mut selected, 0, "Objects"); + }); + ui.checkbox(&mut false, "Collision"); + ui.horizontal(|ui| { + ui.button("Create"); + ui.button("Cancel"); + }) }); }); - + egui_macroquad::draw(); } From c8e5fc953931411a5fee71319b04877e6e290472 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 2 Apr 2022 20:36:23 +0200 Subject: [PATCH 04/81] Bulldoze old GUI, add basic editor tools --- src/editor/RATIONALE.md | 8 + src/editor/actions.rs | 20 +- src/editor/gui/combobox.rs | 127 -- src/editor/gui/context_menu.rs | 244 --- src/editor/gui/editor_menu.rs | 101 - src/editor/gui/mod.rs | 346 ---- src/editor/gui/toolbars/layer_list.rs | 220 --- src/editor/gui/toolbars/mod.rs | 333 ---- src/editor/gui/toolbars/object_list.rs | 156 -- src/editor/gui/toolbars/tileset_details.rs | 125 -- src/editor/gui/toolbars/tileset_list.rs | 133 -- src/editor/gui/toolbars/tool_selector.rs | 178 -- .../gui/windows/background_properties.rs | 306 --- src/editor/gui/windows/confirm_dialog.rs | 78 - src/editor/gui/windows/create_layer.rs | 114 -- src/editor/gui/windows/create_map.rs | 192 -- src/editor/gui/windows/create_object.rs | 164 -- src/editor/gui/windows/create_tileset.rs | 117 -- src/editor/gui/windows/import.rs | 174 -- src/editor/gui/windows/load_map.rs | 177 -- src/editor/gui/windows/mod.rs | 132 -- src/editor/gui/windows/object_properties.rs | 178 -- src/editor/gui/windows/save_map.rs | 113 -- src/editor/gui/windows/tile_properties.rs | 110 -- src/editor/gui/windows/tileset_properties.rs | 228 --- src/editor/history.rs | 8 +- src/editor/mod.rs | 1716 ++--------------- src/editor/tools/eraser.rs | 104 - src/editor/tools/mod.rs | 90 - src/editor/tools/placement.rs | 304 --- src/editor/windows/create_layer.rs | 61 + src/editor/windows/mod.rs | 2 + src/main.rs | 6 +- src/map/mod.rs | 45 - 34 files changed, 227 insertions(+), 6183 deletions(-) create mode 100644 src/editor/RATIONALE.md delete mode 100644 src/editor/gui/combobox.rs delete mode 100644 src/editor/gui/context_menu.rs delete mode 100644 src/editor/gui/editor_menu.rs delete mode 100644 src/editor/gui/mod.rs delete mode 100644 src/editor/gui/toolbars/layer_list.rs delete mode 100644 src/editor/gui/toolbars/mod.rs delete mode 100644 src/editor/gui/toolbars/object_list.rs delete mode 100644 src/editor/gui/toolbars/tileset_details.rs delete mode 100644 src/editor/gui/toolbars/tileset_list.rs delete mode 100644 src/editor/gui/toolbars/tool_selector.rs delete mode 100644 src/editor/gui/windows/background_properties.rs delete mode 100644 src/editor/gui/windows/confirm_dialog.rs delete mode 100644 src/editor/gui/windows/create_layer.rs delete mode 100644 src/editor/gui/windows/create_map.rs delete mode 100644 src/editor/gui/windows/create_object.rs delete mode 100644 src/editor/gui/windows/create_tileset.rs delete mode 100644 src/editor/gui/windows/import.rs delete mode 100644 src/editor/gui/windows/load_map.rs delete mode 100644 src/editor/gui/windows/mod.rs delete mode 100644 src/editor/gui/windows/object_properties.rs delete mode 100644 src/editor/gui/windows/save_map.rs delete mode 100644 src/editor/gui/windows/tile_properties.rs delete mode 100644 src/editor/gui/windows/tileset_properties.rs delete mode 100644 src/editor/tools/eraser.rs delete mode 100644 src/editor/tools/mod.rs delete mode 100644 src/editor/tools/placement.rs create mode 100644 src/editor/windows/create_layer.rs create mode 100644 src/editor/windows/mod.rs diff --git a/src/editor/RATIONALE.md b/src/editor/RATIONALE.md new file mode 100644 index 0000000000..74ca84c445 --- /dev/null +++ b/src/editor/RATIONALE.md @@ -0,0 +1,8 @@ +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. +- `CreateLayerWindow` does not allow undoing, only the main editor context does. As such, its `ui` +method doesn't need to be immutable, because the changes done to the window are irrelevant to the +history and can occur without having to keep track of them. (PD: TextEdit changes are kept track of +internally by egui and can be undone/redone without any external code) \ No newline at end of file diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 62553fb716..820c5820a8 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -6,13 +6,14 @@ use macroquad::prelude::*; use core::error::{Error, ErrorKind, Result}; -use crate::editor::gui::windows::Window; use crate::map::{MapBackgroundLayer, MapObject, MapObjectKind}; use crate::{ map::{Map, MapLayer, MapLayerKind, MapTile, MapTileset}, Resources, }; +use super::EditorTool; + /// These are all the actions available for the GUI and other sub-systems of the editor. /// If you need to perform multiple actions in one call, use the `Batch` variant. #[derive(Debug, Clone)] @@ -20,7 +21,7 @@ pub enum EditorAction { Batch(Vec), Undo, Redo, - SelectTool(Option), + SelectTool(EditorTool), OpenBackgroundPropertiesWindow, UpdateBackground { color: Color, @@ -142,13 +143,14 @@ impl EditorAction { 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]) + pub fn then(mut self, action: EditorAction) -> Self { + match &mut self { + EditorAction::Batch(batch) => { + batch.push(action); + self + } + _ => Self::batch(&[self, action]), + } } } 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 f6a8c35be5..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"], - 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..5f1613306f 100644 --- a/src/editor/history.rs +++ b/src/editor/history.rs @@ -17,10 +17,14 @@ impl EditorHistory { } } - 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); + self.undo_stack.push(Box::new(action)); self.redo_stack.clear(); } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 132132d1ae..40556f5955 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,23 +1,14 @@ use std::any::TypeId; +use std::ops::ControlFlow; use std::path::Path; use crate::{exit_to_main_menu, quit_to_desktop, Resources}; mod camera; +mod windows; 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 actions; use actions::{ @@ -29,12 +20,6 @@ use actions::{ mod input; mod history; -mod tools; - -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; @@ -43,12 +28,7 @@ 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; @@ -63,393 +43,123 @@ use macroquad::{ use core::text::{draw_aligned_text, HorizontalAlignment, VerticalAlignment}; +use self::windows::CreateLayerResult; + 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(Clone, PartialEq, Eq, Debug)] +pub enum EditorTool { + Cursor, + TilePlacer, + ObjectPlacer, + SpawnPointPlacer, + Eraser, } -#[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, +pub struct NewEditorContext { + pub selected_tool: EditorTool, + pub map_resource: MapResource, + pub selected_layer: Option, } -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 { +impl NewEditorContext { + pub fn new(map_resource: MapResource) -> Self { + Self { 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, + selected_tool: EditorTool::Cursor, + selected_layer: None, } } - 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(); + pub fn ui(&self, egui_ctx: &egui::Context) -> Option { + let mut action = None; - match layer.kind { - MapLayerKind::TileLayer => { - self.selected_object = None; - } - MapLayerKind::ObjectLayer => { - self.selected_tileset = None; - self.selected_tile = None; - } - } - } + egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show(egui_ctx, |ui| { + let tool = &self.selected_tool; - 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; + let mut add_tool = |tool_name, tool_variant| { + ui.add(egui::SelectableLabel::new(tool == &tool_variant, tool_name)) + .clicked() + .then(|| action = Some(EditorAction::SelectTool(tool_variant))); + }; + add_tool("Cursor", EditorTool::Cursor); + add_tool("Tiles", EditorTool::TilePlacer); + add_tool("Objects", EditorTool::ObjectPlacer); + add_tool("Eraser", EditorTool::Eraser); + }); + egui::SidePanel::new(egui::containers::panel::Side::Right, "Layers").show(egui_ctx, |ui| { + ui.heading("Layers"); + for (layer_name, layer) in self.map_resource.map.layers.iter() { + ui.horizontal(|ui| { + ui.label(format!( + "({}) {}", + match layer.kind { + MapLayerKind::TileLayer => "T", + MapLayerKind::ObjectLayer => "O", + }, + layer_name + )); + let mut is_visible = layer.is_visible; + if ui.checkbox(&mut is_visible, "Visible").clicked() { + action = Some(EditorAction::UpdateLayer { + id: layer_name.clone(), + is_visible, + }); } - } - } else { - self.selected_tileset = None; - self.selected_tile = None; + }); } - } + ui.horizontal(|ui| { + if ui.button("+").clicked() { + action = Some(EditorAction::OpenCreateLayerWindow); + } + ui.button("-"); + ui.button("Up"); + ui.button("Down"); + }); + ui.separator(); + ui.heading("Tilesets"); + ui.horizontal(|ui| { + ui.button("+"); + ui.button("-"); + ui.button("Edit"); + }); + }); - 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; - } - } + action } +} - 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; - } +pub struct Editor { + ctx: NewEditorContext, - 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()); + history: EditorHistory, - if let Some(tile_id) = tile_id { - if tile_id < tileset.first_tile_id + tileset.tile_cnt { - self.selected_tile = Some(tile_id); - return; - } - } + create_layer_window: Option, +} - self.selected_tile = Some(tileset.first_tile_id); +impl Editor { + pub fn new(map_resource: MapResource) -> Self { + Self { + ctx: NewEditorContext::new(map_resource), + history: EditorHistory::new(), + create_layer_window: None, } } - // 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(()); - + pub fn apply_action(&mut self, action: EditorAction) { + dbg!("Applying action:", &action); 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::Batch(batch) => batch + .into_iter() + .for_each(|action| self.apply_action(action)), + EditorAction::Undo => self.history.undo(&mut self.ctx.map_resource.map).unwrap(), + EditorAction::Redo => self.history.redo(&mut self.ctx.map_resource.map).unwrap(), + EditorAction::SelectTool(tool) => self.ctx.selected_tool = tool, 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); + self.create_layer_window = Some(Default::default()) } EditorAction::CreateLayer { id, @@ -458,1226 +168,70 @@ impl Editor { 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); + self.history + .apply(action, &mut self.ctx.map_resource.map) + .unwrap(); } 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(); + self.history + .apply(action, &mut self.ctx.map_resource.map) + .unwrap(); } - EditorAction::ExitToMainMenu => { - exit_to_main_menu(); - } - EditorAction::QuitToDesktop => { - quit_to_desktop(); - } - } - if let Err(err) = res { - panic!("Error: {}", err); + _ => todo!(), } - - 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 { + fn draw(mut node: RefMut) + where + Self: Sized, + { + egui_macroquad::ui(|egui_ctx| { + if let Some(action) = node.ctx.ui(egui_ctx) { 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(window) = &mut node.create_layer_window { + match window.ui(egui_ctx) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(CreateLayerResult::Create { + has_collision, + layer_kind, + layer_name, + }) => { + node.apply_action(EditorAction::CreateLayer { + has_collision, + kind: layer_kind, + index: None, + id: layer_name, }); - - 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); - } + node.create_layer_window = None; } - } - - 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 => { - if &object.id == "sproinger" { - let texture_res = - resources.textures.get("sproinger").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() - }, - ); - } else { - 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, - ); - } - } + ControlFlow::Break(CreateLayerResult::Close) => { + node.create_layer_window = None; } } } - } - - 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); - } - - { - egui_macroquad::ui(|egui_ctx| { - egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show( - egui_ctx, - |ui| { - ui.selectable_value(&mut 0, 0, "Cursor"); - ui.selectable_value(&mut 0, 1, "Tiles"); - ui.selectable_value(&mut 0, 2, "Spawnpoints"); - ui.selectable_value(&mut 0, 3, "Eraser"); - }, - ); - egui::SidePanel::new(egui::containers::panel::Side::Right, "Layers").show( - egui_ctx, - |ui| { - ui.heading("Layers"); - for layer in ["Unnamed Layer"] { - ui.horizontal(|ui| { - ui.label(layer); - ui.button("Visible"); - }); - } - ui.horizontal(|ui| { - ui.button("+"); - ui.button("-"); - ui.button("Up"); - ui.button("Down"); - }); - ui.separator(); - ui.heading("Tilesets"); - ui.horizontal(|ui| { - ui.button("+"); - ui.button("-"); - ui.button("Edit"); - }); - }, - ); - - let mut selected = 0; - egui::Window::new("Create Layer").show(egui_ctx, |ui| { - ui.text_edit_singleline(&mut "Unnamed Layer"); - egui::ComboBox::new("layer type", "Type").show_ui(ui, |ui| { - ui.selectable_value(&mut selected, 0, "Tiles"); - ui.selectable_value(&mut selected, 0, "Objects"); - }); - ui.checkbox(&mut false, "Collision"); - ui.horizontal(|ui| { - ui.button("Create"); - ui.button("Cancel"); - }) - }); - }); - - egui_macroquad::draw(); - } - - if let Some(action) = res { - node.apply_action(action); - } + egui_macroquad::draw(); } } -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 => { - if &object.id == "sproinger" { - let texture_res = resources.textures.get("sproinger").unwrap(); - res = texture_res.meta.frame_size; - } else { - 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) +#[derive(Debug, Clone)] +enum DraggedObject { + MapObject { + id: String, + kind: MapObjectKind, + index: usize, + layer_id: String, + click_offset: Vec2, + }, + SpawnPoint { + index: usize, + click_offset: Vec2, + }, } 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 843ea90afa..0000000000 --- a/src/editor/tools/placement.rs +++ /dev/null @@ -1,304 +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 - 1, coords.y + y - 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/windows/create_layer.rs b/src/editor/windows/create_layer.rs new file mode 100644 index 0000000000..5bfa618d06 --- /dev/null +++ b/src/editor/windows/create_layer.rs @@ -0,0 +1,61 @@ +use std::ops::ControlFlow; + +use crate::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) -> 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"); + }); + ui.checkbox(&mut self.has_collision, "Collision"); + ui.horizontal(|ui| { + if ui.button("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/mod.rs b/src/editor/windows/mod.rs new file mode 100644 index 0000000000..b9a5f6dcc2 --- /dev/null +++ b/src/editor/windows/mod.rs @@ -0,0 +1,2 @@ +pub mod create_layer; +pub use create_layer::*; diff --git a/src/main.rs b/src/main.rs index c5669aa1b5..b8102c60e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ pub mod drawables; pub use drawables::*; pub use physics::*; -use editor::{Editor, EditorCamera, EditorInputScheme}; +use editor::{EditorCamera, EditorInputScheme}; use map::{Map, MapLayerKind, MapObjectKind}; @@ -107,6 +107,8 @@ fn window_conf() -> Conf { async fn init_game() -> Result { use gui::MainMenuResult; + use crate::editor::Editor; + match gui::show_main_menu().await { MainMenuResult::LocalGame { map, players } => { let game = Game::new(GameMode::Local, *map, &players)?; @@ -132,7 +134,7 @@ async fn init_game() -> Result { 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(Editor::new(map_resource)); } MainMenuResult::ReloadResources => { reload_resources(); diff --git a/src/map/mod.rs b/src/map/mod.rs index a755080a4e..b4cc362c52 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -15,7 +15,6 @@ use core::text::ToStringHelper; use core::Result; use crate::{ - editor::gui::combobox::ComboBoxValue, json::{self, TiledMap}, Resources, }; @@ -444,27 +443,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 @@ -585,29 +563,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, From 452f18164660c595d5b38dd2fe56b43ef5cd1cff Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 2 Apr 2022 21:19:37 +0200 Subject: [PATCH 05/81] Add deleting, selecting, and reordering layers --- src/editor/mod.rs | 117 +++++++++++++++++++++++++---- src/editor/windows/create_layer.rs | 4 +- 2 files changed, 105 insertions(+), 16 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 40556f5955..c3e14fa1ab 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -74,8 +74,16 @@ impl NewEditorContext { } } + 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 ui(&self, egui_ctx: &egui::Context) -> Option { let mut action = None; + let map = &self.map_resource.map; egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show(egui_ctx, |ui| { let tool = &self.selected_tool; @@ -85,23 +93,37 @@ impl NewEditorContext { .clicked() .then(|| action = Some(EditorAction::SelectTool(tool_variant))); }; + add_tool("Cursor", EditorTool::Cursor); - add_tool("Tiles", EditorTool::TilePlacer); - add_tool("Objects", EditorTool::ObjectPlacer); - add_tool("Eraser", EditorTool::Eraser); + match self.selected_layer_type() { + Some(MapLayerKind::TileLayer) => { + add_tool("Tiles", EditorTool::TilePlacer); + add_tool("Eraser", EditorTool::Eraser); + } + Some(MapLayerKind::ObjectLayer) => add_tool("Objects", EditorTool::ObjectPlacer), + None => (), + } }); egui::SidePanel::new(egui::containers::panel::Side::Right, "Layers").show(egui_ctx, |ui| { ui.heading("Layers"); - for (layer_name, layer) in self.map_resource.map.layers.iter() { + for (layer_name, layer) in map.draw_order.iter().map(|id| (id, &map.layers[id])) { ui.horizontal(|ui| { - ui.label(format!( - "({}) {}", - match layer.kind { - MapLayerKind::TileLayer => "T", - MapLayerKind::ObjectLayer => "O", - }, - layer_name - )); + if ui + .selectable_label( + self.selected_layer.as_ref() == Some(layer_name), + format!( + "({}) {}", + match layer.kind { + MapLayerKind::TileLayer => "T", + MapLayerKind::ObjectLayer => "O", + }, + layer_name + ), + ) + .clicked() + { + action = Some(EditorAction::SelectLayer(layer_name.clone())); + } let mut is_visible = layer.is_visible; if ui.checkbox(&mut is_visible, "Visible").clicked() { action = Some(EditorAction::UpdateLayer { @@ -115,9 +137,58 @@ impl NewEditorContext { if ui.button("+").clicked() { action = Some(EditorAction::OpenCreateLayerWindow); } - ui.button("-"); - ui.button("Up"); - ui.button("Down"); + + match &self.selected_layer { + Some(layer) => { + if ui.button("-").clicked() { + action = Some(EditorAction::DeleteLayer(layer.clone())); + } + let selected_layer_idx = { + self.map_resource + .map + .draw_order + .iter() + .enumerate() + .find(|(_idx, id)| &layer == id) + .map(|(idx, _)| idx) + .unwrap_or(usize::MAX) + }; + + if ui + .add_enabled(selected_layer_idx > 0, egui::Button::new("Up")) + .clicked() + { + action = Some(EditorAction::SetLayerDrawOrderIndex { + id: layer.clone(), + index: selected_layer_idx - 1, + }); + } + + if ui + .add_enabled( + selected_layer_idx < map.draw_order.len() - 1, + egui::Button::new("Down"), + ) + .clicked() + { + action = Some(EditorAction::SetLayerDrawOrderIndex { + id: layer.clone(), + index: selected_layer_idx + 1, + }); + } + } + None => { + ui.add_enabled_ui( + false, + #[allow(unused_must_use)] + |ui| { + ui.button("-"); + ui.button("Up"); + ui.button("Down"); + }, + ); + } + } }); ui.separator(); ui.heading("Tilesets"); @@ -151,6 +222,7 @@ impl Editor { pub fn apply_action(&mut self, action: EditorAction) { dbg!("Applying action:", &action); + match action { EditorAction::Batch(batch) => batch .into_iter() @@ -172,12 +244,27 @@ impl Editor { .apply(action, &mut self.ctx.map_resource.map) .unwrap(); } + EditorAction::DeleteLayer(id) => { + let action = DeleteLayerAction::new(id); + self.history + .apply(action, &mut self.ctx.map_resource.map) + .unwrap(); + } EditorAction::UpdateLayer { id, is_visible } => { let action = UpdateLayerAction::new(id, is_visible); self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); } + EditorAction::SetLayerDrawOrderIndex { id, index } => { + let action = SetLayerDrawOrderIndexAction::new(id, index); + self.history + .apply(action, &mut self.ctx.map_resource.map) + .unwrap(); + } + EditorAction::SelectLayer(id) => { + self.ctx.selected_layer = Some(id); + } _ => todo!(), } diff --git a/src/editor/windows/create_layer.rs b/src/editor/windows/create_layer.rs index 5bfa618d06..0fcff8480e 100644 --- a/src/editor/windows/create_layer.rs +++ b/src/editor/windows/create_layer.rs @@ -41,7 +41,9 @@ impl CreateLayerWindow { ui.selectable_value(&mut self.layer_kind, MapLayerKind::TileLayer, "Tiles"); ui.selectable_value(&mut self.layer_kind, MapLayerKind::ObjectLayer, "Objects"); }); - ui.checkbox(&mut self.has_collision, "Collision"); + if self.layer_kind == MapLayerKind::TileLayer { + ui.checkbox(&mut self.has_collision, "Collision"); + } ui.horizontal(|ui| { if ui.button("Create").clicked() { action = ControlFlow::Break(CreateLayerResult::Create { From 7c7eb83af89dda3a66f79e5eb5623a06ca117fae Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 2 Apr 2022 21:22:51 +0200 Subject: [PATCH 06/81] Run cargo fix --- src/editor/mod.rs | 35 +++++++++-------------------------- src/main.rs | 2 +- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index c3e14fa1ab..ef4682ad18 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,8 +1,4 @@ -use std::any::TypeId; use std::ops::ControlFlow; -use std::path::Path; - -use crate::{exit_to_main_menu, quit_to_desktop, Resources}; mod camera; mod windows; @@ -12,9 +8,8 @@ pub use camera::EditorCamera; mod actions; use actions::{ - CreateLayerAction, CreateObjectAction, CreateTilesetAction, DeleteLayerAction, - DeleteObjectAction, DeleteTilesetAction, EditorAction, PlaceTileAction, RemoveTileAction, - SetLayerDrawOrderIndexAction, UndoableAction, UpdateTilesetAction, + CreateLayerAction, DeleteLayerAction, EditorAction, SetLayerDrawOrderIndexAction, + UndoableAction, }; mod input; @@ -24,31 +19,19 @@ mod history; use history::EditorHistory; pub use input::EditorInputScheme; -use crate::editor::actions::{ - CreateSpawnPointAction, DeleteSpawnPointAction, ImportAction, MoveSpawnPointAction, - UpdateBackgroundAction, UpdateLayerAction, UpdateObjectAction, UpdateTileAttributesAction, -}; -use crate::editor::input::{collect_editor_input, EditorInput}; -use crate::gui::SELECTION_HIGHLIGHT_COLOR; -use crate::map::{MapObject, MapObjectKind}; -use crate::player::IDLE_ANIMATION_ID; +use crate::editor::actions::UpdateLayerAction; + +use crate::map::MapObjectKind; + use macroquad::{ - color, - experimental::{ - collections::storage, - scene::{Node, RefMut}, - }, + experimental::scene::{Node, RefMut}, prelude::*, }; -use core::text::{draw_aligned_text, HorizontalAlignment, VerticalAlignment}; - use self::windows::CreateLayerResult; -use super::map::{Map, MapLayerKind}; -use crate::resources::{ - map_name_to_filename, MapResource, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION, -}; +use super::map::MapLayerKind; +use crate::resources::MapResource; #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { diff --git a/src/main.rs b/src/main.rs index b8102c60e1..1eff634b08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,7 +117,7 @@ async fn init_game() -> Result { start_music("fish_tide"); } MainMenuResult::Editor { - input_scheme, + input_scheme: _, is_new_map, } => { let map_resource = if is_new_map { From 21dba171529200fa2df64734f5d68fd290ab91c9 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 3 Apr 2022 12:47:38 +0200 Subject: [PATCH 07/81] Clean up, misc changes --- src/editor/actions.rs | 53 ++++++- src/editor/data.rs | 221 +++++++++++++++++++++++++++ src/editor/mod.rs | 231 ++++++----------------------- src/editor/windows/create_layer.rs | 15 +- src/main.rs | 4 +- 5 files changed, 333 insertions(+), 191 deletions(-) create mode 100644 src/editor/data.rs diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 820c5820a8..684aeddd0d 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -1,4 +1,3 @@ -use std::any::TypeId; use std::cmp::Ordering; use macroquad::experimental::collections::storage; @@ -42,7 +41,6 @@ pub enum EditorAction { layer_id: String, index: usize, }, - CloseWindow(TypeId), SelectTile { id: u32, tileset_id: String, @@ -152,6 +150,57 @@ impl EditorAction { _ => Self::batch(&[self, action]), } } + + pub fn then_do(&mut self, action: EditorAction) { + match self { + EditorAction::Batch(batch) => { + batch.push(action); + } + _ => { + let mut temp = Self::Batch(Vec::with_capacity(2)); + std::mem::swap(&mut temp, self); + self.then_do(temp); + self.then_do(action); + } + } + } +} + +pub trait EditorActionExt { + fn then(self, action: Option) -> Option; + fn then_some(self, action: EditorAction) -> Option; + fn then_do(&mut self, action: Option); + fn then_do_some(&mut self, action: EditorAction); +} + +impl EditorActionExt for Option { + fn then(self, action: Option) -> Option { + match action { + Some(action) => self.then_some(action), + None => self, + } + } + + fn then_some(self, action: EditorAction) -> Option { + match self { + Some(self_action) => Some(self_action.then(action)), + None => Some(action), + } + } + + fn then_do(&mut self, action: Option) { + match action { + Some(action) => self.then_do_some(action), + None => (), + } + } + + fn then_do_some(&mut self, action: EditorAction) { + match self { + Some(self_action) => self_action.then_do(action), + None => *self = Some(action), + } + } } /// All actions that modify map data should implement this trait diff --git a/src/editor/data.rs b/src/editor/data.rs new file mode 100644 index 0000000000..dff3f9792f --- /dev/null +++ b/src/editor/data.rs @@ -0,0 +1,221 @@ +use crate::{map::MapLayerKind, resources::MapResource}; + +use super::actions::{EditorAction, EditorActionExt}; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum EditorTool { + Cursor, + TilePlacer, + ObjectPlacer, + SpawnPointPlacer, + Eraser, +} + +pub struct EditorData { + pub selected_tool: EditorTool, + pub map_resource: MapResource, + pub selected_layer: Option, + pub selected_tileset: Option, +} + +impl EditorData { + pub fn new(map_resource: MapResource) -> Self { + Self { + map_resource, + selected_tool: EditorTool::Cursor, + selected_layer: None, + selected_tileset: None, + } + } + + 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) + } +} + +/// UI-related functions +impl EditorData { + pub fn ui(&self, egui_ctx: &egui::Context) -> Option { + // Draw toolbar + let mut action = self.draw_toolbar(egui_ctx); + // Draw side panel + action.then_do(self.draw_side_panel(egui_ctx)); + + action + } + + fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { + let mut action = None; + + egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show(egui_ctx, |ui| { + let tool = &self.selected_tool; + + let mut add_tool = |tool_name, tool_variant| { + if ui + .add(egui::SelectableLabel::new(tool == &tool_variant, tool_name)) + .clicked() + { + action.then_do_some(EditorAction::SelectTool(tool_variant)); + } + }; + + add_tool("Cursor", EditorTool::Cursor); + match self.selected_layer_type() { + Some(MapLayerKind::TileLayer) => { + add_tool("Tiles", EditorTool::TilePlacer); + add_tool("Eraser", EditorTool::Eraser); + } + Some(MapLayerKind::ObjectLayer) => add_tool("Objects", EditorTool::ObjectPlacer), + None => (), + } + }); + + action + } + + fn draw_side_panel(&self, egui_ctx: &egui::Context) -> Option { + let mut action = None; + + egui::SidePanel::new(egui::containers::panel::Side::Right, "Side panel").show( + egui_ctx, + |ui| { + action = self.draw_layer_info(ui); + ui.separator(); + match self.selected_layer_type() { + Some(MapLayerKind::TileLayer) => { + self.draw_tileset_info(ui).map(|act| action = Some(act)); + } + Some(MapLayerKind::ObjectLayer) => { + // TODO + } + None => (), + } + }, + ); + + action + } + + fn draw_layer_info(&self, ui: &mut egui::Ui) -> Option { + ui.heading("Layers"); + self.draw_layer_list(ui) // Draw layer list + .then(self.draw_layer_utils(ui)) // Draw layer util buttons (+ - Up Down) + } + + fn draw_layer_list(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + let map = &self.map_resource.map; + + ui.group(|ui| { + for (layer_name, layer) in map.draw_order.iter().map(|id| (id, &map.layers[id])) { + ui.horizontal(|ui| { + if ui + .selectable_label( + self.selected_layer.as_ref() == Some(layer_name), + format!( + "({}) {}", + match layer.kind { + MapLayerKind::TileLayer => "T", + MapLayerKind::ObjectLayer => "O", + }, + layer_name + ), + ) + .clicked() + { + action.then_do_some(EditorAction::SelectLayer(layer_name.clone())); + } + let mut is_visible = layer.is_visible; + if ui.checkbox(&mut is_visible, "Visible").clicked() { + action.then_do_some(EditorAction::UpdateLayer { + id: layer_name.clone(), + is_visible, + }); + } + }); + } + }); + + action + } + + fn draw_layer_utils(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + let map = &self.map_resource.map; + + ui.horizontal(|ui| { + if ui.button("+").clicked() { + action.then_do_some(EditorAction::OpenCreateLayerWindow); + } + + ui.add_enabled_ui(self.selected_layer.is_some(), |ui| { + if ui.button("-").clicked() { + action.then_do_some(EditorAction::DeleteLayer( + self.selected_layer.as_ref().unwrap().clone(), + )); + } + let selected_layer_idx = self + .selected_layer + .as_ref() + .and_then(|layer| { + 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() + { + action.then_do_some(EditorAction::SetLayerDrawOrderIndex { + id: self.selected_layer.as_ref().unwrap().clone(), + index: selected_layer_idx - 1, + }); + } + + if ui + .add_enabled( + selected_layer_idx + 1 < map.draw_order.len(), + egui::Button::new("Down"), + ) + .clicked() + { + action.then_do_some(EditorAction::SetLayerDrawOrderIndex { + id: self.selected_layer.as_ref().unwrap().clone(), + index: selected_layer_idx + 1, + }); + } + }); + }); + + action + } + + fn draw_tileset_info(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + + ui.heading("Tilesets"); + ui.group(|ui| { + for (tileset_name, _tileset) in self.map_resource.map.tilesets.iter() { + let is_selected = self.selected_tileset.as_ref() == Some(tileset_name); + + if ui.selectable_label(is_selected, tileset_name).clicked() { + action.then_do_some(EditorAction::SelectTileset(tileset_name.clone())); + } + } + }); + ui.horizontal(|ui| { + ui.button("+"); + ui.button("-"); + ui.button("Edit"); + }); + + action + } +} diff --git a/src/editor/mod.rs b/src/editor/mod.rs index ef4682ad18..81c9805a0b 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,7 +1,9 @@ use std::ops::ControlFlow; mod camera; +pub mod data; mod windows; +pub use data::*; pub use camera::EditorCamera; @@ -30,174 +32,32 @@ use macroquad::{ use self::windows::CreateLayerResult; -use super::map::MapLayerKind; use crate::resources::MapResource; -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum EditorTool { - Cursor, - TilePlacer, - ObjectPlacer, - SpawnPointPlacer, - Eraser, -} - -pub struct NewEditorContext { - pub selected_tool: EditorTool, - pub map_resource: MapResource, - pub selected_layer: Option, -} - -impl NewEditorContext { - pub fn new(map_resource: MapResource) -> Self { - Self { - map_resource, - selected_tool: EditorTool::Cursor, - selected_layer: None, - } - } - - 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 ui(&self, egui_ctx: &egui::Context) -> Option { - let mut action = None; - let map = &self.map_resource.map; - - egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show(egui_ctx, |ui| { - let tool = &self.selected_tool; - - let mut add_tool = |tool_name, tool_variant| { - ui.add(egui::SelectableLabel::new(tool == &tool_variant, tool_name)) - .clicked() - .then(|| action = Some(EditorAction::SelectTool(tool_variant))); - }; - - add_tool("Cursor", EditorTool::Cursor); - match self.selected_layer_type() { - Some(MapLayerKind::TileLayer) => { - add_tool("Tiles", EditorTool::TilePlacer); - add_tool("Eraser", EditorTool::Eraser); - } - Some(MapLayerKind::ObjectLayer) => add_tool("Objects", EditorTool::ObjectPlacer), - None => (), - } - }); - egui::SidePanel::new(egui::containers::panel::Side::Right, "Layers").show(egui_ctx, |ui| { - ui.heading("Layers"); - for (layer_name, layer) in map.draw_order.iter().map(|id| (id, &map.layers[id])) { - ui.horizontal(|ui| { - if ui - .selectable_label( - self.selected_layer.as_ref() == Some(layer_name), - format!( - "({}) {}", - match layer.kind { - MapLayerKind::TileLayer => "T", - MapLayerKind::ObjectLayer => "O", - }, - layer_name - ), - ) - .clicked() - { - action = Some(EditorAction::SelectLayer(layer_name.clone())); - } - let mut is_visible = layer.is_visible; - if ui.checkbox(&mut is_visible, "Visible").clicked() { - action = Some(EditorAction::UpdateLayer { - id: layer_name.clone(), - is_visible, - }); - } - }); - } - ui.horizontal(|ui| { - if ui.button("+").clicked() { - action = Some(EditorAction::OpenCreateLayerWindow); - } - - match &self.selected_layer { - Some(layer) => { - if ui.button("-").clicked() { - action = Some(EditorAction::DeleteLayer(layer.clone())); - } - let selected_layer_idx = { - self.map_resource - .map - .draw_order - .iter() - .enumerate() - .find(|(_idx, id)| &layer == id) - .map(|(idx, _)| idx) - .unwrap_or(usize::MAX) - }; - - if ui - .add_enabled(selected_layer_idx > 0, egui::Button::new("Up")) - .clicked() - { - action = Some(EditorAction::SetLayerDrawOrderIndex { - id: layer.clone(), - index: selected_layer_idx - 1, - }); - } - - if ui - .add_enabled( - selected_layer_idx < map.draw_order.len() - 1, - egui::Button::new("Down"), - ) - .clicked() - { - action = Some(EditorAction::SetLayerDrawOrderIndex { - id: layer.clone(), - index: selected_layer_idx + 1, - }); - } - } - None => { - ui.add_enabled_ui( - false, - #[allow(unused_must_use)] - |ui| { - ui.button("-"); - ui.button("Up"); - ui.button("Down"); - }, - ); - } - } - }); - ui.separator(); - ui.heading("Tilesets"); - ui.horizontal(|ui| { - ui.button("+"); - ui.button("-"); - ui.button("Edit"); - }); - }); - - action - } -} - pub struct Editor { - ctx: NewEditorContext, + ctx: EditorData, history: EditorHistory, create_layer_window: Option, } +/// 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, +} + +impl EditorNode { + pub fn new(editor: Editor) -> Self { + Self { editor } + } +} + impl Editor { pub fn new(map_resource: MapResource) -> Self { Self { - ctx: NewEditorContext::new(map_resource), + ctx: EditorData::new(map_resource), history: EditorHistory::new(), create_layer_window: None, } @@ -232,6 +92,7 @@ impl Editor { self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); + self.ctx.selected_layer = None; } EditorAction::UpdateLayer { id, is_visible } => { let action = UpdateLayerAction::new(id, is_visible); @@ -252,40 +113,42 @@ impl Editor { _ => todo!(), } } + + pub fn ui(&mut self, egui_ctx: &egui::Context) { + if let Some(action) = self.ctx.ui(egui_ctx) { + self.apply_action(action); + } + + if let Some(window) = &mut self.create_layer_window { + match window.ui(egui_ctx, &self.ctx.map_resource.map) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(CreateLayerResult::Create { + has_collision, + layer_kind, + layer_name, + }) => { + self.apply_action(EditorAction::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; + } + } + } + } } -impl Node for Editor { +impl Node for EditorNode { fn draw(mut node: RefMut) where Self: Sized, { - egui_macroquad::ui(|egui_ctx| { - if let Some(action) = node.ctx.ui(egui_ctx) { - node.apply_action(action); - } - - if let Some(window) = &mut node.create_layer_window { - match window.ui(egui_ctx) { - ControlFlow::Continue(()) => (), - ControlFlow::Break(CreateLayerResult::Create { - has_collision, - layer_kind, - layer_name, - }) => { - node.apply_action(EditorAction::CreateLayer { - has_collision, - kind: layer_kind, - index: None, - id: layer_name, - }); - node.create_layer_window = None; - } - ControlFlow::Break(CreateLayerResult::Close) => { - node.create_layer_window = None; - } - } - } - }); + egui_macroquad::ui(|egui_ctx| node.editor.ui(egui_ctx)); egui_macroquad::draw(); } diff --git a/src/editor/windows/create_layer.rs b/src/editor/windows/create_layer.rs index 0fcff8480e..3588409d25 100644 --- a/src/editor/windows/create_layer.rs +++ b/src/editor/windows/create_layer.rs @@ -1,6 +1,6 @@ use std::ops::ControlFlow; -use crate::map::MapLayerKind; +use crate::map::{Map, MapLayerKind}; pub struct CreateLayerWindow { layer_name: String, @@ -27,7 +27,7 @@ impl Default for CreateLayerWindow { } impl CreateLayerWindow { - pub fn ui(&mut self, egui_ctx: &egui::Context) -> ControlFlow { + 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| { @@ -44,8 +44,17 @@ impl CreateLayerWindow { 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.button("Create").clicked() { + 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, diff --git a/src/main.rs b/src/main.rs index 1eff634b08..7cd3d14664 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,7 +107,7 @@ fn window_conf() -> Conf { async fn init_game() -> Result { use gui::MainMenuResult; - use crate::editor::Editor; + use crate::editor::{Editor, EditorNode}; match gui::show_main_menu().await { MainMenuResult::LocalGame { map, players } => { @@ -134,7 +134,7 @@ async fn init_game() -> Result { let position = map_resource.map.get_size() * 0.5; scene::add_node(EditorCamera::new(position)); - scene::add_node(Editor::new(map_resource)); + scene::add_node(EditorNode::new(Editor::new(map_resource))); } MainMenuResult::ReloadResources => { reload_resources(); From 9d64c767a00b9ffad8653f840ed680f08f98a78e Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 3 Apr 2022 12:49:53 +0200 Subject: [PATCH 08/81] Some structure renames --- src/editor/actions.rs | 36 ++++++++++++++++++------------------ src/editor/data.rs | 32 ++++++++++++++++---------------- src/editor/history.rs | 6 +++--- src/editor/mod.rs | 35 ++++++++++++++++------------------- 4 files changed, 53 insertions(+), 56 deletions(-) diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 684aeddd0d..1d086b32c0 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -16,8 +16,8 @@ use super::EditorTool; /// These are all the actions available for the GUI and other sub-systems of the 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(EditorTool), @@ -136,14 +136,14 @@ pub enum EditorAction { QuitToDesktop, } -impl EditorAction { - pub fn batch(actions: &[EditorAction]) -> Self { +impl UiAction { + pub fn batch(actions: &[UiAction]) -> Self { Self::Batch(actions.to_vec()) } - pub fn then(mut self, action: EditorAction) -> Self { + pub fn then(mut self, action: UiAction) -> Self { match &mut self { - EditorAction::Batch(batch) => { + UiAction::Batch(batch) => { batch.push(action); self } @@ -151,9 +151,9 @@ impl EditorAction { } } - pub fn then_do(&mut self, action: EditorAction) { + pub fn then_do(&mut self, action: UiAction) { match self { - EditorAction::Batch(batch) => { + UiAction::Batch(batch) => { batch.push(action); } _ => { @@ -166,36 +166,36 @@ impl EditorAction { } } -pub trait EditorActionExt { - fn then(self, action: Option) -> Option; - fn then_some(self, action: EditorAction) -> Option; - fn then_do(&mut self, action: Option); - fn then_do_some(&mut self, action: EditorAction); +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 EditorActionExt for Option { - fn then(self, action: Option) -> Option { +impl UiActionExt for Option { + fn then(self, action: Option) -> Option { match action { Some(action) => self.then_some(action), None => self, } } - fn then_some(self, action: EditorAction) -> Option { + fn then_some(self, action: UiAction) -> Option { match self { Some(self_action) => Some(self_action.then(action)), None => Some(action), } } - fn then_do(&mut self, action: Option) { + fn then_do(&mut self, action: Option) { match action { Some(action) => self.then_do_some(action), None => (), } } - fn then_do_some(&mut self, action: EditorAction) { + fn then_do_some(&mut self, action: UiAction) { match self { Some(self_action) => self_action.then_do(action), None => *self = Some(action), diff --git a/src/editor/data.rs b/src/editor/data.rs index dff3f9792f..2bbf777f61 100644 --- a/src/editor/data.rs +++ b/src/editor/data.rs @@ -1,6 +1,6 @@ use crate::{map::MapLayerKind, resources::MapResource}; -use super::actions::{EditorAction, EditorActionExt}; +use super::actions::{UiAction, UiActionExt}; #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { @@ -38,7 +38,7 @@ impl EditorData { /// UI-related functions impl EditorData { - pub fn ui(&self, egui_ctx: &egui::Context) -> Option { + pub fn ui(&self, egui_ctx: &egui::Context) -> Option { // Draw toolbar let mut action = self.draw_toolbar(egui_ctx); // Draw side panel @@ -47,7 +47,7 @@ impl EditorData { action } - fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { + fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { let mut action = None; egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show(egui_ctx, |ui| { @@ -58,7 +58,7 @@ impl EditorData { .add(egui::SelectableLabel::new(tool == &tool_variant, tool_name)) .clicked() { - action.then_do_some(EditorAction::SelectTool(tool_variant)); + action.then_do_some(UiAction::SelectTool(tool_variant)); } }; @@ -76,7 +76,7 @@ impl EditorData { action } - fn draw_side_panel(&self, egui_ctx: &egui::Context) -> Option { + fn draw_side_panel(&self, egui_ctx: &egui::Context) -> Option { let mut action = None; egui::SidePanel::new(egui::containers::panel::Side::Right, "Side panel").show( @@ -99,13 +99,13 @@ impl EditorData { action } - fn draw_layer_info(&self, ui: &mut egui::Ui) -> Option { + fn draw_layer_info(&self, ui: &mut egui::Ui) -> Option { ui.heading("Layers"); self.draw_layer_list(ui) // Draw layer list .then(self.draw_layer_utils(ui)) // Draw layer util buttons (+ - Up Down) } - fn draw_layer_list(&self, ui: &mut egui::Ui) -> Option { + fn draw_layer_list(&self, ui: &mut egui::Ui) -> Option { let mut action = None; let map = &self.map_resource.map; @@ -126,11 +126,11 @@ impl EditorData { ) .clicked() { - action.then_do_some(EditorAction::SelectLayer(layer_name.clone())); + action.then_do_some(UiAction::SelectLayer(layer_name.clone())); } let mut is_visible = layer.is_visible; if ui.checkbox(&mut is_visible, "Visible").clicked() { - action.then_do_some(EditorAction::UpdateLayer { + action.then_do_some(UiAction::UpdateLayer { id: layer_name.clone(), is_visible, }); @@ -142,18 +142,18 @@ impl EditorData { action } - fn draw_layer_utils(&self, ui: &mut egui::Ui) -> Option { + fn draw_layer_utils(&self, ui: &mut egui::Ui) -> Option { let mut action = None; let map = &self.map_resource.map; ui.horizontal(|ui| { if ui.button("+").clicked() { - action.then_do_some(EditorAction::OpenCreateLayerWindow); + action.then_do_some(UiAction::OpenCreateLayerWindow); } ui.add_enabled_ui(self.selected_layer.is_some(), |ui| { if ui.button("-").clicked() { - action.then_do_some(EditorAction::DeleteLayer( + action.then_do_some(UiAction::DeleteLayer( self.selected_layer.as_ref().unwrap().clone(), )); } @@ -173,7 +173,7 @@ impl EditorData { .add_enabled(selected_layer_idx > 0, egui::Button::new("Up")) .clicked() { - action.then_do_some(EditorAction::SetLayerDrawOrderIndex { + action.then_do_some(UiAction::SetLayerDrawOrderIndex { id: self.selected_layer.as_ref().unwrap().clone(), index: selected_layer_idx - 1, }); @@ -186,7 +186,7 @@ impl EditorData { ) .clicked() { - action.then_do_some(EditorAction::SetLayerDrawOrderIndex { + action.then_do_some(UiAction::SetLayerDrawOrderIndex { id: self.selected_layer.as_ref().unwrap().clone(), index: selected_layer_idx + 1, }); @@ -197,7 +197,7 @@ impl EditorData { action } - fn draw_tileset_info(&self, ui: &mut egui::Ui) -> Option { + fn draw_tileset_info(&self, ui: &mut egui::Ui) -> Option { let mut action = None; ui.heading("Tilesets"); @@ -206,7 +206,7 @@ impl EditorData { let is_selected = self.selected_tileset.as_ref() == Some(tileset_name); if ui.selectable_label(is_selected, tileset_name).clicked() { - action.then_do_some(EditorAction::SelectTileset(tileset_name.clone())); + action.then_do_some(UiAction::SelectTileset(tileset_name.clone())); } } }); diff --git a/src/editor/history.rs b/src/editor/history.rs index 5f1613306f..baa690d7de 100644 --- a/src/editor/history.rs +++ b/src/editor/history.rs @@ -4,14 +4,14 @@ use crate::Map; use super::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(), } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 81c9805a0b..1650072d06 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -10,15 +10,14 @@ pub use camera::EditorCamera; mod actions; use actions::{ - CreateLayerAction, DeleteLayerAction, EditorAction, SetLayerDrawOrderIndexAction, - UndoableAction, + CreateLayerAction, DeleteLayerAction, SetLayerDrawOrderIndexAction, UiAction, UndoableAction, }; mod input; mod history; -use history::EditorHistory; +use history::ActionHistory; pub use input::EditorInputScheme; use crate::editor::actions::UpdateLayerAction; @@ -37,7 +36,7 @@ use crate::resources::MapResource; pub struct Editor { ctx: EditorData, - history: EditorHistory, + history: ActionHistory, create_layer_window: Option, } @@ -58,25 +57,23 @@ impl Editor { pub fn new(map_resource: MapResource) -> Self { Self { ctx: EditorData::new(map_resource), - history: EditorHistory::new(), + history: ActionHistory::new(), create_layer_window: None, } } - pub fn apply_action(&mut self, action: EditorAction) { + pub fn apply_action(&mut self, action: UiAction) { dbg!("Applying action:", &action); match action { - EditorAction::Batch(batch) => batch + UiAction::Batch(batch) => batch .into_iter() .for_each(|action| self.apply_action(action)), - EditorAction::Undo => self.history.undo(&mut self.ctx.map_resource.map).unwrap(), - EditorAction::Redo => self.history.redo(&mut self.ctx.map_resource.map).unwrap(), - EditorAction::SelectTool(tool) => self.ctx.selected_tool = tool, - EditorAction::OpenCreateLayerWindow => { - self.create_layer_window = Some(Default::default()) - } - EditorAction::CreateLayer { + UiAction::Undo => self.history.undo(&mut self.ctx.map_resource.map).unwrap(), + UiAction::Redo => self.history.redo(&mut self.ctx.map_resource.map).unwrap(), + UiAction::SelectTool(tool) => self.ctx.selected_tool = tool, + UiAction::OpenCreateLayerWindow => self.create_layer_window = Some(Default::default()), + UiAction::CreateLayer { id, kind, has_collision, @@ -87,26 +84,26 @@ impl Editor { .apply(action, &mut self.ctx.map_resource.map) .unwrap(); } - EditorAction::DeleteLayer(id) => { + UiAction::DeleteLayer(id) => { let action = DeleteLayerAction::new(id); self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); self.ctx.selected_layer = None; } - EditorAction::UpdateLayer { id, is_visible } => { + UiAction::UpdateLayer { id, is_visible } => { let action = UpdateLayerAction::new(id, is_visible); self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); } - EditorAction::SetLayerDrawOrderIndex { id, index } => { + UiAction::SetLayerDrawOrderIndex { id, index } => { let action = SetLayerDrawOrderIndexAction::new(id, index); self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); } - EditorAction::SelectLayer(id) => { + UiAction::SelectLayer(id) => { self.ctx.selected_layer = Some(id); } @@ -127,7 +124,7 @@ impl Editor { layer_kind, layer_name, }) => { - self.apply_action(EditorAction::CreateLayer { + self.apply_action(UiAction::CreateLayer { has_collision, kind: layer_kind, index: None, From 84ee56dc15cfdecd8f209636ed025cbdd4316462 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 3 Apr 2022 13:21:50 +0200 Subject: [PATCH 09/81] Reorganize actions --- src/editor/RATIONALE.md | 9 +- src/editor/actions.rs | 1149 +---------------- src/editor/actions/create_layer.rs | 82 ++ src/editor/actions/create_object.rs | 64 + src/editor/actions/create_spawn_point.rs | 32 + src/editor/actions/create_tileset.rs | 73 ++ src/editor/actions/delete_layer.rs | 79 ++ src/editor/actions/delete_object.rs | 61 + src/editor/actions/delete_spawn_point.rs | 49 + src/editor/actions/delete_tileset.rs | 48 + src/editor/actions/import.rs | 99 ++ src/editor/actions/move_spawn_point.rs | 60 + src/editor/actions/place_tile.rs | 121 ++ src/editor/actions/remove_tile.rs | 103 ++ .../actions/set_layer_draw_order_index.rs | 81 ++ src/editor/actions/update_background.rs | 62 + src/editor/actions/update_layer.rs | 59 + src/editor/actions/update_object.rs | 87 ++ src/editor/actions/update_tile_attributes.rs | 91 ++ src/editor/actions/update_tileset.rs | 85 ++ src/editor/history.rs | 2 +- src/editor/mod.rs | 15 +- 22 files changed, 1389 insertions(+), 1122 deletions(-) create mode 100644 src/editor/actions/create_layer.rs create mode 100644 src/editor/actions/create_object.rs create mode 100644 src/editor/actions/create_spawn_point.rs create mode 100644 src/editor/actions/create_tileset.rs create mode 100644 src/editor/actions/delete_layer.rs create mode 100644 src/editor/actions/delete_object.rs create mode 100644 src/editor/actions/delete_spawn_point.rs create mode 100644 src/editor/actions/delete_tileset.rs create mode 100644 src/editor/actions/import.rs create mode 100644 src/editor/actions/move_spawn_point.rs create mode 100644 src/editor/actions/place_tile.rs create mode 100644 src/editor/actions/remove_tile.rs create mode 100644 src/editor/actions/set_layer_draw_order_index.rs create mode 100644 src/editor/actions/update_background.rs create mode 100644 src/editor/actions/update_layer.rs create mode 100644 src/editor/actions/update_object.rs create mode 100644 src/editor/actions/update_tile_attributes.rs create mode 100644 src/editor/actions/update_tileset.rs diff --git a/src/editor/RATIONALE.md b/src/editor/RATIONALE.md index 74ca84c445..fdf83341a2 100644 --- a/src/editor/RATIONALE.md +++ b/src/editor/RATIONALE.md @@ -5,4 +5,11 @@ tools are known at compiletime. - `CreateLayerWindow` does not allow undoing, only the main editor context does. As such, its `ui` method doesn't need to be immutable, because the changes done to the window are irrelevant to the history and can occur without having to keep track of them. (PD: TextEdit changes are kept track of -internally by egui and can be undone/redone without any external code) \ No newline at end of file +internally by egui and can be undone/redone without any external code) +- 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. \ No newline at end of file diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 1d086b32c0..3c5cc42385 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -1,15 +1,9 @@ -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::{MapBackgroundLayer, MapObject, MapObjectKind}; -use crate::{ - map::{Map, MapLayer, MapLayerKind, MapTile, MapTileset}, - Resources, -}; +use crate::map::{Map, MapLayerKind, MapTileset}; +use crate::map::{MapBackgroundLayer, MapObjectKind}; use super::EditorTool; @@ -227,1121 +221,56 @@ pub trait UndoableAction { } } -#[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")); - } - } 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(), - } - } -} - -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; - } - } - - 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")); - } - - 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, -} - -impl DeleteObjectAction { - pub fn new(index: usize, layer_id: String) -> Self { - DeleteObjectAction { - index, - layer_id, - object: None, - } - } -} - -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", - )); - } - - 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")); - } - - 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, - } - } -} - -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, -} +mod update_background; +pub use update_background::*; -impl CreateSpawnPointAction { - pub fn new(position: Vec2) -> Self { - CreateSpawnPointAction { position } - } -} +mod set_layer_draw_order_index; +pub use set_layer_draw_order_index::*; -impl UndoableAction for CreateSpawnPointAction { - fn apply(&mut self, map: &mut Map) -> Result<()> { - map.spawn_points.push(self.position); +mod update_tile_attributes; +pub use update_tile_attributes::*; - Ok(()) - } +mod create_layer; +pub use create_layer::*; - fn undo(&mut self, map: &mut Map) -> Result<()> { - map.spawn_points.pop(); +mod delete_layer; +pub use delete_layer::*; - Ok(()) - } -} +mod update_layer; +pub use update_layer::*; -#[derive(Debug)] -pub struct DeleteSpawnPointAction { - index: usize, - spawn_point: Option, -} +mod import; +pub use import::*; -impl DeleteSpawnPointAction { - pub fn new(index: usize) -> Self { - DeleteSpawnPointAction { - index, - spawn_point: None, - } - } -} +mod create_tileset; +pub use create_tileset::*; -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); +mod delete_tileset; +pub use delete_tileset::*; - Ok(()) - } +mod update_tileset; +pub use update_tileset::*; - 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")); - } +mod create_object; +pub use create_object::*; - Ok(()) - } -} +mod delete_object; +pub use delete_object::*; -#[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(()) - } -} - -#[derive(Debug)] -pub struct PlaceTileAction { - id: u32, - layer_id: String, - tileset_id: String, - coords: UVec2, - replaced_tile: Option, -} - -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, - } - } -} +mod update_object; +pub use update_object::*; -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 create_spawn_point; +pub use create_spawn_point::*; - let i = map.to_index(self.coords); +mod delete_spawn_point; +pub use delete_spawn_point::*; - 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 move_spawn_point; +pub use move_spawn_point::*; - let tile = MapTile { - tile_id: self.id, - tileset_id: self.tileset_id.clone(), - texture_id, - texture_coords, - attributes: vec![], - }; +mod place_tile; +pub use place_tile::*; - 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", - )); - } - - 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, &"PlaceTileAction (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, - &"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", - )); - } - - 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 - } -} - -#[derive(Debug)] -pub struct RemoveTileAction { - layer_id: String, - coords: UVec2, - tile: Option, -} - -impl RemoveTileAction { - pub fn new(layer_id: String, coords: UVec2) -> Self { - RemoveTileAction { - layer_id, - coords, - tile: None, - } - } -} - -impl UndoableAction for RemoveTileAction { - fn apply(&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, &"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", - )); - } - - 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, &"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..699ffabbbe --- /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(&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..bdc65fa370 --- /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(&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..a6d1409e6c --- /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(&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..992e3e98b7 --- /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 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(()) + } +} diff --git a/src/editor/actions/delete_layer.rs b/src/editor/actions/delete_layer.rs new file mode 100644 index 0000000000..ea39b744a2 --- /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(&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..ee21c88a8e --- /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(&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..ab0a12f4ec --- /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(&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..3e19e80c77 --- /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(&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..5ed62cab09 --- /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(&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_spawn_point.rs b/src/editor/actions/move_spawn_point.rs new file mode 100644 index 0000000000..9abc347c76 --- /dev/null +++ b/src/editor/actions/move_spawn_point.rs @@ -0,0 +1,60 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use std::cmp::Ordering; + +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(&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, + &"MoveSpawnPoint: 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, &"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..766a7cbf18 --- /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(&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..93e4916a68 --- /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(&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..f81b2fecd2 --- /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(&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..b13987104a --- /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(&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..454bda7d3b --- /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(&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..379090e005 --- /dev/null +++ b/src/editor/actions/update_object.rs @@ -0,0 +1,87 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use macroquad::prelude::Vec2; + +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, + position: Vec2, + object: Option, +} + +impl UpdateObject { + pub fn new( + layer_id: String, + index: usize, + id: String, + kind: MapObjectKind, + position: Vec2, + ) -> Self { + UpdateObject { + layer_id, + index, + id, + kind, + position, + object: None, + } + } +} + +impl UndoableAction for UpdateObject { + 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, + &"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..0dcab0ffcf --- /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(&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..aa0f960a8c --- /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(&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/history.rs b/src/editor/history.rs index baa690d7de..65f37eed38 100644 --- a/src/editor/history.rs +++ b/src/editor/history.rs @@ -2,7 +2,7 @@ use core::Result; use crate::Map; -use super::UndoableAction; +use super::actions::UndoableAction; pub struct ActionHistory { undo_stack: Vec>, diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 1650072d06..c40f36bce7 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -8,10 +8,7 @@ pub use data::*; pub use camera::EditorCamera; mod actions; - -use actions::{ - CreateLayerAction, DeleteLayerAction, SetLayerDrawOrderIndexAction, UiAction, UndoableAction, -}; +use actions::{UiAction, UiActionExt}; mod input; @@ -20,8 +17,6 @@ mod history; use history::ActionHistory; pub use input::EditorInputScheme; -use crate::editor::actions::UpdateLayerAction; - use crate::map::MapObjectKind; use macroquad::{ @@ -79,26 +74,26 @@ impl Editor { has_collision, index, } => { - let action = CreateLayerAction::new(id, kind, has_collision, index); + let action = actions::CreateLayer::new(id, kind, has_collision, index); self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); } UiAction::DeleteLayer(id) => { - let action = DeleteLayerAction::new(id); + let action = actions::DeleteLayer::new(id); self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); self.ctx.selected_layer = None; } UiAction::UpdateLayer { id, is_visible } => { - let action = UpdateLayerAction::new(id, is_visible); + let action = actions::UpdateLayer::new(id, is_visible); self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); } UiAction::SetLayerDrawOrderIndex { id, index } => { - let action = SetLayerDrawOrderIndexAction::new(id, index); + let action = actions::SetLayerDrawOrderIndex::new(id, index); self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); From 6df5ceec115f51750e1fae70d4f91a3989c1fab8 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 3 Apr 2022 14:08:41 +0200 Subject: [PATCH 10/81] Add create tileset window --- src/editor/actions/create_tileset.rs | 12 ++--- src/editor/data.rs | 20 +++++--- src/editor/mod.rs | 34 ++++++++++++- src/editor/windows/create_tileset.rs | 76 ++++++++++++++++++++++++++++ src/editor/windows/mod.rs | 5 +- 5 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 src/editor/windows/create_tileset.rs diff --git a/src/editor/actions/create_tileset.rs b/src/editor/actions/create_tileset.rs index 992e3e98b7..00944e8803 100644 --- a/src/editor/actions/create_tileset.rs +++ b/src/editor/actions/create_tileset.rs @@ -16,18 +16,18 @@ use crate::map::Map; use super::UndoableAction; #[derive(Debug)] -pub struct CreateTilesetAction { +pub struct CreateTileset { id: String, texture_id: String, } -impl CreateTilesetAction { +impl CreateTileset { pub fn new(id: String, texture_id: String) -> Self { - CreateTilesetAction { id, texture_id } + CreateTileset { id, texture_id } } } -impl UndoableAction for CreateTilesetAction { +impl UndoableAction for CreateTileset { fn apply(&mut self, map: &mut Map) -> Result<()> { let resources = storage::get::(); if let Some(texture_entry) = resources.textures.get(&self.texture_id).cloned() { @@ -56,7 +56,7 @@ impl UndoableAction for CreateTilesetAction { } else { return Err(Error::new_const( ErrorKind::EditorAction, - &"CreateTilesetAction: The specified texture does not exist", + &"CreateTileset: The specified texture does not exist", )); } @@ -65,7 +65,7 @@ impl UndoableAction for CreateTilesetAction { 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")); + 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/data.rs b/src/editor/data.rs index 2bbf777f61..993697e29d 100644 --- a/src/editor/data.rs +++ b/src/editor/data.rs @@ -198,9 +198,22 @@ impl EditorData { } fn draw_tileset_info(&self, ui: &mut egui::Ui) -> Option { + ui.heading("Tilesets"); + let mut action = self.draw_tileset_list(ui); + ui.horizontal(|ui| { + if ui.button("+").clicked() { + action.then_do_some(UiAction::OpenCreateTilesetWindow); + } + ui.button("-"); + ui.button("Edit"); + }); + + action + } + + fn draw_tileset_list(&self, ui: &mut egui::Ui) -> Option { let mut action = None; - ui.heading("Tilesets"); ui.group(|ui| { for (tileset_name, _tileset) in self.map_resource.map.tilesets.iter() { let is_selected = self.selected_tileset.as_ref() == Some(tileset_name); @@ -210,11 +223,6 @@ impl EditorData { } } }); - ui.horizontal(|ui| { - ui.button("+"); - ui.button("-"); - ui.button("Edit"); - }); action } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index c40f36bce7..05d17309ee 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -17,14 +17,14 @@ mod history; use history::ActionHistory; pub use input::EditorInputScheme; -use crate::map::MapObjectKind; +use crate::{editor::windows::CreateTilesetWindow, map::MapObjectKind, Resources}; use macroquad::{ experimental::scene::{Node, RefMut}, prelude::*, }; -use self::windows::CreateLayerResult; +use self::windows::{CreateLayerResult, CreateTilesetResult}; use crate::resources::MapResource; @@ -34,6 +34,7 @@ pub struct Editor { history: ActionHistory, create_layer_window: Option, + create_tileset_window: Option, } /// Used to interface with macroquad. Necessary because using `node: RefMut` really limits @@ -54,6 +55,7 @@ impl Editor { ctx: EditorData::new(map_resource), history: ActionHistory::new(), create_layer_window: None, + create_tileset_window: None, } } @@ -68,6 +70,9 @@ impl Editor { UiAction::Redo => self.history.redo(&mut self.ctx.map_resource.map).unwrap(), UiAction::SelectTool(tool) => self.ctx.selected_tool = tool, UiAction::OpenCreateLayerWindow => self.create_layer_window = Some(Default::default()), + UiAction::OpenCreateTilesetWindow => { + self.create_tileset_window = Some(CreateTilesetWindow::new()) + } UiAction::CreateLayer { id, kind, @@ -79,6 +84,12 @@ impl Editor { .apply(action, &mut self.ctx.map_resource.map) .unwrap(); } + UiAction::CreateTileset { id, texture_id } => { + let action = actions::CreateTileset::new(id, texture_id); + self.history + .apply(action, &mut self.ctx.map_resource.map) + .unwrap(); + } UiAction::DeleteLayer(id) => { let action = actions::DeleteLayer::new(id); self.history @@ -132,6 +143,25 @@ impl Editor { } } } + + if let Some(window) = &mut self.create_tileset_window { + match window.ui(egui_ctx, &self.ctx.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; + } + } + } } } diff --git a/src/editor/windows/create_tileset.rs b/src/editor/windows/create_tileset.rs new file mode 100644 index 0000000000..f1324df668 --- /dev/null +++ b/src/editor/windows/create_tileset.rs @@ -0,0 +1,76 @@ +use std::ops::ControlFlow; + +use macroquad::prelude::collections::storage; + +use crate::{ + map::{Map, MapLayerKind}, + resources::TextureKind, + Resources, +}; + +pub struct CreateTilesetWindow { + tileset_name: String, + texture_options: Vec, + texture_idx: usize, +} +pub enum CreateTilesetResult { + Create { + tileset_name: String, + texture: String, + }, + Close, +} + +impl CreateTilesetWindow { + pub fn new() -> 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 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/mod.rs b/src/editor/windows/mod.rs index b9a5f6dcc2..ae2b93bc94 100644 --- a/src/editor/windows/mod.rs +++ b/src/editor/windows/mod.rs @@ -1,2 +1,5 @@ -pub mod create_layer; +mod create_layer; pub use create_layer::*; + +mod create_tileset; +pub use create_tileset::*; From bf2e5aa819b012dce0e72780f2f06ef2a774a667 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 3 Apr 2022 15:20:48 +0200 Subject: [PATCH 11/81] More tileset progress; Draw selected tileset --- src/editor/data.rs | 64 ++++++++++++++++++++++++++++++++++++++-------- src/editor/mod.rs | 12 ++++++++- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/editor/data.rs b/src/editor/data.rs index 993697e29d..492b0106db 100644 --- a/src/editor/data.rs +++ b/src/editor/data.rs @@ -1,4 +1,10 @@ -use crate::{map::MapLayerKind, resources::MapResource}; +use macroquad::prelude::collections::storage; + +use crate::{ + map::MapLayerKind, + resources::{MapResource, TextureResource}, + Resources, +}; use super::actions::{UiAction, UiActionExt}; @@ -199,16 +205,9 @@ impl EditorData { fn draw_tileset_info(&self, ui: &mut egui::Ui) -> Option { ui.heading("Tilesets"); - let mut action = self.draw_tileset_list(ui); - ui.horizontal(|ui| { - if ui.button("+").clicked() { - action.then_do_some(UiAction::OpenCreateTilesetWindow); - } - ui.button("-"); - ui.button("Edit"); - }); - - action + self.draw_tileset_list(ui) // Draw tileset list + .then(self.draw_tileset_utils(ui)) // Draw tileset utils + .then(self.draw_tileset_image(ui)) // Draw tileset image } fn draw_tileset_list(&self, ui: &mut egui::Ui) -> Option { @@ -226,4 +225,47 @@ impl EditorData { action } + + fn draw_tileset_utils(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + + ui.horizontal(|ui| { + if ui.button("+").clicked() { + action.then_do_some(UiAction::OpenCreateTilesetWindow); + } + ui.add_enabled_ui(self.selected_tileset.is_some(), |ui| { + if ui.button("-").clicked() { + action.then_do_some(UiAction::DeleteTileset( + self.selected_tileset.as_ref().unwrap().clone(), + )); + } + ui.button("Edit").on_hover_text("Does not do anything yet"); + }) + }); + + action + } + + fn draw_tileset_image(&self, ui: &mut egui::Ui) -> Option { + if let Some(tileset) = self + .selected_tileset + .as_ref() + .and_then(|selected_tileset| self.map_resource.map.tilesets.get(selected_tileset)) + { + let tileset_texture: &TextureResource = + &storage::get::().textures[&tileset.texture_id]; + let texture_size = tileset_texture.meta.size; + + ui.image( + egui::TextureId::User( + tileset_texture + .texture + .raw_miniquad_texture_handle() + .gl_internal_id() as u64, + ), + egui::Vec2::new(texture_size.x, texture_size.y), + ); + } + None + } } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 05d17309ee..9d70e5f337 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -60,7 +60,7 @@ impl Editor { } pub fn apply_action(&mut self, action: UiAction) { - dbg!("Applying action:", &action); + dbg!(&action); match action { UiAction::Batch(batch) => batch @@ -97,6 +97,13 @@ impl Editor { .unwrap(); self.ctx.selected_layer = None; } + UiAction::DeleteTileset(id) => { + let action = actions::DeleteTileset::new(id); + self.history + .apply(action, &mut self.ctx.map_resource.map) + .unwrap(); + self.ctx.selected_tileset = None; + } UiAction::UpdateLayer { id, is_visible } => { let action = actions::UpdateLayer::new(id, is_visible); self.history @@ -112,6 +119,9 @@ impl Editor { UiAction::SelectLayer(id) => { self.ctx.selected_layer = Some(id); } + UiAction::SelectTileset(id) => { + self.ctx.selected_tileset = Some(id); + } _ => todo!(), } From 04a249999ac7c20fa32bf5739dd004f0c88fda7c Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 3 Apr 2022 15:56:10 +0200 Subject: [PATCH 12/81] Partial commit --- src/editor/data.rs | 33 ++++++++++++++++++++++----------- src/editor/mod.rs | 15 --------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/editor/data.rs b/src/editor/data.rs index 492b0106db..d3d70606e5 100644 --- a/src/editor/data.rs +++ b/src/editor/data.rs @@ -2,7 +2,7 @@ use macroquad::prelude::collections::storage; use crate::{ map::MapLayerKind, - resources::{MapResource, TextureResource}, + resources::{MapResource, TextureResource, TextureKind}, Resources, }; @@ -11,7 +11,7 @@ use super::actions::{UiAction, UiActionExt}; #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { Cursor, - TilePlacer, + TilePlacer { tile_id: u32 }, ObjectPlacer, SpawnPointPlacer, Eraser, @@ -254,17 +254,28 @@ impl EditorData { { let tileset_texture: &TextureResource = &storage::get::().textures[&tileset.texture_id]; + let texture_id = egui::TextureId::User( + tileset_texture + .texture + .raw_miniquad_texture_handle() + .gl_internal_id() as u64, + ); let texture_size = tileset_texture.meta.size; + let tile_size = tileset.tile_size - ui.image( - egui::TextureId::User( - tileset_texture - .texture - .raw_miniquad_texture_handle() - .gl_internal_id() as u64, - ), - egui::Vec2::new(texture_size.x, texture_size.y), - ); + 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 image_response.clicked() { + let mouse_pos = ui.input().pointer.interact_pos().unwrap() - image_bounds.min; + } + + if let EditorTool::TilePlacer { tile_id } = self.selected_tool { + let painter = ui.painter_at(image_bounds); + } } None } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 9d70e5f337..8d6c4b4e03 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -185,18 +185,3 @@ impl Node for EditorNode { egui_macroquad::draw(); } } - -#[derive(Debug, Clone)] -enum DraggedObject { - MapObject { - id: String, - kind: MapObjectKind, - index: usize, - layer_id: String, - click_offset: Vec2, - }, - SpawnPoint { - index: usize, - click_offset: Vec2, - }, -} From cb70e5050f63d5632cffdcc5b948a2eeb175673d Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 3 Apr 2022 18:13:51 +0200 Subject: [PATCH 13/81] Add tile selection --- src/editor/data.rs | 115 +++++++++++++++++++++++++++++++-------------- src/editor/mod.rs | 14 +++++- 2 files changed, 92 insertions(+), 37 deletions(-) diff --git a/src/editor/data.rs b/src/editor/data.rs index d3d70606e5..4b9cea5ced 100644 --- a/src/editor/data.rs +++ b/src/editor/data.rs @@ -2,7 +2,7 @@ use macroquad::prelude::collections::storage; use crate::{ map::MapLayerKind, - resources::{MapResource, TextureResource, TextureKind}, + resources::{MapResource, TextureKind, TextureResource}, Resources, }; @@ -11,17 +11,22 @@ use super::actions::{UiAction, UiActionExt}; #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { Cursor, - TilePlacer { tile_id: u32 }, + TilePlacer, ObjectPlacer, SpawnPointPlacer, Eraser, } +pub struct TileSelection { + pub tileset: String, + pub tile_id: u32, +} + pub struct EditorData { pub selected_tool: EditorTool, pub map_resource: MapResource, pub selected_layer: Option, - pub selected_tileset: Option, + pub selected_tile: Option, } impl EditorData { @@ -30,7 +35,7 @@ impl EditorData { map_resource, selected_tool: EditorTool::Cursor, selected_layer: None, - selected_tileset: None, + selected_tile: None, } } @@ -215,7 +220,11 @@ impl EditorData { ui.group(|ui| { for (tileset_name, _tileset) in self.map_resource.map.tilesets.iter() { - let is_selected = self.selected_tileset.as_ref() == Some(tileset_name); + let is_selected = self + .selected_tile + .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())); @@ -233,10 +242,10 @@ impl EditorData { if ui.button("+").clicked() { action.then_do_some(UiAction::OpenCreateTilesetWindow); } - ui.add_enabled_ui(self.selected_tileset.is_some(), |ui| { + ui.add_enabled_ui(self.selected_tile.is_some(), |ui| { if ui.button("-").clicked() { action.then_do_some(UiAction::DeleteTileset( - self.selected_tileset.as_ref().unwrap().clone(), + self.selected_tile.as_ref().unwrap().tileset.clone(), )); } ui.button("Edit").on_hover_text("Does not do anything yet"); @@ -247,36 +256,72 @@ impl EditorData { } fn draw_tileset_image(&self, ui: &mut egui::Ui) -> Option { - if let Some(tileset) = self - .selected_tileset - .as_ref() - .and_then(|selected_tileset| self.map_resource.map.tilesets.get(selected_tileset)) - { - let tileset_texture: &TextureResource = - &storage::get::().textures[&tileset.texture_id]; - let texture_id = egui::TextureId::User( - tileset_texture - .texture - .raw_miniquad_texture_handle() - .gl_internal_id() as u64, - ); - let texture_size = tileset_texture.meta.size; - let tile_size = tileset.tile_size - - 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 image_response.clicked() { - let mouse_pos = ui.input().pointer.interact_pos().unwrap() - image_bounds.min; - } + let mut action = None; + + if let Some(selection) = &self.selected_tile { + 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 = egui::TextureId::User( + tileset_texture + .texture + .raw_miniquad_texture_handle() + .gl_internal_id() as u64, + ); + 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 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; - if let EditorTool::TilePlacer { tile_id } = self.selected_tool { - let painter = ui.painter_at(image_bounds); + action.then_do_some(UiAction::SelectTile { + id: tile_id, + tileset_id: selection.tileset.clone(), + }); + } + } + + 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), + ); + } } } - None + + action } } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 8d6c4b4e03..e9bc3e5ff1 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -102,7 +102,7 @@ impl Editor { self.history .apply(action, &mut self.ctx.map_resource.map) .unwrap(); - self.ctx.selected_tileset = None; + self.ctx.selected_tile = None; } UiAction::UpdateLayer { id, is_visible } => { let action = actions::UpdateLayer::new(id, is_visible); @@ -120,7 +120,17 @@ impl Editor { self.ctx.selected_layer = Some(id); } UiAction::SelectTileset(id) => { - self.ctx.selected_tileset = Some(id); + self.ctx.selected_tile = Some(TileSelection { + tileset: id, + tile_id: 0, + }); + } + UiAction::SelectTile { id, tileset_id } => { + self.ctx.selected_tile = Some(TileSelection { + tileset: tileset_id, + tile_id: id, + }); + self.ctx.selected_tool = EditorTool::TilePlacer; } _ => todo!(), From 2d5e8e61a4d15682bbd3a611d8751be964210896 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 3 Apr 2022 19:15:06 +0200 Subject: [PATCH 14/81] Add temp plot for showing level --- src/editor/data.rs | 70 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/src/editor/data.rs b/src/editor/data.rs index 4b9cea5ced..6310d6ba1c 100644 --- a/src/editor/data.rs +++ b/src/editor/data.rs @@ -50,11 +50,73 @@ impl EditorData { /// UI-related functions impl EditorData { pub fn ui(&self, egui_ctx: &egui::Context) -> Option { - // Draw toolbar - let mut action = self.draw_toolbar(egui_ctx); - // Draw side panel - action.then_do(self.draw_side_panel(egui_ctx)); + self.draw_toolbar(egui_ctx) + .then(self.draw_side_panel(egui_ctx)) + .then(self.draw_level(egui_ctx)) + } + fn draw_level(&self, egui_ctx: &egui::Context) -> Option { + let mut action = None; + egui::CentralPanel::default().show(egui_ctx, |ui| { + egui::plot::Plot::new("level_plot") + .data_aspect(1.0) + .y_axis_formatter(|y, _| format!("{}", -y)) + .allow_boxed_zoom(false) + .show(ui, |plot_ui| { + for (layer_name, layer) in self.map_resource.map.layers.iter() { + match layer.kind { + MapLayerKind::TileLayer => { + for (tile_idx, tile) in layer.tiles.iter().enumerate() { + if let Some(tile) = tile { + let tileset = + &self.map_resource.map.tilesets[&tile.tileset_id]; + let x = + tile_idx % self.map_resource.map.grid_size.x as usize; + let y = + tile_idx / self.map_resource.map.grid_size.x as usize; + let tileset_texture: &TextureResource = + &storage::get::().textures[&tile.texture_id]; + let texture_id = egui::TextureId::User( + tileset_texture + .texture + .raw_miniquad_texture_handle() + .gl_internal_id() + as u64, + ); + let tileset_uv_tile_size = egui::Vec2 { + x: 1. / tileset.grid_size.x as f32, + y: 1. / tileset.grid_size.y as f32, + }; + let tileset_x = (tile.tile_id % tileset.grid_size.x) as f32 + * tileset_uv_tile_size.x; + let tileset_y = (tile.tile_id / tileset.grid_size.x) as f32 + * tileset_uv_tile_size.y; + + let tile_image = egui::plot::PlotImage::new( + texture_id, + egui::plot::Value::new( + x as f32 + 0.5, + -(y as f32) - 0.5, + ), + egui::Vec2::new(1., 1.), + ) + .uv(egui::Rect::from_min_size( + egui::Pos2 { + x: tileset_x, + y: tileset_y, + }, + tileset_uv_tile_size, + )); + + plot_ui.image(tile_image) + } + } + } + MapLayerKind::ObjectLayer => (), + } + } + }) + }); action } From 484b02aade2e7eb8685d790bc8b1e281a3fb5d00 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 8 Apr 2022 11:53:32 +0200 Subject: [PATCH 15/81] Add temp custom painter --- src/editor/data.rs | 138 +++++++++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 56 deletions(-) diff --git a/src/editor/data.rs b/src/editor/data.rs index 6310d6ba1c..4a110cb1a7 100644 --- a/src/editor/data.rs +++ b/src/editor/data.rs @@ -8,6 +8,26 @@ use crate::{ use super::actions::{UiAction, UiActionExt}; +trait RectMap { + fn map(self, from: egui::Rect, to: egui::Rect) -> egui::Rect; +} + +impl RectMap for egui::Rect { + fn map(self, from: egui::Rect, to: egui::Rect) -> egui::Rect { + let min_origin_offset = from.min.to_vec2(); + let max_origin_offset = from.max.to_vec2(); + let min_target_offset = to.min.to_vec2(); + let max_target_offset = to.max.to_vec2(); + + let min = + (self.min - min_origin_offset).to_vec2() / from.size() * to.size() + min_target_offset; + let max = + (self.max - max_origin_offset).to_vec2() / from.size() * to.size() + max_target_offset; + + egui::Rect::from_min_max(min.to_pos2(), max.to_pos2()) + } +} + #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { Cursor, @@ -27,6 +47,8 @@ pub struct EditorData { pub map_resource: MapResource, pub selected_layer: Option, pub selected_tile: Option, + pub view_top_left: egui::Pos2, + pub view_scale: f32, } impl EditorData { @@ -36,6 +58,8 @@ impl EditorData { selected_tool: EditorTool::Cursor, selected_layer: None, selected_tile: None, + view_top_left: egui::Pos2::ZERO, + view_scale: 32., } } @@ -57,66 +81,68 @@ impl EditorData { fn draw_level(&self, egui_ctx: &egui::Context) -> Option { let mut action = None; - egui::CentralPanel::default().show(egui_ctx, |ui| { - egui::plot::Plot::new("level_plot") - .data_aspect(1.0) - .y_axis_formatter(|y, _| format!("{}", -y)) - .allow_boxed_zoom(false) - .show(ui, |plot_ui| { - for (layer_name, layer) in self.map_resource.map.layers.iter() { - match layer.kind { - MapLayerKind::TileLayer => { - for (tile_idx, tile) in layer.tiles.iter().enumerate() { - if let Some(tile) = tile { - let tileset = - &self.map_resource.map.tilesets[&tile.tileset_id]; - let x = - tile_idx % self.map_resource.map.grid_size.x as usize; - let y = - tile_idx / self.map_resource.map.grid_size.x as usize; - let tileset_texture: &TextureResource = - &storage::get::().textures[&tile.texture_id]; - let texture_id = egui::TextureId::User( - tileset_texture - .texture - .raw_miniquad_texture_handle() - .gl_internal_id() - as u64, - ); - let tileset_uv_tile_size = egui::Vec2 { - x: 1. / tileset.grid_size.x as f32, - y: 1. / tileset.grid_size.y as f32, - }; - let tileset_x = (tile.tile_id % tileset.grid_size.x) as f32 - * tileset_uv_tile_size.x; - let tileset_y = (tile.tile_id / tileset.grid_size.x) as f32 - * tileset_uv_tile_size.y; - - let tile_image = egui::plot::PlotImage::new( - texture_id, - egui::plot::Value::new( - x as f32 + 0.5, - -(y as f32) - 0.5, - ), - egui::Vec2::new(1., 1.), - ) - .uv(egui::Rect::from_min_size( - egui::Pos2 { - x: tileset_x, - y: tileset_y, - }, - tileset_uv_tile_size, - )); - - plot_ui.image(tile_image) - } + egui::CentralPanel::default() + .frame( + egui::Frame::dark_canvas(&egui_ctx.style()) + .margin(egui::style::Margin::symmetric(0., 0.)), + ) + .show(egui_ctx, |ui| { + let level_view = egui::Rect::from_min_size( + self.view_top_left, + ui.available_size() / self.view_scale, + ); + let (res, painter) = + ui.allocate_painter(ui.available_size(), egui::Sense::click_and_drag()); + let target_view = res.rect; + for (layer_name, layer) in self.map_resource.map.layers.iter() { + match layer.kind { + MapLayerKind::TileLayer => { + for (tile_idx, tile) in layer.tiles.iter().enumerate() { + if let Some(tile) = tile { + let tileset = &self.map_resource.map.tilesets[&tile.tileset_id]; + let x = tile_idx % self.map_resource.map.grid_size.x as usize; + let y = tile_idx / self.map_resource.map.grid_size.x as usize; + let tileset_texture: &TextureResource = + &storage::get::().textures[&tile.texture_id]; + let texture_id = egui::TextureId::User( + tileset_texture + .texture + .raw_miniquad_texture_handle() + .gl_internal_id() + as u64, + ); + let tileset_uv_tile_size = egui::Vec2 { + x: 1. / tileset.grid_size.x as f32, + y: 1. / tileset.grid_size.y as f32, + }; + let tileset_x = (tile.tile_id % tileset.grid_size.x) as f32 + * tileset_uv_tile_size.x; + let tileset_y = (tile.tile_id / tileset.grid_size.x) as f32 + * tileset_uv_tile_size.y; + let target_rect = egui::Rect::from_min_size( + egui::pos2(x as f32, y as f32), + egui::vec2(1., 1.), + ) + .map(level_view, target_view); + let uv = egui::Rect::from_min_size( + egui::Pos2 { + x: tileset_x, + y: tileset_y, + }, + tileset_uv_tile_size, + ); + + let mut mesh = egui::Mesh::with_texture(texture_id); + mesh.add_rect_with_uv(target_rect, uv, egui::Color32::WHITE); + + painter.add(egui::Shape::mesh(mesh)); } } - MapLayerKind::ObjectLayer => (), } + MapLayerKind::ObjectLayer => (), } - }) - }); + } + }); action } From 01c1d77533bd6c31ca956c0b32e7ca3a3db90983 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 8 Apr 2022 12:30:10 +0200 Subject: [PATCH 16/81] Draw level + grid with macroquad --- src/editor/actions.rs | 2 +- src/editor/mod.rs | 148 ++++++++++++++++++++------- src/editor/{data.rs => state.rs} | 84 ++------------- src/editor/windows/create_tileset.rs | 6 +- 4 files changed, 124 insertions(+), 116 deletions(-) rename src/editor/{data.rs => state.rs} (75%) diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 3c5cc42385..dd709672c8 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -5,7 +5,7 @@ use core::error::Result; use crate::map::{Map, MapLayerKind, MapTileset}; use crate::map::{MapBackgroundLayer, MapObjectKind}; -use super::EditorTool; +use super::state::EditorTool; /// These are all the actions available for the GUI and other sub-systems of the editor. /// If you need to perform multiple actions in one call, use the `Batch` variant. diff --git a/src/editor/mod.rs b/src/editor/mod.rs index e9bc3e5ff1..2f286e5b37 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,14 +1,14 @@ use std::ops::ControlFlow; mod camera; -pub mod data; +pub mod state; mod windows; -pub use data::*; +pub use state::*; pub use camera::EditorCamera; mod actions; -use actions::{UiAction, UiActionExt}; +use actions::UiAction; mod input; @@ -17,19 +17,16 @@ mod history; use history::ActionHistory; pub use input::EditorInputScheme; -use crate::{editor::windows::CreateTilesetWindow, map::MapObjectKind, Resources}; +use crate::{editor::windows::CreateTilesetWindow, map::Map}; -use macroquad::{ - experimental::scene::{Node, RefMut}, - prelude::*, -}; +use macroquad::experimental::scene::{Node, RefMut}; use self::windows::{CreateLayerResult, CreateTilesetResult}; use crate::resources::MapResource; pub struct Editor { - ctx: EditorData, + state: EditorState, history: ActionHistory, @@ -49,10 +46,39 @@ impl EditorNode { } } +impl Node for EditorNode { + fn draw(mut node: RefMut) + where + Self: Sized, + { + node.editor.draw(); + + egui_macroquad::ui(|egui_ctx| node.editor.ui(egui_ctx)); + + egui_macroquad::draw(); + } +} + 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 DOUBLE_CLICK_THRESHOLD: f32 = 0.25; + + const MESSAGE_TIMEOUT: f32 = 2.5; + pub fn new(map_resource: MapResource) -> Self { Self { - ctx: EditorData::new(map_resource), + state: EditorState::new(map_resource), history: ActionHistory::new(), create_layer_window: None, create_tileset_window: None, @@ -66,9 +92,9 @@ impl Editor { UiAction::Batch(batch) => batch .into_iter() .for_each(|action| self.apply_action(action)), - UiAction::Undo => self.history.undo(&mut self.ctx.map_resource.map).unwrap(), - UiAction::Redo => self.history.redo(&mut self.ctx.map_resource.map).unwrap(), - UiAction::SelectTool(tool) => self.ctx.selected_tool = tool, + UiAction::Undo => self.history.undo(&mut self.state.map_resource.map).unwrap(), + UiAction::Redo => self.history.redo(&mut self.state.map_resource.map).unwrap(), + UiAction::SelectTool(tool) => self.state.selected_tool = tool, UiAction::OpenCreateLayerWindow => self.create_layer_window = Some(Default::default()), UiAction::OpenCreateTilesetWindow => { self.create_tileset_window = Some(CreateTilesetWindow::new()) @@ -81,56 +107,56 @@ impl Editor { } => { let action = actions::CreateLayer::new(id, kind, has_collision, index); self.history - .apply(action, &mut self.ctx.map_resource.map) + .apply(action, &mut self.state.map_resource.map) .unwrap(); } UiAction::CreateTileset { id, texture_id } => { let action = actions::CreateTileset::new(id, texture_id); self.history - .apply(action, &mut self.ctx.map_resource.map) + .apply(action, &mut self.state.map_resource.map) .unwrap(); } UiAction::DeleteLayer(id) => { let action = actions::DeleteLayer::new(id); self.history - .apply(action, &mut self.ctx.map_resource.map) + .apply(action, &mut self.state.map_resource.map) .unwrap(); - self.ctx.selected_layer = None; + self.state.selected_layer = None; } UiAction::DeleteTileset(id) => { let action = actions::DeleteTileset::new(id); self.history - .apply(action, &mut self.ctx.map_resource.map) + .apply(action, &mut self.state.map_resource.map) .unwrap(); - self.ctx.selected_tile = None; + self.state.selected_tile = None; } UiAction::UpdateLayer { id, is_visible } => { let action = actions::UpdateLayer::new(id, is_visible); self.history - .apply(action, &mut self.ctx.map_resource.map) + .apply(action, &mut self.state.map_resource.map) .unwrap(); } UiAction::SetLayerDrawOrderIndex { id, index } => { let action = actions::SetLayerDrawOrderIndex::new(id, index); self.history - .apply(action, &mut self.ctx.map_resource.map) + .apply(action, &mut self.state.map_resource.map) .unwrap(); } UiAction::SelectLayer(id) => { - self.ctx.selected_layer = Some(id); + self.state.selected_layer = Some(id); } UiAction::SelectTileset(id) => { - self.ctx.selected_tile = Some(TileSelection { + self.state.selected_tile = Some(TileSelection { tileset: id, tile_id: 0, }); } UiAction::SelectTile { id, tileset_id } => { - self.ctx.selected_tile = Some(TileSelection { + self.state.selected_tile = Some(TileSelection { tileset: tileset_id, tile_id: id, }); - self.ctx.selected_tool = EditorTool::TilePlacer; + self.state.selected_tool = EditorTool::TilePlacer; } _ => todo!(), @@ -138,12 +164,12 @@ impl Editor { } pub fn ui(&mut self, egui_ctx: &egui::Context) { - if let Some(action) = self.ctx.ui(egui_ctx) { + if let Some(action) = self.state.ui(egui_ctx) { self.apply_action(action); } if let Some(window) = &mut self.create_layer_window { - match window.ui(egui_ctx, &self.ctx.map_resource.map) { + match window.ui(egui_ctx, &self.state.map_resource.map) { ControlFlow::Continue(()) => (), ControlFlow::Break(CreateLayerResult::Create { has_collision, @@ -165,7 +191,7 @@ impl Editor { } if let Some(window) = &mut self.create_tileset_window { - match window.ui(egui_ctx, &self.ctx.map_resource.map) { + match window.ui(egui_ctx, &self.state.map_resource.map) { ControlFlow::Continue(()) => (), ControlFlow::Break(CreateTilesetResult::Create { tileset_name, @@ -183,15 +209,67 @@ impl Editor { } } } + + pub fn draw(&self) { + let map = &self.state.map_resource.map; + { + map.draw_background(None, !self.state.is_parallax_enabled); + map.draw(None, false); + } + + if self.state.should_draw_grid { + self::draw_grid(map); + } + } } -impl Node for EditorNode { - fn draw(mut node: RefMut) - where - Self: Sized, - { - egui_macroquad::ui(|egui_ctx| node.editor.ui(egui_ctx)); +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, + }; - egui_macroquad::draw(); + 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/data.rs b/src/editor/state.rs similarity index 75% rename from src/editor/data.rs rename to src/editor/state.rs index 4a110cb1a7..76fc827a13 100644 --- a/src/editor/data.rs +++ b/src/editor/state.rs @@ -42,24 +42,26 @@ pub struct TileSelection { pub tile_id: u32, } -pub struct EditorData { +/// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the +/// editor. +pub struct EditorState { pub selected_tool: EditorTool, pub map_resource: MapResource, pub selected_layer: Option, pub selected_tile: Option, - pub view_top_left: egui::Pos2, - pub view_scale: f32, + pub is_parallax_enabled: bool, + pub should_draw_grid: bool, } -impl EditorData { +impl EditorState { pub fn new(map_resource: MapResource) -> Self { Self { map_resource, selected_tool: EditorTool::Cursor, selected_layer: None, selected_tile: None, - view_top_left: egui::Pos2::ZERO, - view_scale: 32., + is_parallax_enabled: true, + should_draw_grid: true, } } @@ -72,78 +74,10 @@ impl EditorData { } /// UI-related functions -impl EditorData { +impl EditorState { pub fn ui(&self, egui_ctx: &egui::Context) -> Option { self.draw_toolbar(egui_ctx) .then(self.draw_side_panel(egui_ctx)) - .then(self.draw_level(egui_ctx)) - } - - fn draw_level(&self, egui_ctx: &egui::Context) -> Option { - let mut action = None; - egui::CentralPanel::default() - .frame( - egui::Frame::dark_canvas(&egui_ctx.style()) - .margin(egui::style::Margin::symmetric(0., 0.)), - ) - .show(egui_ctx, |ui| { - let level_view = egui::Rect::from_min_size( - self.view_top_left, - ui.available_size() / self.view_scale, - ); - let (res, painter) = - ui.allocate_painter(ui.available_size(), egui::Sense::click_and_drag()); - let target_view = res.rect; - for (layer_name, layer) in self.map_resource.map.layers.iter() { - match layer.kind { - MapLayerKind::TileLayer => { - for (tile_idx, tile) in layer.tiles.iter().enumerate() { - if let Some(tile) = tile { - let tileset = &self.map_resource.map.tilesets[&tile.tileset_id]; - let x = tile_idx % self.map_resource.map.grid_size.x as usize; - let y = tile_idx / self.map_resource.map.grid_size.x as usize; - let tileset_texture: &TextureResource = - &storage::get::().textures[&tile.texture_id]; - let texture_id = egui::TextureId::User( - tileset_texture - .texture - .raw_miniquad_texture_handle() - .gl_internal_id() - as u64, - ); - let tileset_uv_tile_size = egui::Vec2 { - x: 1. / tileset.grid_size.x as f32, - y: 1. / tileset.grid_size.y as f32, - }; - let tileset_x = (tile.tile_id % tileset.grid_size.x) as f32 - * tileset_uv_tile_size.x; - let tileset_y = (tile.tile_id / tileset.grid_size.x) as f32 - * tileset_uv_tile_size.y; - let target_rect = egui::Rect::from_min_size( - egui::pos2(x as f32, y as f32), - egui::vec2(1., 1.), - ) - .map(level_view, target_view); - let uv = egui::Rect::from_min_size( - egui::Pos2 { - x: tileset_x, - y: tileset_y, - }, - tileset_uv_tile_size, - ); - - let mut mesh = egui::Mesh::with_texture(texture_id); - mesh.add_rect_with_uv(target_rect, uv, egui::Color32::WHITE); - - painter.add(egui::Shape::mesh(mesh)); - } - } - } - MapLayerKind::ObjectLayer => (), - } - } - }); - action } fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { diff --git a/src/editor/windows/create_tileset.rs b/src/editor/windows/create_tileset.rs index f1324df668..0bef0e5656 100644 --- a/src/editor/windows/create_tileset.rs +++ b/src/editor/windows/create_tileset.rs @@ -2,11 +2,7 @@ use std::ops::ControlFlow; use macroquad::prelude::collections::storage; -use crate::{ - map::{Map, MapLayerKind}, - resources::TextureKind, - Resources, -}; +use crate::{map::Map, resources::TextureKind, Resources}; pub struct CreateTilesetWindow { tileset_name: String, From fa3636de58651912955672ba114a101935b115c0 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 8 Apr 2022 20:52:27 +0200 Subject: [PATCH 17/81] Draw level in render target --- src/editor/input.rs | 148 ++++++++++++++++++++++---------------------- src/editor/mod.rs | 57 +++++++++++++++-- src/editor/state.rs | 36 ++++++++++- src/main.rs | 4 +- 4 files changed, 162 insertions(+), 83 deletions(-) diff --git a/src/editor/input.rs b/src/editor/input.rs index 8f986a16ef..d01b94c5d0 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,100 @@ 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; - } +impl EditorInputScheme { + pub fn collect_input(&self) -> EditorInput { + let mut input = EditorInput::default(); + + match self { + 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; + 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); + 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::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 { - 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 2f286e5b37..a1b06db350 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -19,7 +19,10 @@ pub use input::EditorInputScheme; use crate::{editor::windows::CreateTilesetWindow, map::Map}; -use macroquad::experimental::scene::{Node, RefMut}; +use macroquad::{ + experimental::scene::{Node, RefMut}, + prelude::{collections::storage, render_target, RenderTarget}, +}; use self::windows::{CreateLayerResult, CreateTilesetResult}; @@ -28,32 +31,70 @@ use crate::resources::MapResource; pub struct Editor { state: EditorState, + camera_pos: macroquad::prelude::Vec2, + camera_scale: f32, + history: ActionHistory, create_layer_window: Option, create_tileset_window: Option, + + level_render_target: RenderTarget, } /// 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, + input_scheme: EditorInputScheme, } impl EditorNode { - pub fn new(editor: Editor) -> Self { - Self { editor } + const CAMERA_PAN_SPEED: f32 = 5.0; + + pub fn new(editor: Editor, input_scheme: EditorInputScheme) -> Self { + Self { + editor, + accept_mouse_input: true, + input_scheme, + } } } impl Node for EditorNode { + fn fixed_update(mut node: RefMut) { + use macroquad::prelude::*; + + let input = node.input_scheme.collect_input(); + + node.editor.camera_pos += input.camera_move_direction * Self::CAMERA_PAN_SPEED; + + let camera = Some(Camera2D { + offset: vec2(0.0, 0.0), + target: node.editor.camera_pos, + zoom: vec2( + node.editor.camera_scale / node.editor.level_render_target.texture.width(), + node.editor.camera_scale / node.editor.level_render_target.texture.height(), + ) * 2.0, + render_target: Some(node.editor.level_render_target), + ..Camera2D::default() + }); + + scene::set_camera(0, camera); + } + fn draw(mut node: RefMut) where Self: Sized, { node.editor.draw(); - egui_macroquad::ui(|egui_ctx| node.editor.ui(egui_ctx)); + egui_macroquad::ui(|egui_ctx| { + node.editor.ui(egui_ctx); + node.accept_mouse_input = !egui_ctx.wants_pointer_input() + }); egui_macroquad::draw(); } @@ -62,7 +103,6 @@ impl Node for EditorNode { 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; @@ -82,6 +122,9 @@ impl Editor { history: ActionHistory::new(), create_layer_window: None, create_tileset_window: None, + camera_pos: Default::default(), + camera_scale: 1., + level_render_target: render_target(1, 1), } } @@ -164,7 +207,7 @@ impl Editor { } pub fn ui(&mut self, egui_ctx: &egui::Context) { - if let Some(action) = self.state.ui(egui_ctx) { + if let Some(action) = self.state.ui(egui_ctx, &mut self.level_render_target) { self.apply_action(action); } @@ -211,6 +254,8 @@ impl Editor { } pub fn draw(&self) { + use macroquad::prelude::*; + let map = &self.state.map_resource.map; { map.draw_background(None, !self.state.is_parallax_enabled); diff --git a/src/editor/state.rs b/src/editor/state.rs index 76fc827a13..168b2b9211 100644 --- a/src/editor/state.rs +++ b/src/editor/state.rs @@ -1,4 +1,4 @@ -use macroquad::prelude::collections::storage; +use macroquad::prelude::{collections::storage, render_target, RenderTarget}; use crate::{ map::MapLayerKind, @@ -75,9 +75,41 @@ impl EditorState { /// UI-related functions impl EditorState { - pub fn ui(&self, egui_ctx: &egui::Context) -> Option { + pub fn ui( + &self, + egui_ctx: &egui::Context, + level_render_target: &mut RenderTarget, + ) -> Option { self.draw_toolbar(egui_ctx) .then(self.draw_side_panel(egui_ctx)) + .then(self.draw_level(egui_ctx, level_render_target)) + } + + fn draw_level( + &self, + egui_ctx: &egui::Context, + level_render_target: &mut RenderTarget, + ) -> Option { + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(egui_ctx, |ui| { + let (width, height) = (ui.available_width() as u32, ui.available_height() as u32); + let texture_id = egui::TextureId::User( + level_render_target + .texture + .raw_miniquad_texture_handle() + .gl_internal_id() as u64, + ); + ui.image(texture_id, ui.available_size()); + if width != level_render_target.texture.width() as u32 + || height != level_render_target.texture.height() as u32 + { + level_render_target.delete(); + *level_render_target = render_target(width, height); + dbg!("Remade level render target"); + } + }); + None } fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { diff --git a/src/main.rs b/src/main.rs index 7cd3d14664..31d8acc131 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,7 +117,7 @@ async fn init_game() -> Result { start_music("fish_tide"); } MainMenuResult::Editor { - input_scheme: _, + input_scheme, is_new_map, } => { let map_resource = if is_new_map { @@ -134,7 +134,7 @@ async fn init_game() -> Result { let position = map_resource.map.get_size() * 0.5; scene::add_node(EditorCamera::new(position)); - scene::add_node(EditorNode::new(Editor::new(map_resource))); + scene::add_node(EditorNode::new(Editor::new(map_resource), input_scheme)); } MainMenuResult::ReloadResources => { reload_resources(); From 6280f74b8918e83aeccbf214c94e3cc00f7201d9 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 12:26:24 +0200 Subject: [PATCH 18/81] Partial commit --- src/editor/mod.rs | 24 ++++++++++++++---------- src/editor/state.rs | 27 ++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index a1b06db350..0ad3321f56 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -31,8 +31,7 @@ use crate::resources::MapResource; pub struct Editor { state: EditorState, - camera_pos: macroquad::prelude::Vec2, - camera_scale: f32, + level_view: LevelView, history: ActionHistory, @@ -69,15 +68,15 @@ impl Node for EditorNode { let input = node.input_scheme.collect_input(); - node.editor.camera_pos += input.camera_move_direction * Self::CAMERA_PAN_SPEED; + node.editor.level_view.position += input.camera_move_direction * Self::CAMERA_PAN_SPEED; let camera = Some(Camera2D { offset: vec2(0.0, 0.0), - target: node.editor.camera_pos, + target: node.editor.level_view.position, zoom: vec2( - node.editor.camera_scale / node.editor.level_render_target.texture.width(), - node.editor.camera_scale / node.editor.level_render_target.texture.height(), - ) * 2.0, + node.editor.level_view.scale / node.editor.level_render_target.texture.width(), + node.editor.level_view.scale / node.editor.level_render_target.texture.height(), + ), render_target: Some(node.editor.level_render_target), ..Camera2D::default() }); @@ -122,8 +121,10 @@ impl Editor { history: ActionHistory::new(), create_layer_window: None, create_tileset_window: None, - camera_pos: Default::default(), - camera_scale: 1., + level_view: LevelView { + position: Default::default(), + scale: 1., + }, level_render_target: render_target(1, 1), } } @@ -207,7 +208,10 @@ impl Editor { } pub fn ui(&mut self, egui_ctx: &egui::Context) { - if let Some(action) = self.state.ui(egui_ctx, &mut self.level_render_target) { + if let Some(action) = + self.state + .ui(egui_ctx, &mut self.level_render_target, &self.level_view) + { self.apply_action(action); } diff --git a/src/editor/state.rs b/src/editor/state.rs index 168b2b9211..2f8316926d 100644 --- a/src/editor/state.rs +++ b/src/editor/state.rs @@ -73,34 +73,55 @@ impl EditorState { } } +// FIXME: This is very ugly, and shouldn't be passed into the editor state as parameter. Is there some +// better way to do this? +pub struct LevelView { + pub position: macroquad::prelude::Vec2, + pub scale: f32, +} + /// UI-related functions impl EditorState { pub fn ui( &self, egui_ctx: &egui::Context, level_render_target: &mut RenderTarget, + level_view: &LevelView, ) -> Option { self.draw_toolbar(egui_ctx) .then(self.draw_side_panel(egui_ctx)) - .then(self.draw_level(egui_ctx, level_render_target)) + .then(self.draw_level(egui_ctx, level_render_target, level_view)) } fn draw_level( &self, egui_ctx: &egui::Context, level_render_target: &mut RenderTarget, + level_view: &LevelView, ) -> Option { egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { - let (width, height) = (ui.available_width() as u32, ui.available_height() as u32); let texture_id = egui::TextureId::User( level_render_target .texture .raw_miniquad_texture_handle() .gl_internal_id() as u64, ); - ui.image(texture_id, ui.available_size()); + let image = + egui::Image::new(texture_id, ui.available_size()).sense(egui::Sense::click()); + let response = ui.add(image); + if response.clicked() { + let cursor_pos = ui.input().pointer.interact_pos().unwrap(); + let tile_size = egui::vec2( + self.map_resource.map.tile_size.x, + self.map_resource.map.tile_size.y, + ); + let tile_pos = + (cursor_pos - response.rect.min) / tile_size + level_view.position; + } + + let (width, height) = (ui.available_width() as u32, ui.available_height() as u32); if width != level_render_target.texture.width() as u32 || height != level_render_target.texture.height() as u32 { From dcd0f703dddf1c215c93ab17aac0e0161bfc0e7e Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 15:03:27 +0200 Subject: [PATCH 19/81] Implement tile placement and removal --- src/editor/mod.rs | 42 ++++++++++-- src/editor/state.rs | 162 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 183 insertions(+), 21 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 0ad3321f56..e0abf0a7f9 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -17,7 +17,10 @@ mod history; use history::ActionHistory; pub use input::EditorInputScheme; -use crate::{editor::windows::CreateTilesetWindow, map::Map}; +use crate::{ + editor::windows::CreateTilesetWindow, + map::{Map, MapLayerKind}, +}; use macroquad::{ experimental::scene::{Node, RefMut}, @@ -70,13 +73,18 @@ impl Node for EditorNode { node.editor.level_view.position += input.camera_move_direction * Self::CAMERA_PAN_SPEED; + let target_size = vec2( + node.editor.level_render_target.texture.width(), + node.editor.level_render_target.texture.height(), + ); + let zoom = vec2( + node.editor.level_view.scale / target_size.x, + node.editor.level_view.scale / target_size.y, + ) * 2.; let camera = Some(Camera2D { - offset: vec2(0.0, 0.0), + offset: vec2(-1., -1.), target: node.editor.level_view.position, - zoom: vec2( - node.editor.level_view.scale / node.editor.level_render_target.texture.width(), - node.editor.level_view.scale / node.editor.level_render_target.texture.height(), - ), + zoom, render_target: Some(node.editor.level_render_target), ..Camera2D::default() }); @@ -187,6 +195,11 @@ impl Editor { .unwrap(); } UiAction::SelectLayer(id) => { + if self.state.map_resource.map.layers[&id].kind == MapLayerKind::ObjectLayer + && self.state.selected_tool == EditorTool::TilePlacer + { + self.state.selected_tool = EditorTool::Cursor; + } self.state.selected_layer = Some(id); } UiAction::SelectTileset(id) => { @@ -202,6 +215,23 @@ impl Editor { }); self.state.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.state.map_resource.map) + .unwrap(); + } + UiAction::RemoveTile { coords, layer_id } => { + let action = actions::RemoveTile::new(layer_id, coords); + self.history + .apply(action, &mut self.state.map_resource.map) + .unwrap(); + } _ => todo!(), } diff --git a/src/editor/state.rs b/src/editor/state.rs index 2f8316926d..c2f6a6d47e 100644 --- a/src/editor/state.rs +++ b/src/editor/state.rs @@ -8,6 +8,10 @@ use crate::{ use super::actions::{UiAction, UiActionExt}; +fn macroquad_to_egui_vec2(v: macroquad::prelude::Vec2) -> egui::Vec2 { + egui::vec2(v.x, v.y) +} + trait RectMap { fn map(self, from: egui::Rect, to: egui::Rect) -> egui::Rect; } @@ -76,7 +80,9 @@ impl EditorState { // FIXME: This is very ugly, and shouldn't be passed into the editor state as parameter. Is there some // better way to do this? 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, } @@ -99,6 +105,8 @@ impl EditorState { level_render_target: &mut RenderTarget, level_view: &LevelView, ) -> Option { + let mut action = None; + egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { @@ -108,29 +116,153 @@ impl EditorState { .raw_miniquad_texture_handle() .gl_internal_id() as u64, ); - let image = - egui::Image::new(texture_id, ui.available_size()).sense(egui::Sense::click()); - let response = ui.add(image); - if response.clicked() { - let cursor_pos = ui.input().pointer.interact_pos().unwrap(); - let tile_size = egui::vec2( - self.map_resource.map.tile_size.x, - self.map_resource.map.tile_size.y, - ); - let tile_pos = - (cursor_pos - response.rect.min) / tile_size + level_view.position; - } + let level_img = egui::Image::new(texture_id, ui.available_size()) + .sense(egui::Sense::click_and_drag()); + let response = ui.add(level_img); + let cursor_position = if response.hovered() { + let map = &self.map_resource.map; + let tile_size = macroquad_to_egui_vec2(map.tile_size); + let level_view_pos = macroquad_to_egui_vec2(level_view.position); + let screen_to_world = |pos: egui::Pos2| -> egui::Pos2 { + pos - response.rect.min.to_vec2() + level_view_pos + }; + let world_to_screen = |pos: egui::Pos2| -> egui::Pos2 { + pos + response.rect.min.to_vec2() - level_view_pos + }; + + let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); + let cursor_px_pos = screen_to_world(cursor_screen_pos); + let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor(); + + if self.selected_tool == EditorTool::TilePlacer + && cursor_tile_pos.x >= 0. + && cursor_tile_pos.y >= 0. + { + match (&self.selected_tile, &self.selected_layer) { + (Some(selected_tile), Some(selected_layer)) => { + egui::containers::Area::new("selected tile overlay") + .order(egui::Order::Background) + .interactable(false) + .fixed_pos(world_to_screen( + (cursor_tile_pos * tile_size).to_pos2(), + )) + .show(egui_ctx, |ui| { + let tileset = &map.tilesets[&selected_tile.tileset]; + let texture_id = egui::TextureId::User( + storage::get::().textures + [&tileset.texture_id] + .texture + .raw_miniquad_texture_handle() + .gl_internal_id() + as u64, + ); + 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 (response, painter) = + ui.allocate_painter(tile_size, egui::Sense::hover()); + let mut tile_mesh = egui::Mesh::with_texture(texture_id); + tile_mesh.add_rect_with_uv( + response.rect, + uv, + egui::Color32::from_rgba_unmultiplied( + 0xff, 0xff, 0xff, 200, + ), + ); + + painter.add(egui::Shape::mesh(tile_mesh)); + }); + + if response.clicked() || response.dragged() { + if ui.input().pointer.button_down(egui::PointerButton::Primary) + { + action.then_do_some(UiAction::PlaceTile { + id: selected_tile.tile_id, + layer_id: selected_layer.clone(), + tileset_id: selected_tile.tileset.clone(), + coords: macroquad::math::UVec2::new( + cursor_tile_pos.x as u32, + cursor_tile_pos.y as u32, + ), + }); + } else if ui + .input() + .pointer + .button_down(egui::PointerButton::Secondary) + { + action.then_do_some(UiAction::RemoveTile { + layer_id: selected_layer.clone(), + coords: macroquad::math::UVec2::new( + cursor_tile_pos.x as u32, + cursor_tile_pos.y as u32, + ), + }); + } + } + } + + _ => (), + } + } - let (width, height) = (ui.available_width() as u32, ui.available_height() as u32); + Some((cursor_px_pos, cursor_tile_pos)) + } else { + None + }; + + let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); if width != level_render_target.texture.width() as u32 || height != level_render_target.texture.height() as u32 { level_render_target.delete(); *level_render_target = render_target(width, height); - dbg!("Remade level render target"); + println!("Remade level render target, size: ({}, {})", width, height); + } + + if let Some((cursor_px_pos, cursor_tile_pos)) = cursor_position { + egui::containers::Area::new("pointer pos overlay") + .order(egui::Order::Tooltip) + .fixed_pos( + response.rect.min + + egui::vec2( + ui.spacing().window_margin.left, + ui.spacing().window_margin.top, + ), + ) + .interactable(false) + .drag_bounds(egui::Rect::EVERYTHING) // disable clip rect + .show(egui_ctx, |ui| { + egui::Frame::popup(&egui_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 + }); } }); - None + + action } fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { From e943849ffad449a0ea760aba1e1bcf90f627be9d Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 15:54:54 +0200 Subject: [PATCH 20/81] Clean up --- src/editor/mod.rs | 5 +- src/editor/state.rs | 353 +++++++++++++++++++++++--------------------- src/editor/util.rs | 34 +++++ 3 files changed, 220 insertions(+), 172 deletions(-) create mode 100644 src/editor/util.rs diff --git a/src/editor/mod.rs b/src/editor/mod.rs index e0abf0a7f9..08e1c81add 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -2,6 +2,7 @@ use std::ops::ControlFlow; mod camera; pub mod state; +mod util; mod windows; pub use state::*; @@ -24,7 +25,7 @@ use crate::{ use macroquad::{ experimental::scene::{Node, RefMut}, - prelude::{collections::storage, render_target, RenderTarget}, + prelude::{render_target, RenderTarget}, }; use self::windows::{CreateLayerResult, CreateTilesetResult}; @@ -119,8 +120,6 @@ impl Editor { const OBJECT_SELECTION_RECT_SIZE: f32 = 75.0; const OBJECT_SELECTION_RECT_PADDING: f32 = 8.0; - const DOUBLE_CLICK_THRESHOLD: f32 = 0.25; - const MESSAGE_TIMEOUT: f32 = 2.5; pub fn new(map_resource: MapResource) -> Self { diff --git a/src/editor/state.rs b/src/editor/state.rs index c2f6a6d47e..323a12b66a 100644 --- a/src/editor/state.rs +++ b/src/editor/state.rs @@ -1,35 +1,31 @@ -use macroquad::prelude::{collections::storage, render_target, RenderTarget}; +use macroquad::prelude::{collections::storage, RenderTarget}; use crate::{ map::MapLayerKind, - resources::{MapResource, TextureKind, TextureResource}, + resources::{MapResource, TextureResource}, Resources, }; -use super::actions::{UiAction, UiActionExt}; +use super::{ + actions::{UiAction, UiActionExt}, + util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, +}; -fn macroquad_to_egui_vec2(v: macroquad::prelude::Vec2) -> egui::Vec2 { - egui::vec2(v.x, v.y) +// TODO: Factor in level view scale +fn screen_to_world_pos( + p: egui::Pos2, + level_top_left: egui::Vec2, + level_view: &LevelView, +) -> egui::Pos2 { + p - level_top_left + level_view.position.to_egui() } -trait RectMap { - fn map(self, from: egui::Rect, to: egui::Rect) -> egui::Rect; -} - -impl RectMap for egui::Rect { - fn map(self, from: egui::Rect, to: egui::Rect) -> egui::Rect { - let min_origin_offset = from.min.to_vec2(); - let max_origin_offset = from.max.to_vec2(); - let min_target_offset = to.min.to_vec2(); - let max_target_offset = to.max.to_vec2(); - - let min = - (self.min - min_origin_offset).to_vec2() / from.size() * to.size() + min_target_offset; - let max = - (self.max - max_origin_offset).to_vec2() / from.size() * to.size() + max_target_offset; - - egui::Rect::from_min_max(min.to_pos2(), max.to_pos2()) - } +fn world_to_screen_pos( + p: egui::Pos2, + level_top_left: egui::Vec2, + level_view: &LevelView, +) -> egui::Pos2 { + p + level_top_left - level_view.position.to_egui() } #[derive(Clone, PartialEq, Eq, Debug)] @@ -110,161 +106,185 @@ impl EditorState { egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { - let texture_id = egui::TextureId::User( - level_render_target - .texture - .raw_miniquad_texture_handle() - .gl_internal_id() as u64, - ); + let texture_id = level_render_target.texture.egui_id(); let level_img = egui::Image::new(texture_id, ui.available_size()) .sense(egui::Sense::click_and_drag()); + let response = ui.add(level_img); - let cursor_position = if response.hovered() { - let map = &self.map_resource.map; - let tile_size = macroquad_to_egui_vec2(map.tile_size); - let level_view_pos = macroquad_to_egui_vec2(level_view.position); - let screen_to_world = |pos: egui::Pos2| -> egui::Pos2 { - pos - response.rect.min.to_vec2() + level_view_pos - }; - let world_to_screen = |pos: egui::Pos2| -> egui::Pos2 { - pos + response.rect.min.to_vec2() - level_view_pos - }; - - let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); - let cursor_px_pos = screen_to_world(cursor_screen_pos); - let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor(); - - if self.selected_tool == EditorTool::TilePlacer - && cursor_tile_pos.x >= 0. - && cursor_tile_pos.y >= 0. - { - match (&self.selected_tile, &self.selected_layer) { - (Some(selected_tile), Some(selected_layer)) => { - egui::containers::Area::new("selected tile overlay") - .order(egui::Order::Background) - .interactable(false) - .fixed_pos(world_to_screen( - (cursor_tile_pos * tile_size).to_pos2(), - )) - .show(egui_ctx, |ui| { - let tileset = &map.tilesets[&selected_tile.tileset]; - let texture_id = egui::TextureId::User( - storage::get::().textures - [&tileset.texture_id] - .texture - .raw_miniquad_texture_handle() - .gl_internal_id() - as u64, - ); - 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 (response, painter) = - ui.allocate_painter(tile_size, egui::Sense::hover()); - let mut tile_mesh = egui::Mesh::with_texture(texture_id); - tile_mesh.add_rect_with_uv( - response.rect, - uv, - egui::Color32::from_rgba_unmultiplied( - 0xff, 0xff, 0xff, 200, - ), - ); - - painter.add(egui::Shape::mesh(tile_mesh)); - }); - - if response.clicked() || response.dragged() { - if ui.input().pointer.button_down(egui::PointerButton::Primary) - { - action.then_do_some(UiAction::PlaceTile { - id: selected_tile.tile_id, - layer_id: selected_layer.clone(), - tileset_id: selected_tile.tileset.clone(), - coords: macroquad::math::UVec2::new( - cursor_tile_pos.x as u32, - cursor_tile_pos.y as u32, - ), - }); - } else if ui - .input() - .pointer - .button_down(egui::PointerButton::Secondary) - { - action.then_do_some(UiAction::RemoveTile { - layer_id: selected_layer.clone(), - coords: macroquad::math::UVec2::new( - cursor_tile_pos.x as u32, - cursor_tile_pos.y as u32, - ), - }); - } - } - } - - _ => (), - } - } - Some((cursor_px_pos, cursor_tile_pos)) - } else { - None - }; + action.then_do(self.draw_level_overlays(egui_ctx, ui, &response, level_view)); let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); - if width != level_render_target.texture.width() as u32 - || height != level_render_target.texture.height() as u32 - { - level_render_target.delete(); - *level_render_target = render_target(width, height); - println!("Remade level render target, size: ({}, {})", width, height); - } + level_render_target.resize_if_appropiate(width, height); + }); - if let Some((cursor_px_pos, cursor_tile_pos)) = cursor_position { - egui::containers::Area::new("pointer pos overlay") - .order(egui::Order::Tooltip) - .fixed_pos( - response.rect.min - + egui::vec2( - ui.spacing().window_margin.left, - ui.spacing().window_margin.top, - ), - ) + action + } + + fn draw_level_overlays( + &self, + egui_ctx: &egui::Context, + ui: &mut egui::Ui, + level_response: &egui::Response, + level_view: &LevelView, + ) -> Option { + let action; + + if level_response.hovered() { + let map = &self.map_resource.map; + let tile_size = map.tile_size.to_egui(); + + let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); + let cursor_px_pos = screen_to_world_pos( + cursor_screen_pos, + level_response.rect.min.to_vec2(), + level_view, + ); + let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); + + action = self.draw_level_placement_overlay( + egui_ctx, + level_response, + cursor_tile_pos, + level_view, + ); + + let level_top_left = level_response.rect.min; + self.draw_level_pointer_pos_overlay( + egui_ctx, + ui, + level_top_left, + cursor_px_pos, + cursor_tile_pos, + ); + } else { + action = None; + } + + action + } + + fn draw_level_placement_overlay( + &self, + egui_ctx: &egui::Context, + level_response: &egui::Response, + cursor_tile_pos: egui::Pos2, + level_view: &LevelView, + ) -> Option { + let mut action = None; + let map = &self.map_resource.map; + let tile_size = map.tile_size.to_egui(); + let level_top_left = level_response.rect.min.to_vec2(); + + if self.selected_tool == EditorTool::TilePlacer + && cursor_tile_pos.x >= 0. + && cursor_tile_pos.y >= 0. + { + match (&self.selected_tile, &self.selected_layer) { + (Some(selected_tile), Some(selected_layer)) => { + egui::containers::Area::new("selected tile overlay") + .order(egui::Order::Background) .interactable(false) - .drag_bounds(egui::Rect::EVERYTHING) // disable clip rect + .fixed_pos(world_to_screen_pos( + (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), + level_top_left, + level_view, + )) .show(egui_ctx, |ui| { - egui::Frame::popup(&egui_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 + let tileset = &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 (response, painter) = + ui.allocate_painter(tile_size, egui::Sense::hover()); + let mut tile_mesh = egui::Mesh::with_texture(texture_id); + tile_mesh.add_rect_with_uv( + response.rect, + uv, + egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), + ); + + painter.add(egui::Shape::mesh(tile_mesh)); }); + + if level_response.clicked() || level_response.dragged() { + let input = egui_ctx.input(); + if input.pointer.button_down(egui::PointerButton::Primary) { + action.then_do_some(UiAction::PlaceTile { + id: selected_tile.tile_id, + layer_id: selected_layer.clone(), + tileset_id: selected_tile.tileset.clone(), + 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) { + action.then_do_some(UiAction::RemoveTile { + layer_id: selected_layer.clone(), + coords: macroquad::math::UVec2::new( + cursor_tile_pos.x as u32, + cursor_tile_pos.y as u32, + ), + }); + } + } } - }); + _ => (), + } + } action } + fn draw_level_pointer_pos_overlay( + &self, + egui_ctx: &egui::Context, + ui: &mut egui::Ui, + level_top_left: egui::Pos2, + cursor_px_pos: egui::Pos2, + cursor_tile_pos: egui::Pos2, + ) { + egui::containers::Area::new("pointer pos overlay") + .order(egui::Order::Tooltip) + .fixed_pos( + 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(egui_ctx, |ui| { + egui::Frame::popup(&egui_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 + }); + } + fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { let mut action = None; @@ -469,12 +489,7 @@ impl EditorState { 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 = egui::TextureId::User( - tileset_texture - .texture - .raw_miniquad_texture_handle() - .gl_internal_id() as u64, - ); + 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, diff --git a/src/editor/util.rs b/src/editor/util.rs new file mode 100644 index 0000000000..c0c2e05bc1 --- /dev/null +++ b/src/editor/util.rs @@ -0,0 +1,34 @@ +pub trait EguiCompatibleVec { + fn to_egui(self) -> egui::Vec2; +} + +impl EguiCompatibleVec for macroquad::math::Vec2 { + fn to_egui(self) -> egui::Vec2 { + egui::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); + } + } +} From 419d07595db9eb191af220b52dccd34bd68fe625 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 15:59:47 +0200 Subject: [PATCH 21/81] More cleaning up and renaming --- src/editor/actions.rs | 6 +- src/editor/actions/create_layer.rs | 2 +- src/editor/actions/create_object.rs | 2 +- src/editor/actions/create_spawn_point.rs | 2 +- src/editor/actions/create_tileset.rs | 2 +- src/editor/actions/delete_layer.rs | 2 +- src/editor/actions/delete_object.rs | 2 +- src/editor/actions/delete_spawn_point.rs | 2 +- src/editor/actions/delete_tileset.rs | 2 +- src/editor/actions/import.rs | 2 +- src/editor/actions/move_spawn_point.rs | 2 +- src/editor/actions/place_tile.rs | 2 +- src/editor/actions/remove_tile.rs | 2 +- .../actions/set_layer_draw_order_index.rs | 2 +- src/editor/actions/update_background.rs | 2 +- src/editor/actions/update_layer.rs | 2 +- src/editor/actions/update_object.rs | 2 +- src/editor/actions/update_tile_attributes.rs | 2 +- src/editor/actions/update_tileset.rs | 2 +- src/editor/camera.rs | 57 ------------------- src/editor/history.rs | 4 +- src/editor/mod.rs | 22 ++++--- src/editor/state.rs | 16 ++---- src/editor/view.rs | 8 +++ 24 files changed, 46 insertions(+), 103 deletions(-) delete mode 100644 src/editor/camera.rs create mode 100644 src/editor/view.rs diff --git a/src/editor/actions.rs b/src/editor/actions.rs index dd709672c8..c86d9ee8a6 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -199,14 +199,10 @@ impl UiActionExt for Option { /// All actions that modify map data should implement this trait pub trait UndoableAction { - fn apply(&mut self, _map: &mut Map) -> Result<()>; + fn apply_to(&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. diff --git a/src/editor/actions/create_layer.rs b/src/editor/actions/create_layer.rs index 699ffabbbe..149324ef7b 100644 --- a/src/editor/actions/create_layer.rs +++ b/src/editor/actions/create_layer.rs @@ -32,7 +32,7 @@ impl CreateLayer { } impl UndoableAction for CreateLayer { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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 { diff --git a/src/editor/actions/create_object.rs b/src/editor/actions/create_object.rs index bdc65fa370..2c8f88abfe 100644 --- a/src/editor/actions/create_object.rs +++ b/src/editor/actions/create_object.rs @@ -34,7 +34,7 @@ impl CreateObject { } impl UndoableAction for CreateObject { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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); diff --git a/src/editor/actions/create_spawn_point.rs b/src/editor/actions/create_spawn_point.rs index a6d1409e6c..18f3948d8f 100644 --- a/src/editor/actions/create_spawn_point.rs +++ b/src/editor/actions/create_spawn_point.rs @@ -18,7 +18,7 @@ impl CreateSpawnPoint { } impl UndoableAction for CreateSpawnPoint { - fn apply(&mut self, map: &mut Map) -> Result<()> { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { map.spawn_points.push(self.position); Ok(()) diff --git a/src/editor/actions/create_tileset.rs b/src/editor/actions/create_tileset.rs index 00944e8803..905b548e0d 100644 --- a/src/editor/actions/create_tileset.rs +++ b/src/editor/actions/create_tileset.rs @@ -28,7 +28,7 @@ impl CreateTileset { } impl UndoableAction for CreateTileset { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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( diff --git a/src/editor/actions/delete_layer.rs b/src/editor/actions/delete_layer.rs index ea39b744a2..97dfee6802 100644 --- a/src/editor/actions/delete_layer.rs +++ b/src/editor/actions/delete_layer.rs @@ -28,7 +28,7 @@ impl DeleteLayer { } impl UndoableAction for DeleteLayer { - fn apply(&mut self, map: &mut Map) -> Result<()> { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { if let Some(layer) = map.layers.remove(&self.id) { self.layer = Some(layer); } else { diff --git a/src/editor/actions/delete_object.rs b/src/editor/actions/delete_object.rs index ee21c88a8e..06023506c5 100644 --- a/src/editor/actions/delete_object.rs +++ b/src/editor/actions/delete_object.rs @@ -28,7 +28,7 @@ impl DeleteObject { } impl UndoableAction for DeleteObject { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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); diff --git a/src/editor/actions/delete_spawn_point.rs b/src/editor/actions/delete_spawn_point.rs index ab0a12f4ec..d0c696733c 100644 --- a/src/editor/actions/delete_spawn_point.rs +++ b/src/editor/actions/delete_spawn_point.rs @@ -26,7 +26,7 @@ impl DeleteSpawnPoint { } impl UndoableAction for DeleteSpawnPoint { - fn apply(&mut self, map: &mut Map) -> Result<()> { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { let spawn_point = map.spawn_points.remove(self.index); self.spawn_point = Some(spawn_point); diff --git a/src/editor/actions/delete_tileset.rs b/src/editor/actions/delete_tileset.rs index 3e19e80c77..bc15d9cc96 100644 --- a/src/editor/actions/delete_tileset.rs +++ b/src/editor/actions/delete_tileset.rs @@ -23,7 +23,7 @@ impl DeleteTileset { } impl UndoableAction for DeleteTileset { - fn apply(&mut self, map: &mut Map) -> Result<()> { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { if let Some(tileset) = map.tilesets.remove(&self.id) { self.tileset = Some(tileset); } else { diff --git a/src/editor/actions/import.rs b/src/editor/actions/import.rs index 5ed62cab09..cf65878d98 100644 --- a/src/editor/actions/import.rs +++ b/src/editor/actions/import.rs @@ -40,7 +40,7 @@ impl Import { } impl UndoableAction for Import { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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() { diff --git a/src/editor/actions/move_spawn_point.rs b/src/editor/actions/move_spawn_point.rs index 9abc347c76..5e998ad3d9 100644 --- a/src/editor/actions/move_spawn_point.rs +++ b/src/editor/actions/move_spawn_point.rs @@ -30,7 +30,7 @@ impl MoveSpawnPoint { } impl UndoableAction for MoveSpawnPoint { - fn apply(&mut self, map: &mut Map) -> Result<()> { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { let old_position = map.spawn_points.remove(self.index); self.old_position = Some(old_position); diff --git a/src/editor/actions/place_tile.rs b/src/editor/actions/place_tile.rs index 766a7cbf18..566d87db1b 100644 --- a/src/editor/actions/place_tile.rs +++ b/src/editor/actions/place_tile.rs @@ -36,7 +36,7 @@ impl PlaceTile { } impl UndoableAction for PlaceTile { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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); diff --git a/src/editor/actions/remove_tile.rs b/src/editor/actions/remove_tile.rs index 93e4916a68..1a8bd7ff2e 100644 --- a/src/editor/actions/remove_tile.rs +++ b/src/editor/actions/remove_tile.rs @@ -32,7 +32,7 @@ impl RemoveTile { } impl UndoableAction for RemoveTile { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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) { diff --git a/src/editor/actions/set_layer_draw_order_index.rs b/src/editor/actions/set_layer_draw_order_index.rs index f81b2fecd2..e246a49ae6 100644 --- a/src/editor/actions/set_layer_draw_order_index.rs +++ b/src/editor/actions/set_layer_draw_order_index.rs @@ -26,7 +26,7 @@ impl SetLayerDrawOrderIndex { } impl UndoableAction for SetLayerDrawOrderIndex { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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 { diff --git a/src/editor/actions/update_background.rs b/src/editor/actions/update_background.rs index b13987104a..e5625d19af 100644 --- a/src/editor/actions/update_background.rs +++ b/src/editor/actions/update_background.rs @@ -32,7 +32,7 @@ impl UpdateBackground { } impl UndoableAction for UpdateBackground { - fn apply(&mut self, map: &mut Map) -> Result<()> { + fn apply_to(&mut self, map: &mut Map) -> Result<()> { self.old_color = Some(map.background_color); map.background_color = self.color; diff --git a/src/editor/actions/update_layer.rs b/src/editor/actions/update_layer.rs index 454bda7d3b..e8f2da1476 100644 --- a/src/editor/actions/update_layer.rs +++ b/src/editor/actions/update_layer.rs @@ -26,7 +26,7 @@ impl UpdateLayer { } impl UndoableAction for UpdateLayer { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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; diff --git a/src/editor/actions/update_object.rs b/src/editor/actions/update_object.rs index 379090e005..cb50c0dd00 100644 --- a/src/editor/actions/update_object.rs +++ b/src/editor/actions/update_object.rs @@ -44,7 +44,7 @@ impl UpdateObject { } impl UndoableAction for UpdateObject { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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()); diff --git a/src/editor/actions/update_tile_attributes.rs b/src/editor/actions/update_tile_attributes.rs index 0dcab0ffcf..014fb97ae5 100644 --- a/src/editor/actions/update_tile_attributes.rs +++ b/src/editor/actions/update_tile_attributes.rs @@ -28,7 +28,7 @@ impl UpdateTileAttributes { } impl UndoableAction for UpdateTileAttributes { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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()); diff --git a/src/editor/actions/update_tileset.rs b/src/editor/actions/update_tileset.rs index aa0f960a8c..609217f258 100644 --- a/src/editor/actions/update_tileset.rs +++ b/src/editor/actions/update_tileset.rs @@ -30,7 +30,7 @@ impl UpdateTileset { } impl UndoableAction for UpdateTileset { - fn apply(&mut self, map: &mut Map) -> Result<()> { + 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(); 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/history.rs b/src/editor/history.rs index 65f37eed38..99defcafbe 100644 --- a/src/editor/history.rs +++ b/src/editor/history.rs @@ -23,7 +23,7 @@ impl ActionHistory { map: &mut Map, ) -> Result<()> { if !action.is_redundant(map) { - action.apply(map)?; + action.apply_to(map)?; self.undo_stack.push(Box::new(action)); self.redo_stack.clear(); } @@ -42,7 +42,7 @@ impl ActionHistory { 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); } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 08e1c81add..5eddec453a 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,12 +1,9 @@ use std::ops::ControlFlow; -mod camera; -pub mod state; +mod state; mod util; +mod view; mod windows; -pub use state::*; - -pub use camera::EditorCamera; mod actions; use actions::UiAction; @@ -19,7 +16,10 @@ use history::ActionHistory; pub use input::EditorInputScheme; use crate::{ - editor::windows::CreateTilesetWindow, + editor::{ + state::{EditorTool, TileSelection}, + windows::CreateTilesetWindow, + }, map::{Map, MapLayerKind}, }; @@ -28,12 +28,16 @@ use macroquad::{ prelude::{render_target, RenderTarget}, }; -use self::windows::{CreateLayerResult, CreateTilesetResult}; +use self::{ + state::State, + view::LevelView, + windows::{CreateLayerResult, CreateTilesetResult}, +}; use crate::resources::MapResource; pub struct Editor { - state: EditorState, + state: State, level_view: LevelView, @@ -124,7 +128,7 @@ impl Editor { pub fn new(map_resource: MapResource) -> Self { Self { - state: EditorState::new(map_resource), + state: State::new(map_resource), history: ActionHistory::new(), create_layer_window: None, create_tileset_window: None, diff --git a/src/editor/state.rs b/src/editor/state.rs index 323a12b66a..5a6f1fdd5a 100644 --- a/src/editor/state.rs +++ b/src/editor/state.rs @@ -9,6 +9,7 @@ use crate::{ use super::{ actions::{UiAction, UiActionExt}, util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, + view::LevelView, }; // TODO: Factor in level view scale @@ -44,7 +45,7 @@ pub struct TileSelection { /// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the /// editor. -pub struct EditorState { +pub struct State { pub selected_tool: EditorTool, pub map_resource: MapResource, pub selected_layer: Option, @@ -53,7 +54,7 @@ pub struct EditorState { pub should_draw_grid: bool, } -impl EditorState { +impl State { pub fn new(map_resource: MapResource) -> Self { Self { map_resource, @@ -73,17 +74,8 @@ impl EditorState { } } -// FIXME: This is very ugly, and shouldn't be passed into the editor state as parameter. Is there some -// better way to do this? -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, -} - /// UI-related functions -impl EditorState { +impl State { pub fn ui( &self, egui_ctx: &egui::Context, diff --git a/src/editor/view.rs b/src/editor/view.rs new file mode 100644 index 0000000000..f101252a9d --- /dev/null +++ b/src/editor/view.rs @@ -0,0 +1,8 @@ +// FIXME: This is very ugly, and shouldn't be passed into the editor state as parameter. Is there some +// better way to do this? +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, +} From 816b322a56e9bbc3e3a8c121d0f9b903dc9ab981 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 16:12:53 +0200 Subject: [PATCH 22/81] More cleanup and organization --- src/editor/mod.rs | 1 + src/editor/state.rs | 541 ---------------------- src/editor/state/mod.rs | 48 ++ src/editor/state/ui.rs | 23 + src/editor/state/ui/level.rs | 222 +++++++++ src/editor/state/ui/side_panel/layers.rs | 107 +++++ src/editor/state/ui/side_panel/mod.rs | 31 ++ src/editor/state/ui/side_panel/tileset.rs | 127 +++++ src/editor/state/ui/toolbar.rs | 40 ++ src/main.rs | 5 +- 10 files changed, 600 insertions(+), 545 deletions(-) delete mode 100644 src/editor/state.rs create mode 100644 src/editor/state/mod.rs create mode 100644 src/editor/state/ui.rs create mode 100644 src/editor/state/ui/level.rs create mode 100644 src/editor/state/ui/side_panel/layers.rs create mode 100644 src/editor/state/ui/side_panel/mod.rs create mode 100644 src/editor/state/ui/side_panel/tileset.rs create mode 100644 src/editor/state/ui/toolbar.rs diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 5eddec453a..f8bd6283c5 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -74,6 +74,7 @@ impl Node for EditorNode { fn fixed_update(mut node: RefMut) { use macroquad::prelude::*; + // TODO: Move into Editor::draw let input = node.input_scheme.collect_input(); node.editor.level_view.position += input.camera_move_direction * Self::CAMERA_PAN_SPEED; diff --git a/src/editor/state.rs b/src/editor/state.rs deleted file mode 100644 index 5a6f1fdd5a..0000000000 --- a/src/editor/state.rs +++ /dev/null @@ -1,541 +0,0 @@ -use macroquad::prelude::{collections::storage, RenderTarget}; - -use crate::{ - map::MapLayerKind, - resources::{MapResource, TextureResource}, - Resources, -}; - -use super::{ - actions::{UiAction, UiActionExt}, - util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, - view::LevelView, -}; - -// TODO: Factor in level view scale -fn screen_to_world_pos( - p: egui::Pos2, - level_top_left: egui::Vec2, - level_view: &LevelView, -) -> egui::Pos2 { - p - level_top_left + level_view.position.to_egui() -} - -fn world_to_screen_pos( - p: egui::Pos2, - level_top_left: egui::Vec2, - level_view: &LevelView, -) -> egui::Pos2 { - p + level_top_left - level_view.position.to_egui() -} - -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum EditorTool { - Cursor, - TilePlacer, - ObjectPlacer, - SpawnPointPlacer, - Eraser, -} - -pub struct TileSelection { - pub tileset: String, - pub tile_id: u32, -} - -/// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the -/// editor. -pub struct State { - pub selected_tool: EditorTool, - pub map_resource: MapResource, - pub selected_layer: Option, - pub selected_tile: Option, - pub is_parallax_enabled: bool, - pub should_draw_grid: bool, -} - -impl State { - pub fn new(map_resource: MapResource) -> Self { - Self { - map_resource, - selected_tool: EditorTool::Cursor, - selected_layer: None, - selected_tile: None, - is_parallax_enabled: true, - should_draw_grid: true, - } - } - - 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) - } -} - -/// UI-related functions -impl State { - pub fn ui( - &self, - egui_ctx: &egui::Context, - level_render_target: &mut RenderTarget, - level_view: &LevelView, - ) -> Option { - self.draw_toolbar(egui_ctx) - .then(self.draw_side_panel(egui_ctx)) - .then(self.draw_level(egui_ctx, level_render_target, level_view)) - } - - fn draw_level( - &self, - egui_ctx: &egui::Context, - level_render_target: &mut RenderTarget, - level_view: &LevelView, - ) -> Option { - let mut action = None; - - egui::CentralPanel::default() - .frame(egui::Frame::none()) - .show(egui_ctx, |ui| { - let texture_id = level_render_target.texture.egui_id(); - let level_img = egui::Image::new(texture_id, ui.available_size()) - .sense(egui::Sense::click_and_drag()); - - let response = ui.add(level_img); - - action.then_do(self.draw_level_overlays(egui_ctx, ui, &response, level_view)); - - let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); - level_render_target.resize_if_appropiate(width, height); - }); - - action - } - - fn draw_level_overlays( - &self, - egui_ctx: &egui::Context, - ui: &mut egui::Ui, - level_response: &egui::Response, - level_view: &LevelView, - ) -> Option { - let action; - - if level_response.hovered() { - let map = &self.map_resource.map; - let tile_size = map.tile_size.to_egui(); - - let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); - let cursor_px_pos = screen_to_world_pos( - cursor_screen_pos, - level_response.rect.min.to_vec2(), - level_view, - ); - let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); - - action = self.draw_level_placement_overlay( - egui_ctx, - level_response, - cursor_tile_pos, - level_view, - ); - - let level_top_left = level_response.rect.min; - self.draw_level_pointer_pos_overlay( - egui_ctx, - ui, - level_top_left, - cursor_px_pos, - cursor_tile_pos, - ); - } else { - action = None; - } - - action - } - - fn draw_level_placement_overlay( - &self, - egui_ctx: &egui::Context, - level_response: &egui::Response, - cursor_tile_pos: egui::Pos2, - level_view: &LevelView, - ) -> Option { - let mut action = None; - let map = &self.map_resource.map; - let tile_size = map.tile_size.to_egui(); - let level_top_left = level_response.rect.min.to_vec2(); - - if self.selected_tool == EditorTool::TilePlacer - && cursor_tile_pos.x >= 0. - && cursor_tile_pos.y >= 0. - { - match (&self.selected_tile, &self.selected_layer) { - (Some(selected_tile), Some(selected_layer)) => { - egui::containers::Area::new("selected tile overlay") - .order(egui::Order::Background) - .interactable(false) - .fixed_pos(world_to_screen_pos( - (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), - level_top_left, - level_view, - )) - .show(egui_ctx, |ui| { - let tileset = &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 (response, painter) = - ui.allocate_painter(tile_size, egui::Sense::hover()); - let mut tile_mesh = egui::Mesh::with_texture(texture_id); - tile_mesh.add_rect_with_uv( - response.rect, - uv, - egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), - ); - - painter.add(egui::Shape::mesh(tile_mesh)); - }); - - if level_response.clicked() || level_response.dragged() { - let input = egui_ctx.input(); - if input.pointer.button_down(egui::PointerButton::Primary) { - action.then_do_some(UiAction::PlaceTile { - id: selected_tile.tile_id, - layer_id: selected_layer.clone(), - tileset_id: selected_tile.tileset.clone(), - 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) { - action.then_do_some(UiAction::RemoveTile { - layer_id: selected_layer.clone(), - coords: macroquad::math::UVec2::new( - cursor_tile_pos.x as u32, - cursor_tile_pos.y as u32, - ), - }); - } - } - } - - _ => (), - } - } - action - } - - fn draw_level_pointer_pos_overlay( - &self, - egui_ctx: &egui::Context, - ui: &mut egui::Ui, - level_top_left: egui::Pos2, - cursor_px_pos: egui::Pos2, - cursor_tile_pos: egui::Pos2, - ) { - egui::containers::Area::new("pointer pos overlay") - .order(egui::Order::Tooltip) - .fixed_pos( - 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(egui_ctx, |ui| { - egui::Frame::popup(&egui_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 - }); - } - - fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { - let mut action = None; - - egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show(egui_ctx, |ui| { - let tool = &self.selected_tool; - - let mut add_tool = |tool_name, tool_variant| { - if ui - .add(egui::SelectableLabel::new(tool == &tool_variant, tool_name)) - .clicked() - { - action.then_do_some(UiAction::SelectTool(tool_variant)); - } - }; - - add_tool("Cursor", EditorTool::Cursor); - match self.selected_layer_type() { - Some(MapLayerKind::TileLayer) => { - add_tool("Tiles", EditorTool::TilePlacer); - add_tool("Eraser", EditorTool::Eraser); - } - Some(MapLayerKind::ObjectLayer) => add_tool("Objects", EditorTool::ObjectPlacer), - None => (), - } - }); - - action - } - - fn draw_side_panel(&self, egui_ctx: &egui::Context) -> Option { - let mut action = None; - - egui::SidePanel::new(egui::containers::panel::Side::Right, "Side panel").show( - egui_ctx, - |ui| { - action = self.draw_layer_info(ui); - ui.separator(); - match self.selected_layer_type() { - Some(MapLayerKind::TileLayer) => { - self.draw_tileset_info(ui).map(|act| action = Some(act)); - } - Some(MapLayerKind::ObjectLayer) => { - // TODO - } - None => (), - } - }, - ); - - action - } - - fn draw_layer_info(&self, ui: &mut egui::Ui) -> Option { - ui.heading("Layers"); - self.draw_layer_list(ui) // Draw layer list - .then(self.draw_layer_utils(ui)) // Draw layer util buttons (+ - Up Down) - } - - fn draw_layer_list(&self, ui: &mut egui::Ui) -> Option { - let mut action = None; - let map = &self.map_resource.map; - - ui.group(|ui| { - for (layer_name, layer) in map.draw_order.iter().map(|id| (id, &map.layers[id])) { - ui.horizontal(|ui| { - if ui - .selectable_label( - self.selected_layer.as_ref() == Some(layer_name), - format!( - "({}) {}", - match layer.kind { - MapLayerKind::TileLayer => "T", - MapLayerKind::ObjectLayer => "O", - }, - layer_name - ), - ) - .clicked() - { - action.then_do_some(UiAction::SelectLayer(layer_name.clone())); - } - let mut is_visible = layer.is_visible; - if ui.checkbox(&mut is_visible, "Visible").clicked() { - action.then_do_some(UiAction::UpdateLayer { - id: layer_name.clone(), - is_visible, - }); - } - }); - } - }); - - action - } - - fn draw_layer_utils(&self, ui: &mut egui::Ui) -> Option { - let mut action = None; - let map = &self.map_resource.map; - - ui.horizontal(|ui| { - if ui.button("+").clicked() { - action.then_do_some(UiAction::OpenCreateLayerWindow); - } - - ui.add_enabled_ui(self.selected_layer.is_some(), |ui| { - if ui.button("-").clicked() { - action.then_do_some(UiAction::DeleteLayer( - self.selected_layer.as_ref().unwrap().clone(), - )); - } - let selected_layer_idx = self - .selected_layer - .as_ref() - .and_then(|layer| { - 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() - { - action.then_do_some(UiAction::SetLayerDrawOrderIndex { - id: self.selected_layer.as_ref().unwrap().clone(), - index: selected_layer_idx - 1, - }); - } - - if ui - .add_enabled( - selected_layer_idx + 1 < map.draw_order.len(), - egui::Button::new("Down"), - ) - .clicked() - { - action.then_do_some(UiAction::SetLayerDrawOrderIndex { - id: self.selected_layer.as_ref().unwrap().clone(), - index: selected_layer_idx + 1, - }); - } - }); - }); - - action - } - - fn draw_tileset_info(&self, ui: &mut egui::Ui) -> Option { - ui.heading("Tilesets"); - self.draw_tileset_list(ui) // Draw tileset list - .then(self.draw_tileset_utils(ui)) // Draw tileset utils - .then(self.draw_tileset_image(ui)) // Draw tileset image - } - - fn draw_tileset_list(&self, ui: &mut egui::Ui) -> Option { - let mut action = None; - - ui.group(|ui| { - for (tileset_name, _tileset) in self.map_resource.map.tilesets.iter() { - let is_selected = self - .selected_tile - .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())); - } - } - }); - - action - } - - fn draw_tileset_utils(&self, ui: &mut egui::Ui) -> Option { - let mut action = None; - - ui.horizontal(|ui| { - if ui.button("+").clicked() { - action.then_do_some(UiAction::OpenCreateTilesetWindow); - } - ui.add_enabled_ui(self.selected_tile.is_some(), |ui| { - if ui.button("-").clicked() { - action.then_do_some(UiAction::DeleteTileset( - self.selected_tile.as_ref().unwrap().tileset.clone(), - )); - } - ui.button("Edit").on_hover_text("Does not do anything yet"); - }) - }); - - action - } - - fn draw_tileset_image(&self, ui: &mut egui::Ui) -> Option { - let mut action = None; - - if let Some(selection) = &self.selected_tile { - 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 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; - - action.then_do_some(UiAction::SelectTile { - id: tile_id, - tileset_id: selection.tileset.clone(), - }); - } - } - - 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), - ); - } - } - } - - action - } -} diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs new file mode 100644 index 0000000000..3656b3edb9 --- /dev/null +++ b/src/editor/state/mod.rs @@ -0,0 +1,48 @@ +mod ui; + +use crate::{map::MapLayerKind, resources::MapResource}; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum EditorTool { + Cursor, + TilePlacer, + ObjectPlacer, + SpawnPointPlacer, + Eraser, +} + +pub struct TileSelection { + pub tileset: String, + pub tile_id: u32, +} + +/// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the +/// editor. +pub struct State { + pub selected_tool: EditorTool, + pub map_resource: MapResource, + pub selected_layer: Option, + pub selected_tile: Option, + pub is_parallax_enabled: bool, + pub should_draw_grid: bool, +} + +impl State { + pub fn new(map_resource: MapResource) -> Self { + Self { + map_resource, + selected_tool: EditorTool::Cursor, + selected_layer: None, + selected_tile: None, + is_parallax_enabled: true, + should_draw_grid: true, + } + } + + 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) + } +} diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs new file mode 100644 index 0000000000..6b78e93ce8 --- /dev/null +++ b/src/editor/state/ui.rs @@ -0,0 +1,23 @@ +mod level; +mod side_panel; +mod toolbar; + +use macroquad::prelude::RenderTarget; + +use crate::editor::{ + actions::{UiAction, UiActionExt}, + view::LevelView, +}; + +impl super::State { + pub fn ui( + &self, + egui_ctx: &egui::Context, + level_render_target: &mut RenderTarget, + level_view: &LevelView, + ) -> Option { + self.draw_toolbar(egui_ctx) + .then(self.draw_side_panel(egui_ctx)) + .then(self.draw_level(egui_ctx, level_render_target, level_view)) + } +} diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs new file mode 100644 index 0000000000..98d8402563 --- /dev/null +++ b/src/editor/state/ui/level.rs @@ -0,0 +1,222 @@ +use macroquad::prelude::{collections::storage, RenderTarget}; + +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::EditorTool, + util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, + view::LevelView, + }, + Resources, +}; + +use super::super::State; + +impl State { + pub(super) fn draw_level( + &self, + egui_ctx: &egui::Context, + level_render_target: &mut RenderTarget, + level_view: &LevelView, + ) -> Option { + let mut action = None; + + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(egui_ctx, |ui| { + let texture_id = level_render_target.texture.egui_id(); + let level_img = egui::Image::new(texture_id, ui.available_size()) + .sense(egui::Sense::click_and_drag()); + + let response = ui.add(level_img); + + action.then_do(self.draw_level_overlays(egui_ctx, ui, &response, level_view)); + + let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); + level_render_target.resize_if_appropiate(width, height); + }); + + action + } + + fn draw_level_overlays( + &self, + egui_ctx: &egui::Context, + ui: &mut egui::Ui, + level_response: &egui::Response, + level_view: &LevelView, + ) -> Option { + let action; + + if level_response.hovered() { + let map = &self.map_resource.map; + let tile_size = map.tile_size.to_egui(); + + let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); + let cursor_px_pos = screen_to_world_pos( + cursor_screen_pos, + level_response.rect.min.to_vec2(), + level_view, + ); + let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); + + action = self.draw_level_placement_overlay( + egui_ctx, + level_response, + cursor_tile_pos, + level_view, + ); + + let level_top_left = level_response.rect.min; + self.draw_level_pointer_pos_overlay( + egui_ctx, + ui, + level_top_left, + cursor_px_pos, + cursor_tile_pos, + ); + } else { + action = None; + } + + action + } + + fn draw_level_placement_overlay( + &self, + egui_ctx: &egui::Context, + level_response: &egui::Response, + cursor_tile_pos: egui::Pos2, + level_view: &LevelView, + ) -> Option { + let mut action = None; + let map = &self.map_resource.map; + let tile_size = map.tile_size.to_egui(); + let level_top_left = level_response.rect.min.to_vec2(); + + if self.selected_tool == EditorTool::TilePlacer + && cursor_tile_pos.x >= 0. + && cursor_tile_pos.y >= 0. + { + match (&self.selected_tile, &self.selected_layer) { + (Some(selected_tile), Some(selected_layer)) => { + egui::containers::Area::new("selected tile overlay") + .order(egui::Order::Background) + .interactable(false) + .fixed_pos(world_to_screen_pos( + (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), + level_top_left, + level_view, + )) + .show(egui_ctx, |ui| { + let tileset = &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 (response, painter) = + ui.allocate_painter(tile_size, egui::Sense::hover()); + let mut tile_mesh = egui::Mesh::with_texture(texture_id); + tile_mesh.add_rect_with_uv( + response.rect, + uv, + egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), + ); + + painter.add(egui::Shape::mesh(tile_mesh)); + }); + + if level_response.clicked() || level_response.dragged() { + let input = egui_ctx.input(); + if input.pointer.button_down(egui::PointerButton::Primary) { + action.then_do_some(UiAction::PlaceTile { + id: selected_tile.tile_id, + layer_id: selected_layer.clone(), + tileset_id: selected_tile.tileset.clone(), + 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) { + action.then_do_some(UiAction::RemoveTile { + layer_id: selected_layer.clone(), + coords: macroquad::math::UVec2::new( + cursor_tile_pos.x as u32, + cursor_tile_pos.y as u32, + ), + }); + } + } + } + + _ => (), + } + } + action + } + + fn draw_level_pointer_pos_overlay( + &self, + egui_ctx: &egui::Context, + ui: &mut egui::Ui, + level_top_left: egui::Pos2, + cursor_px_pos: egui::Pos2, + cursor_tile_pos: egui::Pos2, + ) { + egui::containers::Area::new("pointer pos overlay") + .order(egui::Order::Tooltip) + .fixed_pos( + 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(egui_ctx, |ui| { + egui::Frame::popup(&egui_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 + }); + } +} + +// TODO: Factor in level view scale +fn screen_to_world_pos( + p: egui::Pos2, + level_top_left: egui::Vec2, + level_view: &LevelView, +) -> egui::Pos2 { + p - level_top_left + level_view.position.to_egui() +} + +fn world_to_screen_pos( + p: egui::Pos2, + level_top_left: egui::Vec2, + level_view: &LevelView, +) -> egui::Pos2 { + p + level_top_left - level_view.position.to_egui() +} 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..7f2c931ed2 --- /dev/null +++ b/src/editor/state/ui/side_panel/layers.rs @@ -0,0 +1,107 @@ +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::State, + }, + map::MapLayerKind, +}; + +impl State { + pub(super) fn draw_layer_info(&self, ui: &mut egui::Ui) -> Option { + ui.heading("Layers"); + self.draw_layer_list(ui) // Draw layer list + .then(self.draw_layer_utils(ui)) // Draw layer util buttons (+ - Up Down) + } + + fn draw_layer_list(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + let map = &self.map_resource.map; + + ui.group(|ui| { + for (layer_name, layer) in map.draw_order.iter().map(|id| (id, &map.layers[id])) { + ui.horizontal(|ui| { + if ui + .selectable_label( + self.selected_layer.as_ref() == Some(layer_name), + format!( + "({}) {}", + match layer.kind { + MapLayerKind::TileLayer => "T", + MapLayerKind::ObjectLayer => "O", + }, + layer_name + ), + ) + .clicked() + { + action.then_do_some(UiAction::SelectLayer(layer_name.clone())); + } + let mut is_visible = layer.is_visible; + if ui.checkbox(&mut is_visible, "Visible").clicked() { + action.then_do_some(UiAction::UpdateLayer { + id: layer_name.clone(), + is_visible, + }); + } + }); + } + }); + + action + } + + fn draw_layer_utils(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + let map = &self.map_resource.map; + + ui.horizontal(|ui| { + if ui.button("+").clicked() { + action.then_do_some(UiAction::OpenCreateLayerWindow); + } + + ui.add_enabled_ui(self.selected_layer.is_some(), |ui| { + if ui.button("-").clicked() { + action.then_do_some(UiAction::DeleteLayer( + self.selected_layer.as_ref().unwrap().clone(), + )); + } + let selected_layer_idx = self + .selected_layer + .as_ref() + .and_then(|layer| { + 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() + { + action.then_do_some(UiAction::SetLayerDrawOrderIndex { + id: self.selected_layer.as_ref().unwrap().clone(), + index: selected_layer_idx - 1, + }); + } + + if ui + .add_enabled( + selected_layer_idx + 1 < map.draw_order.len(), + egui::Button::new("Down"), + ) + .clicked() + { + action.then_do_some(UiAction::SetLayerDrawOrderIndex { + id: self.selected_layer.as_ref().unwrap().clone(), + index: selected_layer_idx + 1, + }); + } + }); + }); + + action + } +} 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..18fbf025e6 --- /dev/null +++ b/src/editor/state/ui/side_panel/mod.rs @@ -0,0 +1,31 @@ +mod layers; +mod tileset; + +use crate::{editor::actions::UiAction, map::MapLayerKind}; + +use super::super::State; + +impl State { + pub(super) fn draw_side_panel(&self, egui_ctx: &egui::Context) -> Option { + let mut action = None; + + egui::SidePanel::new(egui::containers::panel::Side::Right, "Side panel").show( + egui_ctx, + |ui| { + action = self.draw_layer_info(ui); + ui.separator(); + match self.selected_layer_type() { + Some(MapLayerKind::TileLayer) => { + self.draw_tileset_info(ui).map(|act| action = Some(act)); + } + Some(MapLayerKind::ObjectLayer) => { + // TODO + } + None => (), + } + }, + ); + + action + } +} 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..0e3da4a446 --- /dev/null +++ b/src/editor/state/ui/side_panel/tileset.rs @@ -0,0 +1,127 @@ +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::EditorTool, + util::EguiTextureHandler, + }, + resources::TextureResource, + Resources, +}; + +use super::State; + +impl State { + pub(super) fn draw_tileset_info(&self, ui: &mut egui::Ui) -> Option { + ui.heading("Tilesets"); + self.draw_tileset_list(ui) // Draw tileset list + .then(self.draw_tileset_utils(ui)) // Draw tileset utils + .then(self.draw_tileset_image(ui)) // Draw tileset image + } + + fn draw_tileset_list(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + + ui.group(|ui| { + for (tileset_name, _tileset) in self.map_resource.map.tilesets.iter() { + let is_selected = self + .selected_tile + .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())); + } + } + }); + + action + } + + fn draw_tileset_utils(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + + ui.horizontal(|ui| { + if ui.button("+").clicked() { + action.then_do_some(UiAction::OpenCreateTilesetWindow); + } + ui.add_enabled_ui(self.selected_tile.is_some(), |ui| { + if ui.button("-").clicked() { + action.then_do_some(UiAction::DeleteTileset( + self.selected_tile.as_ref().unwrap().tileset.clone(), + )); + } + ui.button("Edit").on_hover_text("Does not do anything yet"); + }) + }); + + action + } + + fn draw_tileset_image(&self, ui: &mut egui::Ui) -> Option { + let mut action = None; + + if let Some(selection) = &self.selected_tile { + 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 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; + + action.then_do_some(UiAction::SelectTile { + id: tile_id, + tileset_id: selection.tileset.clone(), + }); + } + } + + 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), + ); + } + } + } + + action + } +} diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs new file mode 100644 index 0000000000..678208b26c --- /dev/null +++ b/src/editor/state/ui/toolbar.rs @@ -0,0 +1,40 @@ +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::EditorTool, + }, + map::MapLayerKind, +}; + +use super::super::State; + +impl State { + pub(super) fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { + let mut action = None; + + egui::SidePanel::new(egui::containers::panel::Side::Left, "Tools").show(egui_ctx, |ui| { + let tool = &self.selected_tool; + + let mut add_tool = |tool_name, tool_variant| { + if ui + .add(egui::SelectableLabel::new(tool == &tool_variant, tool_name)) + .clicked() + { + action.then_do_some(UiAction::SelectTool(tool_variant)); + } + }; + + add_tool("Cursor", EditorTool::Cursor); + match self.selected_layer_type() { + Some(MapLayerKind::TileLayer) => { + add_tool("Tiles", EditorTool::TilePlacer); + add_tool("Eraser", EditorTool::Eraser); + } + Some(MapLayerKind::ObjectLayer) => add_tool("Objects", EditorTool::ObjectPlacer), + None => (), + } + }); + + action + } +} diff --git a/src/main.rs b/src/main.rs index 31d8acc131..20ac246f97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ pub mod drawables; pub use drawables::*; pub use physics::*; -use editor::{EditorCamera, EditorInputScheme}; +use editor::EditorInputScheme; use map::{Map, MapLayerKind, MapObjectKind}; @@ -131,9 +131,6 @@ 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(EditorNode::new(Editor::new(map_resource), input_scheme)); } MainMenuResult::ReloadResources => { From 123283a25a6c271fb332efc2fc740c91185711a2 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 16:56:29 +0200 Subject: [PATCH 23/81] Add editor menu --- src/editor/mod.rs | 52 ++++++++++++++++++++++++++++---- src/editor/windows/menu.rs | 61 ++++++++++++++++++++++++++++++++++++++ src/editor/windows/mod.rs | 3 ++ 3 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 src/editor/windows/menu.rs diff --git a/src/editor/mod.rs b/src/editor/mod.rs index f8bd6283c5..64284aebc2 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -18,7 +18,7 @@ pub use input::EditorInputScheme; use crate::{ editor::{ state::{EditorTool, TileSelection}, - windows::CreateTilesetWindow, + windows::{CreateTilesetWindow, MenuWindow}, }, map::{Map, MapLayerKind}, }; @@ -31,7 +31,7 @@ use macroquad::{ use self::{ state::State, view::LevelView, - windows::{CreateLayerResult, CreateTilesetResult}, + windows::{CreateLayerResult, CreateTilesetResult, MenuResult}, }; use crate::resources::MapResource; @@ -45,6 +45,7 @@ pub struct Editor { create_layer_window: Option, create_tileset_window: Option, + menu_window: Option, level_render_target: RenderTarget, } @@ -74,7 +75,6 @@ impl Node for EditorNode { fn fixed_update(mut node: RefMut) { use macroquad::prelude::*; - // TODO: Move into Editor::draw let input = node.input_scheme.collect_input(); node.editor.level_view.position += input.camera_move_direction * Self::CAMERA_PAN_SPEED; @@ -96,6 +96,16 @@ impl Node for EditorNode { }); scene::set_camera(0, camera); + + if input.toggle_menu { + node.editor.menu_window = if node.editor.menu_window.is_some() { + None + } else { + Some(MenuWindow::new( + node.editor.state.map_resource.meta.is_user_map, + )) + }; + } } fn draw(mut node: RefMut) @@ -131,13 +141,15 @@ impl Editor { Self { state: State::new(map_resource), history: ActionHistory::new(), - create_layer_window: None, - create_tileset_window: None, level_view: LevelView { position: Default::default(), scale: 1., }, level_render_target: render_target(1, 1), + + create_layer_window: None, + create_tileset_window: None, + menu_window: None, } } @@ -236,6 +248,12 @@ impl Editor { .apply(action, &mut self.state.map_resource.map) .unwrap(); } + UiAction::ExitToMainMenu => { + crate::exit_to_main_menu(); + } + UiAction::QuitToDesktop => { + crate::quit_to_desktop(); + } _ => todo!(), } @@ -289,6 +307,30 @@ impl Editor { } } } + + if let Some(window) = &mut self.menu_window { + match window.ui(egui_ctx) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(MenuResult::OpenCreateMapWindow) => { + self.apply_action(UiAction::OpenCreateMapWindow) + } + ControlFlow::Break(MenuResult::OpenLoadMapWindow) => { + self.apply_action(UiAction::OpenLoadMapWindow) + } + ControlFlow::Break(MenuResult::OpenSaveMapWindow) => { + self.apply_action(UiAction::OpenSaveMapWindow) + } + ControlFlow::Break(MenuResult::SaveMap) => { + self.apply_action(UiAction::SaveMap(None)) + } + ControlFlow::Break(MenuResult::ExitToMainMenu) => { + self.apply_action(UiAction::ExitToMainMenu) + } + ControlFlow::Break(MenuResult::QuitToDesktop) => { + self.apply_action(UiAction::QuitToDesktop) + } + } + } } pub fn draw(&self) { diff --git a/src/editor/windows/menu.rs b/src/editor/windows/menu.rs new file mode 100644 index 0000000000..2970852657 --- /dev/null +++ b/src/editor/windows/menu.rs @@ -0,0 +1,61 @@ +use std::ops::ControlFlow; + +use macroquad::prelude::collections::storage; + +use crate::{map::Map, resources::TextureKind, Resources}; + +pub struct MenuWindow { + can_save: bool, +} + +pub enum MenuResult { + OpenCreateMapWindow, + OpenLoadMapWindow, + SaveMap, + OpenSaveMapWindow, + ExitToMainMenu, + QuitToDesktop, +} + +impl MenuWindow { + pub fn new(can_save: bool) -> Self { + Self { can_save } + } + + pub fn ui(&mut self, egui_ctx: &egui::Context) -> 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(self.can_save, |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 index ae2b93bc94..f5495f2d28 100644 --- a/src/editor/windows/mod.rs +++ b/src/editor/windows/mod.rs @@ -3,3 +3,6 @@ pub use create_layer::*; mod create_tileset; pub use create_tileset::*; + +mod menu; +pub use menu::*; From ab6f80e8edf6c3d0f3c27d85349ee7f5dc4a83a2 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 17:00:47 +0200 Subject: [PATCH 24/81] Run `cargo fix` --- src/editor/mod.rs | 2 -- src/editor/windows/menu.rs | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 64284aebc2..aab8803b73 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -334,8 +334,6 @@ impl Editor { } pub fn draw(&self) { - use macroquad::prelude::*; - let map = &self.state.map_resource.map; { map.draw_background(None, !self.state.is_parallax_enabled); diff --git a/src/editor/windows/menu.rs b/src/editor/windows/menu.rs index 2970852657..50ffe705f1 100644 --- a/src/editor/windows/menu.rs +++ b/src/editor/windows/menu.rs @@ -1,9 +1,5 @@ use std::ops::ControlFlow; -use macroquad::prelude::collections::storage; - -use crate::{map::Map, resources::TextureKind, Resources}; - pub struct MenuWindow { can_save: bool, } From ff36fa450944266f1c774552a730b961a76d5f3e Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 17:06:27 +0200 Subject: [PATCH 25/81] Fix some clippy warnings --- src/editor/actions.rs | 5 +- src/editor/state/ui/level.rs | 126 ++++++++++++-------------- src/editor/state/ui/side_panel/mod.rs | 7 +- 3 files changed, 67 insertions(+), 71 deletions(-) diff --git a/src/editor/actions.rs b/src/editor/actions.rs index c86d9ee8a6..3b03401df1 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -183,9 +183,8 @@ impl UiActionExt for Option { } fn then_do(&mut self, action: Option) { - match action { - Some(action) => self.then_do_some(action), - None => (), + if let Some(action) = action { + self.then_do_some(action); } } diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs index 98d8402563..7c93ac08bf 100644 --- a/src/editor/state/ui/level.rs +++ b/src/editor/state/ui/level.rs @@ -98,75 +98,69 @@ impl State { && cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { - match (&self.selected_tile, &self.selected_layer) { - (Some(selected_tile), Some(selected_layer)) => { - egui::containers::Area::new("selected tile overlay") - .order(egui::Order::Background) - .interactable(false) - .fixed_pos(world_to_screen_pos( - (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), - level_top_left, - level_view, - )) - .show(egui_ctx, |ui| { - let tileset = &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 (response, painter) = - ui.allocate_painter(tile_size, egui::Sense::hover()); - let mut tile_mesh = egui::Mesh::with_texture(texture_id); - tile_mesh.add_rect_with_uv( - response.rect, - uv, - egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), - ); - - painter.add(egui::Shape::mesh(tile_mesh)); + if let (Some(selected_tile), Some(selected_layer)) = + (&self.selected_tile, &self.selected_layer) + { + egui::containers::Area::new("selected tile overlay") + .order(egui::Order::Background) + .interactable(false) + .fixed_pos(world_to_screen_pos( + (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), + level_top_left, + level_view, + )) + .show(egui_ctx, |ui| { + let tileset = &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 (response, painter) = + ui.allocate_painter(tile_size, egui::Sense::hover()); + let mut tile_mesh = egui::Mesh::with_texture(texture_id); + tile_mesh.add_rect_with_uv( + response.rect, + uv, + egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), + ); + + painter.add(egui::Shape::mesh(tile_mesh)); + }); + + if level_response.clicked() || level_response.dragged() { + let input = egui_ctx.input(); + if input.pointer.button_down(egui::PointerButton::Primary) { + action.then_do_some(UiAction::PlaceTile { + id: selected_tile.tile_id, + layer_id: selected_layer.clone(), + tileset_id: selected_tile.tileset.clone(), + 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) { + action.then_do_some(UiAction::RemoveTile { + layer_id: selected_layer.clone(), + coords: macroquad::math::UVec2::new( + cursor_tile_pos.x as u32, + cursor_tile_pos.y as u32, + ), }); - - if level_response.clicked() || level_response.dragged() { - let input = egui_ctx.input(); - if input.pointer.button_down(egui::PointerButton::Primary) { - action.then_do_some(UiAction::PlaceTile { - id: selected_tile.tile_id, - layer_id: selected_layer.clone(), - tileset_id: selected_tile.tileset.clone(), - 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) { - action.then_do_some(UiAction::RemoveTile { - layer_id: selected_layer.clone(), - coords: macroquad::math::UVec2::new( - cursor_tile_pos.x as u32, - cursor_tile_pos.y as u32, - ), - }); - } } } - - _ => (), } } action diff --git a/src/editor/state/ui/side_panel/mod.rs b/src/editor/state/ui/side_panel/mod.rs index 18fbf025e6..330481ec53 100644 --- a/src/editor/state/ui/side_panel/mod.rs +++ b/src/editor/state/ui/side_panel/mod.rs @@ -1,7 +1,10 @@ mod layers; mod tileset; -use crate::{editor::actions::UiAction, map::MapLayerKind}; +use crate::{ + editor::actions::{UiAction, UiActionExt}, + map::MapLayerKind, +}; use super::super::State; @@ -16,7 +19,7 @@ impl State { ui.separator(); match self.selected_layer_type() { Some(MapLayerKind::TileLayer) => { - self.draw_tileset_info(ui).map(|act| action = Some(act)); + action.then_do(self.draw_tileset_info(ui)); } Some(MapLayerKind::ObjectLayer) => { // TODO From 932ba3eaf528ac7bd5991a2f6b29534a331b3b84 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 9 Apr 2022 17:37:20 +0200 Subject: [PATCH 26/81] Partial commit: Drawing objects --- src/editor/state/ui/level.rs | 44 ++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs index 7c93ac08bf..fdcda44bdc 100644 --- a/src/editor/state/ui/level.rs +++ b/src/editor/state/ui/level.rs @@ -7,6 +7,7 @@ use crate::{ util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, view::LevelView, }, + map::MapObjectKind, Resources, }; @@ -30,6 +31,19 @@ impl State { let response = ui.add(level_img); + let resources = storage::get::(); + for (_layer_name, layer) in self.map_resource.map.layers.iter() { + for object in layer.objects.iter() { + match object.kind { + MapObjectKind::Decoration => { + let sprite = &resources.decoration[&object.id].sprite; + } + MapObjectKind::Item => (), + MapObjectKind::Environment => (), + } + } + } + action.then_do(self.draw_level_overlays(egui_ctx, ui, &response, level_view)); let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); @@ -88,16 +102,37 @@ impl State { level_response: &egui::Response, cursor_tile_pos: egui::Pos2, level_view: &LevelView, + ) -> Option { + let action; + + action = match self.selected_tool { + EditorTool::TilePlacer => self.draw_level_tile_placement_overlay( + egui_ctx, + level_response, + cursor_tile_pos, + level_view, + ), + // TODO: Spawnpoint placement overlay + // TODO: Object placement overlay + _ => None, + }; + + action + } + + fn draw_level_tile_placement_overlay( + &self, + egui_ctx: &egui::Context, + level_response: &egui::Response, + cursor_tile_pos: egui::Pos2, + level_view: &LevelView, ) -> Option { let mut action = None; let map = &self.map_resource.map; let tile_size = map.tile_size.to_egui(); let level_top_left = level_response.rect.min.to_vec2(); - if self.selected_tool == EditorTool::TilePlacer - && cursor_tile_pos.x >= 0. - && cursor_tile_pos.y >= 0. - { + if cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { if let (Some(selected_tile), Some(selected_layer)) = (&self.selected_tile, &self.selected_layer) { @@ -163,6 +198,7 @@ impl State { } } } + action } From 8ab59f5ebf697156471b256e6d9201b1553df4dc Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Tue, 12 Apr 2022 18:00:05 +0200 Subject: [PATCH 27/81] Object drawing (messy impl) --- Cargo.lock | 72 +++--- Cargo.toml | 1 - assets/textures.json | 10 +- assets/textures/ui/editor_icons/error.png | Bin 0 -> 239 bytes src/editor/state/mod.rs | 12 + src/editor/state/ui/level.rs | 294 ++++++++++++++++++---- src/editor/util.rs | 4 +- 7 files changed, 300 insertions(+), 93 deletions(-) create mode 100644 assets/textures/ui/editor_icons/error.png diff --git a/Cargo.lock b/Cargo.lock index d83ba85bc6..8b220a997a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ 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", @@ -108,9 +108,9 @@ checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" dependencies = [ "bytemuck_derive", ] @@ -372,9 +372,9 @@ dependencies = [ [[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", @@ -490,9 +490,9 @@ 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", ] @@ -522,9 +522,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" +checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" [[package]] name = "libloading" @@ -548,9 +548,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if", ] @@ -793,9 +793,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "owned_ttf_parser" @@ -808,9 +808,9 @@ dependencies = [ [[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" @@ -826,9 +826,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", ] @@ -882,9 +882,9 @@ dependencies = [ [[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", ] @@ -1066,9 +1066,9 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smithay-client-toolkit" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" dependencies = [ "bitflags", "dlib", @@ -1151,9 +1151,9 @@ checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" dependencies = [ "proc-macro2", "quote", @@ -1213,9 +1213,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", @@ -1223,9 +1223,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", @@ -1238,9 +1238,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", @@ -1248,9 +1248,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", @@ -1261,9 +1261,9 @@ 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" @@ -1340,9 +1340,9 @@ dependencies = [ [[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", diff --git a/Cargo.toml b/Cargo.toml index 9ee8ae8f23..06d2aa92ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,4 +33,3 @@ egui-macroquad = "0.9.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2.74" - diff --git a/assets/textures.json b/assets/textures.json index 3ff74bf204..77eb8532f4 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", @@ -514,4 +522,4 @@ "y": 128 } } -] +] \ 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 0000000000000000000000000000000000000000..aff533df4f98acfd4f3d5a65bc4e149baec0ae43 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJZJsWUArY;~G07=k<|i^Rax1bL z?=M{V;quOXf+kZ$Y<-qUw0QRj*;dFla8KELcpdM2*BxIbJY~pgI{2EwsnEBWJwW)* z-;d4T_!LBvVx9i4c+60t` literal 0 HcmV?d00001 diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 3656b3edb9..61ea2138e0 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -16,6 +16,16 @@ pub struct TileSelection { pub tile_id: u32, } +pub enum DraggableEntityKind { + Object { layer_id: String, index: usize }, + SpawnPoint { index: usize }, +} + +pub struct DraggableEntity { + kind: DraggableEntityKind, + click_offset: egui::Vec2, +} + /// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the /// editor. pub struct State { @@ -25,6 +35,7 @@ pub struct State { pub selected_tile: Option, pub is_parallax_enabled: bool, pub should_draw_grid: bool, + pub entity_being_dragged: Option, } impl State { @@ -36,6 +47,7 @@ impl State { selected_tile: None, is_parallax_enabled: true, should_draw_grid: true, + entity_being_dragged: None, } } diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs index fdcda44bdc..3082eb9d15 100644 --- a/src/editor/state/ui/level.rs +++ b/src/editor/state/ui/level.rs @@ -8,7 +8,7 @@ use crate::{ view::LevelView, }, map::MapObjectKind, - Resources, + AnimatedSpriteMetadata, Resources, Sprite, }; use super::super::State; @@ -25,26 +25,215 @@ impl State { egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { + const FULL_UV: egui::Rect = + egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)); let texture_id = level_render_target.texture.egui_id(); - let level_img = egui::Image::new(texture_id, ui.available_size()) - .sense(egui::Sense::click_and_drag()); - let response = ui.add(level_img); + 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)); let resources = storage::get::(); - for (_layer_name, layer) in self.map_resource.map.layers.iter() { + for layer_idx in self.map_resource.map.draw_order.iter() { + let layer = if let Some(layer) = self.map_resource.map.layers.get(layer_idx) { + layer + } else { + continue; + }; for object in layer.objects.iter() { + let draw_object = + |texture_id: egui::TextureId, + offset: macroquad::math::Vec2, + dest_size: egui::Vec2, + uv: egui::Rect, + tint: egui::Color32| { + let position_in_lvl = + (object.position + offset).into_egui().to_pos2(); + + let target = egui::Rect::from_min_size( + world_to_screen_pos( + position_in_lvl, + response.rect.min, + level_view, + ), + dest_size, + ); + + let mut mesh = egui::Mesh::with_texture(texture_id); + mesh.add_rect_with_uv(target, uv, tint); + painter.add(egui::Shape::mesh(mesh)); + }; + + let draw_invalid_object = || { + 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, + ); + }; + + let draw_animated_sprite = + |sprite: &AnimatedSpriteMetadata, row: Option| { + 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); + } else { + // Invalid texture ID + draw_invalid_object(); + } + }; + match object.kind { MapObjectKind::Decoration => { - let sprite = &resources.decoration[&object.id].sprite; + if let Some(meta) = resources.decoration.get(&object.id) { + draw_animated_sprite( + &meta.sprite, + meta.sprite.animations.first().map(|a| a.row), + ); + } else { + // Invalid object ID + draw_invalid_object(); + } + + /* + if response.hovered() { + if target.contains(ui.input().pointer.hover_pos().unwrap()) { + painter.add(egui::Shape::rect_stroke( + target, + egui::Rounding::none(), + egui::Stroke::new(2., egui::Color32::GRAY), + )); + egui::containers::show_tooltip_at_pointer( + egui_ctx, + egui::Id::new("object info"), + |ui| { + ui.centered_and_justified(|ui| { + ui.heading(&object.id); + ui.label( + egui::RichText::new("Decoration").small(), + ); + }); + ui.separator(); + ui.label(egui::RichText::new("Position: ").weak()); + ui.label( + egui::RichText::new(format!( + "({}, {})", + object.position.x, object.position.y + )) + .monospace(), + ); + }, + ); + } + }*/ + } + + MapObjectKind::Item => { + if let Some(meta) = resources.items.get(&object.id) { + 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(), + ), + ); + } else { + // Invalid object ID + draw_invalid_object(); + } } - MapObjectKind::Item => (), - MapObjectKind::Environment => (), - } + 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, + ); + + draw_object( + texture_id, + (0., 0.).into(), + dest_size, + uv, + egui::Color32::WHITE, + ); + } else { + // Invalid object ID + draw_invalid_object(); + } + } + }; } } - action.then_do(self.draw_level_overlays(egui_ctx, ui, &response, level_view)); + action.then_do( + self.draw_level_overlays(egui_ctx, ui, &response, &painter, level_view), + ); let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); level_render_target.resize_if_appropiate(width, height); @@ -58,13 +247,14 @@ impl State { egui_ctx: &egui::Context, ui: &mut egui::Ui, level_response: &egui::Response, + painter: &egui::Painter, level_view: &LevelView, ) -> Option { let action; if level_response.hovered() { let map = &self.map_resource.map; - let tile_size = map.tile_size.to_egui(); + let tile_size = map.tile_size.into_egui(); let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); let cursor_px_pos = screen_to_world_pos( @@ -77,6 +267,7 @@ impl State { action = self.draw_level_placement_overlay( egui_ctx, level_response, + painter, cursor_tile_pos, level_view, ); @@ -100,6 +291,7 @@ impl State { &self, egui_ctx: &egui::Context, level_response: &egui::Response, + painter: &egui::Painter, cursor_tile_pos: egui::Pos2, level_view: &LevelView, ) -> Option { @@ -109,6 +301,7 @@ impl State { EditorTool::TilePlacer => self.draw_level_tile_placement_overlay( egui_ctx, level_response, + painter, cursor_tile_pos, level_view, ), @@ -124,55 +317,50 @@ impl State { &self, egui_ctx: &egui::Context, level_response: &egui::Response, + painter: &egui::Painter, cursor_tile_pos: egui::Pos2, level_view: &LevelView, ) -> Option { let mut action = None; let map = &self.map_resource.map; - let tile_size = map.tile_size.to_egui(); - let level_top_left = level_response.rect.min.to_vec2(); + let tile_size = map.tile_size.into_egui(); + let level_top_left = level_response.rect.min; if cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { if let (Some(selected_tile), Some(selected_layer)) = (&self.selected_tile, &self.selected_layer) { - egui::containers::Area::new("selected tile overlay") - .order(egui::Order::Background) - .interactable(false) - .fixed_pos(world_to_screen_pos( - (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), - level_top_left, - level_view, - )) - .show(egui_ctx, |ui| { - let tileset = &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 (response, painter) = - ui.allocate_painter(tile_size, egui::Sense::hover()); - let mut tile_mesh = egui::Mesh::with_texture(texture_id); - tile_mesh.add_rect_with_uv( - response.rect, - uv, - egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), - ); - - painter.add(egui::Shape::mesh(tile_mesh)); - }); + let tileset = &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( + world_to_screen_pos( + (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), + level_top_left, + level_view, + ), + tile_size, + ), + uv, + egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), + ); if level_response.clicked() || level_response.dragged() { let input = egui_ctx.input(); @@ -240,13 +428,13 @@ fn screen_to_world_pos( level_top_left: egui::Vec2, level_view: &LevelView, ) -> egui::Pos2 { - p - level_top_left + level_view.position.to_egui() + p - level_top_left + level_view.position.into_egui() } fn world_to_screen_pos( p: egui::Pos2, - level_top_left: egui::Vec2, + level_top_left: egui::Pos2, level_view: &LevelView, ) -> egui::Pos2 { - p + level_top_left - level_view.position.to_egui() + p + level_top_left.to_vec2() - level_view.position.into_egui() } diff --git a/src/editor/util.rs b/src/editor/util.rs index c0c2e05bc1..177c95af4c 100644 --- a/src/editor/util.rs +++ b/src/editor/util.rs @@ -1,9 +1,9 @@ pub trait EguiCompatibleVec { - fn to_egui(self) -> egui::Vec2; + fn into_egui(self) -> egui::Vec2; } impl EguiCompatibleVec for macroquad::math::Vec2 { - fn to_egui(self) -> egui::Vec2 { + fn into_egui(self) -> egui::Vec2 { egui::vec2(self.x, self.y) } } From b18cd537f4e435d97d630302674fe6b22ef46654 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Tue, 12 Apr 2022 19:08:21 +0200 Subject: [PATCH 28/81] Add object tooltips + misc changes --- src/editor/actions.rs | 1 + src/editor/mod.rs | 12 +- src/editor/state/mod.rs | 12 +- src/editor/state/ui/level.rs | 422 +++++++++++++++++++---------------- src/map/mod.rs | 10 + 5 files changed, 258 insertions(+), 199 deletions(-) diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 3b03401df1..61611e6b2e 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -80,6 +80,7 @@ pub enum UiAction { SelectObject { index: usize, layer_id: String, + cursor_offset: egui::Vec2, }, CreateObject { id: String, diff --git a/src/editor/mod.rs b/src/editor/mod.rs index aab8803b73..1495f6835f 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -17,7 +17,7 @@ pub use input::EditorInputScheme; use crate::{ editor::{ - state::{EditorTool, TileSelection}, + state::{EditorTool, SelectableEntity, TileSelection}, windows::{CreateTilesetWindow, MenuWindow}, }, map::{Map, MapLayerKind}, @@ -254,6 +254,16 @@ impl Editor { UiAction::QuitToDesktop => { crate::quit_to_desktop(); } + UiAction::SelectObject { + layer_id, + index, + cursor_offset, + } => { + self.state.selected_entity = Some(SelectableEntity { + click_offset: cursor_offset, + kind: state::SelectableEntityKind::Object { index, layer_id }, + }) + } _ => todo!(), } diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 61ea2138e0..ca34fa7b82 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -16,14 +16,14 @@ pub struct TileSelection { pub tile_id: u32, } -pub enum DraggableEntityKind { +pub enum SelectableEntityKind { Object { layer_id: String, index: usize }, SpawnPoint { index: usize }, } -pub struct DraggableEntity { - kind: DraggableEntityKind, - click_offset: egui::Vec2, +pub struct SelectableEntity { + pub kind: SelectableEntityKind, + pub click_offset: egui::Vec2, } /// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the @@ -35,7 +35,7 @@ pub struct State { pub selected_tile: Option, pub is_parallax_enabled: bool, pub should_draw_grid: bool, - pub entity_being_dragged: Option, + pub selected_entity: Option, } impl State { @@ -47,7 +47,7 @@ impl State { selected_tile: None, is_parallax_enabled: true, should_draw_grid: true, - entity_being_dragged: None, + selected_entity: None, } } diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs index 3082eb9d15..8ce3cc3e22 100644 --- a/src/editor/state/ui/level.rs +++ b/src/editor/state/ui/level.rs @@ -7,7 +7,7 @@ use crate::{ util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, view::LevelView, }, - map::MapObjectKind, + map::{MapObject, MapObjectKind}, AnimatedSpriteMetadata, Resources, Sprite, }; @@ -25,8 +25,6 @@ impl State { egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { - const FULL_UV: egui::Rect = - egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)); let texture_id = level_render_target.texture.egui_id(); let (response, painter) = @@ -39,207 +37,245 @@ impl State { ); painter.add(egui::Shape::mesh(level_mesh)); - let resources = storage::get::(); - for layer_idx in self.map_resource.map.draw_order.iter() { - let layer = if let Some(layer) = self.map_resource.map.layers.get(layer_idx) { - layer + self.draw_objects(egui_ctx, ui, &response, &painter, level_view); + + action.then_do( + self.draw_level_overlays(egui_ctx, ui, &response, &painter, level_view), + ); + + let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); + level_render_target.resize_if_appropiate(width, height); + }); + + action + } + + fn draw_objects( + &self, + egui_ctx: &egui::Context, + ui: &mut egui::Ui, + response: &egui::Response, + painter: &egui::Painter, + level_view: &LevelView, + ) -> Option { + const FULL_UV: egui::Rect = + egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)); + let mut action = None; + + let resources = storage::get::(); + for layer_idx in self.map_resource.map.draw_order.iter() { + let layer = if let Some(layer) = self.map_resource.map.layers.get(layer_idx) { + layer + } else { + continue; + }; + for (object_idx, object) in layer.objects.iter().enumerate() { + 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 = (object.position + offset).into_egui().to_pos2(); + + let dest = egui::Rect::from_min_size( + world_to_screen_pos(position_in_lvl, response.rect.min, level_view), + dest_size, + ); + + let mut mesh = egui::Mesh::with_texture(texture_id); + mesh.add_rect_with_uv(dest, uv, tint); + 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, + ) + }; + + 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) } else { - continue; - }; - for object in layer.objects.iter() { - let draw_object = - |texture_id: egui::TextureId, - offset: macroquad::math::Vec2, - dest_size: egui::Vec2, - uv: egui::Rect, - tint: egui::Color32| { - let position_in_lvl = - (object.position + offset).into_egui().to_pos2(); - - let target = egui::Rect::from_min_size( - world_to_screen_pos( - position_in_lvl, - response.rect.min, - level_view, - ), - dest_size, - ); - - let mut mesh = egui::Mesh::with_texture(texture_id); - mesh.add_rect_with_uv(target, uv, tint); - painter.add(egui::Shape::mesh(mesh)); - }; - - let draw_invalid_object = || { - 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( + // 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, ); - }; - - let draw_animated_sprite = - |sprite: &AnimatedSpriteMetadata, row: Option| { - 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); - } else { - // Invalid texture ID - draw_invalid_object(); - } - }; - - match object.kind { - MapObjectKind::Decoration => { - if let Some(meta) = resources.decoration.get(&object.id) { - draw_animated_sprite( - &meta.sprite, - meta.sprite.animations.first().map(|a| a.row), - ); - } else { - // Invalid object ID - draw_invalid_object(); - } - - /* - if response.hovered() { - if target.contains(ui.input().pointer.hover_pos().unwrap()) { - painter.add(egui::Shape::rect_stroke( - target, - egui::Rounding::none(), - egui::Stroke::new(2., egui::Color32::GRAY), - )); - egui::containers::show_tooltip_at_pointer( - egui_ctx, - egui::Id::new("object info"), - |ui| { - ui.centered_and_justified(|ui| { - ui.heading(&object.id); - ui.label( - egui::RichText::new("Decoration").small(), - ); - }); - ui.separator(); - ui.label(egui::RichText::new("Position: ").weak()); - ui.label( - egui::RichText::new(format!( - "({}, {})", - object.position.x, object.position.y - )) - .monospace(), - ); - }, - ); - } - }*/ - } - - MapObjectKind::Item => { - if let Some(meta) = resources.items.get(&object.id) { - 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(), - ), - ); - } else { - // Invalid object ID - draw_invalid_object(); - } - } - 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, - ); - - draw_object( - texture_id, - (0., 0.).into(), - dest_size, - uv, - egui::Color32::WHITE, - ); - } else { - // Invalid object ID - draw_invalid_object(); - } - } - }; + is_valid = true; + } else { + // Invalid object ID + dest = draw_invalid_object(); + is_valid = false; + } + } + }; + + if self.selected_tool == EditorTool::Cursor + && ui + .input() + .pointer + .hover_pos() + .map(|hover_pos| dest.contains(hover_pos)) + .unwrap_or(false) + { + if response.hovered() { + painter.add(egui::Shape::rect_stroke( + dest, + egui::Rounding::none(), + egui::Stroke::new(2., egui::Color32::GRAY), + )); + self.show_object_info_tooltip(egui_ctx, object, is_valid); + } + if response.clicked() { + let click_pos = ui.input().pointer.interact_pos().unwrap(); + action.then_do_some(UiAction::SelectObject { + index: object_idx, + layer_id: layer.id.clone(), + cursor_offset: dest.min - click_pos, + }); } } + } + } - action.then_do( - self.draw_level_overlays(egui_ctx, ui, &response, &painter, level_view), - ); + action + } - let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); - level_render_target.resize_if_appropiate(width, height); + 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()); }); - - action + 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), + ); + } + }); } fn draw_level_overlays( @@ -362,6 +398,8 @@ impl State { egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), ); + painter.add(egui::Shape::mesh(tile_mesh)); + if level_response.clicked() || level_response.dragged() { let input = egui_ctx.input(); if input.pointer.button_down(egui::PointerButton::Primary) { diff --git a/src/map/mod.rs b/src/map/mod.rs index b4cc362c52..a4d819accc 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -523,6 +523,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"; From d0d1ce5caddc8954540f29dfe693f1d2b2cc4e7d Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Tue, 12 Apr 2022 19:11:15 +0200 Subject: [PATCH 29/81] Update error/unknown sprite --- assets/textures/ui/editor_icons/error.png | Bin 239 -> 4363 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/textures/ui/editor_icons/error.png b/assets/textures/ui/editor_icons/error.png index aff533df4f98acfd4f3d5a65bc4e149baec0ae43..4e676ddde8ad088738fd7541ce2aa000f2695de9 100644 GIT binary patch literal 4363 zcmcIn3vg7`8Qzcr;b}A!Vq=EO!tff-?tQ;tfsh0UB_Sk15`mg??>#rUvb* zOi&C27^yH&uu@8`QNZG3sNjGJh*DcABFHclr?fJHU?H?T#sXr`-Q6rf6OyTQXEwQe z&wu{&|KIuk@BF8suwY8R-aqN>a=H5DP0cC7{|V=t(hdK;oqp*>mn%syva<`bG*dxD z(UeKVNJ^5#Q7+fW=hm-jo_4Ts!1Csd-1oOWn)2D-tr0T|{U;8r&YQE6DBY30_V;<& z>y?LsJ8N&IJ{!!O`{|NyBc>e4`d6>T$?tD_I(u2)?yJsR+k8fP{eH>@ulh=Rj$+#3*JjV(~?qmGJo%7_Ry_bdT`^kTbKV4SMfCoDG zdLjV}Tf1E2CPXZ#&PQQFK@~>O=Wf~C>?RD&=bp{wllfLQsx+o9vQhD(f)aJnd{xri z6UGzcA~JRmKw(Hk0{&n~j`-YBzcPMyUVGg{)FeFL=Z(%gP}M~ z7%*=HT3*USIw?gBH8o*pRoVXN;+pD3eiT5#a0uH{30teu44a`!vjf+O;d>Uq(B|hS z)@V1DKp?S1D4bi3(})Aou63wnp@qCfC}dXID$1?KVGeE0CKN70ck%4PbZnlO95(b0 z<~k$yB!wbI2d2(Q!qk$pZ3M%nU1FO4R_+wGqDW+CJ6w^7k+Y4UX4Zs8Icdb_pd1)R zJ~vHb29PwM$P!ABIZiqg3{BQKhNLu3 z2O=dQph&C^6owOkq)@y-Ls8*SEDJn8$5g8vzB%(_rKg!H_HlPr$g(0QX%HwH!vb1l zMIbREeiB5LQgoiAAm19z4BNof5B+VuI#FrZBUE_>sUnYK(MdoGI4V)&NFZW$MhwZ2 zh{IVq8!lrvI3(zln9ohcUzR$=^H;@Q3N(p7Uds6!A1~`<{O<6qpn)N#-GoD#7^=ls zy)dji$1z;|eW4LS{?Z%+qZ5h+!ZA3&=$zODU07c*wKtYL?6dF=^Q@iR5 zgCjZ0060?Q0MD`x?G+6`g`osdCrM2gyXs3{ipJqwR1pX~-Z=fz?7zPLs7YtNr4UiH?(mcm= zm}IRew?zqeLQ)CE$s{e)i399^L@BPmK zVQNDfDKrUTd-=3`%Kq^AsGahlldX5yk)TS)xM|2X+AyIl+Y%uVQQrN3*6WmaUF#*j zTb)1rK1DSvCOzJ7t5WQDP7KV+PMJpgQ@K6;=_(U(!z$z6sFQ9Z&(Zwv7GxreW8om*#Fusc-5&CTDx`=il^$*F2+g z*U_slG=9=;#f69Sno7|}-chK$eB|XbOJ{AcQvV@j_FvS;_xj4TD@lzPuDJ4QjuD5H zfwwQrJCT=Mc9d+)*m>v1lpeG0oMFxd>Q67_R`vZ_laKzV{9IFpwf6aeuZ;iO*3tFz zlFz@q#kx`RV%Z~a+|2jSG zW7kl9$uB%Nu6;IVa!N+9XV&pQjTt@ccc@{(xpxl@oLjW4&)8l@*{;_oG2zpjPXBE9 z_C34KUQ2p&YvlGDmktMRUp_b}r!=#FQtJ6Mc5_|#uPzY}rFuqNr?OUGZFtkQCi6X) z|N4QmFK@h9?U^%j2>8`PYfICW+JYw^oPPf76J9u@Q9gI0?<={_G`*9STr~L>9XNP= z>`B6J{20`qoNe876@T&8tIIEL&b;~Vo?TAIdSzeJZ;w21c3+d-@X@lRO9u@8bi?(D zS8aLwQ$Jli{HuSCs7xAOH@okii`LxoFJ^CIce`BEA6fg_(;L3L0Sh4oGrw`6@YA`z z5j2z(Jxrt+giDMPJ$M~}3zWUXt_`0>xb%?a0BVB4>i|M=O>+lO6WsmrgZ2&}6k=UM z|ES{te7+$EkQD;Bd_y+}kOByW7+n$}z7QL{<-n)|MjbHffL8%`0Je4 Date: Tue, 12 Apr 2022 19:43:04 +0200 Subject: [PATCH 30/81] Dragging objects --- src/editor/actions.rs | 1 + src/editor/mod.rs | 13 +++++++++++++ src/editor/state/mod.rs | 12 ++++++++++++ src/editor/state/ui/level.rs | 37 +++++++++++++++++++++++++++++++++--- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 61611e6b2e..06f6bae742 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -82,6 +82,7 @@ pub enum UiAction { layer_id: String, cursor_offset: egui::Vec2, }, + DeselectObject, CreateObject { id: String, kind: MapObjectKind, diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 1495f6835f..82085c2d07 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -248,6 +248,18 @@ impl Editor { .apply(action, &mut self.state.map_resource.map) .unwrap(); } + UiAction::UpdateObject { + id, + index, + kind, + layer_id, + position, + } => { + let action = actions::UpdateObject::new(layer_id, index, id, kind, position); + self.history + .apply(action, &mut self.state.map_resource.map) + .unwrap(); + } UiAction::ExitToMainMenu => { crate::exit_to_main_menu(); } @@ -264,6 +276,7 @@ impl Editor { kind: state::SelectableEntityKind::Object { index, layer_id }, }) } + UiAction::DeselectObject => self.state.selected_entity = None, _ => todo!(), } diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index ca34fa7b82..4259cdab6b 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -26,6 +26,18 @@ pub struct SelectableEntity { pub click_offset: egui::Vec2, } +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, + } + } +} + /// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the /// editor. pub struct State { diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs index 8ce3cc3e22..f158782f23 100644 --- a/src/editor/state/ui/level.rs +++ b/src/editor/state/ui/level.rs @@ -37,7 +37,7 @@ impl State { ); painter.add(egui::Shape::mesh(level_mesh)); - self.draw_objects(egui_ctx, ui, &response, &painter, level_view); + action.then_do(self.draw_objects(egui_ctx, ui, &response, &painter, level_view)); action.then_do( self.draw_level_overlays(egui_ctx, ui, &response, &painter, level_view), @@ -216,7 +216,38 @@ impl State { } }; - if self.selected_tool == EditorTool::Cursor + if self + .selected_entity + .as_ref() + .map(|selection| selection.kind.is_object(&layer.id, object_idx)) + .unwrap_or(false) + { + if response.dragged() { + painter.add(egui::Shape::rect_stroke( + dest, + egui::Rounding::none(), + egui::Stroke::new(2., egui::Color32::WHITE), + )); + let cursor_offset = self.selected_entity.as_ref().unwrap().click_offset; + let cursor_level_pos = screen_to_world_pos( + ui.input().pointer.interact_pos().unwrap() + cursor_offset, + response.rect.min.to_vec2(), + level_view, + ); + action.then_do_some(UiAction::UpdateObject { + id: object.id.clone(), + index: object_idx, + kind: object.kind, + layer_id: layer.id.clone(), + position: macroquad::math::Vec2::new( + cursor_level_pos.x, + cursor_level_pos.y, + ), + }); + } else if response.drag_released() { + action.then_do_some(UiAction::DeselectObject); + } + } else if self.selected_tool == EditorTool::Cursor && ui .input() .pointer @@ -232,7 +263,7 @@ impl State { )); self.show_object_info_tooltip(egui_ctx, object, is_valid); } - if response.clicked() { + if response.drag_started() { let click_pos = ui.input().pointer.interact_pos().unwrap(); action.then_do_some(UiAction::SelectObject { index: object_idx, From 82a27e49792fb3e68c9da778b91f01d232b3b9bf Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Tue, 12 Apr 2022 19:45:07 +0200 Subject: [PATCH 31/81] Add undo/redo functionality --- src/editor/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 82085c2d07..c18361f781 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -106,6 +106,13 @@ impl Node for EditorNode { )) }; } + + if input.undo { + node.editor.apply_action(UiAction::Undo); + } + if input.redo { + node.editor.apply_action(UiAction::Redo); + } } fn draw(mut node: RefMut) From 6b94846618d09228e3993e1d868fbf96c33907a2 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Tue, 12 Apr 2022 19:58:44 +0200 Subject: [PATCH 32/81] Separate move object action from update object --- src/editor/actions.rs | 7 +++ src/editor/actions/move_object.rs | 75 +++++++++++++++++++++++++++++ src/editor/actions/update_object.rs | 11 +---- src/editor/mod.rs | 6 +-- src/editor/state/ui/level.rs | 4 +- 5 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 src/editor/actions/move_object.rs diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 06f6bae742..035978aa2c 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -98,6 +98,10 @@ pub enum UiAction { index: usize, id: String, kind: MapObjectKind, + }, + MoveObject { + layer_id: String, + index: usize, position: Vec2, }, CreateSpawnPoint(Vec2), @@ -257,6 +261,9 @@ pub use delete_object::*; mod update_object; pub use update_object::*; +mod move_object; +pub use move_object::*; + mod create_spawn_point; pub use create_spawn_point::*; diff --git a/src/editor/actions/move_object.rs b/src/editor/actions/move_object.rs new file mode 100644 index 0000000000..e4a00558df --- /dev/null +++ b/src/editor/actions/move_object.rs @@ -0,0 +1,75 @@ +use core::error::ErrorKind; + +use core::error::Error; + +use core::error::Result; + +use macroquad::prelude::Vec2; + +use crate::map::Map; + +use super::UndoableAction; + +use crate::map::MapObject; + +use crate::map::MapObjectKind; + +#[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/update_object.rs b/src/editor/actions/update_object.rs index cb50c0dd00..b2bce566b7 100644 --- a/src/editor/actions/update_object.rs +++ b/src/editor/actions/update_object.rs @@ -20,24 +20,16 @@ pub struct UpdateObject { index: usize, id: String, kind: MapObjectKind, - position: Vec2, object: Option, } impl UpdateObject { - pub fn new( - layer_id: String, - index: usize, - id: String, - kind: MapObjectKind, - position: Vec2, - ) -> Self { + pub fn new(layer_id: String, index: usize, id: String, kind: MapObjectKind) -> Self { UpdateObject { layer_id, index, id, kind, - position, object: None, } } @@ -51,7 +43,6 @@ impl UndoableAction for UpdateObject { object.id = self.id.clone(); object.kind = self.kind; - object.position = self.position; } else { return Err(Error::new_const( ErrorKind::EditorAction, diff --git a/src/editor/mod.rs b/src/editor/mod.rs index c18361f781..a11cbadad7 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -255,14 +255,12 @@ impl Editor { .apply(action, &mut self.state.map_resource.map) .unwrap(); } - UiAction::UpdateObject { - id, + UiAction::MoveObject { index, - kind, layer_id, position, } => { - let action = actions::UpdateObject::new(layer_id, index, id, kind, position); + let action = actions::MoveObject::new(layer_id, index, position); self.history .apply(action, &mut self.state.map_resource.map) .unwrap(); diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs index f158782f23..47d7a98f45 100644 --- a/src/editor/state/ui/level.rs +++ b/src/editor/state/ui/level.rs @@ -234,10 +234,8 @@ impl State { response.rect.min.to_vec2(), level_view, ); - action.then_do_some(UiAction::UpdateObject { - id: object.id.clone(), + action.then_do_some(UiAction::MoveObject { index: object_idx, - kind: object.kind, layer_id: layer.id.clone(), position: macroquad::math::Vec2::new( cursor_level_pos.x, From 7b76324d866aa325bf358af9d24d6a62845d7cdc Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 15 Apr 2022 13:05:55 +0200 Subject: [PATCH 33/81] Add save window --- src/editor/input.rs | 94 ++++++++++++++++++---------------- src/editor/mod.rs | 70 +++++++++++++++++++------ src/editor/windows/mod.rs | 3 ++ src/editor/windows/save_map.rs | 82 +++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 60 deletions(-) create mode 100644 src/editor/windows/save_map.rs diff --git a/src/editor/input.rs b/src/editor/input.rs index d01b94c5d0..46137bd055 100644 --- a/src/editor/input.rs +++ b/src/editor/input.rs @@ -30,67 +30,71 @@ pub struct EditorInput { } impl EditorInputScheme { - pub fn collect_input(&self) -> EditorInput { + pub fn collect_input(&self, keyboard_enabled: bool, mouse_enabled: bool) -> EditorInput { let mut input = EditorInput::default(); match self { 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 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; + } } - 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; + 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); + 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::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 { - 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) => { diff --git a/src/editor/mod.rs b/src/editor/mod.rs index a11cbadad7..d1bc6611ec 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,4 +1,4 @@ -use std::ops::ControlFlow; +use std::{ops::ControlFlow, path::Path}; mod state; mod util; @@ -17,27 +17,28 @@ pub use input::EditorInputScheme; use crate::{ editor::{ - state::{EditorTool, SelectableEntity, TileSelection}, + state::{EditorTool, TileSelection}, windows::{CreateTilesetWindow, MenuWindow}, }, map::{Map, MapLayerKind}, + resources::{map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION}, + Resources, }; use macroquad::{ experimental::scene::{Node, RefMut}, - prelude::{render_target, RenderTarget}, + prelude::{collections::storage, render_target, RenderTarget}, }; use self::{ - state::State, view::LevelView, - windows::{CreateLayerResult, CreateTilesetResult, MenuResult}, + windows::{CreateLayerResult, CreateTilesetResult, MenuResult, SaveMapResult}, }; use crate::resources::MapResource; pub struct Editor { - state: State, + state: state::State, level_view: LevelView, @@ -46,6 +47,7 @@ pub struct Editor { create_layer_window: Option, create_tileset_window: Option, menu_window: Option, + save_map_window: Option, level_render_target: RenderTarget, } @@ -56,6 +58,7 @@ pub struct EditorNode { editor: Editor, accept_mouse_input: bool, + accept_kb_input: bool, input_scheme: EditorInputScheme, } @@ -66,6 +69,7 @@ impl EditorNode { Self { editor, accept_mouse_input: true, + accept_kb_input: true, input_scheme, } } @@ -75,7 +79,9 @@ impl Node for EditorNode { fn fixed_update(mut node: RefMut) { use macroquad::prelude::*; - let input = node.input_scheme.collect_input(); + let input = node + .input_scheme + .collect_input(node.accept_kb_input, node.accept_mouse_input); node.editor.level_view.position += input.camera_move_direction * Self::CAMERA_PAN_SPEED; @@ -123,7 +129,8 @@ impl Node for EditorNode { egui_macroquad::ui(|egui_ctx| { node.editor.ui(egui_ctx); - node.accept_mouse_input = !egui_ctx.wants_pointer_input() + node.accept_mouse_input = !egui_ctx.wants_pointer_input(); + node.accept_kb_input = !egui_ctx.wants_keyboard_input(); }); egui_macroquad::draw(); @@ -146,7 +153,7 @@ impl Editor { pub fn new(map_resource: MapResource) -> Self { Self { - state: State::new(map_resource), + state: state::State::new(map_resource), history: ActionHistory::new(), level_view: LevelView { position: Default::default(), @@ -157,6 +164,7 @@ impl Editor { create_layer_window: None, create_tileset_window: None, menu_window: None, + save_map_window: None, } } @@ -172,7 +180,10 @@ impl Editor { UiAction::SelectTool(tool) => self.state.selected_tool = tool, UiAction::OpenCreateLayerWindow => self.create_layer_window = Some(Default::default()), UiAction::OpenCreateTilesetWindow => { - self.create_tileset_window = Some(CreateTilesetWindow::new()) + self.create_tileset_window = Some(CreateTilesetWindow::new()); + } + UiAction::OpenSaveMapWindow => { + self.save_map_window = Some(windows::SaveMapWindow::new()); } UiAction::CreateLayer { id, @@ -276,12 +287,32 @@ impl Editor { index, cursor_offset, } => { - self.state.selected_entity = Some(SelectableEntity { + self.state.selected_entity = Some(state::SelectableEntity { click_offset: cursor_offset, kind: state::SelectableEntityKind::Object { index, layer_id }, }) } UiAction::DeselectObject => self.state.selected_entity = None, + UiAction::SaveMap(name) => { + let mut map_resource = self.state.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.state.map_resource = map_resource; + } + } _ => todo!(), } @@ -317,7 +348,18 @@ impl Editor { } } - if let Some(window) = &mut self.create_tileset_window { + 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(Some(name))); + self.save_map_window = None; + } + ControlFlow::Break(SaveMapResult::Close) => { + self.save_map_window = None; + } + } + } else if let Some(window) = &mut self.create_tileset_window { match window.ui(egui_ctx, &self.state.map_resource.map) { ControlFlow::Continue(()) => (), ControlFlow::Break(CreateTilesetResult::Create { @@ -334,9 +376,7 @@ impl Editor { self.create_tileset_window = None; } } - } - - if let Some(window) = &mut self.menu_window { + } else if let Some(window) = &mut self.menu_window { match window.ui(egui_ctx) { ControlFlow::Continue(()) => (), ControlFlow::Break(MenuResult::OpenCreateMapWindow) => { diff --git a/src/editor/windows/mod.rs b/src/editor/windows/mod.rs index f5495f2d28..5995c2b943 100644 --- a/src/editor/windows/mod.rs +++ b/src/editor/windows/mod.rs @@ -6,3 +6,6 @@ pub use create_tileset::*; mod menu; pub use menu::*; + +mod save_map; +pub use save_map::*; \ No newline at end of file diff --git a/src/editor/windows/save_map.rs b/src/editor/windows/save_map.rs new file mode 100644 index 0000000000..810b2dca68 --- /dev/null +++ b/src/editor/windows/save_map.rs @@ -0,0 +1,82 @@ +use std::{ + ops::ControlFlow, + path::{Path, PathBuf}, +}; + +use macroquad::prelude::collections::storage; + +use crate::{ + map::Map, + resources::{ + is_valid_map_export_path, map_name_to_filename, TextureKind, MAP_EXPORTS_DEFAULT_DIR, + MAP_EXPORTS_EXTENSION, + }, + Resources, +}; + +pub struct SaveMapWindow { + map_name: String, + overwrite_existing: bool, +} +pub enum SaveMapResult { + Save { name: String }, + Close, +} + +impl SaveMapWindow { + pub fn new() -> Self { + Self { + map_name: String::new(), + overwrite_existing: false, + } + } + + 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) + .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 + } +} From 3b5c6cbb3a2304bdd42d61c71966c99b989bdd7d Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 15 Apr 2022 20:24:19 +0200 Subject: [PATCH 34/81] Clean up --- src/editor/actions.rs | 4 +++- src/editor/actions/move_object.rs | 4 ---- src/editor/actions/update_object.rs | 2 -- src/editor/mod.rs | 6 +++--- src/editor/state/ui/level.rs | 2 +- src/editor/windows/save_map.rs | 8 ++------ 6 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 035978aa2c..d208a6199e 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -129,7 +129,9 @@ pub enum UiAction { OpenCreateMapWindow, OpenMap(usize), OpenLoadMapWindow, - SaveMap(Option), + SaveMap { + name: Option, + }, OpenSaveMapWindow, DeleteMap(usize), ExitToMainMenu, diff --git a/src/editor/actions/move_object.rs b/src/editor/actions/move_object.rs index e4a00558df..308f85f342 100644 --- a/src/editor/actions/move_object.rs +++ b/src/editor/actions/move_object.rs @@ -10,10 +10,6 @@ use crate::map::Map; use super::UndoableAction; -use crate::map::MapObject; - -use crate::map::MapObjectKind; - #[derive(Debug)] pub struct MoveObject { layer_id: String, diff --git a/src/editor/actions/update_object.rs b/src/editor/actions/update_object.rs index b2bce566b7..db791dff51 100644 --- a/src/editor/actions/update_object.rs +++ b/src/editor/actions/update_object.rs @@ -4,8 +4,6 @@ use core::error::Error; use core::error::Result; -use macroquad::prelude::Vec2; - use crate::map::Map; use super::UndoableAction; diff --git a/src/editor/mod.rs b/src/editor/mod.rs index d1bc6611ec..6097d56f58 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -293,7 +293,7 @@ impl Editor { }) } UiAction::DeselectObject => self.state.selected_entity = None, - UiAction::SaveMap(name) => { + UiAction::SaveMap { name } => { let mut map_resource = self.state.map_resource.clone(); if let Some(name) = name { @@ -352,7 +352,7 @@ impl Editor { match window.ui(egui_ctx) { ControlFlow::Continue(()) => (), ControlFlow::Break(SaveMapResult::Save { name }) => { - self.apply_action(UiAction::SaveMap(Some(name))); + self.apply_action(UiAction::SaveMap { name: Some(name) }); self.save_map_window = None; } ControlFlow::Break(SaveMapResult::Close) => { @@ -389,7 +389,7 @@ impl Editor { self.apply_action(UiAction::OpenSaveMapWindow) } ControlFlow::Break(MenuResult::SaveMap) => { - self.apply_action(UiAction::SaveMap(None)) + self.apply_action(UiAction::SaveMap { name: None }) } ControlFlow::Break(MenuResult::ExitToMainMenu) => { self.apply_action(UiAction::ExitToMainMenu) diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs index 47d7a98f45..4cb2170e4a 100644 --- a/src/editor/state/ui/level.rs +++ b/src/editor/state/ui/level.rs @@ -8,7 +8,7 @@ use crate::{ view::LevelView, }, map::{MapObject, MapObjectKind}, - AnimatedSpriteMetadata, Resources, Sprite, + AnimatedSpriteMetadata, Resources, }; use super::super::State; diff --git a/src/editor/windows/save_map.rs b/src/editor/windows/save_map.rs index 810b2dca68..b7d00c405d 100644 --- a/src/editor/windows/save_map.rs +++ b/src/editor/windows/save_map.rs @@ -1,14 +1,10 @@ -use std::{ - ops::ControlFlow, - path::{Path, PathBuf}, -}; +use std::{ops::ControlFlow, path::Path}; use macroquad::prelude::collections::storage; use crate::{ - map::Map, resources::{ - is_valid_map_export_path, map_name_to_filename, TextureKind, MAP_EXPORTS_DEFAULT_DIR, + is_valid_map_export_path, map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION, }, Resources, From a9c4c20d885b487d285f06f864436b9baa5d2117 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 15 Apr 2022 20:57:44 +0200 Subject: [PATCH 35/81] Clean up object rendering --- src/editor/state/ui/level.rs | 507 --------------------------- src/editor/state/ui/level/mod.rs | 251 +++++++++++++ src/editor/state/ui/level/objects.rs | 310 ++++++++++++++++ 3 files changed, 561 insertions(+), 507 deletions(-) delete mode 100644 src/editor/state/ui/level.rs create mode 100644 src/editor/state/ui/level/mod.rs create mode 100644 src/editor/state/ui/level/objects.rs diff --git a/src/editor/state/ui/level.rs b/src/editor/state/ui/level.rs deleted file mode 100644 index 4cb2170e4a..0000000000 --- a/src/editor/state/ui/level.rs +++ /dev/null @@ -1,507 +0,0 @@ -use macroquad::prelude::{collections::storage, RenderTarget}; - -use crate::{ - editor::{ - actions::{UiAction, UiActionExt}, - state::EditorTool, - util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, - view::LevelView, - }, - map::{MapObject, MapObjectKind}, - AnimatedSpriteMetadata, Resources, -}; - -use super::super::State; - -impl State { - pub(super) fn draw_level( - &self, - egui_ctx: &egui::Context, - level_render_target: &mut RenderTarget, - level_view: &LevelView, - ) -> Option { - let mut action = None; - - egui::CentralPanel::default() - .frame(egui::Frame::none()) - .show(egui_ctx, |ui| { - let texture_id = 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)); - - action.then_do(self.draw_objects(egui_ctx, ui, &response, &painter, level_view)); - - action.then_do( - self.draw_level_overlays(egui_ctx, ui, &response, &painter, level_view), - ); - - let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); - level_render_target.resize_if_appropiate(width, height); - }); - - action - } - - fn draw_objects( - &self, - egui_ctx: &egui::Context, - ui: &mut egui::Ui, - response: &egui::Response, - painter: &egui::Painter, - level_view: &LevelView, - ) -> Option { - const FULL_UV: egui::Rect = - egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)); - let mut action = None; - - let resources = storage::get::(); - for layer_idx in self.map_resource.map.draw_order.iter() { - let layer = if let Some(layer) = self.map_resource.map.layers.get(layer_idx) { - layer - } else { - continue; - }; - for (object_idx, object) in layer.objects.iter().enumerate() { - 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 = (object.position + offset).into_egui().to_pos2(); - - let dest = egui::Rect::from_min_size( - world_to_screen_pos(position_in_lvl, response.rect.min, level_view), - dest_size, - ); - - let mut mesh = egui::Mesh::with_texture(texture_id); - mesh.add_rect_with_uv(dest, uv, tint); - 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, - ) - }; - - 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) - } 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, - ); - is_valid = true; - } else { - // Invalid object ID - dest = draw_invalid_object(); - is_valid = false; - } - } - }; - - if self - .selected_entity - .as_ref() - .map(|selection| selection.kind.is_object(&layer.id, object_idx)) - .unwrap_or(false) - { - if response.dragged() { - painter.add(egui::Shape::rect_stroke( - dest, - egui::Rounding::none(), - egui::Stroke::new(2., egui::Color32::WHITE), - )); - let cursor_offset = self.selected_entity.as_ref().unwrap().click_offset; - let cursor_level_pos = screen_to_world_pos( - ui.input().pointer.interact_pos().unwrap() + cursor_offset, - response.rect.min.to_vec2(), - level_view, - ); - action.then_do_some(UiAction::MoveObject { - index: object_idx, - layer_id: layer.id.clone(), - position: macroquad::math::Vec2::new( - cursor_level_pos.x, - cursor_level_pos.y, - ), - }); - } else if response.drag_released() { - action.then_do_some(UiAction::DeselectObject); - } - } else if self.selected_tool == EditorTool::Cursor - && ui - .input() - .pointer - .hover_pos() - .map(|hover_pos| dest.contains(hover_pos)) - .unwrap_or(false) - { - if response.hovered() { - painter.add(egui::Shape::rect_stroke( - dest, - egui::Rounding::none(), - egui::Stroke::new(2., egui::Color32::GRAY), - )); - self.show_object_info_tooltip(egui_ctx, object, is_valid); - } - if response.drag_started() { - let click_pos = ui.input().pointer.interact_pos().unwrap(); - action.then_do_some(UiAction::SelectObject { - index: object_idx, - layer_id: layer.id.clone(), - cursor_offset: dest.min - click_pos, - }); - } - } - } - } - - action - } - - 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), - ); - } - }); - } - - fn draw_level_overlays( - &self, - egui_ctx: &egui::Context, - ui: &mut egui::Ui, - level_response: &egui::Response, - painter: &egui::Painter, - level_view: &LevelView, - ) -> Option { - let action; - - if level_response.hovered() { - let map = &self.map_resource.map; - let tile_size = map.tile_size.into_egui(); - - let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); - let cursor_px_pos = screen_to_world_pos( - cursor_screen_pos, - level_response.rect.min.to_vec2(), - level_view, - ); - let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); - - action = self.draw_level_placement_overlay( - egui_ctx, - level_response, - painter, - cursor_tile_pos, - level_view, - ); - - let level_top_left = level_response.rect.min; - self.draw_level_pointer_pos_overlay( - egui_ctx, - ui, - level_top_left, - cursor_px_pos, - cursor_tile_pos, - ); - } else { - action = None; - } - - action - } - - fn draw_level_placement_overlay( - &self, - egui_ctx: &egui::Context, - level_response: &egui::Response, - painter: &egui::Painter, - cursor_tile_pos: egui::Pos2, - level_view: &LevelView, - ) -> Option { - let action; - - action = match self.selected_tool { - EditorTool::TilePlacer => self.draw_level_tile_placement_overlay( - egui_ctx, - level_response, - painter, - cursor_tile_pos, - level_view, - ), - // TODO: Spawnpoint placement overlay - // TODO: Object placement overlay - _ => None, - }; - - action - } - - fn draw_level_tile_placement_overlay( - &self, - egui_ctx: &egui::Context, - level_response: &egui::Response, - painter: &egui::Painter, - cursor_tile_pos: egui::Pos2, - level_view: &LevelView, - ) -> Option { - let mut action = None; - let map = &self.map_resource.map; - let tile_size = map.tile_size.into_egui(); - let level_top_left = level_response.rect.min; - - if cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { - if let (Some(selected_tile), Some(selected_layer)) = - (&self.selected_tile, &self.selected_layer) - { - let tileset = &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( - world_to_screen_pos( - (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), - level_top_left, - level_view, - ), - tile_size, - ), - uv, - egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), - ); - - painter.add(egui::Shape::mesh(tile_mesh)); - - if level_response.clicked() || level_response.dragged() { - let input = egui_ctx.input(); - if input.pointer.button_down(egui::PointerButton::Primary) { - action.then_do_some(UiAction::PlaceTile { - id: selected_tile.tile_id, - layer_id: selected_layer.clone(), - tileset_id: selected_tile.tileset.clone(), - 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) { - action.then_do_some(UiAction::RemoveTile { - layer_id: selected_layer.clone(), - coords: macroquad::math::UVec2::new( - cursor_tile_pos.x as u32, - cursor_tile_pos.y as u32, - ), - }); - } - } - } - } - - action - } - - fn draw_level_pointer_pos_overlay( - &self, - egui_ctx: &egui::Context, - ui: &mut egui::Ui, - level_top_left: egui::Pos2, - cursor_px_pos: egui::Pos2, - cursor_tile_pos: egui::Pos2, - ) { - egui::containers::Area::new("pointer pos overlay") - .order(egui::Order::Tooltip) - .fixed_pos( - 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(egui_ctx, |ui| { - egui::Frame::popup(&egui_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 - }); - } -} - -// TODO: Factor in level view scale -fn screen_to_world_pos( - p: egui::Pos2, - level_top_left: egui::Vec2, - level_view: &LevelView, -) -> egui::Pos2 { - p - level_top_left + level_view.position.into_egui() -} - -fn world_to_screen_pos( - p: egui::Pos2, - level_top_left: egui::Pos2, - level_view: &LevelView, -) -> egui::Pos2 { - p + level_top_left.to_vec2() - level_view.position.into_egui() -} diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs new file mode 100644 index 0000000000..2be70ce788 --- /dev/null +++ b/src/editor/state/ui/level/mod.rs @@ -0,0 +1,251 @@ +mod objects; + +use macroquad::prelude::{collections::storage, RenderTarget}; + +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::EditorTool, + util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, + view::LevelView, + }, + Resources, +}; + +use super::super::State; + +impl State { + pub(super) fn draw_level( + &self, + egui_ctx: &egui::Context, + level_render_target: &mut RenderTarget, + level_view: &LevelView, + ) -> Option { + let mut action = None; + + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(egui_ctx, |ui| { + let texture_id = 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)); + + action.then_do(self.draw_objects(egui_ctx, ui, &response, &painter, level_view)); + + action.then_do( + self.draw_level_overlays(egui_ctx, ui, &response, &painter, level_view), + ); + + let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); + level_render_target.resize_if_appropiate(width, height); + }); + + action + } + + fn draw_level_overlays( + &self, + egui_ctx: &egui::Context, + ui: &mut egui::Ui, + level_response: &egui::Response, + painter: &egui::Painter, + level_view: &LevelView, + ) -> Option { + let action; + + if level_response.hovered() { + let map = &self.map_resource.map; + let tile_size = map.tile_size.into_egui(); + + let cursor_screen_pos = ui.input().pointer.interact_pos().unwrap(); + let cursor_px_pos = screen_to_world_pos( + cursor_screen_pos, + level_response.rect.min.to_vec2(), + level_view, + ); + let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); + + action = self.draw_level_placement_overlay( + egui_ctx, + level_response, + painter, + cursor_tile_pos, + level_view, + ); + + let level_top_left = level_response.rect.min; + self.draw_level_pointer_pos_overlay( + egui_ctx, + ui, + level_top_left, + cursor_px_pos, + cursor_tile_pos, + ); + } else { + action = None; + } + + action + } + + fn draw_level_placement_overlay( + &self, + egui_ctx: &egui::Context, + level_response: &egui::Response, + painter: &egui::Painter, + cursor_tile_pos: egui::Pos2, + level_view: &LevelView, + ) -> Option { + let action; + + action = match self.selected_tool { + EditorTool::TilePlacer => self.draw_level_tile_placement_overlay( + egui_ctx, + level_response, + painter, + cursor_tile_pos, + level_view, + ), + // TODO: Spawnpoint placement overlay + // TODO: Object placement overlay + _ => None, + }; + + action + } + + fn draw_level_tile_placement_overlay( + &self, + egui_ctx: &egui::Context, + level_response: &egui::Response, + painter: &egui::Painter, + cursor_tile_pos: egui::Pos2, + level_view: &LevelView, + ) -> Option { + let mut action = None; + let map = &self.map_resource.map; + let tile_size = map.tile_size.into_egui(); + let level_top_left = level_response.rect.min; + + if cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { + if let (Some(selected_tile), Some(selected_layer)) = + (&self.selected_tile, &self.selected_layer) + { + let tileset = &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( + world_to_screen_pos( + (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), + level_top_left, + level_view, + ), + tile_size, + ), + uv, + egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), + ); + + painter.add(egui::Shape::mesh(tile_mesh)); + + if level_response.clicked() || level_response.dragged() { + let input = egui_ctx.input(); + if input.pointer.button_down(egui::PointerButton::Primary) { + action.then_do_some(UiAction::PlaceTile { + id: selected_tile.tile_id, + layer_id: selected_layer.clone(), + tileset_id: selected_tile.tileset.clone(), + 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) { + action.then_do_some(UiAction::RemoveTile { + layer_id: selected_layer.clone(), + coords: macroquad::math::UVec2::new( + cursor_tile_pos.x as u32, + cursor_tile_pos.y as u32, + ), + }); + } + } + } + } + + action + } + + fn draw_level_pointer_pos_overlay( + &self, + egui_ctx: &egui::Context, + ui: &mut egui::Ui, + level_top_left: egui::Pos2, + cursor_px_pos: egui::Pos2, + cursor_tile_pos: egui::Pos2, + ) { + egui::containers::Area::new("pointer pos overlay") + .order(egui::Order::Tooltip) + .fixed_pos( + 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(egui_ctx, |ui| { + egui::Frame::popup(&egui_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 + }); + } +} + +// TODO: Factor in level view scale +fn screen_to_world_pos( + p: egui::Pos2, + level_top_left: egui::Vec2, + level_view: &LevelView, +) -> egui::Pos2 { + p - level_top_left + level_view.position.into_egui() +} + +fn world_to_screen_pos( + p: egui::Pos2, + level_top_left: egui::Pos2, + level_view: &LevelView, +) -> egui::Pos2 { + p + level_top_left.to_vec2() - level_view.position.into_egui() +} diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs new file mode 100644 index 0000000000..71a1f0846f --- /dev/null +++ b/src/editor/state/ui/level/objects.rs @@ -0,0 +1,310 @@ +use std::ops::Deref; + +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::{UiAction, UiActionExt}, + state::{ + ui::level::{screen_to_world_pos, world_to_screen_pos}, + EditorTool, State, + }, + util::{EguiCompatibleVec, EguiTextureHandler}, + view::LevelView, + }, + map::{MapObject, MapObjectKind}, + AnimatedSpriteMetadata, Resources, +}; + +impl State { + pub(super) fn draw_objects( + &self, + egui_ctx: &egui::Context, + ui: &mut egui::Ui, + response: &egui::Response, + painter: &egui::Painter, + level_view: &LevelView, + ) -> Option { + let mut action = None; + + self.map_resource + .map + .draw_order + .iter() + .filter_map(|layer_idx| self.map_resource.map.layers.get(layer_idx)) + .for_each(|layer| { + action.then_do( + self.draw_object_layer(layer, response, painter, ui, level_view, egui_ctx), + ); + }); + + action + } + + fn draw_object_layer( + &self, + layer: &crate::map::MapLayer, + response: &egui::Response, + painter: &egui::Painter, + ui: &mut egui::Ui, + level_view: &LevelView, + egui_ctx: &egui::Context, + ) -> Option { + let mut action = None; + let resources = storage::get::(); + + for (object_idx, object) in layer.objects.iter().enumerate() { + let (dest, is_valid) = draw_object(object, response, level_view, painter, &resources); + + let is_this_object_selected; + let can_select_object; + if let Some(selected_entity) = &self.selected_entity { + is_this_object_selected = selected_entity.kind.is_object(&layer.id, object_idx); + can_select_object = false; + } else { + is_this_object_selected = false; + can_select_object = self.selected_tool == EditorTool::Cursor + && response.hovered() + && ui + .input() + .pointer + .hover_pos() + .map(|hover_pos| dest.contains(hover_pos)) + .unwrap_or(false); + } + + if can_select_object || is_this_object_selected { + painter.add(egui::Shape::rect_stroke( + dest, + egui::Rounding::none(), + egui::Stroke::new( + 1., + if is_this_object_selected { + egui::Color32::WHITE + } else { + egui::Color32::GRAY + }, + ), + )); + } + + if is_this_object_selected { + if response.dragged() { + let cursor_offset = self.selected_entity.as_ref().unwrap().click_offset; + let cursor_level_pos = screen_to_world_pos( + ui.input().pointer.interact_pos().unwrap() + cursor_offset, + response.rect.min.to_vec2(), + level_view, + ); + action.then_do_some(UiAction::MoveObject { + index: object_idx, + layer_id: layer.id.clone(), + position: macroquad::math::Vec2::new( + cursor_level_pos.x, + cursor_level_pos.y, + ), + }); + } else if response.drag_released() { + action.then_do_some(UiAction::DeselectObject); + } + } else if can_select_object { + self.show_object_info_tooltip(egui_ctx, object, is_valid); + + if response.drag_started() { + let click_pos = ui.input().pointer.interact_pos().unwrap(); + action.then_do_some(UiAction::SelectObject { + index: object_idx, + layer_id: layer.id.clone(), + cursor_offset: dest.min - click_pos, + }); + } + } + } + + action + } + + 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), + ); + } + }); + } +} + +fn draw_object( + object: &crate::map::MapObject, + response: &egui::Response, + level_view: &LevelView, + painter: &egui::Painter, + resources: &impl Deref, +) -> (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 = (object.position + offset).into_egui().to_pos2(); + + let dest = egui::Rect::from_min_size( + world_to_screen_pos(position_in_lvl, response.rect.min, level_view), + dest_size, + ); + + let mut mesh = egui::Mesh::with_texture(texture_id); + mesh.add_rect_with_uv(dest, uv, tint); + 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, + ) + }; + + 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) + } 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, + ); + is_valid = true; + } else { + // Invalid object ID + dest = draw_invalid_object(); + is_valid = false; + } + } + }; + + (dest, is_valid) +} From b9cf04dd2174f954225db4b3a9edbfcb0b043448 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 13:47:01 +0200 Subject: [PATCH 36/81] Update egui-macroquad --- Cargo.lock | 30 ++++++++++++++---------------- Cargo.toml | 8 +++++++- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b220a997a..c3f52702cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,8 +259,7 @@ dependencies = [ [[package]] name = "egui-macroquad" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a600e32edf2775b3ff8017eac144f954c2db04a2d8c0eeda703f58f147c4380a" +source = "git+https://github.com/aleokdev/egui-macroquad#d202b747954541e11bc67b15052a67d630e93801" dependencies = [ "egui", "egui-miniquad", @@ -270,8 +269,7 @@ dependencies = [ [[package]] name = "egui-miniquad" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f1c9544e632b65e1adb64cf01e8844c8d228d1179d18dc5525dba6718990e1" +source = "git+https://github.com/not-fl3/egui-miniquad/?rev=8f493a03b98faf976374f8aea7d2448ffa4d115c#8f493a03b98faf976374f8aea7d2448ffa4d115c" dependencies = [ "bytemuck", "copypasta", @@ -522,9 +520,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.122" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" +checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" [[package]] name = "libloading" @@ -566,9 +564,9 @@ dependencies = [ [[package]] name = "macroquad" -version = "0.3.10" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54a31d3996831b27b429f8e9d7b948aa589d3571305eba39f57b4600bd2cd01" +checksum = "e69f39283065a2ebea3ae41294a44a9d61951d255917bdcc9ada0ec97612499f" dependencies = [ "bumpalo", "fontdue", @@ -642,9 +640,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniquad" -version = "0.3.0-alpha.43" +version = "0.3.0-alpha.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b1edf0ba64f267a4cc0ba453d95da072bfa078d755917e1411e4dc20b729da8" +checksum = "a5b39924083bf7da876e2d7125487eef77ea30f0c36a58fdfd04cb436eafaf5e" dependencies = [ "sapp-android", "sapp-darwin", @@ -927,9 +925,9 @@ dependencies = [ [[package]] name = "sapp-darwin" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd874867dc35d9a42bba830cee0210702438082c7d6a39132ab420e6c20a5809" +checksum = "f1282369f486bc334efa2d76fa4d54c20b37664d83da14fc60afbe941814e374" dependencies = [ "cc", ] @@ -960,9 +958,9 @@ 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", ] @@ -1162,9 +1160,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", ] diff --git a/Cargo.toml b/Cargo.toml index 06d2aa92ef..614a9a90bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,9 @@ 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" @@ -33,3 +35,7 @@ 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" } From 9b533fda8fa0e314a11476db04087e86451473eb Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 16:44:42 +0200 Subject: [PATCH 37/81] Object creation --- src/editor/RATIONALE.md | 4 - src/editor/mod.rs | 15 +++- src/editor/state/mod.rs | 22 +++-- src/editor/state/ui.rs | 2 +- src/editor/state/ui/level/mod.rs | 27 ++++-- src/editor/state/ui/level/objects.rs | 118 ++++++++++++++++++++++++++- src/editor/util.rs | 16 ++++ 7 files changed, 182 insertions(+), 22 deletions(-) diff --git a/src/editor/RATIONALE.md b/src/editor/RATIONALE.md index fdf83341a2..47f67c45d5 100644 --- a/src/editor/RATIONALE.md +++ b/src/editor/RATIONALE.md @@ -2,10 +2,6 @@ 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. -- `CreateLayerWindow` does not allow undoing, only the main editor context does. As such, its `ui` -method doesn't need to be immutable, because the changes done to the window are irrelevant to the -history and can occur without having to keep track of them. (PD: TextEdit changes are kept track of -internally by egui and can be undone/redone without any external code) - 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 diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 6097d56f58..6d391f8a7b 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -287,12 +287,12 @@ impl Editor { index, cursor_offset, } => { - self.state.selected_entity = Some(state::SelectableEntity { + self.state.selected_map_entity = Some(state::SelectableEntity { click_offset: cursor_offset, kind: state::SelectableEntityKind::Object { index, layer_id }, }) } - UiAction::DeselectObject => self.state.selected_entity = None, + UiAction::DeselectObject => self.state.selected_map_entity = None, UiAction::SaveMap { name } => { let mut map_resource = self.state.map_resource.clone(); @@ -313,6 +313,17 @@ impl Editor { self.state.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.state.map_resource.map) + .unwrap(); + } _ => todo!(), } diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 4259cdab6b..7092abf5f3 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -1,6 +1,9 @@ mod ui; -use crate::{map::MapLayerKind, resources::MapResource}; +use crate::{ + map::{MapLayerKind, MapObjectKind}, + resources::MapResource, +}; #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { @@ -38,6 +41,13 @@ impl SelectableEntityKind { } } +#[derive(Debug)] +pub struct ObjectSettings { + pub position: egui::Pos2, + pub kind: MapObjectKind, + pub id: Option, +} + /// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the /// editor. pub struct State { @@ -47,7 +57,8 @@ pub struct State { pub selected_tile: Option, pub is_parallax_enabled: bool, pub should_draw_grid: bool, - pub selected_entity: Option, + pub selected_map_entity: Option, + pub object_being_placed: Option, } impl State { @@ -55,11 +66,12 @@ impl State { Self { map_resource, selected_tool: EditorTool::Cursor, - selected_layer: None, - selected_tile: None, is_parallax_enabled: true, should_draw_grid: true, - selected_entity: None, + selected_layer: None, + selected_tile: None, + selected_map_entity: None, + object_being_placed: None, } } diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index 6b78e93ce8..5e0380b5aa 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -11,7 +11,7 @@ use crate::editor::{ impl super::State { pub fn ui( - &self, + &mut self, egui_ctx: &egui::Context, level_render_target: &mut RenderTarget, level_view: &LevelView, diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 2be70ce788..826b8b5e56 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -16,7 +16,7 @@ use super::super::State; impl State { pub(super) fn draw_level( - &self, + &mut self, egui_ctx: &egui::Context, level_render_target: &mut RenderTarget, level_view: &LevelView, @@ -52,16 +52,23 @@ impl State { } fn draw_level_overlays( - &self, + &mut self, egui_ctx: &egui::Context, ui: &mut egui::Ui, level_response: &egui::Response, painter: &egui::Painter, level_view: &LevelView, ) -> Option { - let action; + let mut action = None; + + let level_contains_cursor = ui + .input() + .pointer + .hover_pos() + .map(|pos| level_response.rect.contains(pos)) + .unwrap_or(false); - if level_response.hovered() { + if level_contains_cursor { let map = &self.map_resource.map; let tile_size = map.tile_size.into_egui(); @@ -73,13 +80,13 @@ impl State { ); let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); - action = self.draw_level_placement_overlay( + action.then_do(self.draw_level_placement_overlay( egui_ctx, level_response, painter, cursor_tile_pos, level_view, - ); + )); let level_top_left = level_response.rect.min; self.draw_level_pointer_pos_overlay( @@ -89,6 +96,14 @@ impl State { cursor_px_pos, cursor_tile_pos, ); + + action.then_do(self.draw_level_object_placement_overlay( + egui_ctx, + level_response, + painter, + cursor_tile_pos, + level_view, + )); } else { action = None; } diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 71a1f0846f..6ccd174169 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -7,9 +7,9 @@ use crate::{ actions::{UiAction, UiActionExt}, state::{ ui::level::{screen_to_world_pos, world_to_screen_pos}, - EditorTool, State, + EditorTool, ObjectSettings, State, }, - util::{EguiCompatibleVec, EguiTextureHandler}, + util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec}, view::LevelView, }, map::{MapObject, MapObjectKind}, @@ -17,6 +17,116 @@ use crate::{ }; impl State { + pub(super) fn draw_level_object_placement_overlay( + &mut self, + egui_ctx: &egui::Context, + level_response: &egui::Response, + painter: &egui::Painter, + cursor_tile_pos: egui::Pos2, + level_view: &LevelView, + ) -> Option { + let mut action = None; + if let Some(settings) = &mut self.object_being_placed { + let position = + world_to_screen_pos(settings.position, level_response.rect.min, level_view); + + let mut closed_window = false; + + egui::Window::new("Placing object") + .fixed_pos(position) + .collapsible(false) + .resizable(false) + .show(egui_ctx, |ui| { + 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(), egui::Button::new("Create")); + if create_button.clicked() { + action = Some(UiAction::CreateObject { + id: settings.id.as_ref().unwrap().clone(), + kind: settings.kind.clone(), + position: settings.position.into_macroquad(), + // TODO: Include layer id in object settings + layer_id: self.selected_layer.as_ref().unwrap().clone(), + }); + + closed_window = true; + } + if ui.button("Cancel").clicked() { + closed_window = true; + } + }); + }); + + if closed_window { + self.object_being_placed = None; + } + } else { + if self.selected_tool == EditorTool::ObjectPlacer + && level_response.clicked_by(egui::PointerButton::Primary) + { + let position = screen_to_world_pos( + egui_ctx.input().pointer.interact_pos().unwrap(), + level_response.rect.min.to_vec2(), + level_view, + ); + + self.object_being_placed = Some(ObjectSettings { + position, + kind: MapObjectKind::Item, + id: None, + }); + } + } + action + } + pub(super) fn draw_objects( &self, egui_ctx: &egui::Context, @@ -58,7 +168,7 @@ impl State { let is_this_object_selected; let can_select_object; - if let Some(selected_entity) = &self.selected_entity { + if let Some(selected_entity) = &self.selected_map_entity { is_this_object_selected = selected_entity.kind.is_object(&layer.id, object_idx); can_select_object = false; } else { @@ -90,7 +200,7 @@ impl State { if is_this_object_selected { if response.dragged() { - let cursor_offset = self.selected_entity.as_ref().unwrap().click_offset; + let cursor_offset = self.selected_map_entity.as_ref().unwrap().click_offset; let cursor_level_pos = screen_to_world_pos( ui.input().pointer.interact_pos().unwrap() + cursor_offset, response.rect.min.to_vec2(), diff --git a/src/editor/util.rs b/src/editor/util.rs index 177c95af4c..07a3164280 100644 --- a/src/editor/util.rs +++ b/src/editor/util.rs @@ -8,6 +8,22 @@ impl EguiCompatibleVec for macroquad::math::Vec2 { } } +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; } From bfe2b606116d404563c6dca4d0a6064ac5fc1952 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 17:12:31 +0200 Subject: [PATCH 38/81] Clean up --- src/editor/actions.rs | 2 +- src/editor/mod.rs | 260 +-------------------------- src/editor/state/mod.rs | 187 ++++++++++++++++++- src/editor/state/ui.rs | 2 + src/editor/state/ui/windows.rs | 89 +++++++++ src/editor/windows/create_tileset.rs | 1 + src/editor/windows/menu.rs | 15 +- src/editor/windows/save_map.rs | 1 + 8 files changed, 291 insertions(+), 266 deletions(-) create mode 100644 src/editor/state/ui/windows.rs diff --git a/src/editor/actions.rs b/src/editor/actions.rs index d208a6199e..37c1142cb5 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -7,7 +7,7 @@ use crate::map::{MapBackgroundLayer, MapObjectKind}; use super::state::EditorTool; -/// 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 UiAction { diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 6d391f8a7b..f0150213cc 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -42,13 +42,6 @@ pub struct Editor { level_view: LevelView, - history: ActionHistory, - - create_layer_window: Option, - create_tileset_window: Option, - menu_window: Option, - save_map_window: Option, - level_render_target: RenderTarget, } @@ -104,20 +97,18 @@ impl Node for EditorNode { scene::set_camera(0, camera); if input.toggle_menu { - node.editor.menu_window = if node.editor.menu_window.is_some() { + node.editor.state.menu_window = if node.editor.state.menu_window.is_some() { None } else { - Some(MenuWindow::new( - node.editor.state.map_resource.meta.is_user_map, - )) + Some(Default::default()) }; } if input.undo { - node.editor.apply_action(UiAction::Undo); + node.editor.state.apply_action(UiAction::Undo); } if input.redo { - node.editor.apply_action(UiAction::Redo); + node.editor.state.apply_action(UiAction::Redo); } } @@ -154,178 +145,11 @@ impl Editor { pub fn new(map_resource: MapResource) -> Self { Self { state: state::State::new(map_resource), - history: ActionHistory::new(), level_view: LevelView { position: Default::default(), scale: 1., }, level_render_target: render_target(1, 1), - - create_layer_window: None, - create_tileset_window: None, - menu_window: None, - save_map_window: None, - } - } - - 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.state.map_resource.map).unwrap(), - UiAction::Redo => self.history.redo(&mut self.state.map_resource.map).unwrap(), - UiAction::SelectTool(tool) => self.state.selected_tool = tool, - UiAction::OpenCreateLayerWindow => self.create_layer_window = Some(Default::default()), - UiAction::OpenCreateTilesetWindow => { - self.create_tileset_window = Some(CreateTilesetWindow::new()); - } - UiAction::OpenSaveMapWindow => { - self.save_map_window = Some(windows::SaveMapWindow::new()); - } - UiAction::CreateLayer { - id, - kind, - has_collision, - index, - } => { - let action = actions::CreateLayer::new(id, kind, has_collision, index); - self.history - .apply(action, &mut self.state.map_resource.map) - .unwrap(); - } - UiAction::CreateTileset { id, texture_id } => { - let action = actions::CreateTileset::new(id, texture_id); - self.history - .apply(action, &mut self.state.map_resource.map) - .unwrap(); - } - UiAction::DeleteLayer(id) => { - let action = actions::DeleteLayer::new(id); - self.history - .apply(action, &mut self.state.map_resource.map) - .unwrap(); - self.state.selected_layer = None; - } - UiAction::DeleteTileset(id) => { - let action = actions::DeleteTileset::new(id); - self.history - .apply(action, &mut self.state.map_resource.map) - .unwrap(); - self.state.selected_tile = None; - } - UiAction::UpdateLayer { id, is_visible } => { - let action = actions::UpdateLayer::new(id, is_visible); - self.history - .apply(action, &mut self.state.map_resource.map) - .unwrap(); - } - UiAction::SetLayerDrawOrderIndex { id, index } => { - let action = actions::SetLayerDrawOrderIndex::new(id, index); - self.history - .apply(action, &mut self.state.map_resource.map) - .unwrap(); - } - UiAction::SelectLayer(id) => { - if self.state.map_resource.map.layers[&id].kind == MapLayerKind::ObjectLayer - && self.state.selected_tool == EditorTool::TilePlacer - { - self.state.selected_tool = EditorTool::Cursor; - } - self.state.selected_layer = Some(id); - } - UiAction::SelectTileset(id) => { - self.state.selected_tile = Some(TileSelection { - tileset: id, - tile_id: 0, - }); - } - UiAction::SelectTile { id, tileset_id } => { - self.state.selected_tile = Some(TileSelection { - tileset: tileset_id, - tile_id: id, - }); - self.state.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.state.map_resource.map) - .unwrap(); - } - UiAction::RemoveTile { coords, layer_id } => { - let action = actions::RemoveTile::new(layer_id, coords); - self.history - .apply(action, &mut self.state.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.state.map_resource.map) - .unwrap(); - } - UiAction::ExitToMainMenu => { - crate::exit_to_main_menu(); - } - UiAction::QuitToDesktop => { - crate::quit_to_desktop(); - } - UiAction::SelectObject { - layer_id, - index, - cursor_offset, - } => { - self.state.selected_map_entity = Some(state::SelectableEntity { - click_offset: cursor_offset, - kind: state::SelectableEntityKind::Object { index, layer_id }, - }) - } - UiAction::DeselectObject => self.state.selected_map_entity = None, - UiAction::SaveMap { name } => { - let mut map_resource = self.state.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.state.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.state.map_resource.map) - .unwrap(); - } - - _ => todo!(), } } @@ -334,81 +158,7 @@ impl Editor { self.state .ui(egui_ctx, &mut self.level_render_target, &self.level_view) { - self.apply_action(action); - } - - if let Some(window) = &mut self.create_layer_window { - match window.ui(egui_ctx, &self.state.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.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_tileset_window { - match window.ui(egui_ctx, &self.state.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; - } - } - } else if let Some(window) = &mut self.menu_window { - match window.ui(egui_ctx) { - ControlFlow::Continue(()) => (), - ControlFlow::Break(MenuResult::OpenCreateMapWindow) => { - self.apply_action(UiAction::OpenCreateMapWindow) - } - ControlFlow::Break(MenuResult::OpenLoadMapWindow) => { - self.apply_action(UiAction::OpenLoadMapWindow) - } - ControlFlow::Break(MenuResult::OpenSaveMapWindow) => { - self.apply_action(UiAction::OpenSaveMapWindow) - } - ControlFlow::Break(MenuResult::SaveMap) => { - self.apply_action(UiAction::SaveMap { name: None }) - } - ControlFlow::Break(MenuResult::ExitToMainMenu) => { - self.apply_action(UiAction::ExitToMainMenu) - } - ControlFlow::Break(MenuResult::QuitToDesktop) => { - self.apply_action(UiAction::QuitToDesktop) - } - } + self.state.apply_action(action); } } diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 7092abf5f3..278bb77dae 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -1,10 +1,20 @@ mod ui; +use std::path::Path; + +use macroquad::prelude::collections::storage; + use crate::{ + editor::actions, map::{MapLayerKind, MapObjectKind}, - resources::MapResource, + resources::{ + map_name_to_filename, MapResource, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION, + }, + Resources, }; +use super::{actions::UiAction, history::ActionHistory, windows}; + #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { Cursor, @@ -59,6 +69,13 @@ pub struct State { pub should_draw_grid: bool, pub selected_map_entity: Option, pub object_being_placed: Option, + + create_layer_window: Option, + create_tileset_window: Option, + pub menu_window: Option, + save_map_window: Option, + + history: ActionHistory, } impl State { @@ -72,6 +89,174 @@ impl State { selected_tile: None, selected_map_entity: None, object_being_placed: None, + + create_layer_window: None, + create_tileset_window: None, + menu_window: None, + save_map_window: None, + + history: ActionHistory::new(), + } + } + + 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::OpenCreateLayerWindow => self.create_layer_window = Some(Default::default()), + UiAction::OpenCreateTilesetWindow => { + self.create_tileset_window = Some(Default::default()); + } + UiAction::OpenSaveMapWindow => { + self.save_map_window = Some(Default::default()); + } + 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.selected_tile = 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.selected_tile = Some(TileSelection { + tileset: id, + tile_id: 0, + }); + } + UiAction::SelectTile { id, tileset_id } => { + self.selected_tile = 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::ExitToMainMenu => { + crate::exit_to_main_menu(); + } + UiAction::QuitToDesktop => { + crate::quit_to_desktop(); + } + UiAction::SelectObject { + layer_id, + index, + cursor_offset, + } => { + self.selected_map_entity = Some(SelectableEntity { + click_offset: cursor_offset, + kind: SelectableEntityKind::Object { index, layer_id }, + }) + } + UiAction::DeselectObject => self.selected_map_entity = None, + 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(); + } + + _ => todo!(), } } diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index 5e0380b5aa..5c90dec893 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -1,6 +1,7 @@ mod level; mod side_panel; mod toolbar; +mod windows; use macroquad::prelude::RenderTarget; @@ -19,5 +20,6 @@ impl super::State { self.draw_toolbar(egui_ctx) .then(self.draw_side_panel(egui_ctx)) .then(self.draw_level(egui_ctx, level_render_target, level_view)) + .then(self.draw_windows(egui_ctx)) } } diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs new file mode 100644 index 0000000000..47ae2240b8 --- /dev/null +++ b/src/editor/state/ui/windows.rs @@ -0,0 +1,89 @@ +use std::ops::ControlFlow; + +use crate::editor::{ + actions::{UiAction, UiActionExt}, + state::State, + windows::{CreateLayerResult, CreateTilesetResult, MenuResult, SaveMapResult}, +}; + +impl State { + pub(super) fn draw_windows(&mut self, egui_ctx: &egui::Context) -> Option { + let mut action = None; + + 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, + }) => { + action.then_do_some(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.save_map_window { + match window.ui(egui_ctx) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(SaveMapResult::Save { name }) => { + action.then_do_some(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_tileset_window { + match window.ui(egui_ctx, &self.map_resource.map) { + ControlFlow::Continue(()) => (), + ControlFlow::Break(CreateTilesetResult::Create { + tileset_name, + texture, + }) => { + action.then_do_some(UiAction::CreateTileset { + id: tileset_name, + texture_id: texture, + }); + self.create_tileset_window = None; + } + ControlFlow::Break(CreateTilesetResult::Close) => { + self.create_tileset_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) => { + action.then_do_some(UiAction::OpenCreateMapWindow); + } + ControlFlow::Break(MenuResult::OpenLoadMapWindow) => { + action.then_do_some(UiAction::OpenLoadMapWindow); + } + ControlFlow::Break(MenuResult::OpenSaveMapWindow) => { + action.then_do_some(UiAction::OpenSaveMapWindow); + } + ControlFlow::Break(MenuResult::SaveMap) => { + action.then_do_some(UiAction::SaveMap { name: None }); + } + ControlFlow::Break(MenuResult::ExitToMainMenu) => { + action.then_do_some(UiAction::ExitToMainMenu); + } + ControlFlow::Break(MenuResult::QuitToDesktop) => { + action.then_do_some(UiAction::QuitToDesktop); + } + } + } + + action + } +} diff --git a/src/editor/windows/create_tileset.rs b/src/editor/windows/create_tileset.rs index 0bef0e5656..20f55db72f 100644 --- a/src/editor/windows/create_tileset.rs +++ b/src/editor/windows/create_tileset.rs @@ -4,6 +4,7 @@ use macroquad::prelude::collections::storage; use crate::{map::Map, resources::TextureKind, Resources}; +#[derive(Default)] pub struct CreateTilesetWindow { tileset_name: String, texture_options: Vec, diff --git a/src/editor/windows/menu.rs b/src/editor/windows/menu.rs index 50ffe705f1..4562bd022a 100644 --- a/src/editor/windows/menu.rs +++ b/src/editor/windows/menu.rs @@ -1,8 +1,9 @@ use std::ops::ControlFlow; -pub struct MenuWindow { - can_save: bool, -} +use crate::map::Map; + +#[derive(Default)] +pub struct MenuWindow; pub enum MenuResult { OpenCreateMapWindow, @@ -14,11 +15,7 @@ pub enum MenuResult { } impl MenuWindow { - pub fn new(can_save: bool) -> Self { - Self { can_save } - } - - pub fn ui(&mut self, egui_ctx: &egui::Context) -> ControlFlow { + pub fn ui(&mut self, egui_ctx: &egui::Context, is_user_map: bool) -> ControlFlow { let mut action = ControlFlow::Continue(()); egui::Window::new("Pause") @@ -35,7 +32,7 @@ impl MenuWindow { if ui.button("Open/Import").clicked() { action = ControlFlow::Break(MenuResult::OpenLoadMapWindow); } - ui.add_enabled_ui(self.can_save, |ui| { + ui.add_enabled_ui(is_user_map, |ui| { if ui.button("Save").clicked() { action = ControlFlow::Break(MenuResult::SaveMap); } diff --git a/src/editor/windows/save_map.rs b/src/editor/windows/save_map.rs index b7d00c405d..a7f1ddff7d 100644 --- a/src/editor/windows/save_map.rs +++ b/src/editor/windows/save_map.rs @@ -10,6 +10,7 @@ use crate::{ Resources, }; +#[derive(Default)] pub struct SaveMapWindow { map_name: String, overwrite_existing: bool, From b3c1b59781d05f93a1faa3e15d2e83a4a56c7238 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 17:28:04 +0200 Subject: [PATCH 39/81] Move remaining Editor members to State --- src/editor/RATIONALE.md | 9 ++++++++- src/editor/mod.rs | 29 +++++++++------------------- src/editor/state/mod.rs | 17 ++++++++++++---- src/editor/state/ui.rs | 9 ++------- src/editor/state/ui/level/mod.rs | 23 +++++++--------------- src/editor/state/ui/level/objects.rs | 16 ++++++--------- src/editor/view.rs | 9 +++++++++ 7 files changed, 54 insertions(+), 58 deletions(-) diff --git a/src/editor/RATIONALE.md b/src/editor/RATIONALE.md index 47f67c45d5..cb09e18104 100644 --- a/src/editor/RATIONALE.md +++ b/src/editor/RATIONALE.md @@ -8,4 +8,11 @@ I think it's much more organized this way. 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. \ No newline at end of file +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/mod.rs b/src/editor/mod.rs index f0150213cc..59c712f59f 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -39,10 +39,6 @@ use crate::resources::MapResource; pub struct Editor { state: state::State, - - level_view: LevelView, - - level_render_target: RenderTarget, } /// Used to interface with macroquad. Necessary because using `node: RefMut` really limits @@ -76,21 +72,22 @@ impl Node for EditorNode { .input_scheme .collect_input(node.accept_kb_input, node.accept_mouse_input); - node.editor.level_view.position += input.camera_move_direction * Self::CAMERA_PAN_SPEED; + node.editor.state.level_view.position += + input.camera_move_direction * Self::CAMERA_PAN_SPEED; let target_size = vec2( - node.editor.level_render_target.texture.width(), - node.editor.level_render_target.texture.height(), + node.editor.state.level_render_target.texture.width(), + node.editor.state.level_render_target.texture.height(), ); let zoom = vec2( - node.editor.level_view.scale / target_size.x, - node.editor.level_view.scale / target_size.y, + node.editor.state.level_view.scale / target_size.x, + node.editor.state.level_view.scale / target_size.y, ) * 2.; let camera = Some(Camera2D { offset: vec2(-1., -1.), - target: node.editor.level_view.position, + target: node.editor.state.level_view.position, zoom, - render_target: Some(node.editor.level_render_target), + render_target: Some(node.editor.state.level_render_target), ..Camera2D::default() }); @@ -145,19 +142,11 @@ impl Editor { pub fn new(map_resource: MapResource) -> Self { Self { state: state::State::new(map_resource), - level_view: LevelView { - position: Default::default(), - scale: 1., - }, - level_render_target: render_target(1, 1), } } pub fn ui(&mut self, egui_ctx: &egui::Context) { - if let Some(action) = - self.state - .ui(egui_ctx, &mut self.level_render_target, &self.level_view) - { + if let Some(action) = self.state.ui(egui_ctx) { self.state.apply_action(action); } } diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 278bb77dae..75ce4f7881 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -2,7 +2,7 @@ mod ui; use std::path::Path; -use macroquad::prelude::collections::storage; +use macroquad::prelude::{collections::storage, RenderTarget}; use crate::{ editor::actions, @@ -13,7 +13,7 @@ use crate::{ Resources, }; -use super::{actions::UiAction, history::ActionHistory, windows}; +use super::{actions::UiAction, history::ActionHistory, view::LevelView, windows}; #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { @@ -58,8 +58,6 @@ pub struct ObjectSettings { pub id: Option, } -/// Contains the editor state, i.e. the data whose change is tracked by the [`ActionHistory`] of the -/// editor. pub struct State { pub selected_tool: EditorTool, pub map_resource: MapResource, @@ -76,6 +74,10 @@ pub struct State { save_map_window: Option, history: ActionHistory, + + pub level_view: LevelView, + + pub level_render_target: RenderTarget, } impl State { @@ -96,6 +98,13 @@ impl State { save_map_window: None, history: ActionHistory::new(), + + level_view: LevelView { + position: Default::default(), + scale: 1., + }, + + level_render_target: macroquad::prelude::render_target(1, 1), } } diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index 5c90dec893..51f38ff70f 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -11,15 +11,10 @@ use crate::editor::{ }; impl super::State { - pub fn ui( - &mut self, - egui_ctx: &egui::Context, - level_render_target: &mut RenderTarget, - level_view: &LevelView, - ) -> Option { + pub fn ui(&mut self, egui_ctx: &egui::Context) -> Option { self.draw_toolbar(egui_ctx) .then(self.draw_side_panel(egui_ctx)) - .then(self.draw_level(egui_ctx, level_render_target, level_view)) + .then(self.draw_level(egui_ctx)) .then(self.draw_windows(egui_ctx)) } } diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 826b8b5e56..9829bdfba1 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -15,18 +15,13 @@ use crate::{ use super::super::State; impl State { - pub(super) fn draw_level( - &mut self, - egui_ctx: &egui::Context, - level_render_target: &mut RenderTarget, - level_view: &LevelView, - ) -> Option { + pub(super) fn draw_level(&mut self, egui_ctx: &egui::Context) -> Option { let mut action = None; egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { - let texture_id = level_render_target.texture.egui_id(); + let texture_id = self.level_render_target.texture.egui_id(); let (response, painter) = ui.allocate_painter(ui.available_size(), egui::Sense::click_and_drag()); @@ -38,14 +33,12 @@ impl State { ); painter.add(egui::Shape::mesh(level_mesh)); - action.then_do(self.draw_objects(egui_ctx, ui, &response, &painter, level_view)); + action.then_do(self.draw_objects(egui_ctx, ui, &response, &painter)); - action.then_do( - self.draw_level_overlays(egui_ctx, ui, &response, &painter, level_view), - ); + action.then_do(self.draw_level_overlays(egui_ctx, ui, &response, &painter)); let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); - level_render_target.resize_if_appropiate(width, height); + self.level_render_target.resize_if_appropiate(width, height); }); action @@ -57,7 +50,6 @@ impl State { ui: &mut egui::Ui, level_response: &egui::Response, painter: &egui::Painter, - level_view: &LevelView, ) -> Option { let mut action = None; @@ -76,7 +68,7 @@ impl State { let cursor_px_pos = screen_to_world_pos( cursor_screen_pos, level_response.rect.min.to_vec2(), - level_view, + &self.level_view, ); let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); @@ -85,7 +77,7 @@ impl State { level_response, painter, cursor_tile_pos, - level_view, + &self.level_view, )); let level_top_left = level_response.rect.min; @@ -102,7 +94,6 @@ impl State { level_response, painter, cursor_tile_pos, - level_view, )); } else { action = None; diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 6ccd174169..25eae8f2c4 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -23,12 +23,11 @@ impl State { level_response: &egui::Response, painter: &egui::Painter, cursor_tile_pos: egui::Pos2, - level_view: &LevelView, ) -> Option { let mut action = None; if let Some(settings) = &mut self.object_being_placed { let position = - world_to_screen_pos(settings.position, level_response.rect.min, level_view); + world_to_screen_pos(settings.position, level_response.rect.min, &self.level_view); let mut closed_window = false; @@ -114,7 +113,7 @@ impl State { let position = screen_to_world_pos( egui_ctx.input().pointer.interact_pos().unwrap(), level_response.rect.min.to_vec2(), - level_view, + &self.level_view, ); self.object_being_placed = Some(ObjectSettings { @@ -133,7 +132,6 @@ impl State { ui: &mut egui::Ui, response: &egui::Response, painter: &egui::Painter, - level_view: &LevelView, ) -> Option { let mut action = None; @@ -143,9 +141,7 @@ impl State { .iter() .filter_map(|layer_idx| self.map_resource.map.layers.get(layer_idx)) .for_each(|layer| { - action.then_do( - self.draw_object_layer(layer, response, painter, ui, level_view, egui_ctx), - ); + action.then_do(self.draw_object_layer(layer, response, painter, ui, egui_ctx)); }); action @@ -157,14 +153,14 @@ impl State { response: &egui::Response, painter: &egui::Painter, ui: &mut egui::Ui, - level_view: &LevelView, egui_ctx: &egui::Context, ) -> Option { let mut action = None; let resources = storage::get::(); for (object_idx, object) in layer.objects.iter().enumerate() { - let (dest, is_valid) = draw_object(object, response, level_view, painter, &resources); + let (dest, is_valid) = + draw_object(object, response, &self.level_view, painter, &resources); let is_this_object_selected; let can_select_object; @@ -204,7 +200,7 @@ impl State { let cursor_level_pos = screen_to_world_pos( ui.input().pointer.interact_pos().unwrap() + cursor_offset, response.rect.min.to_vec2(), - level_view, + &self.level_view, ); action.then_do_some(UiAction::MoveObject { index: object_idx, diff --git a/src/editor/view.rs b/src/editor/view.rs index f101252a9d..f1700d0e5b 100644 --- a/src/editor/view.rs +++ b/src/editor/view.rs @@ -6,3 +6,12 @@ pub struct LevelView { /// 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., + } + } +} From 51a3f31a3bc6e3ee04804bfc7bcea31b963f6115 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 19:57:20 +0200 Subject: [PATCH 40/81] Clean up --- src/editor/actions.rs | 22 ------ src/editor/mod.rs | 4 +- src/editor/state/mod.rs | 29 +++----- src/editor/state/ui.rs | 10 +-- src/editor/state/ui/level/mod.rs | 79 +++++++-------------- src/editor/state/ui/level/objects.rs | 86 ++++++++++++++--------- src/editor/state/ui/side_panel/layers.rs | 70 +++++++++--------- src/editor/state/ui/side_panel/mod.rs | 10 +-- src/editor/state/ui/side_panel/tileset.rs | 71 +++++++++---------- src/editor/state/ui/toolbar.rs | 18 +++-- src/editor/state/ui/windows.rs | 24 +++---- 11 files changed, 181 insertions(+), 242 deletions(-) diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 37c1142cb5..c43b39f86c 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -15,26 +15,10 @@ pub enum UiAction { Undo, Redo, SelectTool(EditorTool), - OpenBackgroundPropertiesWindow, 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, - }, SelectTile { id: u32, tileset_id: String, @@ -61,7 +45,6 @@ pub enum UiAction { is_visible: bool, }, SelectTileset(String), - OpenImportWindow(usize), Import { tilesets: Vec, background_color: Option, @@ -126,16 +109,11 @@ pub enum UiAction { tile_size: Vec2, grid_size: UVec2, }, - OpenCreateMapWindow, OpenMap(usize), - OpenLoadMapWindow, SaveMap { name: Option, }, - OpenSaveMapWindow, DeleteMap(usize), - ExitToMainMenu, - QuitToDesktop, } impl UiAction { diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 59c712f59f..1ec0cd0b14 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -146,9 +146,7 @@ impl Editor { } pub fn ui(&mut self, egui_ctx: &egui::Context) { - if let Some(action) = self.state.ui(egui_ctx) { - self.state.apply_action(action); - } + self.state.ui(egui_ctx); } pub fn draw(&self) { diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 75ce4f7881..0138825c2a 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -51,7 +51,7 @@ impl SelectableEntityKind { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ObjectSettings { pub position: egui::Pos2, pub kind: MapObjectKind, @@ -108,6 +108,13 @@ impl State { } } + 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); @@ -118,13 +125,6 @@ impl State { 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::OpenCreateLayerWindow => self.create_layer_window = Some(Default::default()), - UiAction::OpenCreateTilesetWindow => { - self.create_tileset_window = Some(Default::default()); - } - UiAction::OpenSaveMapWindow => { - self.save_map_window = Some(Default::default()); - } UiAction::CreateLayer { id, kind, @@ -216,12 +216,6 @@ impl State { .apply(action, &mut self.map_resource.map) .unwrap(); } - UiAction::ExitToMainMenu => { - crate::exit_to_main_menu(); - } - UiAction::QuitToDesktop => { - crate::quit_to_desktop(); - } UiAction::SelectObject { layer_id, index, @@ -268,11 +262,4 @@ impl State { _ => todo!(), } } - - 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) - } } diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index 51f38ff70f..7bf18b350f 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -11,10 +11,10 @@ use crate::editor::{ }; impl super::State { - pub fn ui(&mut self, egui_ctx: &egui::Context) -> Option { - self.draw_toolbar(egui_ctx) - .then(self.draw_side_panel(egui_ctx)) - .then(self.draw_level(egui_ctx)) - .then(self.draw_windows(egui_ctx)) + pub fn ui(&mut self, egui_ctx: &egui::Context) { + self.draw_toolbar(egui_ctx); + self.draw_side_panel(egui_ctx); + self.draw_level(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 index 9829bdfba1..c479f1f338 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -15,9 +15,7 @@ use crate::{ use super::super::State; impl State { - pub(super) fn draw_level(&mut self, egui_ctx: &egui::Context) -> Option { - let mut action = None; - + pub(super) fn draw_level(&mut self, egui_ctx: &egui::Context) { egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { @@ -33,15 +31,12 @@ impl State { ); painter.add(egui::Shape::mesh(level_mesh)); - action.then_do(self.draw_objects(egui_ctx, ui, &response, &painter)); - - action.then_do(self.draw_level_overlays(egui_ctx, ui, &response, &painter)); + self.draw_objects(egui_ctx, ui, &response, &painter); + self.draw_level_overlays(egui_ctx, ui, &response, &painter); let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); self.level_render_target.resize_if_appropiate(width, height); }); - - action } fn draw_level_overlays( @@ -50,9 +45,7 @@ impl State { ui: &mut egui::Ui, level_response: &egui::Response, painter: &egui::Painter, - ) -> Option { - let mut action = None; - + ) { let level_contains_cursor = ui .input() .pointer @@ -61,8 +54,7 @@ impl State { .unwrap_or(false); if level_contains_cursor { - let map = &self.map_resource.map; - let tile_size = map.tile_size.into_egui(); + 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 = screen_to_world_pos( @@ -72,13 +64,7 @@ impl State { ); let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); - action.then_do(self.draw_level_placement_overlay( - egui_ctx, - level_response, - painter, - cursor_tile_pos, - &self.level_view, - )); + self.draw_level_placement_overlay(egui_ctx, level_response, painter, cursor_tile_pos); let level_top_left = level_response.rect.min; self.draw_level_pointer_pos_overlay( @@ -89,63 +75,50 @@ impl State { cursor_tile_pos, ); - action.then_do(self.draw_level_object_placement_overlay( + self.draw_level_object_placement_overlay( egui_ctx, level_response, painter, cursor_tile_pos, - )); - } else { - action = None; + ); } - - action } fn draw_level_placement_overlay( - &self, + &mut self, egui_ctx: &egui::Context, level_response: &egui::Response, painter: &egui::Painter, cursor_tile_pos: egui::Pos2, - level_view: &LevelView, - ) -> Option { - let action; - - action = match self.selected_tool { + ) { + match self.selected_tool { EditorTool::TilePlacer => self.draw_level_tile_placement_overlay( egui_ctx, level_response, painter, cursor_tile_pos, - level_view, ), // TODO: Spawnpoint placement overlay // TODO: Object placement overlay - _ => None, + _ => (), }; - - action } fn draw_level_tile_placement_overlay( - &self, + &mut self, egui_ctx: &egui::Context, level_response: &egui::Response, painter: &egui::Painter, cursor_tile_pos: egui::Pos2, - level_view: &LevelView, - ) -> Option { - let mut action = None; - let map = &self.map_resource.map; - let tile_size = map.tile_size.into_egui(); + ) { + let tile_size = self.map_resource.map.tile_size.into_egui(); let level_top_left = level_response.rect.min; if cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { if let (Some(selected_tile), Some(selected_layer)) = (&self.selected_tile, &self.selected_layer) { - let tileset = &map.tilesets[&selected_tile.tileset]; + let tileset = &self.map_resource.map.tilesets[&selected_tile.tileset]; let texture_id = storage::get::().textures[&tileset.texture_id] .texture .egui_id(); @@ -169,7 +142,7 @@ impl State { world_to_screen_pos( (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), level_top_left, - level_view, + &self.level_view, ), tile_size, ), @@ -182,18 +155,22 @@ impl State { if level_response.clicked() || level_response.dragged() { let input = egui_ctx.input(); if input.pointer.button_down(egui::PointerButton::Primary) { - action.then_do_some(UiAction::PlaceTile { - id: selected_tile.tile_id, - layer_id: selected_layer.clone(), - tileset_id: selected_tile.tileset.clone(), + 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) { - action.then_do_some(UiAction::RemoveTile { - layer_id: selected_layer.clone(), + 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, @@ -203,8 +180,6 @@ impl State { } } } - - action } fn draw_level_pointer_pos_overlay( diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 25eae8f2c4..e0ce8116e4 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -12,7 +12,7 @@ use crate::{ util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec}, view::LevelView, }, - map::{MapObject, MapObjectKind}, + map::{MapLayer, MapObject, MapObjectKind}, AnimatedSpriteMetadata, Resources, }; @@ -23,15 +23,18 @@ impl State { level_response: &egui::Response, painter: &egui::Painter, cursor_tile_pos: egui::Pos2, - ) -> Option { - let mut action = None; + ) { if let Some(settings) = &mut self.object_being_placed { let position = world_to_screen_pos(settings.position, level_response.rect.min, &self.level_view); - let mut closed_window = false; + enum PlaceObjectResult { + Create, + Close, + Noop, + }; - egui::Window::new("Placing object") + let response = egui::Window::new("Placing object") .fixed_pos(position) .collapsible(false) .resizable(false) @@ -87,24 +90,39 @@ impl State { let create_button = ui.add_enabled(settings.id.is_some(), egui::Button::new("Create")); if create_button.clicked() { - action = Some(UiAction::CreateObject { - id: settings.id.as_ref().unwrap().clone(), - kind: settings.kind.clone(), - position: settings.position.into_macroquad(), - // TODO: Include layer id in object settings - layer_id: self.selected_layer.as_ref().unwrap().clone(), - }); - - closed_window = true; + return PlaceObjectResult::Create; } if ui.button("Cancel").clicked() { - closed_window = true; + return PlaceObjectResult::Close; } - }); + + PlaceObjectResult::Noop + }) }); - if closed_window { - self.object_being_placed = None; + let result = response + .and_then(|r| 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.clone(); + let position = settings.position.into_macroquad(); + // TODO: Include layer id in object settings & render it in window as well + 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 => (), } } else { if self.selected_tool == EditorTool::ObjectPlacer @@ -123,40 +141,41 @@ impl State { }); } } - action } pub(super) fn draw_objects( - &self, + &mut self, egui_ctx: &egui::Context, ui: &mut egui::Ui, response: &egui::Response, painter: &egui::Painter, - ) -> Option { + ) { let mut action = None; - - self.map_resource + for layer in self + .map_resource .map .draw_order .iter() - .filter_map(|layer_idx| self.map_resource.map.layers.get(layer_idx)) - .for_each(|layer| { - action.then_do(self.draw_object_layer(layer, response, painter, ui, egui_ctx)); - }); + .filter_map(|layer_id| self.map_resource.map.layers.get(layer_id)) + { + action.then_do(self.draw_object_layer(layer, response, painter, ui, egui_ctx)); + } - action + if let Some(action) = action { + self.apply_action(action); + } } fn draw_object_layer( &self, - layer: &crate::map::MapLayer, + layer: &MapLayer, response: &egui::Response, painter: &egui::Painter, ui: &mut egui::Ui, egui_ctx: &egui::Context, ) -> Option { - let mut action = None; let resources = storage::get::(); + let mut action = None; for (object_idx, object) in layer.objects.iter().enumerate() { let (dest, is_valid) = @@ -202,7 +221,7 @@ impl State { response.rect.min.to_vec2(), &self.level_view, ); - action.then_do_some(UiAction::MoveObject { + action = Some(UiAction::MoveObject { index: object_idx, layer_id: layer.id.clone(), position: macroquad::math::Vec2::new( @@ -211,14 +230,14 @@ impl State { ), }); } else if response.drag_released() { - action.then_do_some(UiAction::DeselectObject); + action = Some(UiAction::DeselectObject); } } else if can_select_object { self.show_object_info_tooltip(egui_ctx, object, is_valid); if response.drag_started() { let click_pos = ui.input().pointer.interact_pos().unwrap(); - action.then_do_some(UiAction::SelectObject { + action = Some(UiAction::SelectObject { index: object_idx, layer_id: layer.id.clone(), cursor_offset: dest.min - click_pos, @@ -226,7 +245,6 @@ impl State { } } } - action } diff --git a/src/editor/state/ui/side_panel/layers.rs b/src/editor/state/ui/side_panel/layers.rs index 7f2c931ed2..fc6b593951 100644 --- a/src/editor/state/ui/side_panel/layers.rs +++ b/src/editor/state/ui/side_panel/layers.rs @@ -7,33 +7,31 @@ use crate::{ }; impl State { - pub(super) fn draw_layer_info(&self, ui: &mut egui::Ui) -> Option { + pub(super) fn draw_layer_info(&mut self, ui: &mut egui::Ui) { ui.heading("Layers"); - self.draw_layer_list(ui) // Draw layer list - .then(self.draw_layer_utils(ui)) // Draw layer util buttons (+ - Up Down) + self.draw_layer_list(ui); // Draw layer list + self.draw_layer_utils(ui); // Draw layer util buttons (+ - Up Down) } - fn draw_layer_list(&self, ui: &mut egui::Ui) -> Option { + fn draw_layer_list(&mut self, ui: &mut egui::Ui) { let mut action = None; - let map = &self.map_resource.map; ui.group(|ui| { + let map = &self.map_resource.map; for (layer_name, layer) in map.draw_order.iter().map(|id| (id, &map.layers[id])) { ui.horizontal(|ui| { - if ui - .selectable_label( - self.selected_layer.as_ref() == Some(layer_name), - format!( - "({}) {}", - match layer.kind { - MapLayerKind::TileLayer => "T", - MapLayerKind::ObjectLayer => "O", - }, - layer_name - ), - ) - .clicked() - { + let layer_label = ui.selectable_label( + self.selected_layer.as_ref() == Some(layer_name), + format!( + "({}) {}", + match layer.kind { + MapLayerKind::TileLayer => "T", + MapLayerKind::ObjectLayer => "O", + }, + layer_name + ), + ); + if layer_label.clicked() { action.then_do_some(UiAction::SelectLayer(layer_name.clone())); } let mut is_visible = layer.is_visible; @@ -47,29 +45,29 @@ impl State { } }); - action + if let Some(action) = action { + self.apply_action(action); + } } - fn draw_layer_utils(&self, ui: &mut egui::Ui) -> Option { - let mut action = None; - let map = &self.map_resource.map; - + fn draw_layer_utils(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { if ui.button("+").clicked() { - action.then_do_some(UiAction::OpenCreateLayerWindow); + self.create_layer_window = Some(Default::default()); } ui.add_enabled_ui(self.selected_layer.is_some(), |ui| { if ui.button("-").clicked() { - action.then_do_some(UiAction::DeleteLayer( - self.selected_layer.as_ref().unwrap().clone(), - )); + 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| { - map.draw_order + self.map_resource + .map + .draw_order .iter() .enumerate() .find(|(_idx, id)| &layer == id) @@ -81,27 +79,27 @@ impl State { .add_enabled(selected_layer_idx > 0, egui::Button::new("Up")) .clicked() { - action.then_do_some(UiAction::SetLayerDrawOrderIndex { - id: self.selected_layer.as_ref().unwrap().clone(), + 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 < map.draw_order.len(), + selected_layer_idx + 1 < self.map_resource.map.draw_order.len(), egui::Button::new("Down"), ) .clicked() { - action.then_do_some(UiAction::SetLayerDrawOrderIndex { - id: self.selected_layer.as_ref().unwrap().clone(), + let id = self.selected_layer.as_ref().unwrap().clone(); + self.apply_action(UiAction::SetLayerDrawOrderIndex { + id, index: selected_layer_idx + 1, }); } }); }); - - action } } diff --git a/src/editor/state/ui/side_panel/mod.rs b/src/editor/state/ui/side_panel/mod.rs index 330481ec53..88979b4caf 100644 --- a/src/editor/state/ui/side_panel/mod.rs +++ b/src/editor/state/ui/side_panel/mod.rs @@ -9,17 +9,15 @@ use crate::{ use super::super::State; impl State { - pub(super) fn draw_side_panel(&self, egui_ctx: &egui::Context) -> Option { - let mut action = None; - + 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| { - action = self.draw_layer_info(ui); + self.draw_layer_info(ui); ui.separator(); match self.selected_layer_type() { Some(MapLayerKind::TileLayer) => { - action.then_do(self.draw_tileset_info(ui)); + self.draw_tileset_info(ui); } Some(MapLayerKind::ObjectLayer) => { // TODO @@ -28,7 +26,5 @@ impl State { } }, ); - - action } } diff --git a/src/editor/state/ui/side_panel/tileset.rs b/src/editor/state/ui/side_panel/tileset.rs index 0e3da4a446..8b7cac2f70 100644 --- a/src/editor/state/ui/side_panel/tileset.rs +++ b/src/editor/state/ui/side_panel/tileset.rs @@ -13,14 +13,14 @@ use crate::{ use super::State; impl State { - pub(super) fn draw_tileset_info(&self, ui: &mut egui::Ui) -> Option { + pub(super) fn draw_tileset_info(&mut self, ui: &mut egui::Ui) { ui.heading("Tilesets"); - self.draw_tileset_list(ui) // Draw tileset list - .then(self.draw_tileset_utils(ui)) // Draw tileset utils - .then(self.draw_tileset_image(ui)) // Draw tileset image + 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(&self, ui: &mut egui::Ui) -> Option { + fn draw_tileset_list(&mut self, ui: &mut egui::Ui) { let mut action = None; ui.group(|ui| { @@ -37,32 +37,27 @@ impl State { } }); - action + if let Some(action) = action { + self.apply_action(action); + } } - fn draw_tileset_utils(&self, ui: &mut egui::Ui) -> Option { - let mut action = None; - + fn draw_tileset_utils(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { if ui.button("+").clicked() { - action.then_do_some(UiAction::OpenCreateTilesetWindow); + self.create_tileset_window = Some(Default::default()); } ui.add_enabled_ui(self.selected_tile.is_some(), |ui| { if ui.button("-").clicked() { - action.then_do_some(UiAction::DeleteTileset( - self.selected_tile.as_ref().unwrap().tileset.clone(), - )); + let selected_tileset = self.selected_tile.as_ref().unwrap().tileset.clone(); + self.apply_action(UiAction::DeleteTileset(selected_tileset)); } ui.button("Edit").on_hover_text("Does not do anything yet"); }) }); - - action } - fn draw_tileset_image(&self, ui: &mut egui::Ui) -> Option { - let mut action = None; - + fn draw_tileset_image(&mut self, ui: &mut egui::Ui) { if let Some(selection) = &self.selected_tile { if let Some(tileset) = self.map_resource.map.tilesets.get(&selection.tileset) { let tileset_texture: &TextureResource = @@ -84,24 +79,6 @@ impl State { let image_response = ui.add(image); let image_bounds = image_response.rect; - 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; - - action.then_do_some(UiAction::SelectTile { - id: tile_id, - tileset_id: selection.tileset.clone(), - }); - } - } - if let EditorTool::TilePlacer = self.selected_tool { let painter = ui.painter_at(image_bounds); let tile_rect = egui::Rect::from_min_size( @@ -119,9 +96,27 @@ impl State { 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, + }); + } + } } } - - action } } diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs index 678208b26c..00e88e1c83 100644 --- a/src/editor/state/ui/toolbar.rs +++ b/src/editor/state/ui/toolbar.rs @@ -9,23 +9,23 @@ use crate::{ use super::super::State; impl State { - pub(super) fn draw_toolbar(&self, egui_ctx: &egui::Context) -> Option { - let mut action = None; - + 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 tool = &self.selected_tool; - + let selected_layer_type = self.selected_layer_type(); let mut add_tool = |tool_name, tool_variant| { if ui - .add(egui::SelectableLabel::new(tool == &tool_variant, tool_name)) + .add(egui::SelectableLabel::new( + self.selected_tool == tool_variant, + tool_name, + )) .clicked() { - action.then_do_some(UiAction::SelectTool(tool_variant)); + self.apply_action(UiAction::SelectTool(tool_variant)); } }; add_tool("Cursor", EditorTool::Cursor); - match self.selected_layer_type() { + match selected_layer_type { Some(MapLayerKind::TileLayer) => { add_tool("Tiles", EditorTool::TilePlacer); add_tool("Eraser", EditorTool::Eraser); @@ -34,7 +34,5 @@ impl State { None => (), } }); - - action } } diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs index 47ae2240b8..342212f513 100644 --- a/src/editor/state/ui/windows.rs +++ b/src/editor/state/ui/windows.rs @@ -7,9 +7,7 @@ use crate::editor::{ }; impl State { - pub(super) fn draw_windows(&mut self, egui_ctx: &egui::Context) -> Option { - let mut action = None; - + pub(super) fn draw_windows(&mut self, egui_ctx: &egui::Context) { if let Some(window) = &mut self.create_layer_window { match window.ui(egui_ctx, &self.map_resource.map) { ControlFlow::Continue(()) => (), @@ -18,7 +16,7 @@ impl State { layer_kind, layer_name, }) => { - action.then_do_some(UiAction::CreateLayer { + self.apply_action(UiAction::CreateLayer { has_collision, kind: layer_kind, index: None, @@ -36,7 +34,7 @@ impl State { match window.ui(egui_ctx) { ControlFlow::Continue(()) => (), ControlFlow::Break(SaveMapResult::Save { name }) => { - action.then_do_some(UiAction::SaveMap { name: Some(name) }); + self.apply_action(UiAction::SaveMap { name: Some(name) }); self.save_map_window = None; } ControlFlow::Break(SaveMapResult::Close) => { @@ -50,7 +48,7 @@ impl State { tileset_name, texture, }) => { - action.then_do_some(UiAction::CreateTileset { + self.apply_action(UiAction::CreateTileset { id: tileset_name, texture_id: texture, }); @@ -64,26 +62,24 @@ impl State { match window.ui(egui_ctx, self.map_resource.meta.is_user_map) { ControlFlow::Continue(()) => (), ControlFlow::Break(MenuResult::OpenCreateMapWindow) => { - action.then_do_some(UiAction::OpenCreateMapWindow); + todo!("Create map window"); } ControlFlow::Break(MenuResult::OpenLoadMapWindow) => { - action.then_do_some(UiAction::OpenLoadMapWindow); + todo!("Open/load map window") } ControlFlow::Break(MenuResult::OpenSaveMapWindow) => { - action.then_do_some(UiAction::OpenSaveMapWindow); + self.save_map_window = Some(Default::default()); } ControlFlow::Break(MenuResult::SaveMap) => { - action.then_do_some(UiAction::SaveMap { name: None }); + self.apply_action(UiAction::SaveMap { name: None }); } ControlFlow::Break(MenuResult::ExitToMainMenu) => { - action.then_do_some(UiAction::ExitToMainMenu); + crate::exit_to_main_menu(); } ControlFlow::Break(MenuResult::QuitToDesktop) => { - action.then_do_some(UiAction::QuitToDesktop); + crate::quit_to_desktop(); } } } - - action } } From cfd1e5f07d78bdcb637dd082f387c2f080640425 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 20:00:22 +0200 Subject: [PATCH 41/81] Fix & fmt --- src/editor/mod.rs | 25 +++---------------------- src/editor/state/ui.rs | 7 ------- src/editor/state/ui/level/mod.rs | 4 ++-- src/editor/state/ui/level/objects.rs | 4 ++-- src/editor/state/ui/side_panel/mod.rs | 5 +---- src/editor/state/ui/toolbar.rs | 5 +---- src/editor/state/ui/windows.rs | 2 +- src/editor/windows/menu.rs | 2 -- src/editor/windows/mod.rs | 2 +- src/editor/windows/save_map.rs | 7 ------- 10 files changed, 11 insertions(+), 52 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 1ec0cd0b14..79ec1fe92f 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,5 +1,3 @@ -use std::{ops::ControlFlow, path::Path}; - mod state; mod util; mod view; @@ -12,28 +10,11 @@ mod input; mod history; -use history::ActionHistory; pub use input::EditorInputScheme; -use crate::{ - editor::{ - state::{EditorTool, TileSelection}, - windows::{CreateTilesetWindow, MenuWindow}, - }, - map::{Map, MapLayerKind}, - resources::{map_name_to_filename, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION}, - Resources, -}; - -use macroquad::{ - experimental::scene::{Node, RefMut}, - prelude::{collections::storage, render_target, RenderTarget}, -}; - -use self::{ - view::LevelView, - windows::{CreateLayerResult, CreateTilesetResult, MenuResult, SaveMapResult}, -}; +use crate::map::Map; + +use macroquad::experimental::scene::{Node, RefMut}; use crate::resources::MapResource; diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index 7bf18b350f..db45698b22 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -3,13 +3,6 @@ mod side_panel; mod toolbar; mod windows; -use macroquad::prelude::RenderTarget; - -use crate::editor::{ - actions::{UiAction, UiActionExt}, - view::LevelView, -}; - impl super::State { pub fn ui(&mut self, egui_ctx: &egui::Context) { self.draw_toolbar(egui_ctx); diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index c479f1f338..e061b3d471 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -1,10 +1,10 @@ mod objects; -use macroquad::prelude::{collections::storage, RenderTarget}; +use macroquad::prelude::collections::storage; use crate::{ editor::{ - actions::{UiAction, UiActionExt}, + actions::UiAction, state::EditorTool, util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, view::LevelView, diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index e0ce8116e4..43f5ec0cfd 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -21,8 +21,8 @@ impl State { &mut self, egui_ctx: &egui::Context, level_response: &egui::Response, - painter: &egui::Painter, - cursor_tile_pos: egui::Pos2, + _painter: &egui::Painter, + _cursor_tile_pos: egui::Pos2, ) { if let Some(settings) = &mut self.object_being_placed { let position = diff --git a/src/editor/state/ui/side_panel/mod.rs b/src/editor/state/ui/side_panel/mod.rs index 88979b4caf..33992a3e26 100644 --- a/src/editor/state/ui/side_panel/mod.rs +++ b/src/editor/state/ui/side_panel/mod.rs @@ -1,10 +1,7 @@ mod layers; mod tileset; -use crate::{ - editor::actions::{UiAction, UiActionExt}, - map::MapLayerKind, -}; +use crate::map::MapLayerKind; use super::super::State; diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs index 00e88e1c83..df10d89d60 100644 --- a/src/editor/state/ui/toolbar.rs +++ b/src/editor/state/ui/toolbar.rs @@ -1,8 +1,5 @@ use crate::{ - editor::{ - actions::{UiAction, UiActionExt}, - state::EditorTool, - }, + editor::{actions::UiAction, state::EditorTool}, map::MapLayerKind, }; diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs index 342212f513..ef797e7e0a 100644 --- a/src/editor/state/ui/windows.rs +++ b/src/editor/state/ui/windows.rs @@ -1,7 +1,7 @@ use std::ops::ControlFlow; use crate::editor::{ - actions::{UiAction, UiActionExt}, + actions::UiAction, state::State, windows::{CreateLayerResult, CreateTilesetResult, MenuResult, SaveMapResult}, }; diff --git a/src/editor/windows/menu.rs b/src/editor/windows/menu.rs index 4562bd022a..3107c7e8af 100644 --- a/src/editor/windows/menu.rs +++ b/src/editor/windows/menu.rs @@ -1,7 +1,5 @@ use std::ops::ControlFlow; -use crate::map::Map; - #[derive(Default)] pub struct MenuWindow; diff --git a/src/editor/windows/mod.rs b/src/editor/windows/mod.rs index 5995c2b943..1d64204439 100644 --- a/src/editor/windows/mod.rs +++ b/src/editor/windows/mod.rs @@ -8,4 +8,4 @@ mod menu; pub use menu::*; mod save_map; -pub use save_map::*; \ No newline at end of file +pub use save_map::*; diff --git a/src/editor/windows/save_map.rs b/src/editor/windows/save_map.rs index a7f1ddff7d..75a463f08a 100644 --- a/src/editor/windows/save_map.rs +++ b/src/editor/windows/save_map.rs @@ -21,13 +21,6 @@ pub enum SaveMapResult { } impl SaveMapWindow { - pub fn new() -> Self { - Self { - map_name: String::new(), - overwrite_existing: false, - } - } - pub fn ui(&mut self, egui_ctx: &egui::Context) -> ControlFlow { let mut action = ControlFlow::Continue(()); let resources = storage::get::(); From 5a4d5b37931f7194e86468872bd798655238311a Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 20:04:48 +0200 Subject: [PATCH 42/81] Fix build --- src/editor/windows/create_tileset.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/editor/windows/create_tileset.rs b/src/editor/windows/create_tileset.rs index 20f55db72f..30ef225bc7 100644 --- a/src/editor/windows/create_tileset.rs +++ b/src/editor/windows/create_tileset.rs @@ -4,22 +4,14 @@ use macroquad::prelude::collections::storage; use crate::{map::Map, resources::TextureKind, Resources}; -#[derive(Default)] pub struct CreateTilesetWindow { tileset_name: String, texture_options: Vec, texture_idx: usize, } -pub enum CreateTilesetResult { - Create { - tileset_name: String, - texture: String, - }, - Close, -} -impl CreateTilesetWindow { - pub fn new() -> Self { +impl Default for CreateTilesetWindow { + fn default() -> Self { Self { tileset_name: "Unnamed Tileset".to_owned(), texture_options: storage::get::() @@ -32,7 +24,16 @@ impl CreateTilesetWindow { 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(()); From 91bbb5d47106af49b224b0e1dfa4d606525b0907 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 20:16:42 +0200 Subject: [PATCH 43/81] Only drag objects with primary button --- src/editor/state/ui/level/objects.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 43f5ec0cfd..d48ef23a82 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -214,7 +214,7 @@ impl State { } if is_this_object_selected { - if response.dragged() { + if response.dragged_by(egui::PointerButton::Primary) { let cursor_offset = self.selected_map_entity.as_ref().unwrap().click_offset; let cursor_level_pos = screen_to_world_pos( ui.input().pointer.interact_pos().unwrap() + cursor_offset, @@ -235,7 +235,7 @@ impl State { } else if can_select_object { self.show_object_info_tooltip(egui_ctx, object, is_valid); - if response.drag_started() { + if response.drag_started() && ui.input().pointer.primary_down() { let click_pos = ui.input().pointer.interact_pos().unwrap(); action = Some(UiAction::SelectObject { index: object_idx, From 6c496bde1d613824cc85633ed44fe65d2e3cc3b9 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 20:44:54 +0200 Subject: [PATCH 44/81] Remove the old Editor in favor of State --- src/editor/mod.rs | 114 +++------------------- src/editor/state/mod.rs | 69 ++++++++++++- src/editor/state/ui.rs | 2 +- src/editor/state/ui/level/mod.rs | 4 +- src/editor/state/ui/level/objects.rs | 4 +- src/editor/state/ui/side_panel/layers.rs | 4 +- src/editor/state/ui/side_panel/mod.rs | 4 +- src/editor/state/ui/side_panel/tileset.rs | 4 +- src/editor/state/ui/toolbar.rs | 4 +- src/editor/state/ui/windows.rs | 4 +- 10 files changed, 92 insertions(+), 121 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 79ec1fe92f..54084f2303 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -11,6 +11,7 @@ mod input; mod history; pub use input::EditorInputScheme; +pub use state::Editor; use crate::map::Map; @@ -18,10 +19,6 @@ use macroquad::experimental::scene::{Node, RefMut}; use crate::resources::MapResource; -pub struct Editor { - state: state::State, -} - /// Used to interface with macroquad. Necessary because using `node: RefMut` really limits /// what can be done in regards to borrowing. pub struct EditorNode { @@ -53,29 +50,28 @@ impl Node for EditorNode { .input_scheme .collect_input(node.accept_kb_input, node.accept_mouse_input); - node.editor.state.level_view.position += - input.camera_move_direction * Self::CAMERA_PAN_SPEED; + node.editor.level_view.position += input.camera_move_direction * Self::CAMERA_PAN_SPEED; let target_size = vec2( - node.editor.state.level_render_target.texture.width(), - node.editor.state.level_render_target.texture.height(), + node.editor.level_render_target.texture.width(), + node.editor.level_render_target.texture.height(), ); let zoom = vec2( - node.editor.state.level_view.scale / target_size.x, - node.editor.state.level_view.scale / target_size.y, + node.editor.level_view.scale / target_size.x, + node.editor.level_view.scale / target_size.y, ) * 2.; let camera = Some(Camera2D { offset: vec2(-1., -1.), - target: node.editor.state.level_view.position, + target: node.editor.level_view.position, zoom, - render_target: Some(node.editor.state.level_render_target), + render_target: Some(node.editor.level_render_target), ..Camera2D::default() }); scene::set_camera(0, camera); if input.toggle_menu { - node.editor.state.menu_window = if node.editor.state.menu_window.is_some() { + node.editor.menu_window = if node.editor.menu_window.is_some() { None } else { Some(Default::default()) @@ -83,10 +79,10 @@ impl Node for EditorNode { } if input.undo { - node.editor.state.apply_action(UiAction::Undo); + node.editor.apply_action(UiAction::Undo); } if input.redo { - node.editor.state.apply_action(UiAction::Redo); + node.editor.apply_action(UiAction::Redo); } } @@ -105,91 +101,3 @@ impl Node for EditorNode { egui_macroquad::draw(); } } - -impl Editor { - const CAMERA_PAN_THRESHOLD: f32 = 0.025; - - 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 MESSAGE_TIMEOUT: f32 = 2.5; - - pub fn new(map_resource: MapResource) -> Self { - Self { - state: state::State::new(map_resource), - } - } - - pub fn ui(&mut self, egui_ctx: &egui::Context) { - self.state.ui(egui_ctx); - } - - pub fn draw(&self) { - let map = &self.state.map_resource.map; - { - map.draw_background(None, !self.state.is_parallax_enabled); - map.draw(None, false); - } - - if self.state.should_draw_grid { - self::draw_grid(map); - } - } -} - -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/mod.rs b/src/editor/state/mod.rs index 0138825c2a..e16e06065f 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -6,7 +6,7 @@ use macroquad::prelude::{collections::storage, RenderTarget}; use crate::{ editor::actions, - map::{MapLayerKind, MapObjectKind}, + map::{Map, MapLayerKind, MapObjectKind}, resources::{ map_name_to_filename, MapResource, MAP_EXPORTS_DEFAULT_DIR, MAP_EXPORTS_EXTENSION, }, @@ -58,7 +58,7 @@ pub struct ObjectSettings { pub id: Option, } -pub struct State { +pub struct Editor { pub selected_tool: EditorTool, pub map_resource: MapResource, pub selected_layer: Option, @@ -80,7 +80,7 @@ pub struct State { pub level_render_target: RenderTarget, } -impl State { +impl Editor { pub fn new(map_resource: MapResource) -> Self { Self { map_resource, @@ -262,4 +262,67 @@ impl State { _ => todo!(), } } + + pub fn draw(&self) { + 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); + } + } +} + +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 index db45698b22..92d8d683dc 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -3,7 +3,7 @@ mod side_panel; mod toolbar; mod windows; -impl super::State { +impl super::Editor { pub fn ui(&mut self, egui_ctx: &egui::Context) { self.draw_toolbar(egui_ctx); self.draw_side_panel(egui_ctx); diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index e061b3d471..af35fe89e1 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -12,9 +12,9 @@ use crate::{ Resources, }; -use super::super::State; +use super::super::Editor; -impl State { +impl Editor { pub(super) fn draw_level(&mut self, egui_ctx: &egui::Context) { egui::CentralPanel::default() .frame(egui::Frame::none()) diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index d48ef23a82..b6ef646561 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -7,7 +7,7 @@ use crate::{ actions::{UiAction, UiActionExt}, state::{ ui::level::{screen_to_world_pos, world_to_screen_pos}, - EditorTool, ObjectSettings, State, + Editor, EditorTool, ObjectSettings, }, util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec}, view::LevelView, @@ -16,7 +16,7 @@ use crate::{ AnimatedSpriteMetadata, Resources, }; -impl State { +impl Editor { pub(super) fn draw_level_object_placement_overlay( &mut self, egui_ctx: &egui::Context, diff --git a/src/editor/state/ui/side_panel/layers.rs b/src/editor/state/ui/side_panel/layers.rs index fc6b593951..dab25d04b0 100644 --- a/src/editor/state/ui/side_panel/layers.rs +++ b/src/editor/state/ui/side_panel/layers.rs @@ -1,12 +1,12 @@ use crate::{ editor::{ actions::{UiAction, UiActionExt}, - state::State, + state::Editor, }, map::MapLayerKind, }; -impl State { +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 diff --git a/src/editor/state/ui/side_panel/mod.rs b/src/editor/state/ui/side_panel/mod.rs index 33992a3e26..cb11dd5dd4 100644 --- a/src/editor/state/ui/side_panel/mod.rs +++ b/src/editor/state/ui/side_panel/mod.rs @@ -3,9 +3,9 @@ mod tileset; use crate::map::MapLayerKind; -use super::super::State; +use super::super::Editor; -impl State { +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, diff --git a/src/editor/state/ui/side_panel/tileset.rs b/src/editor/state/ui/side_panel/tileset.rs index 8b7cac2f70..713e3b0f4d 100644 --- a/src/editor/state/ui/side_panel/tileset.rs +++ b/src/editor/state/ui/side_panel/tileset.rs @@ -10,9 +10,9 @@ use crate::{ Resources, }; -use super::State; +use super::Editor; -impl State { +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 diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs index df10d89d60..3ca1c88c23 100644 --- a/src/editor/state/ui/toolbar.rs +++ b/src/editor/state/ui/toolbar.rs @@ -3,9 +3,9 @@ use crate::{ map::MapLayerKind, }; -use super::super::State; +use super::super::Editor; -impl State { +impl Editor { 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(); diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs index ef797e7e0a..8bec4a0716 100644 --- a/src/editor/state/ui/windows.rs +++ b/src/editor/state/ui/windows.rs @@ -2,11 +2,11 @@ use std::ops::ControlFlow; use crate::editor::{ actions::UiAction, - state::State, + state::Editor, windows::{CreateLayerResult, CreateTilesetResult, MenuResult, SaveMapResult}, }; -impl State { +impl Editor { pub(super) fn draw_windows(&mut self, egui_ctx: &egui::Context) { if let Some(window) = &mut self.create_layer_window { match window.ui(egui_ctx, &self.map_resource.map) { From 666bb5143440ed4ac971f7a3859d824e82a34022 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 20:46:23 +0200 Subject: [PATCH 45/81] Clean up --- src/editor/history.rs | 1 + src/editor/mod.rs | 4 ---- src/editor/state/ui/level/objects.rs | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/editor/history.rs b/src/editor/history.rs index 99defcafbe..ec42ebaeae 100644 --- a/src/editor/history.rs +++ b/src/editor/history.rs @@ -49,6 +49,7 @@ impl ActionHistory { Ok(()) } + #[allow(dead_code)] pub fn clear(&mut self) { self.undo_stack.clear(); self.redo_stack.clear(); diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 54084f2303..985786f24e 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -13,12 +13,8 @@ mod history; pub use input::EditorInputScheme; pub use state::Editor; -use crate::map::Map; - use macroquad::experimental::scene::{Node, RefMut}; -use crate::resources::MapResource; - /// Used to interface with macroquad. Necessary because using `node: RefMut` really limits /// what can be done in regards to borrowing. pub struct EditorNode { diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index b6ef646561..8774427c31 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -32,7 +32,7 @@ impl Editor { Create, Close, Noop, - }; + } let response = egui::Window::new("Placing object") .fixed_pos(position) From c6649f0e8c5d2e1d503d75fecbbdb90179c76339 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 16 Apr 2022 22:41:20 +0200 Subject: [PATCH 46/81] Allow repositioning object creation --- src/editor/state/ui/level/objects.rs | 32 +++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 8774427c31..bdd7a8cace 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -124,22 +124,30 @@ impl Editor { } PlaceObjectResult::Noop => (), } - } else { - if self.selected_tool == EditorTool::ObjectPlacer - && level_response.clicked_by(egui::PointerButton::Primary) - { - let position = screen_to_world_pos( - egui_ctx.input().pointer.interact_pos().unwrap(), - level_response.rect.min.to_vec2(), - &self.level_view, - ); + } - self.object_being_placed = Some(ObjectSettings { + if self.selected_tool == EditorTool::ObjectPlacer + && (level_response.clicked_by(egui::PointerButton::Primary) + || level_response.dragged_by(egui::PointerButton::Primary)) + { + let position = screen_to_world_pos( + egui_ctx.input().pointer.interact_pos().unwrap(), + level_response.rect.min.to_vec2(), + &self.level_view, + ); + + 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, - }); - } + }) + }; } } From 5b044e5e8ef519de48e4c539b5b905c39955f3be Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Wed, 20 Apr 2022 15:40:29 +0200 Subject: [PATCH 47/81] Only display objects if layer is visible --- src/editor/state/ui/level/objects.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index bdd7a8cace..e038dbe54a 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -166,7 +166,9 @@ impl Editor { .iter() .filter_map(|layer_id| self.map_resource.map.layers.get(layer_id)) { - action.then_do(self.draw_object_layer(layer, response, painter, ui, egui_ctx)); + if layer.is_visible { + action.then_do(self.draw_object_layer(layer, response, painter, ui, egui_ctx)); + } } if let Some(action) = action { From 3bd9800ed48eee3139b860a269e0ab1842e160ef Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 23 Apr 2022 00:09:10 +0200 Subject: [PATCH 48/81] Partial commit --- src/editor/actions.rs | 8 +- src/editor/mod.rs | 21 +---- src/editor/state/mod.rs | 74 +++++++++++++--- src/editor/state/ui.rs | 2 + src/editor/state/ui/level/mod.rs | 5 +- src/editor/state/ui/level/objects.rs | 123 ++++++++++++++++----------- 6 files changed, 142 insertions(+), 91 deletions(-) diff --git a/src/editor/actions.rs b/src/editor/actions.rs index c43b39f86c..37a1ce3d9b 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -5,7 +5,7 @@ use core::error::Result; use crate::map::{Map, MapLayerKind, MapTileset}; use crate::map::{MapBackgroundLayer, MapObjectKind}; -use super::state::EditorTool; +use super::state::{EditorTool, SelectableEntity}; /// 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. @@ -60,11 +60,7 @@ pub enum UiAction { texture_id: String, autotile_mask: Vec, }, - SelectObject { - index: usize, - layer_id: String, - cursor_offset: egui::Vec2, - }, + SelectEntity(SelectableEntity), DeselectObject, CreateObject { id: String, diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 985786f24e..dfd0899315 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -26,8 +26,6 @@ pub struct EditorNode { } impl EditorNode { - const CAMERA_PAN_SPEED: f32 = 5.0; - pub fn new(editor: Editor, input_scheme: EditorInputScheme) -> Self { Self { editor, @@ -46,8 +44,6 @@ impl Node for EditorNode { .input_scheme .collect_input(node.accept_kb_input, node.accept_mouse_input); - node.editor.level_view.position += input.camera_move_direction * Self::CAMERA_PAN_SPEED; - let target_size = vec2( node.editor.level_render_target.texture.width(), node.editor.level_render_target.texture.height(), @@ -64,22 +60,9 @@ impl Node for EditorNode { ..Camera2D::default() }); - scene::set_camera(0, camera); - - if input.toggle_menu { - node.editor.menu_window = if node.editor.menu_window.is_some() { - None - } else { - Some(Default::default()) - }; - } + node.editor.process_input(&input); - if input.undo { - node.editor.apply_action(UiAction::Undo); - } - if input.redo { - node.editor.apply_action(UiAction::Redo); - } + scene::set_camera(0, camera); } fn draw(mut node: RefMut) diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index e16e06065f..2b9223b0cf 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -13,7 +13,9 @@ use crate::{ Resources, }; -use super::{actions::UiAction, history::ActionHistory, view::LevelView, windows}; +use super::{ + actions::UiAction, history::ActionHistory, input::EditorInput, view::LevelView, windows, +}; #[derive(Clone, PartialEq, Eq, Debug)] pub enum EditorTool { @@ -29,11 +31,13 @@ pub struct TileSelection { pub tile_id: u32, } +#[derive(Debug, Clone)] pub enum SelectableEntityKind { Object { layer_id: String, index: usize }, SpawnPoint { index: usize }, } +#[derive(Debug, Clone)] pub struct SelectableEntity { pub kind: SelectableEntityKind, pub click_offset: egui::Vec2, @@ -65,7 +69,6 @@ pub struct Editor { pub selected_tile: Option, pub is_parallax_enabled: bool, pub should_draw_grid: bool, - pub selected_map_entity: Option, pub object_being_placed: Option, create_layer_window: Option, @@ -73,6 +76,8 @@ pub struct Editor { pub menu_window: Option, save_map_window: Option, + pub selection: Option, + history: ActionHistory, pub level_view: LevelView, @@ -89,7 +94,6 @@ impl Editor { should_draw_grid: true, selected_layer: None, selected_tile: None, - selected_map_entity: None, object_being_placed: None, create_layer_window: None, @@ -97,6 +101,8 @@ impl Editor { menu_window: None, save_map_window: None, + selection: None, + history: ActionHistory::new(), level_view: LevelView { @@ -216,17 +222,7 @@ impl Editor { .apply(action, &mut self.map_resource.map) .unwrap(); } - UiAction::SelectObject { - layer_id, - index, - cursor_offset, - } => { - self.selected_map_entity = Some(SelectableEntity { - click_offset: cursor_offset, - kind: SelectableEntityKind::Object { index, layer_id }, - }) - } - UiAction::DeselectObject => self.selected_map_entity = None, + UiAction::SelectEntity(selection) => self.selection = Some(selection), UiAction::SaveMap { name } => { let mut map_resource = self.map_resource.clone(); @@ -258,6 +254,13 @@ impl Editor { .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(); + } _ => todo!(), } @@ -274,6 +277,49 @@ impl Editor { 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; + + // 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 }); + self.selection = None; + } + SelectableEntityKind::SpawnPoint { index } => { + let index = *index; + self.apply_action(UiAction::DeleteSpawnPoint(index)); + self.selection = None; + } + } + } + } + } } fn draw_grid(map: &Map) { diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index 92d8d683dc..b6468e20df 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -1,3 +1,5 @@ +use crate::editor::input::EditorInput; + mod level; mod side_panel; mod toolbar; diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index af35fe89e1..1bb08bc9bf 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -5,7 +5,7 @@ use macroquad::prelude::collections::storage; use crate::{ editor::{ actions::UiAction, - state::EditorTool, + state::{EditorTool, SelectableEntity, SelectableEntityKind}, util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, view::LevelView, }, @@ -34,6 +34,9 @@ impl Editor { self.draw_objects(egui_ctx, ui, &response, &painter); self.draw_level_overlays(egui_ctx, ui, &response, &painter); + // Drag selected object + self.drag_selected_object(&response, ui); + let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); self.level_render_target.resize_if_appropiate(width, height); }); diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index e038dbe54a..7e979059b0 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -7,7 +7,7 @@ use crate::{ actions::{UiAction, UiActionExt}, state::{ ui::level::{screen_to_world_pos, world_to_screen_pos}, - Editor, EditorTool, ObjectSettings, + Editor, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind, }, util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec}, view::LevelView, @@ -176,6 +176,30 @@ impl Editor { } } + pub(super) fn drag_selected_object(&mut self, response: &egui::Response, ui: &egui::Ui) { + if response.dragged_by(egui::PointerButton::Primary) { + if let Some(SelectableEntity { + kind: SelectableEntityKind::Object { index, layer_id }, + click_offset: drag_offset, + .. + }) = &self.selection + { + let cursor_level_pos = screen_to_world_pos( + ui.input().pointer.interact_pos().unwrap() + *drag_offset, + response.rect.min.to_vec2(), + &self.level_view, + ); + let index = *index; + let layer_id = layer_id.clone(); + self.apply_action(UiAction::MoveObject { + index, + layer_id, + position: macroquad::math::Vec2::new(cursor_level_pos.x, cursor_level_pos.y), + }); + } + } + } + fn draw_object_layer( &self, layer: &MapLayer, @@ -185,36 +209,54 @@ impl Editor { egui_ctx: &egui::Context, ) -> Option { let resources = storage::get::(); - let mut action = None; + let mut to_select = None; + let is_layer_selected = self + .selected_layer + .as_ref() + .map(|id| &layer.id == id) + .unwrap_or(false); for (object_idx, object) in layer.objects.iter().enumerate() { let (dest, is_valid) = draw_object(object, response, &self.level_view, painter, &resources); - let is_this_object_selected; - let can_select_object; - if let Some(selected_entity) = &self.selected_map_entity { - is_this_object_selected = selected_entity.kind.is_object(&layer.id, object_idx); - can_select_object = false; - } else { - is_this_object_selected = false; - can_select_object = self.selected_tool == EditorTool::Cursor - && response.hovered() - && ui - .input() - .pointer - .hover_pos() - .map(|hover_pos| dest.contains(hover_pos)) - .unwrap_or(false); - } + let is_hovered = is_layer_selected + && response.hovered() + && ui + .input() + .pointer + .hover_pos() + .map(|hover_pos| dest.contains(hover_pos)) + .unwrap_or(false); + + if is_hovered { + if (response.drag_started() || response.clicked()) && is_hovered { + let click_pos = ui.input().pointer.interact_pos().unwrap(); + + to_select = Some(SelectableEntity { + click_offset: dest.min - click_pos, + kind: SelectableEntityKind::Object { + index: object_idx, + layer_id: layer.id.clone(), + }, + }); + } + + let is_selected = matches!( + &self.selection, + Some(SelectableEntity { + kind: SelectableEntityKind::Object { index, layer_id }, + click_offset, + .. + }) if index == &object_idx && layer_id == &layer.id + ); - if can_select_object || is_this_object_selected { painter.add(egui::Shape::rect_stroke( dest, egui::Rounding::none(), egui::Stroke::new( 1., - if is_this_object_selected { + if is_selected { egui::Color32::WHITE } else { egui::Color32::GRAY @@ -222,40 +264,19 @@ impl Editor { ), )); } + } - if is_this_object_selected { - if response.dragged_by(egui::PointerButton::Primary) { - let cursor_offset = self.selected_map_entity.as_ref().unwrap().click_offset; - let cursor_level_pos = screen_to_world_pos( - ui.input().pointer.interact_pos().unwrap() + cursor_offset, - response.rect.min.to_vec2(), - &self.level_view, - ); - action = Some(UiAction::MoveObject { - index: object_idx, - layer_id: layer.id.clone(), - position: macroquad::math::Vec2::new( - cursor_level_pos.x, - cursor_level_pos.y, - ), - }); - } else if response.drag_released() { - action = Some(UiAction::DeselectObject); - } - } else if can_select_object { - self.show_object_info_tooltip(egui_ctx, object, is_valid); - - if response.drag_started() && ui.input().pointer.primary_down() { - let click_pos = ui.input().pointer.interact_pos().unwrap(); - action = Some(UiAction::SelectObject { - index: object_idx, - layer_id: layer.id.clone(), - cursor_offset: dest.min - click_pos, - }); - } + if is_layer_selected { + if let Some(to_select) = to_select { + Some(UiAction::SelectEntity(to_select)) + } else if ui.input().pointer.any_down() { + Some(UiAction::DeselectObject) + } else { + None } + } else { + None } - action } fn show_object_info_tooltip( From 56cc5a98263c7aa665b94f40d0462a3d6294fffa Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 23 Apr 2022 00:39:57 +0200 Subject: [PATCH 49/81] Misc selection changes --- src/editor/mod.rs | 1 - src/editor/state/mod.rs | 3 ++ src/editor/state/ui.rs | 2 -- src/editor/state/ui/level/mod.rs | 24 +++++-------- src/editor/state/ui/level/objects.rs | 52 +++++++++++++--------------- 5 files changed, 35 insertions(+), 47 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index dfd0899315..e077603990 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -4,7 +4,6 @@ mod view; mod windows; mod actions; -use actions::UiAction; mod input; diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 2b9223b0cf..aee570651a 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -261,6 +261,9 @@ impl Editor { .apply(action, &mut self.map_resource.map) .unwrap(); } + UiAction::DeselectObject => { + self.selection = None; + } _ => todo!(), } diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index b6468e20df..92d8d683dc 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -1,5 +1,3 @@ -use crate::editor::input::EditorInput; - mod level; mod side_panel; mod toolbar; diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 1bb08bc9bf..111a602507 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -5,7 +5,7 @@ use macroquad::prelude::collections::storage; use crate::{ editor::{ actions::UiAction, - state::{EditorTool, SelectableEntity, SelectableEntityKind}, + state::EditorTool, util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, view::LevelView, }, @@ -31,8 +31,8 @@ impl Editor { ); painter.add(egui::Shape::mesh(level_mesh)); - self.draw_objects(egui_ctx, ui, &response, &painter); - self.draw_level_overlays(egui_ctx, ui, &response, &painter); + self.handle_objects(egui_ctx, ui, &response, &painter); + self.draw_level_overlays(ui, &response, &painter); // Drag selected object self.drag_selected_object(&response, ui); @@ -44,7 +44,6 @@ impl Editor { fn draw_level_overlays( &mut self, - egui_ctx: &egui::Context, ui: &mut egui::Ui, level_response: &egui::Response, painter: &egui::Painter, @@ -67,19 +66,13 @@ impl Editor { ); let cursor_tile_pos = (cursor_px_pos.to_vec2() / tile_size).floor().to_pos2(); - self.draw_level_placement_overlay(egui_ctx, level_response, painter, cursor_tile_pos); + self.draw_level_placement_overlay(ui.ctx(), level_response, painter, cursor_tile_pos); let level_top_left = level_response.rect.min; - self.draw_level_pointer_pos_overlay( - egui_ctx, - ui, - level_top_left, - cursor_px_pos, - cursor_tile_pos, - ); + self.draw_level_pointer_pos_overlay(ui, level_top_left, cursor_px_pos, cursor_tile_pos); self.draw_level_object_placement_overlay( - egui_ctx, + ui.ctx(), level_response, painter, cursor_tile_pos, @@ -187,7 +180,6 @@ impl Editor { fn draw_level_pointer_pos_overlay( &self, - egui_ctx: &egui::Context, ui: &mut egui::Ui, level_top_left: egui::Pos2, cursor_px_pos: egui::Pos2, @@ -204,8 +196,8 @@ impl Editor { ) .interactable(false) .drag_bounds(egui::Rect::EVERYTHING) // disable clip rect - .show(egui_ctx, |ui| { - egui::Frame::popup(&egui_ctx.style()) + .show(ui.ctx(), |ui| { + egui::Frame::popup(&ui.ctx().style()) .show(ui, |ui| { ui.label(format!( "Cursor position: ({}, {}) in pixels: ({:.2}, {:.2})", diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 7e979059b0..a360efc2e4 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -4,7 +4,7 @@ use macroquad::prelude::collections::storage; use crate::{ editor::{ - actions::{UiAction, UiActionExt}, + actions::UiAction, state::{ ui::level::{screen_to_world_pos, world_to_screen_pos}, Editor, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind, @@ -151,14 +151,14 @@ impl Editor { } } - pub(super) fn draw_objects( + pub(super) fn handle_objects( &mut self, egui_ctx: &egui::Context, ui: &mut egui::Ui, response: &egui::Response, painter: &egui::Painter, ) { - let mut action = None; + let mut to_select = None; for layer in self .map_resource .map @@ -167,12 +167,15 @@ impl Editor { .filter_map(|layer_id| self.map_resource.map.layers.get(layer_id)) { if layer.is_visible { - action.then_do(self.draw_object_layer(layer, response, painter, ui, egui_ctx)); + to_select = + to_select.or(self.handle_object_layer(layer, response, painter, ui, egui_ctx)); } } - if let Some(action) = action { - self.apply_action(action); + if let Some(to_select) = to_select { + self.apply_action(UiAction::SelectEntity(to_select)); + } else if ui.input().pointer.any_pressed() { + self.apply_action(UiAction::DeselectObject); } } @@ -200,14 +203,14 @@ impl Editor { } } - fn draw_object_layer( + fn handle_object_layer( &self, layer: &MapLayer, response: &egui::Response, painter: &egui::Painter, ui: &mut egui::Ui, egui_ctx: &egui::Context, - ) -> Option { + ) -> Option { let resources = storage::get::(); let mut to_select = None; let is_layer_selected = self @@ -230,7 +233,9 @@ impl Editor { .unwrap_or(false); if is_hovered { - if (response.drag_started() || response.clicked()) && is_hovered { + self.show_object_info_tooltip(egui_ctx, &object, is_valid); + + if response.drag_started() || response.clicked() { let click_pos = ui.input().pointer.interact_pos().unwrap(); to_select = Some(SelectableEntity { @@ -241,16 +246,17 @@ impl Editor { }, }); } + } - let is_selected = matches!( - &self.selection, - Some(SelectableEntity { - kind: SelectableEntityKind::Object { index, layer_id }, - click_offset, - .. - }) if index == &object_idx && layer_id == &layer.id - ); + let is_selected = matches!( + &self.selection, + Some(SelectableEntity { + kind: SelectableEntityKind::Object { index, layer_id }, + .. + }) if index == &object_idx && layer_id == &layer.id + ); + if is_hovered || is_selected { painter.add(egui::Shape::rect_stroke( dest, egui::Rounding::none(), @@ -266,17 +272,7 @@ impl Editor { } } - if is_layer_selected { - if let Some(to_select) = to_select { - Some(UiAction::SelectEntity(to_select)) - } else if ui.input().pointer.any_down() { - Some(UiAction::DeselectObject) - } else { - None - } - } else { - None - } + to_select } fn show_object_info_tooltip( From 208e1dcc099758a1ac35b80f260a7ed380d0f9dd Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 23 Apr 2022 00:40:02 +0200 Subject: [PATCH 50/81] Add TODO --- src/editor/TODO.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/editor/TODO.md diff --git a/src/editor/TODO.md b/src/editor/TODO.md new file mode 100644 index 0000000000..443cf195a1 --- /dev/null +++ b/src/editor/TODO.md @@ -0,0 +1,7 @@ +- [ ] Object list +- [ ] Spawnpoint placement +- [ ] Object placement via rightclick context menu +- [ ] Open/Import window +- [ ] New map window +- [ ] Theme support +- [ ] Gamepad support \ No newline at end of file From 06f93ef0d7e303a57a4805489938798e52953460 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 23 Apr 2022 13:07:14 +0200 Subject: [PATCH 51/81] Object selection improvements --- src/editor/mod.rs | 18 --- src/editor/state/mod.rs | 41 +++++- src/editor/state/ui/level/mod.rs | 12 +- src/editor/state/ui/level/objects.rs | 193 ++++++++++++++++++--------- src/editor/view.rs | 2 - 5 files changed, 170 insertions(+), 96 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index e077603990..69cd34a820 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -43,25 +43,7 @@ impl Node for EditorNode { .input_scheme .collect_input(node.accept_kb_input, node.accept_mouse_input); - let target_size = vec2( - node.editor.level_render_target.texture.width(), - node.editor.level_render_target.texture.height(), - ); - let zoom = vec2( - node.editor.level_view.scale / target_size.x, - node.editor.level_view.scale / target_size.y, - ) * 2.; - let camera = Some(Camera2D { - offset: vec2(-1., -1.), - target: node.editor.level_view.position, - zoom, - render_target: Some(node.editor.level_render_target), - ..Camera2D::default() - }); - node.editor.process_input(&input); - - scene::set_camera(0, camera); } fn draw(mut node: RefMut) diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index aee570651a..aa366ba12b 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -37,10 +37,16 @@ pub enum SelectableEntityKind { 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 click_offset: egui::Vec2, + pub drag_data: Option, } impl SelectableEntityKind { @@ -222,7 +228,16 @@ impl Editor { .apply(action, &mut self.map_resource.map) .unwrap(); } - UiAction::SelectEntity(selection) => self.selection = Some(selection), + UiAction::SelectEntity(selection) => { + if let SelectableEntity { + kind: SelectableEntityKind::Object { layer_id, .. }, + .. + } = &selection + { + self.selected_layer = Some(layer_id.clone()); + } + self.selection = Some(selection); + } UiAction::SaveMap { name } => { let mut map_resource = self.map_resource.clone(); @@ -285,7 +300,27 @@ impl Editor { const CAMERA_PAN_SPEED: f32 = 5.0; // Move camera - self.level_view.position += input.camera_move_direction * CAMERA_PAN_SPEED; + { + 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.; + self.level_view.position += input.camera_move_direction * CAMERA_PAN_SPEED; + let camera = Some(Camera2D { + offset: vec2(-1., -1.), + target: self.level_view.position, + zoom, + render_target: Some(self.level_render_target), + ..Camera2D::default() + }); + + scene::set_camera(0, camera); + } // Undo/redo if input.undo { diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 111a602507..a13608545e 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -31,12 +31,9 @@ impl Editor { ); painter.add(egui::Shape::mesh(level_mesh)); - self.handle_objects(egui_ctx, ui, &response, &painter); + self.handle_objects(ui, &response, &painter); self.draw_level_overlays(ui, &response, &painter); - // Drag selected object - self.drag_selected_object(&response, ui); - let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); self.level_render_target.resize_if_appropiate(width, height); }); @@ -71,12 +68,7 @@ impl Editor { let level_top_left = level_response.rect.min; self.draw_level_pointer_pos_overlay(ui, level_top_left, cursor_px_pos, cursor_tile_pos); - self.draw_level_object_placement_overlay( - ui.ctx(), - level_response, - painter, - cursor_tile_pos, - ); + self.draw_level_object_placement_overlay(ui.ctx(), level_response); } } diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index a360efc2e4..91d13b90a2 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -7,7 +7,7 @@ use crate::{ actions::UiAction, state::{ ui::level::{screen_to_world_pos, world_to_screen_pos}, - Editor, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind, + DragData, Editor, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind, }, util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec}, view::LevelView, @@ -21,8 +21,6 @@ impl Editor { &mut self, egui_ctx: &egui::Context, level_response: &egui::Response, - _painter: &egui::Painter, - _cursor_tile_pos: egui::Pos2, ) { if let Some(settings) = &mut self.object_being_placed { let position = @@ -153,7 +151,6 @@ impl Editor { pub(super) fn handle_objects( &mut self, - egui_ctx: &egui::Context, ui: &mut egui::Ui, response: &egui::Response, painter: &egui::Painter, @@ -167,8 +164,7 @@ impl Editor { .filter_map(|layer_id| self.map_resource.map.layers.get(layer_id)) { if layer.is_visible { - to_select = - to_select.or(self.handle_object_layer(layer, response, painter, ui, egui_ctx)); + to_select = to_select.or(self.handle_object_layer(layer, response, painter, ui)); } } @@ -177,28 +173,86 @@ impl Editor { } else if ui.input().pointer.any_pressed() { self.apply_action(UiAction::DeselectObject); } - } - pub(super) fn drag_selected_object(&mut self, response: &egui::Response, ui: &egui::Ui) { - if response.dragged_by(egui::PointerButton::Primary) { - if let Some(SelectableEntity { - kind: SelectableEntityKind::Object { index, layer_id }, - click_offset: drag_offset, - .. - }) = &self.selection + // Draw selection last (Special case) + if let Some(SelectableEntity { + kind: SelectableEntityKind::Object { index, layer_id }, + drag_data, + }) = self.selection.take() + { + 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 { - let cursor_level_pos = screen_to_world_pos( - ui.input().pointer.interact_pos().unwrap() + *drag_offset, - response.rect.min.to_vec2(), + draw_object( + object, + new_pos, + response, &self.level_view, + painter, + &resources, + 1., ); - let index = *index; - let layer_id = layer_id.clone(); - self.apply_action(UiAction::MoveObject { - index, - layer_id, - position: macroquad::math::Vec2::new(cursor_level_pos.x, cursor_level_pos.y), - }); + + if response.dragged_by(egui::PointerButton::Primary) { + let cursor_level_pos = screen_to_world_pos( + ui.input().pointer.interact_pos().unwrap() + cursor_offset, + response.rect.min.to_vec2(), + &self.level_view, + ); + + self.selection = Some(SelectableEntity { + drag_data: Some(DragData { + new_pos: cursor_level_pos, + cursor_offset, + }), + kind: SelectableEntityKind::Object { + index, + layer_id: layer_id.clone(), + }, + }); + } + 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), + }); + self.selection = Some(SelectableEntity { + drag_data: None, + kind: SelectableEntityKind::Object { index, layer_id }, + }); + } + + is_being_dragged = true; + } else { + is_being_dragged = false; + } + + let (dest, is_valid) = draw_object( + object, + object.position.into_egui().to_pos2(), + response, + &self.level_view, + painter, + &resources, + if is_being_dragged { 0.5 } else { 1.0 }, + ); + + 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); } } } @@ -209,65 +263,70 @@ impl Editor { response: &egui::Response, painter: &egui::Painter, ui: &mut egui::Ui, - egui_ctx: &egui::Context, ) -> Option { let resources = storage::get::(); let mut to_select = None; - let is_layer_selected = self - .selected_layer - .as_ref() - .map(|id| &layer.id == id) - .unwrap_or(false); - - for (object_idx, object) in layer.objects.iter().enumerate() { - let (dest, is_valid) = - draw_object(object, response, &self.level_view, painter, &resources); - - let is_hovered = is_layer_selected - && response.hovered() + + 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 + }) { + let (dest, is_valid) = draw_object( + object, + object.position.into_egui().to_pos2(), + response, + &self.level_view, + painter, + &resources, + 1., + ); + + let is_hovered = response.hovered() && ui .input() .pointer .hover_pos() .map(|hover_pos| dest.contains(hover_pos)) - .unwrap_or(false); + .unwrap_or(false) + && !matches!( + &self.selection, + Some(SelectableEntity { + drag_data: Some(_), + .. + }) + ); if is_hovered { - self.show_object_info_tooltip(egui_ctx, &object, is_valid); + 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 { - click_offset: dest.min - click_pos, 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(), + }), }); } - } - - let is_selected = matches!( - &self.selection, - Some(SelectableEntity { - kind: SelectableEntityKind::Object { index, layer_id }, - .. - }) if index == &object_idx && layer_id == &layer.id - ); - - if is_hovered || is_selected { painter.add(egui::Shape::rect_stroke( dest, egui::Rounding::none(), - egui::Stroke::new( - 1., - if is_selected { - egui::Color32::WHITE - } else { - egui::Color32::GRAY - }, - ), + egui::Stroke::new(1., egui::Color32::GRAY), )); } } @@ -309,10 +368,12 @@ impl Editor { fn draw_object( object: &crate::map::MapObject, + position: egui::Pos2, response: &egui::Response, level_view: &LevelView, painter: &egui::Painter, 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.)); @@ -322,7 +383,7 @@ fn draw_object( uv: egui::Rect, tint: egui::Color32| -> egui::Rect { - let position_in_lvl = (object.position + offset).into_egui().to_pos2(); + let position_in_lvl = position + offset.into_egui(); let dest = egui::Rect::from_min_size( world_to_screen_pos(position_in_lvl, response.rect.min, level_view), @@ -350,7 +411,7 @@ fn draw_object( (0., 0.).into(), dest_size, uv, - egui::Color32::WHITE, + egui::Color32::WHITE.linear_multiply(opacity), ) }; @@ -380,7 +441,13 @@ fn draw_object( }) .unwrap_or(FULL_UV); - draw_object(texture_id, sprite.offset, dest_size, uv, tint) + draw_object( + texture_id, + sprite.offset, + dest_size, + uv, + tint.linear_multiply(opacity), + ) } else { // Invalid texture ID draw_invalid_object() @@ -446,7 +513,7 @@ fn draw_object( (0., 0.).into(), dest_size, uv, - egui::Color32::WHITE, + egui::Color32::WHITE.linear_multiply(opacity), ); is_valid = true; } else { diff --git a/src/editor/view.rs b/src/editor/view.rs index f1700d0e5b..c2db11910a 100644 --- a/src/editor/view.rs +++ b/src/editor/view.rs @@ -1,5 +1,3 @@ -// FIXME: This is very ugly, and shouldn't be passed into the editor state as parameter. Is there some -// better way to do this? pub struct LevelView { /// The view offset in pixels. pub position: macroquad::prelude::Vec2, From 35260b59837aa232e26061305b66fd5b8b73291c Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 23 Apr 2022 13:47:07 +0200 Subject: [PATCH 52/81] Refactor --- src/editor/mod.rs | 2 - src/editor/state/ui.rs | 2 +- src/editor/state/ui/level/mod.rs | 112 ++++++++++----------------- src/editor/state/ui/level/objects.rs | 72 ++++++----------- src/editor/state/ui/toolbar.rs | 1 + src/editor/view.rs | 55 +++++++++++++ 6 files changed, 120 insertions(+), 124 deletions(-) diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 69cd34a820..8f26e3f362 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -37,8 +37,6 @@ impl EditorNode { impl Node for EditorNode { fn fixed_update(mut node: RefMut) { - use macroquad::prelude::*; - let input = node .input_scheme .collect_input(node.accept_kb_input, node.accept_mouse_input); diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index 92d8d683dc..6ef24f0273 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -7,7 +7,7 @@ impl super::Editor { pub fn ui(&mut self, egui_ctx: &egui::Context) { self.draw_toolbar(egui_ctx); self.draw_side_panel(egui_ctx); - self.draw_level(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 index a13608545e..5b14e9b1cf 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -7,7 +7,7 @@ use crate::{ actions::UiAction, state::EditorTool, util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, - view::LevelView, + view::UiLevelView, }, Resources, }; @@ -15,92 +15,83 @@ use crate::{ use super::super::Editor; impl Editor { - pub(super) fn draw_level(&mut self, egui_ctx: &egui::Context) { + pub(super) fn handle_level_view(&mut self, egui_ctx: &egui::Context) { egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { - 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)); + let view = self.draw_level_tiles(ui); - self.handle_objects(ui, &response, &painter); - self.draw_level_overlays(ui, &response, &painter); + self.handle_objects(ui, &view); + self.draw_level_overlays(ui, &view); - let (width, height) = (response.rect.width() as u32, response.rect.height() as u32); + 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 draw_level_overlays( - &mut self, - ui: &mut egui::Ui, - level_response: &egui::Response, - painter: &egui::Painter, - ) { + 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| level_response.rect.contains(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 = screen_to_world_pos( - cursor_screen_pos, - level_response.rect.min.to_vec2(), - &self.level_view, - ); + 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(); - self.draw_level_placement_overlay(ui.ctx(), level_response, painter, cursor_tile_pos); + self.handle_placement(ui.ctx(), view, cursor_tile_pos); - let level_top_left = level_response.rect.min; - self.draw_level_pointer_pos_overlay(ui, level_top_left, cursor_px_pos, cursor_tile_pos); + self.draw_level_pointer_pos_overlay(ui, view, cursor_px_pos, cursor_tile_pos); - self.draw_level_object_placement_overlay(ui.ctx(), level_response); + self.draw_level_object_placement_overlay(ui.ctx(), view); } } - fn draw_level_placement_overlay( + fn handle_placement( &mut self, egui_ctx: &egui::Context, - level_response: &egui::Response, - painter: &egui::Painter, + view: &UiLevelView, cursor_tile_pos: egui::Pos2, ) { match self.selected_tool { - EditorTool::TilePlacer => self.draw_level_tile_placement_overlay( - egui_ctx, - level_response, - painter, - cursor_tile_pos, - ), + EditorTool::TilePlacer => self.handle_tile_placement(egui_ctx, view, cursor_tile_pos), // TODO: Spawnpoint placement overlay // TODO: Object placement overlay _ => (), }; } - fn draw_level_tile_placement_overlay( + fn handle_tile_placement( &mut self, egui_ctx: &egui::Context, - level_response: &egui::Response, - painter: &egui::Painter, + view: &UiLevelView, cursor_tile_pos: egui::Pos2, ) { let tile_size = self.map_resource.map.tile_size.into_egui(); - let level_top_left = level_response.rect.min; if cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { if let (Some(selected_tile), Some(selected_layer)) = @@ -127,20 +118,16 @@ impl Editor { let mut tile_mesh = egui::Mesh::with_texture(texture_id); tile_mesh.add_rect_with_uv( egui::Rect::from_min_size( - world_to_screen_pos( - (cursor_tile_pos.to_vec2() * tile_size).to_pos2(), - level_top_left, - &self.level_view, - ), + view.world_to_screen_pos((cursor_tile_pos.to_vec2() * tile_size).to_pos2()), tile_size, ), uv, egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), ); - painter.add(egui::Shape::mesh(tile_mesh)); + view.painter().add(egui::Shape::mesh(tile_mesh)); - if level_response.clicked() || level_response.dragged() { + if view.response().clicked() || view.response().dragged() { let input = egui_ctx.input(); if input.pointer.button_down(egui::PointerButton::Primary) { let id = selected_tile.tile_id; @@ -173,14 +160,14 @@ impl Editor { fn draw_level_pointer_pos_overlay( &self, ui: &mut egui::Ui, - level_top_left: egui::Pos2, + 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( - level_top_left + view.level_top_left() + egui::vec2( ui.spacing().window_margin.left, ui.spacing().window_margin.top, @@ -200,20 +187,3 @@ impl Editor { }); } } - -// TODO: Factor in level view scale -fn screen_to_world_pos( - p: egui::Pos2, - level_top_left: egui::Vec2, - level_view: &LevelView, -) -> egui::Pos2 { - p - level_top_left + level_view.position.into_egui() -} - -fn world_to_screen_pos( - p: egui::Pos2, - level_top_left: egui::Pos2, - level_view: &LevelView, -) -> egui::Pos2 { - p + level_top_left.to_vec2() - level_view.position.into_egui() -} diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 91d13b90a2..94348d7083 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -6,11 +6,10 @@ use crate::{ editor::{ actions::UiAction, state::{ - ui::level::{screen_to_world_pos, world_to_screen_pos}, DragData, Editor, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind, }, util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec}, - view::LevelView, + view::UiLevelView, }, map::{MapLayer, MapObject, MapObjectKind}, AnimatedSpriteMetadata, Resources, @@ -20,11 +19,10 @@ impl Editor { pub(super) fn draw_level_object_placement_overlay( &mut self, egui_ctx: &egui::Context, - level_response: &egui::Response, + view: &UiLevelView, ) { if let Some(settings) = &mut self.object_being_placed { - let position = - world_to_screen_pos(settings.position, level_response.rect.min, &self.level_view); + let position = view.world_to_screen_pos(settings.position); enum PlaceObjectResult { Create, @@ -125,14 +123,11 @@ impl Editor { } if self.selected_tool == EditorTool::ObjectPlacer - && (level_response.clicked_by(egui::PointerButton::Primary) - || level_response.dragged_by(egui::PointerButton::Primary)) + && (view.response().clicked_by(egui::PointerButton::Primary) + || view.response().dragged_by(egui::PointerButton::Primary)) { - let position = screen_to_world_pos( - egui_ctx.input().pointer.interact_pos().unwrap(), - level_response.rect.min.to_vec2(), - &self.level_view, - ); + let position = + view.screen_to_world_pos(egui_ctx.input().pointer.interact_pos().unwrap()); self.object_being_placed = if let Some(settings) = self.object_being_placed.take() { Some(ObjectSettings { @@ -149,12 +144,7 @@ impl Editor { } } - pub(super) fn handle_objects( - &mut self, - ui: &mut egui::Ui, - response: &egui::Response, - painter: &egui::Painter, - ) { + pub(super) fn handle_objects(&mut self, ui: &mut egui::Ui, view: &UiLevelView) { let mut to_select = None; for layer in self .map_resource @@ -164,7 +154,7 @@ impl Editor { .filter_map(|layer_id| self.map_resource.map.layers.get(layer_id)) { if layer.is_visible { - to_select = to_select.or(self.handle_object_layer(layer, response, painter, ui)); + to_select = to_select.or(self.handle_object_layer(layer, view, ui)); } } @@ -190,21 +180,12 @@ impl Editor { cursor_offset, }) = drag_data { - draw_object( - object, - new_pos, - response, - &self.level_view, - painter, - &resources, - 1., - ); + draw_object(object, new_pos, view, &resources, 1.); + let response = view.response(); if response.dragged_by(egui::PointerButton::Primary) { - let cursor_level_pos = screen_to_world_pos( + let cursor_level_pos = view.screen_to_world_pos( ui.input().pointer.interact_pos().unwrap() + cursor_offset, - response.rect.min.to_vec2(), - &self.level_view, ); self.selection = Some(SelectableEntity { @@ -235,17 +216,15 @@ impl Editor { is_being_dragged = false; } - let (dest, is_valid) = draw_object( + let (dest, _is_valid) = draw_object( object, object.position.into_egui().to_pos2(), - response, - &self.level_view, - painter, + view, &resources, if is_being_dragged { 0.5 } else { 1.0 }, ); - painter.add(egui::Shape::rect_stroke( + view.painter().add(egui::Shape::rect_stroke( dest, egui::Rounding::none(), egui::Stroke::new(1., egui::Color32::WHITE), @@ -260,8 +239,7 @@ impl Editor { fn handle_object_layer( &self, layer: &MapLayer, - response: &egui::Response, - painter: &egui::Painter, + view: &UiLevelView, ui: &mut egui::Ui, ) -> Option { let resources = storage::get::(); @@ -284,13 +262,12 @@ impl Editor { let (dest, is_valid) = draw_object( object, object.position.into_egui().to_pos2(), - response, - &self.level_view, - painter, + view, &resources, 1., ); + let response = view.response(); let is_hovered = response.hovered() && ui .input() @@ -323,7 +300,7 @@ impl Editor { }), }); } - painter.add(egui::Shape::rect_stroke( + view.painter().add(egui::Shape::rect_stroke( dest, egui::Rounding::none(), egui::Stroke::new(1., egui::Color32::GRAY), @@ -369,9 +346,7 @@ impl Editor { fn draw_object( object: &crate::map::MapObject, position: egui::Pos2, - response: &egui::Response, - level_view: &LevelView, - painter: &egui::Painter, + view: &UiLevelView, resources: &impl Deref, opacity: f32, ) -> (egui::Rect, bool) { @@ -385,14 +360,11 @@ fn draw_object( -> egui::Rect { let position_in_lvl = position + offset.into_egui(); - let dest = egui::Rect::from_min_size( - world_to_screen_pos(position_in_lvl, response.rect.min, level_view), - dest_size, - ); + let dest = egui::Rect::from_min_size(view.world_to_screen_pos(position_in_lvl), dest_size); let mut mesh = egui::Mesh::with_texture(texture_id); mesh.add_rect_with_uv(dest, uv, tint); - painter.add(egui::Shape::mesh(mesh)); + view.painter().add(egui::Shape::mesh(mesh)); dest }; diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs index 3ca1c88c23..5abf1362a8 100644 --- a/src/editor/state/ui/toolbar.rs +++ b/src/editor/state/ui/toolbar.rs @@ -6,6 +6,7 @@ use crate::{ 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(); diff --git a/src/editor/view.rs b/src/editor/view.rs index c2db11910a..2d6e7b40f4 100644 --- a/src/editor/view.rs +++ b/src/editor/view.rs @@ -1,3 +1,6 @@ +use super::util::EguiCompatibleVec; + +#[derive(Clone, Copy)] pub struct LevelView { /// The view offset in pixels. pub position: macroquad::prelude::Vec2, @@ -13,3 +16,55 @@ impl Default for LevelView { } } } + +impl LevelView { + // TODO: Factor in level view scale + pub fn screen_to_world_pos(&self, p: egui::Pos2) -> egui::Pos2 { + p + self.position.into_egui() + } + + pub fn world_to_screen_pos(&self, p: egui::Pos2) -> egui::Pos2 { + p - self.position.into_egui() + } +} + +#[derive(Clone)] +pub struct UiLevelView { + view: LevelView, + 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 response. + pub fn response(&self) -> &egui::Response { + &self.response + } + + /// Get a reference to the ui level view's painter. + pub fn painter(&self) -> &egui::Painter { + &self.painter + } + + /// 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() + } +} From 62f46e40113b4069bf22b7a587f481a3b3590fa6 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 23 Apr 2022 14:23:57 +0200 Subject: [PATCH 53/81] Add sample context menu --- src/editor/state/ui/level/mod.rs | 47 +++++++++++++++++++++++----- src/editor/state/ui/level/objects.rs | 8 ++--- src/editor/view.rs | 11 +++---- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 5b14e9b1cf..35f76160c7 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -9,6 +9,7 @@ use crate::{ util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, view::UiLevelView, }, + map::MapLayerKind, Resources, }; @@ -19,14 +20,46 @@ impl Editor { egui::CentralPanel::default() .frame(egui::Frame::none()) .show(egui_ctx, |ui| { - let view = self.draw_level_tiles(ui); + let mut view = self.draw_level_tiles(ui); + + view.response = view.response.context_menu(|ui| { + ui.menu_button("Select tool", |ui| { + if ui.button("Cursor").clicked() { + self.selected_tool = EditorTool::Cursor; + ui.close_menu() + } + if ui.button("Spawnpoint Placer").clicked() { + self.selected_tool = EditorTool::SpawnPointPlacer; + ui.close_menu() + } + match self.selected_layer_type() { + Some(MapLayerKind::TileLayer) => { + if ui.button("Tile Placer").clicked() { + self.selected_tool = EditorTool::TilePlacer; + ui.close_menu() + } + if ui.button("Eraser").clicked() { + self.selected_tool = EditorTool::Eraser; + ui.close_menu() + } + } + Some(MapLayerKind::ObjectLayer) => { + if ui.button("Object Placer").clicked() { + self.selected_tool = EditorTool::ObjectPlacer; + ui.close_menu() + } + } + None => (), + } + }); + }); self.handle_objects(ui, &view); self.draw_level_overlays(ui, &view); let (width, height) = ( - view.response().rect.width() as u32, - view.response().rect.height() as u32, + view.response.rect.width() as u32, + view.response.rect.height() as u32, ); self.level_render_target.resize_if_appropiate(width, height); }); @@ -53,7 +86,7 @@ impl Editor { .input() .pointer .hover_pos() - .map(|pos| view.response().rect.contains(pos)) + .map(|pos| view.response.rect.contains(pos)) .unwrap_or(false); if level_contains_cursor { @@ -63,7 +96,7 @@ impl Editor { 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(); - self.handle_placement(ui.ctx(), view, cursor_tile_pos); + self.handle_tool(ui.ctx(), view, cursor_tile_pos); self.draw_level_pointer_pos_overlay(ui, view, cursor_px_pos, cursor_tile_pos); @@ -71,7 +104,7 @@ impl Editor { } } - fn handle_placement( + fn handle_tool( &mut self, egui_ctx: &egui::Context, view: &UiLevelView, @@ -127,7 +160,7 @@ impl Editor { view.painter().add(egui::Shape::mesh(tile_mesh)); - if view.response().clicked() || view.response().dragged() { + if view.response.clicked() || view.response.dragged() { let input = egui_ctx.input(); if input.pointer.button_down(egui::PointerButton::Primary) { let id = selected_tile.tile_id; diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 94348d7083..3b0b94dade 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -123,8 +123,8 @@ impl Editor { } if self.selected_tool == EditorTool::ObjectPlacer - && (view.response().clicked_by(egui::PointerButton::Primary) - || view.response().dragged_by(egui::PointerButton::Primary)) + && (view.response.clicked_by(egui::PointerButton::Primary) + || view.response.dragged_by(egui::PointerButton::Primary)) { let position = view.screen_to_world_pos(egui_ctx.input().pointer.interact_pos().unwrap()); @@ -181,7 +181,7 @@ impl Editor { }) = drag_data { draw_object(object, new_pos, view, &resources, 1.); - let response = view.response(); + let response = &view.response; if response.dragged_by(egui::PointerButton::Primary) { let cursor_level_pos = view.screen_to_world_pos( @@ -267,7 +267,7 @@ impl Editor { 1., ); - let response = view.response(); + let response = &view.response; let is_hovered = response.hovered() && ui .input() diff --git a/src/editor/view.rs b/src/editor/view.rs index 2d6e7b40f4..833f7c0a25 100644 --- a/src/editor/view.rs +++ b/src/editor/view.rs @@ -31,7 +31,7 @@ impl LevelView { #[derive(Clone)] pub struct UiLevelView { view: LevelView, - response: egui::Response, + pub response: egui::Response, painter: egui::Painter, } @@ -44,16 +44,15 @@ impl UiLevelView { } } - /// Get a reference to the ui level view's response. - pub fn response(&self) -> &egui::Response { - &self.response - } - /// 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 From cf0c3c8a1f6125bf96ca5dc6a33fd35c3fe8df48 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 23 Apr 2022 15:07:54 +0200 Subject: [PATCH 54/81] Remove &egui::Context params --- src/editor/state/mod.rs | 3 +- src/editor/state/ui/level/mod.rs | 96 ++-------------------------- src/editor/state/ui/level/objects.rs | 10 +-- src/editor/state/ui/level/tools.rs | 90 ++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 101 deletions(-) create mode 100644 src/editor/state/ui/level/tools.rs diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index aa366ba12b..8e5519b9c8 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -347,14 +347,13 @@ impl Editor { let layer_id = layer_id.clone(); let index = *index; self.apply_action(UiAction::DeleteObject { layer_id, index }); - self.selection = None; } SelectableEntityKind::SpawnPoint { index } => { let index = *index; self.apply_action(UiAction::DeleteSpawnPoint(index)); - self.selection = None; } } + self.selection = None; } } } diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 35f76160c7..6260373143 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -1,16 +1,13 @@ mod objects; - -use macroquad::prelude::collections::storage; +mod tools; use crate::{ editor::{ - actions::UiAction, state::EditorTool, util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, view::UiLevelView, }, map::MapLayerKind, - Resources, }; use super::super::Editor; @@ -96,97 +93,12 @@ impl Editor { 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(); - self.handle_tool(ui.ctx(), view, cursor_tile_pos); + // 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(ui.ctx(), view); - } - } - - fn handle_tool( - &mut self, - egui_ctx: &egui::Context, - view: &UiLevelView, - cursor_tile_pos: egui::Pos2, - ) { - match self.selected_tool { - EditorTool::TilePlacer => self.handle_tile_placement(egui_ctx, view, cursor_tile_pos), - // TODO: Spawnpoint placement overlay - // TODO: Object placement overlay - _ => (), - }; - } - - fn handle_tile_placement( - &mut self, - egui_ctx: &egui::Context, - 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.selected_tile, &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, - ), - 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 = egui_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, - ), - }); - } - } - } + self.draw_level_object_placement_overlay(view); } } diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 3b0b94dade..1ed8642a84 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -16,11 +16,7 @@ use crate::{ }; impl Editor { - pub(super) fn draw_level_object_placement_overlay( - &mut self, - egui_ctx: &egui::Context, - view: &UiLevelView, - ) { + 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); @@ -34,7 +30,7 @@ impl Editor { .fixed_pos(position) .collapsible(false) .resizable(false) - .show(egui_ctx, |ui| { + .show(view.ctx(), |ui| { egui::ComboBox::new("object_kind", "Kind") .selected_text(format!("{}", settings.kind)) .show_ui(ui, |ui| { @@ -127,7 +123,7 @@ impl Editor { || view.response.dragged_by(egui::PointerButton::Primary)) { let position = - view.screen_to_world_pos(egui_ctx.input().pointer.interact_pos().unwrap()); + 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 { diff --git a/src/editor/state/ui/level/tools.rs b/src/editor/state/ui/level/tools.rs new file mode 100644 index 0000000000..48d5b93cfb --- /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.selected_tile, &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, + ), + 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, + ), + }); + } + } + } + } + } +} From f363e5b702300e7550d746d19a50e6b59c9521f4 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 23 Apr 2022 15:22:45 +0200 Subject: [PATCH 55/81] Misc improvements --- src/editor/state/ui/level/mod.rs | 2 +- src/editor/state/ui/level/objects.rs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 6260373143..5ae51163d1 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -94,7 +94,7 @@ impl Editor { 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.handle_tool(view, cursor_tile_pos); self.draw_level_pointer_pos_overlay(ui, view, cursor_px_pos, cursor_tile_pos); diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 1ed8642a84..e2cf849ccd 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -99,7 +99,7 @@ impl Editor { match result { PlaceObjectResult::Create => { let id = settings.id.as_ref().unwrap().clone(); - let kind = settings.kind.clone(); + let kind = settings.kind; let position = settings.position.into_macroquad(); // TODO: Include layer id in object settings & render it in window as well let layer_id = self.selected_layer.as_ref().unwrap().clone(); @@ -150,7 +150,8 @@ impl Editor { .filter_map(|layer_id| self.map_resource.map.layers.get(layer_id)) { if layer.is_visible { - to_select = to_select.or(self.handle_object_layer(layer, view, ui)); + let selected_on_this_layer = self.handle_object_layer(layer, view, ui); + to_select = to_select.or(selected_on_this_layer); } } @@ -280,7 +281,7 @@ impl Editor { ); if is_hovered { - self.show_object_info_tooltip(ui.ctx(), &object, is_valid); + 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(); From 42c6718e67fd8bf5ee10924311b084328194d73f Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 1 May 2022 15:29:55 +0200 Subject: [PATCH 56/81] Clean up --- src/editor/history.rs | 6 +++ src/editor/state/mod.rs | 64 ++++++++++++++++-------------- src/editor/state/ui/level/tools.rs | 2 +- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/editor/history.rs b/src/editor/history.rs index ec42ebaeae..748d8c9813 100644 --- a/src/editor/history.rs +++ b/src/editor/history.rs @@ -55,3 +55,9 @@ impl ActionHistory { self.redo_stack.clear(); } } + +impl Default for ActionHistory { + fn default() -> Self { + Self::new() + } +} diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 8e5519b9c8..fd5f57cf41 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -69,37 +69,43 @@ pub struct ObjectSettings { } pub struct Editor { - pub selected_tool: EditorTool, - pub map_resource: MapResource, - pub selected_layer: Option, - pub selected_tile: Option, - pub is_parallax_enabled: bool, - pub should_draw_grid: bool, - pub object_being_placed: Option, + 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_layer_window: Option, create_tileset_window: Option, - pub menu_window: Option, + menu_window: Option, save_map_window: Option, - pub selection: Option, + selection: Option, history: ActionHistory, - pub level_view: LevelView, + level_view: LevelView, - pub level_render_target: RenderTarget, + level_render_target: RenderTarget, } impl Editor { 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, - selected_tile: None, + tile_palette: None, object_being_placed: None, create_layer_window: None, @@ -109,12 +115,9 @@ impl Editor { selection: None, - history: ActionHistory::new(), + history: Default::default(), - level_view: LevelView { - position: Default::default(), - scale: 1., - }, + level_view: Default::default(), level_render_target: macroquad::prelude::render_target(1, 1), } @@ -166,7 +169,7 @@ impl Editor { self.history .apply(action, &mut self.map_resource.map) .unwrap(); - self.selected_tile = None; + self.tile_palette = None; } UiAction::UpdateLayer { id, is_visible } => { let action = actions::UpdateLayer::new(id, is_visible); @@ -189,13 +192,13 @@ impl Editor { self.selected_layer = Some(id); } UiAction::SelectTileset(id) => { - self.selected_tile = Some(TileSelection { + self.tile_palette = Some(TileSelection { tileset: id, tile_id: 0, }); } UiAction::SelectTile { id, tileset_id } => { - self.selected_tile = Some(TileSelection { + self.tile_palette = Some(TileSelection { tileset: tileset_id, tile_id: id, }); @@ -284,7 +287,17 @@ impl Editor { } } - pub fn draw(&self) { + pub fn draw_level(&self) { + let camera = Some(Camera2D { + offset: vec2(-1., -1.), + target: self.level_view.position, + zoom, + render_target: Some(self.level_render_target), + ..Camera2D::default() + }); + + scene::set_camera(0, camera); + let map = &self.map_resource.map; { map.draw_background(None, !self.is_parallax_enabled); @@ -311,15 +324,6 @@ impl Editor { self.level_view.scale / target_size.y, ) * 2.; self.level_view.position += input.camera_move_direction * CAMERA_PAN_SPEED; - let camera = Some(Camera2D { - offset: vec2(-1., -1.), - target: self.level_view.position, - zoom, - render_target: Some(self.level_render_target), - ..Camera2D::default() - }); - - scene::set_camera(0, camera); } // Undo/redo diff --git a/src/editor/state/ui/level/tools.rs b/src/editor/state/ui/level/tools.rs index 48d5b93cfb..272a4728ca 100644 --- a/src/editor/state/ui/level/tools.rs +++ b/src/editor/state/ui/level/tools.rs @@ -26,7 +26,7 @@ impl Editor { if cursor_tile_pos.x >= 0. && cursor_tile_pos.y >= 0. { if let (Some(selected_tile), Some(selected_layer)) = - (&self.selected_tile, &self.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] From fc0541226e6d21ee2b233425ce49b3908472c20a Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 1 May 2022 15:44:06 +0200 Subject: [PATCH 57/81] Cleanup --- src/editor/mod.rs | 2 +- src/editor/state/mod.rs | 41 ++++++++++++----------- src/editor/state/ui.rs | 9 +++++ src/editor/state/ui/side_panel/mod.rs | 5 ++- src/editor/state/ui/side_panel/objects.rs | 19 +++++++++++ src/editor/state/ui/side_panel/tileset.rs | 8 ++--- 6 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 src/editor/state/ui/side_panel/objects.rs diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 8f26e3f362..6228a1fb04 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -48,7 +48,7 @@ impl Node for EditorNode { where Self: Sized, { - node.editor.draw(); + node.editor.draw_level(); egui_macroquad::ui(|egui_ctx| { node.editor.ui(egui_ctx); diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index fd5f57cf41..d5eb2c642d 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -288,15 +288,27 @@ impl Editor { } pub fn draw_level(&self) { - let camera = Some(Camera2D { - offset: vec2(-1., -1.), - target: self.level_view.position, - zoom, - render_target: Some(self.level_render_target), - ..Camera2D::default() - }); + 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() + } + }; - scene::set_camera(0, camera); + macroquad::experimental::scene::set_camera(0, Some(camera)); let map = &self.map_resource.map; { @@ -313,18 +325,7 @@ impl Editor { const CAMERA_PAN_SPEED: f32 = 5.0; // Move 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.; - self.level_view.position += input.camera_move_direction * CAMERA_PAN_SPEED; - } + self.level_view.position += input.camera_move_direction * CAMERA_PAN_SPEED; // Undo/redo if input.undo { diff --git a/src/editor/state/ui.rs b/src/editor/state/ui.rs index 6ef24f0273..7c8edfcb5a 100644 --- a/src/editor/state/ui.rs +++ b/src/editor/state/ui.rs @@ -5,6 +5,15 @@ 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); diff --git a/src/editor/state/ui/side_panel/mod.rs b/src/editor/state/ui/side_panel/mod.rs index cb11dd5dd4..c4447dd3eb 100644 --- a/src/editor/state/ui/side_panel/mod.rs +++ b/src/editor/state/ui/side_panel/mod.rs @@ -1,4 +1,5 @@ mod layers; +mod objects; mod tileset; use crate::map::MapLayerKind; @@ -11,13 +12,15 @@ impl Editor { egui_ctx, |ui| { self.draw_layer_info(ui); + ui.separator(); + match self.selected_layer_type() { Some(MapLayerKind::TileLayer) => { self.draw_tileset_info(ui); } Some(MapLayerKind::ObjectLayer) => { - // TODO + self.draw_object_info(ui); } None => (), } 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..c0746b949c --- /dev/null +++ b/src/editor/state/ui/side_panel/objects.rs @@ -0,0 +1,19 @@ +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_object_info(&mut self, ui: &egui::Ui) { + // TODO + } +} diff --git a/src/editor/state/ui/side_panel/tileset.rs b/src/editor/state/ui/side_panel/tileset.rs index 713e3b0f4d..2abf77bc97 100644 --- a/src/editor/state/ui/side_panel/tileset.rs +++ b/src/editor/state/ui/side_panel/tileset.rs @@ -26,7 +26,7 @@ impl Editor { ui.group(|ui| { for (tileset_name, _tileset) in self.map_resource.map.tilesets.iter() { let is_selected = self - .selected_tile + .tile_palette .as_ref() .map(|s| &s.tileset == tileset_name) .unwrap_or(false); @@ -47,9 +47,9 @@ impl Editor { if ui.button("+").clicked() { self.create_tileset_window = Some(Default::default()); } - ui.add_enabled_ui(self.selected_tile.is_some(), |ui| { + ui.add_enabled_ui(self.tile_palette.is_some(), |ui| { if ui.button("-").clicked() { - let selected_tileset = self.selected_tile.as_ref().unwrap().tileset.clone(); + 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"); @@ -58,7 +58,7 @@ impl Editor { } fn draw_tileset_image(&mut self, ui: &mut egui::Ui) { - if let Some(selection) = &self.selected_tile { + 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]; From ca2f17f73d18f0be7f566e62b4a415213f82cca0 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 1 May 2022 16:11:15 +0200 Subject: [PATCH 58/81] Fix build --- core/src/input/mapping.rs | 2 +- core/src/input/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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()) }; From 5e96557ab6de9ab61e7d5bfd70a9756018d09d14 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 1 May 2022 18:16:26 +0200 Subject: [PATCH 59/81] Use a table for showing layers --- Cargo.lock | 166 ++++++++++++++++++----- Cargo.toml | 4 +- src/editor/state/ui/side_panel/layers.rs | 83 ++++++++---- src/editor/state/ui/side_panel/mod.rs | 17 ++- 4 files changed, 204 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3f52702cd..968a92404e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,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" @@ -247,9 +247,8 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "egui" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a3cd1d47e12f7a17912595241622e373aa652a4e0fa90b3f9278f90a64aedf7" +version = "0.18.1" +source = "git+https://github.com/aleokdev/egui#57bbde4460106f819044f4a2507caa6c0b9eccaa" dependencies = [ "ahash 0.7.6", "epaint", @@ -259,7 +258,7 @@ dependencies = [ [[package]] name = "egui-macroquad" version = "0.9.0" -source = "git+https://github.com/aleokdev/egui-macroquad#d202b747954541e11bc67b15052a67d630e93801" +source = "git+https://github.com/aleokdev/egui-macroquad#1359301bdc2b043f00e166221a12f96eb034342c" dependencies = [ "egui", "egui-miniquad", @@ -268,8 +267,8 @@ dependencies = [ [[package]] name = "egui-miniquad" -version = "0.9.0" -source = "git+https://github.com/not-fl3/egui-miniquad/?rev=8f493a03b98faf976374f8aea7d2448ffa4d115c#8f493a03b98faf976374f8aea7d2448ffa4d115c" +version = "0.11.0" +source = "git+https://github.com/aleokdev/egui-miniquad#9ca4a5bd7153fe2fc7af03e2ee216bb85fd80b01" dependencies = [ "bytemuck", "copypasta", @@ -278,20 +277,26 @@ dependencies = [ "quad-url", ] +[[package]] +name = "egui_extras" +version = "0.18.0" +source = "git+https://github.com/aleokdev/egui#57bbde4460106f819044f4a2507caa6c0b9eccaa" +dependencies = [ + "egui", +] + [[package]] name = "emath" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a977a80456be58a2c2d48e69c1d0baadef46cecef5a0c98df141c468da006f12" +version = "0.18.0" +source = "git+https://github.com/aleokdev/egui#57bbde4460106f819044f4a2507caa6c0b9eccaa" dependencies = [ "bytemuck", ] [[package]] name = "epaint" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033292846059f08e03a71e1b5db2ee6ab7c9622c3b48da21f4bd13258ebee2db" +version = "0.18.1" +source = "git+https://github.com/aleokdev/egui#57bbde4460106f819044f4a2507caa6c0b9eccaa" dependencies = [ "ab_glyph", "ahash 0.7.6", @@ -299,6 +304,7 @@ dependencies = [ "bytemuck", "emath", "nohash-hasher", + "parking_lot", ] [[package]] @@ -317,6 +323,7 @@ version = "0.4.2" dependencies = [ "egui", "egui-macroquad", + "egui_extras", "ff-particles", "fishfight-core", "fishsticks", @@ -345,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", @@ -520,9 +527,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.123" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libloading" @@ -544,6 +551,16 @@ 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.16" @@ -610,9 +627,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" @@ -712,9 +729,9 @@ dependencies = [ [[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", @@ -722,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", @@ -804,6 +821,29 @@ 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 = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec 1.8.0", + "windows-sys", +] + [[package]] name = "pkg-config" version = "0.3.25" @@ -887,6 +927,15 @@ 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" @@ -986,6 +1035,12 @@ 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" @@ -1003,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", @@ -1023,9 +1078,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944" dependencies = [ "itoa", "ryu", @@ -1092,9 +1147,9 @@ dependencies = [ [[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" @@ -1149,9 +1204,9 @@ checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] name = "syn" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -1385,6 +1440,49 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index 614a9a90bb..cec676e18f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,8 @@ macroquad-platformer = "0.1" hecs = "0.7.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -egui = "0.17.0" +egui = "0.18.1" +egui_extras = { git = "https://github.com/aleokdev/egui" } egui-macroquad = "0.9.0" [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -39,3 +40,4 @@ 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/src/editor/state/ui/side_panel/layers.rs b/src/editor/state/ui/side_panel/layers.rs index dab25d04b0..659cde7917 100644 --- a/src/editor/state/ui/side_panel/layers.rs +++ b/src/editor/state/ui/side_panel/layers.rs @@ -1,3 +1,5 @@ +use egui_extras::Size; + use crate::{ editor::{ actions::{UiAction, UiActionExt}, @@ -17,32 +19,65 @@ impl Editor { let mut action = None; ui.group(|ui| { - let map = &self.map_resource.map; - for (layer_name, layer) in map.draw_order.iter().map(|id| (id, &map.layers[id])) { - ui.horizontal(|ui| { - let layer_label = ui.selectable_label( - self.selected_layer.as_ref() == Some(layer_name), - format!( - "({}) {}", - match layer.kind { - MapLayerKind::TileLayer => "T", - MapLayerKind::ObjectLayer => "O", - }, - layer_name - ), + 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| { + let row_height = 18.0; + body.rows( + 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; + + clicked |= row + .col(|ui| { + ui.label(match layer.kind { + MapLayerKind::TileLayer => "T", + MapLayerKind::ObjectLayer => "O", + }); + }) + .clicked(); + clicked |= row + .col(|ui| { + ui.label(layer_name); + }) + .clicked(); + let mut is_visible = layer.is_visible; + clicked |= row + .col(|ui| { + if ui.checkbox(&mut is_visible, "Visible").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 layer_label.clicked() { - action.then_do_some(UiAction::SelectLayer(layer_name.clone())); - } - let mut is_visible = layer.is_visible; - if ui.checkbox(&mut is_visible, "Visible").clicked() { - action.then_do_some(UiAction::UpdateLayer { - id: layer_name.clone(), - is_visible, - }); - } }); - } }); if let Some(action) = action { diff --git a/src/editor/state/ui/side_panel/mod.rs b/src/editor/state/ui/side_panel/mod.rs index c4447dd3eb..0a39137e89 100644 --- a/src/editor/state/ui/side_panel/mod.rs +++ b/src/editor/state/ui/side_panel/mod.rs @@ -15,14 +15,17 @@ impl Editor { ui.separator(); - match self.selected_layer_type() { - Some(MapLayerKind::TileLayer) => { - self.draw_tileset_info(ui); + 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); + } + } } - Some(MapLayerKind::ObjectLayer) => { - self.draw_object_info(ui); - } - None => (), } }, ); From 6ca2ad3fabe3464eae4031260dd70d9ac8587183 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sun, 1 May 2022 19:15:44 +0200 Subject: [PATCH 60/81] Right click to add objects --- src/editor/TODO.md | 2 +- src/editor/state/mod.rs | 1 - src/editor/state/ui/level/mod.rs | 38 +++++++++++++++++++++------- src/editor/state/ui/level/objects.rs | 21 --------------- src/editor/state/ui/toolbar.rs | 3 +-- 5 files changed, 31 insertions(+), 34 deletions(-) diff --git a/src/editor/TODO.md b/src/editor/TODO.md index 443cf195a1..72d75cbebc 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -1,6 +1,6 @@ - [ ] Object list - [ ] Spawnpoint placement -- [ ] Object placement via rightclick context menu +- [X] Object placement via rightclick context menu - [ ] Open/Import window - [ ] New map window - [ ] Theme support diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index d5eb2c642d..73845753f7 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -21,7 +21,6 @@ use super::{ pub enum EditorTool { Cursor, TilePlacer, - ObjectPlacer, SpawnPointPlacer, Eraser, } diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 5ae51163d1..d697131b32 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -3,11 +3,11 @@ mod tools; use crate::{ editor::{ - state::EditorTool, + state::{EditorTool, ObjectSettings}, util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, view::UiLevelView, }, - map::MapLayerKind, + map::{MapLayerKind, MapObjectKind}, }; use super::super::Editor; @@ -18,6 +18,7 @@ impl Editor { .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| { ui.menu_button("Select tool", |ui| { @@ -40,17 +41,36 @@ impl Editor { ui.close_menu() } } - Some(MapLayerKind::ObjectLayer) => { - if ui.button("Object Placer").clicked() { - self.selected_tool = EditorTool::ObjectPlacer; - ui.close_menu() - } - } - None => (), + _ => (), } }); + if let Some(MapLayerKind::ObjectLayer) = self.selected_layer_type() { + if ui.button("Add object").clicked() { + clicked_add_object = true; + ui.close_menu() + } + } }); + if clicked_add_object { + 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, + }) + }; + } + self.handle_objects(ui, &view); self.draw_level_overlays(ui, &view); diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index e2cf849ccd..2fa914099f 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -117,27 +117,6 @@ impl Editor { PlaceObjectResult::Noop => (), } } - - if self.selected_tool == EditorTool::ObjectPlacer - && (view.response.clicked_by(egui::PointerButton::Primary) - || view.response.dragged_by(egui::PointerButton::Primary)) - { - 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, - }) - }; - } } pub(super) fn handle_objects(&mut self, ui: &mut egui::Ui, view: &UiLevelView) { diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs index 5abf1362a8..93f989f998 100644 --- a/src/editor/state/ui/toolbar.rs +++ b/src/editor/state/ui/toolbar.rs @@ -28,8 +28,7 @@ impl Editor { add_tool("Tiles", EditorTool::TilePlacer); add_tool("Eraser", EditorTool::Eraser); } - Some(MapLayerKind::ObjectLayer) => add_tool("Objects", EditorTool::ObjectPlacer), - None => (), + _ => (), } }); } From 4a725461efc4e4524a0d09ace5712723ca6bcb2a Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Thu, 5 May 2022 12:17:52 +0200 Subject: [PATCH 61/81] Selection text --- Cargo.lock | 30 ++++++++++++------------ src/editor/state/ui/side_panel/layers.rs | 6 ++++- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 968a92404e..35315a04d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,7 +248,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "egui" version = "0.18.1" -source = "git+https://github.com/aleokdev/egui#57bbde4460106f819044f4a2507caa6c0b9eccaa" +source = "git+https://github.com/aleokdev/egui#3c39b807317e640a5437b6e0280b4dccea0e431d" dependencies = [ "ahash 0.7.6", "epaint", @@ -280,7 +280,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.18.0" -source = "git+https://github.com/aleokdev/egui#57bbde4460106f819044f4a2507caa6c0b9eccaa" +source = "git+https://github.com/aleokdev/egui#3c39b807317e640a5437b6e0280b4dccea0e431d" dependencies = [ "egui", ] @@ -288,7 +288,7 @@ dependencies = [ [[package]] name = "emath" version = "0.18.0" -source = "git+https://github.com/aleokdev/egui#57bbde4460106f819044f4a2507caa6c0b9eccaa" +source = "git+https://github.com/aleokdev/egui#3c39b807317e640a5437b6e0280b4dccea0e431d" dependencies = [ "bytemuck", ] @@ -296,7 +296,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.18.1" -source = "git+https://github.com/aleokdev/egui#57bbde4460106f819044f4a2507caa6c0b9eccaa" +source = "git+https://github.com/aleokdev/egui#3c39b807317e640a5437b6e0280b4dccea0e431d" dependencies = [ "ab_glyph", "ahash 0.7.6", @@ -439,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", ] @@ -452,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", ] @@ -563,9 +563,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -761,9 +761,9 @@ 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", ] @@ -1078,9 +1078,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "itoa", "ryu", @@ -1236,9 +1236,9 @@ 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" diff --git a/src/editor/state/ui/side_panel/layers.rs b/src/editor/state/ui/side_panel/layers.rs index 659cde7917..46c20fb936 100644 --- a/src/editor/state/ui/side_panel/layers.rs +++ b/src/editor/state/ui/side_panel/layers.rs @@ -56,7 +56,11 @@ impl Editor { .clicked(); clicked |= row .col(|ui| { - ui.label(layer_name); + if self.selected_layer.as_ref() == Some(layer_name) { + ui.label(format!("[selected] {}", layer_name)); + } else { + ui.label(layer_name); + } }) .clicked(); let mut is_visible = layer.is_visible; From 68252e3ad167c60c21303d83e842b92a7a014cf5 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Thu, 5 May 2022 12:42:51 +0200 Subject: [PATCH 62/81] Add target to object placement window --- src/editor/TODO.md | 1 + src/editor/state/ui/level/objects.rs | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/editor/TODO.md b/src/editor/TODO.md index 72d75cbebc..8495e7f116 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -1,6 +1,7 @@ - [ ] Object list - [ ] Spawnpoint placement - [X] Object placement via rightclick context menu +- [ ] Moving camera via middle click - [ ] Open/Import window - [ ] New map window - [ ] Theme support diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 2fa914099f..8ae1610fec 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -11,7 +11,7 @@ use crate::{ util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec}, view::UiLevelView, }, - map::{MapLayer, MapObject, MapObjectKind}, + map::{MapLayer, MapLayerKind, MapObject, MapObjectKind}, AnimatedSpriteMetadata, Resources, }; @@ -31,6 +31,18 @@ impl Editor { .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| { @@ -79,8 +91,10 @@ impl Editor { }); ui.horizontal(|ui| { - let create_button = - ui.add_enabled(settings.id.is_some(), egui::Button::new("Create")); + 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; } @@ -101,7 +115,6 @@ impl Editor { let id = settings.id.as_ref().unwrap().clone(); let kind = settings.kind; let position = settings.position.into_macroquad(); - // TODO: Include layer id in object settings & render it in window as well let layer_id = self.selected_layer.as_ref().unwrap().clone(); self.apply_action(UiAction::CreateObject { id, From bba2141cd2fac9b61fa5b90d2a1a8157a68b81fe Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Thu, 5 May 2022 13:08:00 +0200 Subject: [PATCH 63/81] Allow moving object placement window around --- src/editor/state/ui/level/objects.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 8ae1610fec..ea94f905e8 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -27,7 +27,7 @@ impl Editor { } let response = egui::Window::new("Placing object") - .fixed_pos(position) + .current_pos(position) .collapsible(false) .resizable(false) .show(view.ctx(), |ui| { @@ -107,7 +107,11 @@ impl Editor { }); let result = response - .and_then(|r| r.inner.map(|r| r.inner)) + .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 { From 5952886eb62f7afbe76270686f771e3158ca8be4 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Thu, 5 May 2022 16:24:14 +0200 Subject: [PATCH 64/81] Draw object list --- src/editor/TODO.md | 2 +- src/editor/state/ui/side_panel/layers.rs | 5 +-- src/editor/state/ui/side_panel/mod.rs | 4 ++- src/editor/state/ui/side_panel/objects.rs | 44 ++++++++++++++++++++--- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/editor/TODO.md b/src/editor/TODO.md index 8495e7f116..b30ace0a80 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -1,4 +1,4 @@ -- [ ] Object list +- [X] Object list - [ ] Spawnpoint placement - [X] Object placement via rightclick context menu - [ ] Moving camera via middle click diff --git a/src/editor/state/ui/side_panel/layers.rs b/src/editor/state/ui/side_panel/layers.rs index 46c20fb936..1530c8f044 100644 --- a/src/editor/state/ui/side_panel/layers.rs +++ b/src/editor/state/ui/side_panel/layers.rs @@ -8,6 +8,8 @@ use crate::{ map::MapLayerKind, }; +use super::TABLE_ROW_HEIGHT; + impl Editor { pub(super) fn draw_layer_info(&mut self, ui: &mut egui::Ui) { ui.heading("Layers"); @@ -37,9 +39,8 @@ impl Editor { }); }) .body(|body| { - let row_height = 18.0; body.rows( - row_height, + 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]; diff --git a/src/editor/state/ui/side_panel/mod.rs b/src/editor/state/ui/side_panel/mod.rs index 0a39137e89..842088d3d2 100644 --- a/src/editor/state/ui/side_panel/mod.rs +++ b/src/editor/state/ui/side_panel/mod.rs @@ -6,6 +6,8 @@ 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( @@ -22,7 +24,7 @@ impl Editor { self.draw_tileset_info(ui); } MapLayerKind::ObjectLayer => { - self.draw_object_info(ui); + 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 index c0746b949c..db83a69ae2 100644 --- a/src/editor/state/ui/side_panel/objects.rs +++ b/src/editor/state/ui/side_panel/objects.rs @@ -1,19 +1,55 @@ +use egui_extras::TableBuilder; use macroquad::prelude::collections::storage; use crate::{ editor::{ actions::{UiAction, UiActionExt}, - state::EditorTool, + state::{EditorTool, SelectableEntity, SelectableEntityKind}, util::EguiTextureHandler, }, + map::MapLayer, resources::TextureResource, Resources, }; -use super::Editor; +use super::{Editor, TABLE_ROW_HEIGHT}; impl Editor { - pub(super) fn draw_object_info(&mut self, ui: &egui::Ui) { - // TODO + pub(super) fn draw_object_info(&self, ui: &mut egui::Ui, layer: &MapLayer) { + println!("Selection: {:?}", self.selection); + + 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]; + row.col(|ui| { + if matches!( + self.selection.as_ref(), + Some(SelectableEntity { + kind: SelectableEntityKind::Object { + layer_id, + index + }, + .. + }) if layer_id == &layer.id && index == &row_index + ) { + ui.label(format!("[selected] {}", &object.id)); + } else { + ui.label(&object.id); + } + }); + }, + ) + }) } } From 88e68695def7737464da70a6afff064b6dceead5 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Thu, 5 May 2022 16:29:29 +0200 Subject: [PATCH 65/81] Do not deselect objects on drag release --- src/editor/state/ui/level/objects.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index ea94f905e8..0506c10bd1 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -160,7 +160,7 @@ impl Editor { // Draw selection last (Special case) if let Some(SelectableEntity { kind: SelectableEntityKind::Object { index, layer_id }, - drag_data, + mut drag_data, }) = self.selection.take() { let resources = storage::get::(); @@ -171,7 +171,7 @@ impl Editor { if let Some(DragData { new_pos, cursor_offset, - }) = drag_data + }) = drag_data.take() { draw_object(object, new_pos, view, &resources, 1.); let response = &view.response; @@ -181,15 +181,9 @@ impl Editor { ui.input().pointer.interact_pos().unwrap() + cursor_offset, ); - self.selection = Some(SelectableEntity { - drag_data: Some(DragData { - new_pos: cursor_level_pos, - cursor_offset, - }), - kind: SelectableEntityKind::Object { - index, - layer_id: layer_id.clone(), - }, + drag_data = Some(DragData { + new_pos: cursor_level_pos, + cursor_offset, }); } if response.drag_released() { @@ -198,10 +192,6 @@ impl Editor { layer_id: layer_id.clone(), position: macroquad::math::vec2(new_pos.x, new_pos.y), }); - self.selection = Some(SelectableEntity { - drag_data: None, - kind: SelectableEntityKind::Object { index, layer_id }, - }); } is_being_dragged = true; @@ -209,6 +199,11 @@ impl Editor { 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(), From 66f23d09ecfcfa2b307adc280a41d66dbdbcc380 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Thu, 5 May 2022 16:34:19 +0200 Subject: [PATCH 66/81] Better selection indicator --- src/editor/state/ui/side_panel/layers.rs | 27 ++++++++++++++++++----- src/editor/state/ui/side_panel/objects.rs | 27 ++++++++++++----------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/editor/state/ui/side_panel/layers.rs b/src/editor/state/ui/side_panel/layers.rs index 1530c8f044..3a6e0d7bef 100644 --- a/src/editor/state/ui/side_panel/layers.rs +++ b/src/editor/state/ui/side_panel/layers.rs @@ -46,28 +46,43 @@ impl Editor { 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| { - ui.label(match layer.kind { + 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| { - if self.selected_layer.as_ref() == Some(layer_name) { - ui.label(format!("[selected] {}", layer_name)); - } else { - ui.label(layer_name); + 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| { - if ui.checkbox(&mut is_visible, "Visible").clicked() { + 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, diff --git a/src/editor/state/ui/side_panel/objects.rs b/src/editor/state/ui/side_panel/objects.rs index db83a69ae2..9b78441b3c 100644 --- a/src/editor/state/ui/side_panel/objects.rs +++ b/src/editor/state/ui/side_panel/objects.rs @@ -1,3 +1,4 @@ +use egui::RichText; use egui_extras::TableBuilder; use macroquad::prelude::collections::storage; @@ -16,8 +17,6 @@ use super::{Editor, TABLE_ROW_HEIGHT}; impl Editor { pub(super) fn draw_object_info(&self, ui: &mut egui::Ui, layer: &MapLayer) { - println!("Selection: {:?}", self.selection); - egui_extras::TableBuilder::new(ui) .column(egui_extras::Size::remainder()) .striped(true) @@ -32,18 +31,20 @@ impl Editor { 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 matches!( - self.selection.as_ref(), - Some(SelectableEntity { - kind: SelectableEntityKind::Object { - layer_id, - index - }, - .. - }) if layer_id == &layer.id && index == &row_index - ) { - ui.label(format!("[selected] {}", &object.id)); + if is_selected { + ui.label(RichText::new(&object.id).strong()); } else { ui.label(&object.id); } From 23ba8af46670475dde62dbb7723c561b33ca5323 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Thu, 5 May 2022 17:28:42 +0200 Subject: [PATCH 67/81] Fix some selection issues --- src/editor/state/ui/level/objects.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 0506c10bd1..2ed840e57c 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -151,11 +151,7 @@ impl Editor { } } - if let Some(to_select) = to_select { - self.apply_action(UiAction::SelectEntity(to_select)); - } else if ui.input().pointer.any_pressed() { - self.apply_action(UiAction::DeselectObject); - } + let mut selection_dest = None; // Draw selection last (Special case) if let Some(SelectableEntity { @@ -211,6 +207,7 @@ impl Editor { &resources, if is_being_dragged { 0.5 } else { 1.0 }, ); + selection_dest = Some(dest); view.painter().add(egui::Shape::rect_stroke( dest, @@ -222,6 +219,16 @@ impl Editor { self.apply_action(action); } } + + if let Some(to_select) = to_select { + self.apply_action(UiAction::SelectEntity(to_select)); + } else if view.response.clicked() + && !selection_dest + .map(|d| d.contains(ui.input().pointer.interact_pos().unwrap())) + .unwrap_or(false) + { + self.apply_action(UiAction::DeselectObject); + } } fn handle_object_layer( From 2b09fb0f4a8a67ba3ec855ad0ac9a9ba1cc63409 Mon Sep 17 00:00:00 2001 From: aleokdev Date: Mon, 9 May 2022 11:53:49 +0200 Subject: [PATCH 68/81] Draw spawnpoints --- src/editor/state/ui/level/mod.rs | 28 ++++++++++++++++++++++++ src/editor/state/ui/level/spawnpoints.rs | 0 2 files changed, 28 insertions(+) create mode 100644 src/editor/state/ui/level/spawnpoints.rs diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index d697131b32..dfb9a8efb2 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -1,6 +1,8 @@ mod objects; mod tools; +use macroquad::prelude::collections::storage; + use crate::{ editor::{ state::{EditorTool, ObjectSettings}, @@ -8,6 +10,7 @@ use crate::{ view::UiLevelView, }, map::{MapLayerKind, MapObjectKind}, + Resources, }; use super::super::Editor; @@ -72,6 +75,7 @@ impl Editor { } self.handle_objects(ui, &view); + self.handle_spawnpoints(&view); self.draw_level_overlays(ui, &view); let (width, height) = ( @@ -82,6 +86,30 @@ impl Editor { }); } + fn handle_spawnpoints(&self, view: &UiLevelView) { + let texture = &storage::get::().textures["spawn_point_icon"]; + let texture_id = texture.texture.egui_id(); + let texture_size = texture.meta.size.into_egui(); + + for spawnpoint in self.map_resource.map.spawn_points.iter() { + // 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)); + } + } + fn draw_level_tiles(&self, ui: &mut egui::Ui) -> UiLevelView { let texture_id = self.level_render_target.texture.egui_id(); 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 From 736a1554cd6b3e2ae020254110654980e2ad6d70 Mon Sep 17 00:00:00 2001 From: aleokdev Date: Mon, 9 May 2022 12:57:04 +0200 Subject: [PATCH 69/81] Spawnpoint placement --- src/editor/TODO.md | 4 ++- src/editor/mod.rs | 50 ++------------------------------ src/editor/node.rs | 49 +++++++++++++++++++++++++++++++ src/editor/state/mod.rs | 7 +++++ src/editor/state/ui/level/mod.rs | 13 +++++++-- src/editor/state/ui/toolbar.rs | 1 + 6 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 src/editor/node.rs diff --git a/src/editor/TODO.md b/src/editor/TODO.md index b30ace0a80..bac7a426bd 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -1,6 +1,8 @@ - [X] Object list -- [ ] Spawnpoint placement +- [X] Spawnpoint placement +- [ ] Spawnpoint selection & movement - [X] Object placement via rightclick context menu +- [ ] Re-add object placement tool - [ ] Moving camera via middle click - [ ] Open/Import window - [ ] New map window diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 6228a1fb04..08ae6c0ac1 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -8,54 +8,8 @@ mod actions; mod input; mod history; +mod node; +pub use node::EditorNode; pub use input::EditorInputScheme; pub use state::Editor; - -use macroquad::experimental::scene::{Node, RefMut}; - -/// 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/node.rs b/src/editor/node.rs new file mode 100644 index 0000000000..818be3c194 --- /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 index 73845753f7..4f19ddaaa8 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -281,6 +281,13 @@ impl Editor { UiAction::DeselectObject => { self.selection = None; } + UiAction::CreateSpawnPoint(pos) => { + let action = actions::CreateSpawnPoint::new(pos); + + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } _ => todo!(), } diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index dfb9a8efb2..4c081bbc97 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -5,8 +5,9 @@ use macroquad::prelude::collections::storage; use crate::{ editor::{ + actions::UiAction, state::{EditorTool, ObjectSettings}, - util::{EguiCompatibleVec, EguiTextureHandler, Resizable}, + util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec, Resizable}, view::UiLevelView, }, map::{MapLayerKind, MapObjectKind}, @@ -86,7 +87,7 @@ impl Editor { }); } - fn handle_spawnpoints(&self, view: &UiLevelView) { + fn handle_spawnpoints(&mut self, view: &UiLevelView) { let texture = &storage::get::().textures["spawn_point_icon"]; let texture_id = texture.texture.egui_id(); let texture_size = texture.meta.size.into_egui(); @@ -108,6 +109,14 @@ impl Editor { ); view.painter().add(egui::Shape::mesh(mesh)); } + + 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 { diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs index 93f989f998..abd10eff78 100644 --- a/src/editor/state/ui/toolbar.rs +++ b/src/editor/state/ui/toolbar.rs @@ -23,6 +23,7 @@ impl Editor { }; add_tool("Cursor", EditorTool::Cursor); + add_tool("Spawn Point Placer", EditorTool::SpawnPointPlacer); match selected_layer_type { Some(MapLayerKind::TileLayer) => { add_tool("Tiles", EditorTool::TilePlacer); From a3a6c64c3650db104b81307a8a8ba0b1e0615fb5 Mon Sep 17 00:00:00 2001 From: aleokdev Date: Mon, 9 May 2022 13:00:40 +0200 Subject: [PATCH 70/81] Moving camera via middle click --- src/editor/TODO.md | 2 +- src/editor/state/ui/level/mod.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/editor/TODO.md b/src/editor/TODO.md index bac7a426bd..86aca09dda 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -3,7 +3,7 @@ - [ ] Spawnpoint selection & movement - [X] Object placement via rightclick context menu - [ ] Re-add object placement tool -- [ ] Moving camera via middle click +- [X] Moving camera via middle click - [ ] Open/Import window - [ ] New map window - [ ] Theme support diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 4c081bbc97..8c988b88c8 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -56,6 +56,13 @@ impl Editor { } }); + if view.response.dragged_by(egui::PointerButton::Middle) { + let drag_delta = egui_ctx.input().pointer.delta(); + + // TODO: take level scale/"tiles per pixel" into account + self.level_view.position -= drag_delta.into_macroquad(); + } + if clicked_add_object { let position = view .screen_to_world_pos(view.ctx().input().pointer.interact_pos().unwrap()); From f9d45a89fa0c5b4d3fe27834a57025feec300a07 Mon Sep 17 00:00:00 2001 From: aleokdev Date: Mon, 9 May 2022 20:23:46 +0200 Subject: [PATCH 71/81] Spawnpoint selection & movement --- src/editor/TODO.md | 2 +- src/editor/actions.rs | 2 - src/editor/actions/move_spawn_point.rs | 18 +- src/editor/state/mod.rs | 27 +-- src/editor/state/ui/level/mod.rs | 255 ++++++++++++++++++++++++- src/editor/state/ui/level/objects.rs | 122 +++--------- 6 files changed, 291 insertions(+), 135 deletions(-) diff --git a/src/editor/TODO.md b/src/editor/TODO.md index 86aca09dda..8e4a91367b 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -1,6 +1,6 @@ - [X] Object list - [X] Spawnpoint placement -- [ ] Spawnpoint selection & movement +- [X] Spawnpoint selection & movement - [X] Object placement via rightclick context menu - [ ] Re-add object placement tool - [X] Moving camera via middle click diff --git a/src/editor/actions.rs b/src/editor/actions.rs index 37a1ce3d9b..187a338315 100644 --- a/src/editor/actions.rs +++ b/src/editor/actions.rs @@ -60,8 +60,6 @@ pub enum UiAction { texture_id: String, autotile_mask: Vec, }, - SelectEntity(SelectableEntity), - DeselectObject, CreateObject { id: String, kind: MapObjectKind, diff --git a/src/editor/actions/move_spawn_point.rs b/src/editor/actions/move_spawn_point.rs index 5e998ad3d9..1a7ca96c1e 100644 --- a/src/editor/actions/move_spawn_point.rs +++ b/src/editor/actions/move_spawn_point.rs @@ -2,8 +2,6 @@ use core::error::ErrorKind; use core::error::Error; -use std::cmp::Ordering; - use core::error::Result; use macroquad::prelude::Vec2; @@ -31,19 +29,9 @@ impl MoveSpawnPoint { impl UndoableAction for MoveSpawnPoint { fn apply_to(&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, - &"MoveSpawnPoint: Index out of bounds", - )) - } - } + self.old_position = Some(map.spawn_points[self.index]); + + map.spawn_points[self.index] = self.position; Ok(()) } diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 4f19ddaaa8..6f874c7472 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -230,16 +230,6 @@ impl Editor { .apply(action, &mut self.map_resource.map) .unwrap(); } - UiAction::SelectEntity(selection) => { - if let SelectableEntity { - kind: SelectableEntityKind::Object { layer_id, .. }, - .. - } = &selection - { - self.selected_layer = Some(layer_id.clone()); - } - self.selection = Some(selection); - } UiAction::SaveMap { name } => { let mut map_resource = self.map_resource.clone(); @@ -278,9 +268,6 @@ impl Editor { .apply(action, &mut self.map_resource.map) .unwrap(); } - UiAction::DeselectObject => { - self.selection = None; - } UiAction::CreateSpawnPoint(pos) => { let action = actions::CreateSpawnPoint::new(pos); @@ -288,6 +275,20 @@ impl Editor { .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(); + } _ => todo!(), } diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 8c988b88c8..720d2d54cd 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -6,7 +6,7 @@ use macroquad::prelude::collections::storage; use crate::{ editor::{ actions::UiAction, - state::{EditorTool, ObjectSettings}, + state::{DragData, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind}, util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec, Resizable}, view::UiLevelView, }, @@ -14,6 +14,8 @@ use crate::{ Resources, }; +use self::objects::draw_object; + use super::super::Editor; impl Editor { @@ -82,9 +84,12 @@ impl Editor { }; } - self.handle_objects(ui, &view); - self.handle_spawnpoints(&view); + 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, @@ -94,12 +99,222 @@ impl Editor { }); } - fn handle_spawnpoints(&mut self, view: &UiLevelView) { + 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.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 + ); - for spawnpoint in self.map_resource.map.spawn_points.iter() { + !(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()); @@ -108,6 +323,20 @@ impl Editor { texture_size, ); + if view.response.drag_started() || view.response.clicked() { + let click_pos = view.ctx().input().pointer.interact_pos().unwrap(); + + if dest.contains(click_pos) { + *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(), + }), + }); + } + } + let mut mesh = egui::Mesh::with_texture(texture_id); mesh.add_rect_with_uv( dest, @@ -115,6 +344,22 @@ impl Editor { 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)); + + 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 self.selected_tool == EditorTool::SpawnPointPlacer && view.response.clicked() { diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 2ed840e57c..82da1c17d2 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -136,8 +136,12 @@ impl Editor { } } - pub(super) fn handle_objects(&mut self, ui: &mut egui::Ui, view: &UiLevelView) { - let mut to_select = None; + pub(super) fn handle_objects( + &mut self, + ui: &mut egui::Ui, + view: &UiLevelView, + to_select: &mut Option, + ) { for layer in self .map_resource .map @@ -146,89 +150,9 @@ impl Editor { .filter_map(|layer_id| self.map_resource.map.layers.get(layer_id)) { if layer.is_visible { - let selected_on_this_layer = self.handle_object_layer(layer, view, ui); - to_select = to_select.or(selected_on_this_layer); - } - } - - let mut selection_dest = None; - - // Draw selection last (Special case) - if let Some(SelectableEntity { - kind: SelectableEntityKind::Object { index, layer_id }, - mut drag_data, - }) = self.selection.take() - { - 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( - ui.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); + self.handle_object_layer(layer, view, ui, to_select); } } - - if let Some(to_select) = to_select { - self.apply_action(UiAction::SelectEntity(to_select)); - } else if view.response.clicked() - && !selection_dest - .map(|d| d.contains(ui.input().pointer.interact_pos().unwrap())) - .unwrap_or(false) - { - self.apply_action(UiAction::DeselectObject); - } } fn handle_object_layer( @@ -236,9 +160,17 @@ impl Editor { layer: &MapLayer, view: &UiLevelView, ui: &mut egui::Ui, - ) -> Option { + to_select: &mut Option, + ) { let resources = storage::get::(); - let mut to_select = None; + + 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!( @@ -252,7 +184,7 @@ impl Editor { }) if index == idx && layer_id == &layer.id ); - !is_selection + !(is_selection && is_selection_being_dragged) }) { let (dest, is_valid) = draw_object( object, @@ -269,22 +201,15 @@ impl Editor { .pointer .hover_pos() .map(|hover_pos| dest.contains(hover_pos)) - .unwrap_or(false) - && !matches!( - &self.selection, - Some(SelectableEntity { - drag_data: Some(_), - .. - }) - ); + .unwrap_or(false); - if is_hovered { + 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 { + *to_select = Some(SelectableEntity { kind: SelectableEntityKind::Object { index: object_idx, layer_id: layer.id.clone(), @@ -294,6 +219,7 @@ impl Editor { new_pos: object.position.into_egui().to_pos2(), }), }); + dbg!("Selected object"); } view.painter().add(egui::Shape::rect_stroke( dest, @@ -302,8 +228,6 @@ impl Editor { )); } } - - to_select } fn show_object_info_tooltip( @@ -338,7 +262,7 @@ impl Editor { } } -fn draw_object( +pub fn draw_object( object: &crate::map::MapObject, position: egui::Pos2, view: &UiLevelView, From b39fa1bab19d0db47499340ec7dc021fa3076fa2 Mon Sep 17 00:00:00 2001 From: aleokdev Date: Sat, 14 May 2022 10:18:33 +0200 Subject: [PATCH 72/81] Add new map window --- src/editor/TODO.md | 2 +- src/editor/state/mod.rs | 28 ++++++++++ src/editor/state/ui/windows.rs | 26 ++++++++- src/editor/windows/create_map.rs | 91 ++++++++++++++++++++++++++++++++ src/editor/windows/mod.rs | 3 ++ 5 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 src/editor/windows/create_map.rs diff --git a/src/editor/TODO.md b/src/editor/TODO.md index 8e4a91367b..1ea94a8b19 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -5,6 +5,6 @@ - [ ] Re-add object placement tool - [X] Moving camera via middle click - [ ] Open/Import window -- [ ] New map window +- [X] New map window - [ ] Theme support - [ ] Gamepad support \ No newline at end of file diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 6f874c7472..74f9ac3509 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -79,6 +79,7 @@ pub struct Editor { tile_palette: Option, object_being_placed: Option, + create_map_window: Option, create_layer_window: Option, create_tileset_window: Option, menu_window: Option, @@ -107,6 +108,7 @@ impl Editor { tile_palette: None, object_being_placed: None, + create_map_window: None, create_layer_window: None, create_tileset_window: None, menu_window: None, @@ -289,11 +291,37 @@ impl Editor { .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(); + } + } + } _ => 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::*; diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs index 8bec4a0716..4e53a5d7fb 100644 --- a/src/editor/state/ui/windows.rs +++ b/src/editor/state/ui/windows.rs @@ -3,7 +3,7 @@ use std::ops::ControlFlow; use crate::editor::{ actions::UiAction, state::Editor, - windows::{CreateLayerResult, CreateTilesetResult, MenuResult, SaveMapResult}, + windows::{CreateLayerResult, CreateMapResult, CreateTilesetResult, MenuResult, SaveMapResult}, }; impl Editor { @@ -41,6 +41,28 @@ impl Editor { 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, + }), + ControlFlow::Break(CreateMapResult::Close) => { + self.create_map_window = None; + } + } } else if let Some(window) = &mut self.create_tileset_window { match window.ui(egui_ctx, &self.map_resource.map) { ControlFlow::Continue(()) => (), @@ -62,7 +84,7 @@ impl Editor { match window.ui(egui_ctx, self.map_resource.meta.is_user_map) { ControlFlow::Continue(()) => (), ControlFlow::Break(MenuResult::OpenCreateMapWindow) => { - todo!("Create map window"); + self.create_map_window = Some(Default::default()); } ControlFlow::Break(MenuResult::OpenLoadMapWindow) => { todo!("Open/load map window") diff --git a/src/editor/windows/create_map.rs b/src/editor/windows/create_map.rs new file mode 100644 index 0000000000..bc5f32a762 --- /dev/null +++ b/src/editor/windows/create_map.rs @@ -0,0 +1,91 @@ +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) + .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/mod.rs b/src/editor/windows/mod.rs index 1d64204439..a059273c3d 100644 --- a/src/editor/windows/mod.rs +++ b/src/editor/windows/mod.rs @@ -1,3 +1,6 @@ +mod create_map; +pub use create_map::*; + mod create_layer; pub use create_layer::*; From e9f1fd83756af56cf3f5c268e48627d62fd86b53 Mon Sep 17 00:00:00 2001 From: aleokdev Date: Sat, 14 May 2022 11:26:46 +0200 Subject: [PATCH 73/81] Add open map window --- src/editor/TODO.md | 4 +- src/editor/state/mod.rs | 9 ++++ src/editor/state/ui/windows.rs | 75 ++++++++++++++++++---------- src/editor/windows/mod.rs | 3 ++ src/editor/windows/open_map.rs | 90 ++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 26 deletions(-) create mode 100644 src/editor/windows/open_map.rs diff --git a/src/editor/TODO.md b/src/editor/TODO.md index 1ea94a8b19..ea7f17625a 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -4,7 +4,9 @@ - [X] Object placement via rightclick context menu - [ ] Re-add object placement tool - [X] Moving camera via middle click -- [ ] Open/Import window +- [X] Open map window +- [ ] Import map window - [X] New map window +- [ ] Background edit window - [ ] Theme support - [ ] Gamepad support \ No newline at end of file diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 74f9ac3509..8f0493483b 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -84,6 +84,7 @@ pub struct Editor { create_tileset_window: Option, menu_window: Option, save_map_window: Option, + open_map_window: Option, selection: Option, @@ -113,6 +114,7 @@ impl Editor { create_tileset_window: None, menu_window: None, save_map_window: None, + open_map_window: None, selection: None, @@ -307,6 +309,13 @@ impl Editor { } } } + UiAction::OpenMap(index) => { + let resources = storage::get::(); + let map_resource = resources.maps.get(index).cloned().unwrap(); + + self.map_resource = map_resource; + self.clear_context(); + } _ => todo!(), } diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs index 4e53a5d7fb..b751ceff88 100644 --- a/src/editor/state/ui/windows.rs +++ b/src/editor/state/ui/windows.rs @@ -3,7 +3,10 @@ use std::ops::ControlFlow; use crate::editor::{ actions::UiAction, state::Editor, - windows::{CreateLayerResult, CreateMapResult, CreateTilesetResult, MenuResult, SaveMapResult}, + windows::{ + CreateLayerResult, CreateMapResult, CreateTilesetResult, MenuResult, OpenMapResult, + SaveMapResult, + }, }; impl Editor { @@ -30,6 +33,25 @@ impl Editor { } } + 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(()) => (), @@ -49,35 +71,35 @@ impl Editor { 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.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.create_tileset_window { - match window.ui(egui_ctx, &self.map_resource.map) { + } else if let Some(window) = &mut self.open_map_window { + match window.ui(egui_ctx) { 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(OpenMapResult::Open { map_index }) => { + self.apply_action(UiAction::OpenMap(map_index)); + self.open_map_window = None; } - ControlFlow::Break(CreateTilesetResult::Close) => { - self.create_tileset_window = None; + ControlFlow::Break(OpenMapResult::Import { map_index }) => { + self.open_map_window = None; + } + ControlFlow::Break(OpenMapResult::Close) => { + self.open_map_window = None; } } } else if let Some(window) = &mut self.menu_window { @@ -85,12 +107,15 @@ impl Editor { ControlFlow::Continue(()) => (), ControlFlow::Break(MenuResult::OpenCreateMapWindow) => { self.create_map_window = Some(Default::default()); + self.menu_window = None; } ControlFlow::Break(MenuResult::OpenLoadMapWindow) => { - todo!("Open/load map window") + 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 }); diff --git a/src/editor/windows/mod.rs b/src/editor/windows/mod.rs index a059273c3d..57e8fb213a 100644 --- a/src/editor/windows/mod.rs +++ b/src/editor/windows/mod.rs @@ -1,6 +1,9 @@ mod create_map; pub use create_map::*; +mod open_map; +pub use open_map::*; + mod create_layer; pub use create_layer::*; diff --git a/src/editor/windows/open_map.rs b/src/editor/windows/open_map.rs new file mode 100644 index 0000000000..9569fad348 --- /dev/null +++ b/src/editor/windows/open_map.rs @@ -0,0 +1,90 @@ +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) + .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 + } +} From 871df3ae0c00a8ae22ec3431c867c5aac801d75f Mon Sep 17 00:00:00 2001 From: aleokdev Date: Sat, 14 May 2022 13:04:37 +0200 Subject: [PATCH 74/81] Small fixes --- src/editor/state/ui/level/mod.rs | 30 ++++++++++++++-------------- src/editor/state/ui/level/objects.rs | 3 ++- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 720d2d54cd..b8dbbc04a2 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -111,6 +111,7 @@ impl Editor { } = &to_select { self.selected_layer = Some(layer_id.clone()); + self.selected_tool = EditorTool::Cursor; } self.selection = Some(to_select); @@ -323,20 +324,6 @@ impl Editor { texture_size, ); - if view.response.drag_started() || view.response.clicked() { - let click_pos = view.ctx().input().pointer.interact_pos().unwrap(); - - if dest.contains(click_pos) { - *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(), - }), - }); - } - } - let mut mesh = egui::Mesh::with_texture(texture_id); mesh.add_rect_with_uv( dest, @@ -351,7 +338,8 @@ impl Editor { .input() .pointer .hover_pos() - .map_or(false, |hover_pos| dest.contains(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( @@ -359,6 +347,18 @@ impl Editor { 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(), + }), + }); + } } } diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index 82da1c17d2..e6c8b97423 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -201,7 +201,8 @@ impl Editor { .pointer .hover_pos() .map(|hover_pos| dest.contains(hover_pos)) - .unwrap_or(false); + .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); From ab6ed070fca0a5a9f2e9d934bdff437ec4d5a36e Mon Sep 17 00:00:00 2001 From: aleokdev Date: Sat, 14 May 2022 14:00:29 +0200 Subject: [PATCH 75/81] Add import window --- src/editor/TODO.md | 3 +- src/editor/state/mod.rs | 14 +++++ src/editor/state/ui/windows.rs | 39 ++++++++++-- src/editor/windows/create_map.rs | 1 + src/editor/windows/import.rs | 105 +++++++++++++++++++++++++++++++ src/editor/windows/mod.rs | 3 + src/editor/windows/open_map.rs | 1 + src/editor/windows/save_map.rs | 1 + 8 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 src/editor/windows/import.rs diff --git a/src/editor/TODO.md b/src/editor/TODO.md index ea7f17625a..b901897be5 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -5,8 +5,9 @@ - [ ] Re-add object placement tool - [X] Moving camera via middle click - [X] Open map window -- [ ] Import map window +- [X] Import map window - [X] New map window - [ ] Background edit window +- [ ] Map zoom - [ ] Theme support - [ ] Gamepad support \ No newline at end of file diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 8f0493483b..8b05be6f55 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -85,6 +85,7 @@ pub struct Editor { menu_window: Option, save_map_window: Option, open_map_window: Option, + import_window: Option, selection: Option, @@ -115,6 +116,7 @@ impl Editor { menu_window: None, save_map_window: None, open_map_window: None, + import_window: None, selection: None, @@ -317,6 +319,18 @@ impl Editor { 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(); + } + _ => todo!(), } } diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs index b751ceff88..73502777aa 100644 --- a/src/editor/state/ui/windows.rs +++ b/src/editor/state/ui/windows.rs @@ -1,12 +1,17 @@ use std::ops::ControlFlow; -use crate::editor::{ - actions::UiAction, - state::Editor, - windows::{ - CreateLayerResult, CreateMapResult, CreateTilesetResult, MenuResult, OpenMapResult, - SaveMapResult, +use macroquad::prelude::collections::storage; + +use crate::{ + editor::{ + actions::UiAction, + state::Editor, + windows::{ + CreateLayerResult, CreateMapResult, CreateTilesetResult, ImportResult, ImportWindow, + MenuResult, OpenMapResult, SaveMapResult, + }, }, + Resources, }; impl Editor { @@ -96,12 +101,34 @@ impl Editor { 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(()) => (), diff --git a/src/editor/windows/create_map.rs b/src/editor/windows/create_map.rs index bc5f32a762..bd47148a7a 100644 --- a/src/editor/windows/create_map.rs +++ b/src/editor/windows/create_map.rs @@ -40,6 +40,7 @@ impl CreateMapWindow { 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); 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/mod.rs b/src/editor/windows/mod.rs index 57e8fb213a..be21551880 100644 --- a/src/editor/windows/mod.rs +++ b/src/editor/windows/mod.rs @@ -4,6 +4,9 @@ pub use create_map::*; mod open_map; pub use open_map::*; +mod import; +pub use import::*; + mod create_layer; pub use create_layer::*; diff --git a/src/editor/windows/open_map.rs b/src/editor/windows/open_map.rs index 9569fad348..05ed1217d5 100644 --- a/src/editor/windows/open_map.rs +++ b/src/editor/windows/open_map.rs @@ -24,6 +24,7 @@ impl OpenMapWindow { 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::(); diff --git a/src/editor/windows/save_map.rs b/src/editor/windows/save_map.rs index 75a463f08a..3b2f919686 100644 --- a/src/editor/windows/save_map.rs +++ b/src/editor/windows/save_map.rs @@ -27,6 +27,7 @@ impl SaveMapWindow { 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(); From 10dae3f130a8f3121fef77769017db7c67dd0f92 Mon Sep 17 00:00:00 2001 From: aleokdev Date: Wed, 18 May 2022 15:37:31 +0200 Subject: [PATCH 76/81] Re-add object placement tool --- src/editor/TODO.md | 2 +- src/editor/state/mod.rs | 1 + src/editor/state/ui/level/mod.rs | 27 +++------------------------ src/editor/state/ui/toolbar.rs | 3 +++ 4 files changed, 8 insertions(+), 25 deletions(-) diff --git a/src/editor/TODO.md b/src/editor/TODO.md index b901897be5..7cc1673ca7 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -2,7 +2,7 @@ - [X] Spawnpoint placement - [X] Spawnpoint selection & movement - [X] Object placement via rightclick context menu -- [ ] Re-add object placement tool +- [X] Re-add object placement tool - [X] Moving camera via middle click - [X] Open map window - [X] Import map window diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 8b05be6f55..14aa2a1ac0 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -23,6 +23,7 @@ pub enum EditorTool { TilePlacer, SpawnPointPlacer, Eraser, + ObjectPlacer, } pub struct TileSelection { diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index b8dbbc04a2..763a0b8472 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -27,29 +27,6 @@ impl Editor { let mut clicked_add_object = false; view.response = view.response.context_menu(|ui| { - ui.menu_button("Select tool", |ui| { - if ui.button("Cursor").clicked() { - self.selected_tool = EditorTool::Cursor; - ui.close_menu() - } - if ui.button("Spawnpoint Placer").clicked() { - self.selected_tool = EditorTool::SpawnPointPlacer; - ui.close_menu() - } - match self.selected_layer_type() { - Some(MapLayerKind::TileLayer) => { - if ui.button("Tile Placer").clicked() { - self.selected_tool = EditorTool::TilePlacer; - ui.close_menu() - } - if ui.button("Eraser").clicked() { - self.selected_tool = EditorTool::Eraser; - ui.close_menu() - } - } - _ => (), - } - }); if let Some(MapLayerKind::ObjectLayer) = self.selected_layer_type() { if ui.button("Add object").clicked() { clicked_add_object = true; @@ -65,7 +42,9 @@ impl Editor { self.level_view.position -= drag_delta.into_macroquad(); } - if clicked_add_object { + 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()); diff --git a/src/editor/state/ui/toolbar.rs b/src/editor/state/ui/toolbar.rs index abd10eff78..66e16e2c87 100644 --- a/src/editor/state/ui/toolbar.rs +++ b/src/editor/state/ui/toolbar.rs @@ -29,6 +29,9 @@ impl Editor { add_tool("Tiles", EditorTool::TilePlacer); add_tool("Eraser", EditorTool::Eraser); } + Some(MapLayerKind::ObjectLayer) => { + add_tool("Object Placer", EditorTool::ObjectPlacer); + } _ => (), } }); From 891cadda37f1c31e98217c9dd66c8f720e0752e9 Mon Sep 17 00:00:00 2001 From: aleokdev Date: Fri, 20 May 2022 20:05:38 +0200 Subject: [PATCH 77/81] Partial commit --- src/editor/node.rs | 2 +- src/editor/state/mod.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/editor/node.rs b/src/editor/node.rs index 818be3c194..590975da3b 100644 --- a/src/editor/node.rs +++ b/src/editor/node.rs @@ -40,7 +40,7 @@ impl Node for EditorNode { egui_macroquad::ui(|egui_ctx| { node.editor.ui(egui_ctx); - node.accept_mouse_input = !egui_ctx.wants_pointer_input(); + //node.accept_mouse_input = !egui_ctx.wants_pointer_input(); node.accept_kb_input = !egui_ctx.wants_keyboard_input(); }); diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 14aa2a1ac0..35385be300 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -98,6 +98,10 @@ pub struct Editor { } 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, @@ -386,6 +390,11 @@ impl Editor { // 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); From a55423d0dc3aedef371e974a3b25eea6bf201702 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 20 May 2022 20:30:59 +0200 Subject: [PATCH 78/81] Add map zoom --- src/editor/TODO.md | 2 +- src/editor/state/ui/level/objects.rs | 5 ++++- src/editor/state/ui/level/tools.rs | 2 +- src/editor/view.rs | 9 +++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/editor/TODO.md b/src/editor/TODO.md index 7cc1673ca7..586d5aa4bc 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -8,6 +8,6 @@ - [X] Import map window - [X] New map window - [ ] Background edit window -- [ ] Map zoom +- [X] Map zoom - [ ] Theme support - [ ] Gamepad support \ No newline at end of file diff --git a/src/editor/state/ui/level/objects.rs b/src/editor/state/ui/level/objects.rs index e6c8b97423..9e473f9dfb 100644 --- a/src/editor/state/ui/level/objects.rs +++ b/src/editor/state/ui/level/objects.rs @@ -280,7 +280,10 @@ pub fn draw_object( -> 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); + 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); diff --git a/src/editor/state/ui/level/tools.rs b/src/editor/state/ui/level/tools.rs index 272a4728ca..c983cdcde7 100644 --- a/src/editor/state/ui/level/tools.rs +++ b/src/editor/state/ui/level/tools.rs @@ -50,7 +50,7 @@ impl Editor { 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, + tile_size * view.view.scale, ), uv, egui::Color32::from_rgba_unmultiplied(0xff, 0xff, 0xff, 200), diff --git a/src/editor/view.rs b/src/editor/view.rs index 833f7c0a25..0901076da0 100644 --- a/src/editor/view.rs +++ b/src/editor/view.rs @@ -20,17 +20,17 @@ impl Default for LevelView { impl LevelView { // TODO: Factor in level view scale pub fn screen_to_world_pos(&self, p: egui::Pos2) -> egui::Pos2 { - p + self.position.into_egui() + (p.to_vec2() / self.scale + self.position.into_egui()).to_pos2() } pub fn world_to_screen_pos(&self, p: egui::Pos2) -> egui::Pos2 { - p - self.position.into_egui() + ((p.to_vec2() - self.position.into_egui()) * self.scale).to_pos2() } } #[derive(Clone)] pub struct UiLevelView { - view: LevelView, + pub view: LevelView, pub response: egui::Response, painter: egui::Painter, } @@ -60,7 +60,8 @@ impl UiLevelView { // 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() + 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 { From c61ed919a4582922c43c5d283a68dfd4d0bde0b7 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 20 May 2022 20:56:45 +0200 Subject: [PATCH 79/81] Partial commit & off to do some git trickery --- src/editor/windows/background_properties.rs | 66 +++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/editor/windows/background_properties.rs diff --git a/src/editor/windows/background_properties.rs b/src/editor/windows/background_properties.rs new file mode 100644 index 0000000000..f944fa1645 --- /dev/null +++ b/src/editor/windows/background_properties.rs @@ -0,0 +1,66 @@ +use std::ops::ControlFlow; + +use crate::map::{Map, MapLayerKind}; + +pub struct BackgroundPropertiesWindow { + color: Color, + layers: Vec, + layer_texture_id: Option, + layer_depth: f32, + selected_layer: Option, +} + +impl BackgroundPropertiesWindow { + pub fn new(color: Color, layers: Vec) -> Self { + Self { + color, + layers, + layer_texture_id: None, + layer_depth: 0.0, + selected_layer: None, + } + } + + 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 + } +} From f449bef77884b898d2dd9ceedadc34116eafae37 Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Fri, 20 May 2022 22:22:23 +0200 Subject: [PATCH 80/81] Fix drag/ctx menu quirks --- src/editor/state/ui/level/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index 763a0b8472..ef03b22999 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -26,20 +26,19 @@ impl Editor { 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 let Some(MapLayerKind::ObjectLayer) = self.selected_layer_type() { + view.response = view.response.context_menu(|ui| { if ui.button("Add object").clicked() { clicked_add_object = true; ui.close_menu() } - } - }); + }); + } if view.response.dragged_by(egui::PointerButton::Middle) { let drag_delta = egui_ctx.input().pointer.delta(); - // TODO: take level scale/"tiles per pixel" into account - self.level_view.position -= drag_delta.into_macroquad(); + self.level_view.position -= drag_delta.into_macroquad() / view.view.scale; } if clicked_add_object From 856ae35d64ba01ed47f96b9c4baed4ee467c8ded Mon Sep 17 00:00:00 2001 From: alexdevteam Date: Sat, 21 May 2022 01:19:31 +0200 Subject: [PATCH 81/81] Add background edit window --- src/editor/TODO.md | 2 +- src/editor/state/mod.rs | 9 ++ src/editor/state/ui/level/mod.rs | 14 +- src/editor/state/ui/windows.rs | 6 + src/editor/windows/background_properties.rs | 171 +++++++++++++++----- src/editor/windows/mod.rs | 3 + 6 files changed, 156 insertions(+), 49 deletions(-) diff --git a/src/editor/TODO.md b/src/editor/TODO.md index 586d5aa4bc..ed78ed60dc 100644 --- a/src/editor/TODO.md +++ b/src/editor/TODO.md @@ -7,7 +7,7 @@ - [X] Open map window - [X] Import map window - [X] New map window -- [ ] Background edit window +- [X] Background edit window - [X] Map zoom - [ ] Theme support - [ ] Gamepad support \ No newline at end of file diff --git a/src/editor/state/mod.rs b/src/editor/state/mod.rs index 35385be300..bea1cc854d 100644 --- a/src/editor/state/mod.rs +++ b/src/editor/state/mod.rs @@ -82,6 +82,7 @@ pub struct Editor { create_map_window: Option, create_layer_window: Option, + background_properties_window: Option, create_tileset_window: Option, menu_window: Option, save_map_window: Option, @@ -116,6 +117,7 @@ impl Editor { object_being_placed: None, create_map_window: None, + background_properties_window: None, create_layer_window: None, create_tileset_window: None, menu_window: None, @@ -336,6 +338,13 @@ impl Editor { .unwrap(); } + UiAction::UpdateBackground { color, layers } => { + let action = actions::UpdateBackground::new(color, layers); + self.history + .apply(action, &mut self.map_resource.map) + .unwrap(); + } + _ => todo!(), } } diff --git a/src/editor/state/ui/level/mod.rs b/src/editor/state/ui/level/mod.rs index ef03b22999..f514fdb284 100644 --- a/src/editor/state/ui/level/mod.rs +++ b/src/editor/state/ui/level/mod.rs @@ -8,7 +8,7 @@ use crate::{ actions::UiAction, state::{DragData, EditorTool, ObjectSettings, SelectableEntity, SelectableEntityKind}, util::{EguiCompatibleVec, EguiTextureHandler, MqCompatibleVec, Resizable}, - view::UiLevelView, + view::UiLevelView, windows::BackgroundPropertiesWindow, }, map::{MapLayerKind, MapObjectKind}, Resources, @@ -26,14 +26,18 @@ impl Editor { let mut view = self.draw_level_tiles(ui); let mut clicked_add_object = false; - if let Some(MapLayerKind::ObjectLayer) = self.selected_layer_type() { - view.response = view.response.context_menu(|ui| { + 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(); diff --git a/src/editor/state/ui/windows.rs b/src/editor/state/ui/windows.rs index 73502777aa..2f11f11adf 100644 --- a/src/editor/state/ui/windows.rs +++ b/src/editor/state/ui/windows.rs @@ -16,6 +16,12 @@ use crate::{ 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(()) => (), diff --git a/src/editor/windows/background_properties.rs b/src/editor/windows/background_properties.rs index f944fa1645..9f5457085a 100644 --- a/src/editor/windows/background_properties.rs +++ b/src/editor/windows/background_properties.rs @@ -1,64 +1,149 @@ use std::ops::ControlFlow; -use crate::map::{Map, MapLayerKind}; +use macroquad::prelude::{collections::storage, Color}; + +use crate::{ + editor::actions::{UiAction, UiActionExt}, + map::{Map, MapBackgroundLayer, MapLayerKind}, + resources::TextureKind, + Resources, +}; pub struct BackgroundPropertiesWindow { - color: Color, - layers: Vec, - layer_texture_id: Option, + valid_layer_texture_ids: Vec, layer_depth: f32, selected_layer: Option, } -impl BackgroundPropertiesWindow { - pub fn new(color: Color, layers: Vec) -> Self { +impl Default for BackgroundPropertiesWindow { + fn default() -> Self { + let resources = storage::get::(); Self { - color, - layers, - layer_texture_id: None, - layer_depth: 0.0, - selected_layer: None, + 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(), } } +} - pub fn ui(&mut self, egui_ctx: &egui::Context, map: &Map) -> ControlFlow { - let mut action = ControlFlow::Continue(()); +impl BackgroundPropertiesWindow { + pub fn ui(&mut self, egui_ctx: &egui::Context, map: &Map) -> Option { + let mut action = None; - 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), + 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| { - 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, + 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"); } - if ui.button("Cancel").clicked() { - action = ControlFlow::Break(CreateLayerResult::Close) - } - }) + }); }); action diff --git a/src/editor/windows/mod.rs b/src/editor/windows/mod.rs index be21551880..e958e464a4 100644 --- a/src/editor/windows/mod.rs +++ b/src/editor/windows/mod.rs @@ -1,3 +1,6 @@ +mod background_properties; +pub use background_properties::*; + mod create_map; pub use create_map::*;