diff options
Diffstat (limited to '')
| -rw-r--r-- | Cargo.lock | 404 | ||||
| -rw-r--r-- | Cargo.toml | 79 | ||||
| -rw-r--r-- | assets/config.toml | 1 | ||||
| -rw-r--r-- | assets/themes/Rose Pine.toml | 33 | ||||
| -rw-r--r-- | assets/windows/iced_builder.manifest (renamed from iced_builder/assets/windows/iced_builder.manifest) | 0 | ||||
| -rw-r--r-- | assets/windows/iced_builder.rc (renamed from iced_builder/assets/windows/iced_builder.rc) | 0 | ||||
| -rw-r--r-- | build.rs (renamed from iced_builder/build.rs) | 0 | ||||
| -rw-r--r-- | fonts/icons.toml (renamed from iced_builder/fonts/icons.toml) | 0 | ||||
| -rw-r--r-- | fonts/icons.ttf (renamed from iced_builder/fonts/icons.ttf) | bin | 6348 -> 6348 bytes | |||
| -rw-r--r-- | iced_builder/Cargo.toml | 57 | ||||
| -rw-r--r-- | iced_builder/src/lib.rs | 9 | ||||
| -rw-r--r-- | iced_drop/.gitignore | 3 | ||||
| -rw-r--r-- | iced_drop/Cargo.toml | 8 | ||||
| -rw-r--r-- | iced_drop/LICENSE | 21 | ||||
| -rw-r--r-- | iced_drop/README.md | 73 | ||||
| -rw-r--r-- | iced_drop/src/lib.rs | 53 | ||||
| -rw-r--r-- | iced_drop/src/widget.rs | 2 | ||||
| -rw-r--r-- | iced_drop/src/widget/droppable.rs | 499 | ||||
| -rw-r--r-- | iced_drop/src/widget/operation.rs | 1 | ||||
| -rw-r--r-- | iced_drop/src/widget/operation/drop.rs | 90 | ||||
| -rw-r--r-- | rustfmt.toml (renamed from iced_builder/rustfmt.toml) | 0 | ||||
| -rw-r--r-- | src/config.rs | 121 | ||||
| -rw-r--r-- | src/dialogs.rs (renamed from iced_builder/src/dialogs.rs) | 19 | ||||
| -rw-r--r-- | src/environment.rs | 43 | ||||
| -rw-r--r-- | src/error.rs (renamed from iced_builder/src/error.rs) | 27 | ||||
| -rw-r--r-- | src/icon.rs (renamed from iced_builder/src/icon.rs) | 0 | ||||
| -rw-r--r-- | src/main.rs (renamed from iced_builder/src/main.rs) | 124 | ||||
| -rw-r--r-- | src/panes.rs (renamed from iced_builder/src/panes.rs) | 0 | ||||
| -rw-r--r-- | src/panes/code_view.rs (renamed from iced_builder/src/panes/code_view.rs) | 9 | ||||
| -rw-r--r-- | src/panes/designer_view.rs (renamed from iced_builder/src/panes/designer_view.rs) | 0 | ||||
| -rw-r--r-- | src/panes/element_list.rs (renamed from iced_builder/src/panes/element_list.rs) | 8 | ||||
| -rw-r--r-- | src/panes/style.rs (renamed from iced_builder/src/panes/style.rs) | 0 | ||||
| -rw-r--r-- | src/theme.rs | 381 | ||||
| -rw-r--r-- | src/types.rs (renamed from iced_builder/src/types.rs) | 10 | ||||
| -rw-r--r-- | src/types/element_name.rs (renamed from iced_builder/src/types/element_name.rs) | 19 | ||||
| -rw-r--r-- | src/types/project.rs (renamed from iced_builder/src/types/project.rs) | 109 | ||||
| -rwxr-xr-x | src/types/rendered_element.rs (renamed from iced_builder/src/types/rendered_element.rs) | 144 | ||||
| -rw-r--r-- | src/widget.rs (renamed from iced_builder/src/widget.rs) | 0 |
38 files changed, 1083 insertions, 1264 deletions
@@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -293,7 +293,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -322,13 +322,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -372,12 +372,6 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" @@ -425,16 +419,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] -name = "blob-uuid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc15853171b33280f5614e77f5fa4debd33f51a86c44daa4ba3d759674c561" -dependencies = [ - "base64 0.13.1", - "uuid", -] - -[[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -500,7 +484,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -574,9 +558,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.5" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "jobserver", "libc", @@ -983,7 +967,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -1013,6 +997,16 @@ dependencies = [ ] [[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] name = "dirs-sys" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1024,6 +1018,17 @@ dependencies = [ ] [[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1037,7 +1042,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -1173,7 +1178,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -1291,12 +1296,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" [[package]] -name = "float_next_after" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" - -[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1375,7 +1374,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -1469,7 +1468,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -1503,6 +1502,15 @@ dependencies = [ ] [[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] name = "gdk-pixbuf-sys" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1959,7 +1967,6 @@ checksum = "88acfabc84ec077eaf9ede3457ffa3a104626d79022a9bf7f296093b1d60c73f" dependencies = [ "iced_core", "iced_futures", - "iced_highlighter", "iced_renderer", "iced_widget", "iced_winit", @@ -1969,31 +1976,31 @@ dependencies = [ [[package]] name = "iced_anim" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0436df8d38e8b2bde6f3666b015640934b5d40649108387baaaac14605b38192" +checksum = "386fbcdd1584feb1b11bb8c32b44bc5849663ed39c38a0ac8b191fa78773de44" dependencies = [ "iced", "iced_anim_derive", - "serde", ] [[package]] name = "iced_anim_derive" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b89898a5300d2800451406a3d6cfaed89ef08797dc392143f7c16c5c747e95" +checksum = "c22810793dcb18c4dedbf7d72f391f84877836f727917c3491df8141fe651b85" dependencies = [ "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] name = "iced_builder" version = "0.1.0" dependencies = [ - "blob-uuid", + "dirs-next", "embed-resource 3.0.1", + "fxhash", "iced", "iced_anim", "iced_drop", @@ -2004,7 +2011,11 @@ dependencies = [ "serde_json", "thiserror 2.0.9", "tokio", + "tokio-stream", + "toml", + "uuid", "windows_exe_info", + "xdg", ] [[package]] @@ -2030,6 +2041,7 @@ dependencies = [ [[package]] name = "iced_drop" version = "0.1.0" +source = "git+https://github.com/jhannyj/iced_drop?rev=d259ec4dff098852d995d3bcaa5551a88330636f#d259ec4dff098852d995d3bcaa5551a88330636f" dependencies = [ "iced", ] @@ -2091,7 +2103,6 @@ dependencies = [ "image", "kamadak-exif", "log", - "lyon_path", "once_cell", "raw-window-handle", "rustc-hash 2.1.0", @@ -2100,17 +2111,6 @@ dependencies = [ ] [[package]] -name = "iced_highlighter" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad88b25a1328cd4bb0b72d8e20f8207c0433649dc788f67e911423b9406f45c" -dependencies = [ - "iced_core", - "once_cell", - "syntect", -] - -[[package]] name = "iced_renderer" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2167,7 +2167,6 @@ dependencies = [ "iced_glyphon", "iced_graphics", "log", - "lyon", "once_cell", "resvg", "rustc-hash 2.1.0", @@ -2181,12 +2180,10 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81429e1b950b0e4bca65be4c4278fea6678ea782030a411778f26fa9f8983e1d" dependencies = [ - "iced_highlighter", "iced_renderer", "iced_runtime", "num-traits", "once_cell", - "qrcode", "rustc-hash 2.1.0", "thiserror 1.0.69", "unicode-segmentation", @@ -2327,7 +2324,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -2561,12 +2558,6 @@ dependencies = [ ] [[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2613,58 +2604,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" [[package]] -name = "lyon" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" -dependencies = [ - "lyon_algorithms", - "lyon_tessellation", -] - -[[package]] -name = "lyon_algorithms" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08" -dependencies = [ - "lyon_path", - "num-traits", -] - -[[package]] -name = "lyon_geom" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570" -dependencies = [ - "arrayvec", - "euclid", - "num-traits", -] - -[[package]] -name = "lyon_path" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e" -dependencies = [ - "lyon_geom", - "num-traits", -] - -[[package]] -name = "lyon_tessellation" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" -dependencies = [ - "float_next_after", - "lyon_path", - "num-traits", -] - -[[package]] name = "lzma-rs" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2867,7 +2806,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -2898,7 +2836,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -3183,7 +3121,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -3263,7 +3201,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -3356,9 +3294,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", @@ -3366,9 +3304,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", @@ -3376,24 +3314,24 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -3404,29 +3342,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -3452,19 +3390,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] -name = "plist" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" -dependencies = [ - "base64 0.22.1", - "indexmap", - "quick-xml 0.32.0", - "serde", - "time", -] - -[[package]] name = "png" version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3547,21 +3472,6 @@ dependencies = [ ] [[package]] -name = "qrcode" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166f136dfdb199f98186f3649cf7a0536534a61417a1a30221b492b4fb60ce3f" - -[[package]] -name = "quick-xml" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", -] - -[[package]] name = "quick-xml" version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3572,9 +3482,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -3709,11 +3619,11 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-channel", @@ -3744,6 +3654,7 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tower", "tower-service", "url", "wasm-bindgen", @@ -3770,12 +3681,14 @@ dependencies = [ [[package]] name = "rfd" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f6f80a9b882647d9014673ca9925d30ffc9750f2eed2b4490e189eaebd01e8" +checksum = "6a24763657bff09769a8ccf12c8b8a50416fb035fe199263b4c5071e4e3f006f" dependencies = [ "ashpd", "block2", + "core-foundation 0.10.0", + "core-foundation-sys", "glib-sys", "gobject-sys", "gtk-sys", @@ -3788,7 +3701,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -4019,22 +3932,22 @@ checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -4057,7 +3970,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -4126,21 +4039,15 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simplecss" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" dependencies = [ "log", ] [[package]] name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" @@ -4321,7 +4228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" dependencies = [ "kurbo 0.11.1", - "siphasher 1.0.1", + "siphasher", ] [[package]] @@ -4348,9 +4255,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.91" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -4374,7 +4281,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -4389,14 +4296,12 @@ dependencies = [ "fnv", "once_cell", "onig", - "plist", "regex-syntax", "serde", "serde_derive", "serde_json", "thiserror 1.0.69", "walkdir", - "yaml-rust", ] [[package]] @@ -4450,12 +4355,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -4496,7 +4402,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -4507,7 +4413,7 @@ checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -4528,12 +4434,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde", "time-core", - "time-macros", ] [[package]] @@ -4543,16 +4447,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] name = "tiny-skia" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4652,6 +4546,17 @@ dependencies = [ ] [[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] name = "tokio-util" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4699,6 +4604,27 @@ dependencies = [ ] [[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4723,7 +4649,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -4872,7 +4798,7 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" dependencies = [ - "base64 0.22.1", + "base64", "data-url", "flate2", "fontdb 0.18.0", @@ -4883,7 +4809,7 @@ dependencies = [ "roxmltree", "rustybuzz", "simplecss", - "siphasher 1.0.1", + "siphasher", "strict-num", "svgtypes", "tiny-skia-path", @@ -4912,6 +4838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", + "serde", ] [[package]] @@ -4998,7 +4925,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", "wasm-bindgen-shared", ] @@ -5033,7 +4960,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5152,7 +5079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml 0.36.2", + "quick-xml", "quote", ] @@ -5626,9 +5553,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.7" +version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba50bc8ef4b6f1a75c9274fb95aa9a8f63fbc66c56f391bd85cf68d51e7b1a3" +checksum = "f5d74280aabb958072864bff6cfbcf9025cf8bfacdde5e32b5e12920ef703b0f" dependencies = [ "ahash 0.8.11", "android-activity", @@ -5678,9 +5605,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] @@ -5755,6 +5682,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" [[package]] +name = "xdg" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" + +[[package]] name = "xdg-home" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5796,15 +5729,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - -[[package]] name = "yazi" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5830,7 +5754,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", "synstructure", ] @@ -5917,7 +5841,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", "zvariant_utils 2.1.0", ] @@ -5930,7 +5854,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", "zbus_names 4.1.0", "zvariant 5.1.0", "zvariant_utils 3.0.2", @@ -5983,7 +5907,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -6003,7 +5927,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", "synstructure", ] @@ -6024,7 +5948,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -6046,7 +5970,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -6167,7 +6091,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", "zvariant_utils 2.1.0", ] @@ -6180,7 +6104,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", "zvariant_utils 3.0.2", ] @@ -6192,7 +6116,7 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.95", ] [[package]] @@ -6205,6 +6129,6 @@ dependencies = [ "quote", "serde", "static_assertions", - "syn 2.0.91", + "syn 2.0.95", "winnow", ] @@ -1,6 +1,73 @@ -[workspace] -resolver = "2" -members = [ - "iced_drop", - "iced_builder" -] +[package] +name = "iced_builder" +description = "GUI builder for iced, built with iced." +version = "0.1.0" +edition = "2021" +authors = ["pml68 <contact@pml68.dev>"] +repository = "https://github.com/pml68/iced-builder" +license = "GPL-3.0-or-later" +keywords = ["gui", "iced"] + +[dependencies] +iced = { version = "0.13.1", features = [ "image","svg","advanced","tokio"] } +# iced_aw = { version = "0.11.0", default-features = false, features = ["menu","color_picker"] } +iced_anim = { version = "0.2.0", features = ["derive"] } +serde = { version = "1.0.217", features = ["derive"] } +serde_json = "1.0.134" +toml = "0.8.19" +tokio = { version = "1.42", features = ["fs"] } +tokio-stream = { version = "0.1", features = ["fs"] } +rfd = { version = "0.15.1", default-features = false, features = ["async-std", "gtk3"] } +rust-format = "0.3.4" +uuid = { version = "1.11.0", features = ["v4", "serde"] } +fxhash = "0.2.1" +thiserror = "2.0.9" +dirs-next = "2.0.0" + +[dependencies.iced_drop] +git = "https://github.com/jhannyj/iced_drop" +rev = "d259ec4dff098852d995d3bcaa5551a88330636f" + +[target.'cfg(target_os = "macos")'.dependencies] +xdg = "2.5.2" + +[build-dependencies] +iced_fontello = "0.13.1" + +[target.'cfg(windows)'.build-dependencies] +embed-resource = "3.0.1" +windows_exe_info = "0.4" + +[profile.dev] +opt-level = 1 + +[profile.dev.package."*"] +opt-level = 3 + +[[bin]] +name = "iced-builder" +path = "src/main.rs" + +[lints.rust] +missing_debug_implementations = "deny" +# missing_docs = "deny" +unsafe_code = "deny" +unused_results = "deny" + +[lints.clippy] +type-complexity = "allow" +semicolon_if_nothing_returned = "deny" +trivially-copy-pass-by-ref = "deny" +default_trait_access = "deny" +match-wildcard-for-single-variants = "deny" +redundant-closure-for-method-calls = "deny" +filter_map_next = "deny" +manual_let_else = "deny" +unused_async = "deny" +from_over_into = "deny" +needless_borrow = "deny" +new_without_default = "deny" +useless_conversion = "deny" + +[lints.rustdoc] +broken_intra_doc_links = "forbid" diff --git a/assets/config.toml b/assets/config.toml new file mode 100644 index 0000000..8ef1bb3 --- /dev/null +++ b/assets/config.toml @@ -0,0 +1 @@ +theme = "Rose Pine" diff --git a/assets/themes/Rose Pine.toml b/assets/themes/Rose Pine.toml new file mode 100644 index 0000000..df7342b --- /dev/null +++ b/assets/themes/Rose Pine.toml @@ -0,0 +1,33 @@ +is_dark = true + +[palette] +background = "#26233a" +text = "#e0def4" +primary = "#9ccfd8" +success = "#f6c177" +danger = "#eb6f92" + +[background] +base = { color = "#191724", text = "#e0def4" } +weak = { color = "#1f1d2e", text = "#e0def4" } +strong = { color = "#26233a", text = "#f4ebd3" } + +[primary] +base = { color = "#eb6f92", text = "#000000" } +weak = { color = "#f6c177", text = "#000000" } +strong = { color = "#ebbcba", text = "#000000" } + +[secondary] +base = { color = "#31748f", text = "#ffffff" } +weak = { color = "#9ccfd8", text = "#000000" } +strong = { color = "#c4a7e7", text = "#000000" } + +[success] +base = { color = "#9ccfd8", text = "#000000" } +weak = { color = "#6e6a86", text = "#ffffff" } +strong = { color = "#908caa", text = "#000000" } + +[danger] +base = { color = "#eb6f92", text = "#ffffff" } +weak = { color = "#f6c177", text = "#ffffff" } +strong = { color = "#524f67", text = "#ffffff" } diff --git a/iced_builder/assets/windows/iced_builder.manifest b/assets/windows/iced_builder.manifest index 82039bf..82039bf 100644 --- a/iced_builder/assets/windows/iced_builder.manifest +++ b/assets/windows/iced_builder.manifest diff --git a/iced_builder/assets/windows/iced_builder.rc b/assets/windows/iced_builder.rc index 7255b65..7255b65 100644 --- a/iced_builder/assets/windows/iced_builder.rc +++ b/assets/windows/iced_builder.rc diff --git a/iced_builder/build.rs b/build.rs index 438ce37..438ce37 100644 --- a/iced_builder/build.rs +++ b/build.rs diff --git a/iced_builder/fonts/icons.toml b/fonts/icons.toml index a70c0e7..a70c0e7 100644 --- a/iced_builder/fonts/icons.toml +++ b/fonts/icons.toml diff --git a/iced_builder/fonts/icons.ttf b/fonts/icons.ttf Binary files differindex 7af6b0e..7af6b0e 100644 --- a/iced_builder/fonts/icons.ttf +++ b/fonts/icons.ttf diff --git a/iced_builder/Cargo.toml b/iced_builder/Cargo.toml deleted file mode 100644 index a1b41cc..0000000 --- a/iced_builder/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -name = "iced_builder" -description = "GUI builder for iced, built with iced." -version = "0.1.0" -edition = "2021" -authors = ["pml68 <contact@pml68.dev>"] -repository = "https://github.com/pml68/iced-builder" -license = "GPL-3.0-or-later" -keywords = ["gui", "iced"] - -[dependencies] -iced = { version = "0.13.1", features = [ "image","svg","canvas","qr_code","advanced","tokio","highlighter"] } -# iced_aw = { version = "0.11.0", default-features = false, features = ["menu","color_picker"] } -iced_anim = { version = "0.1.4", features = ["derive", "serde"] } -iced_drop = { path = "../iced_drop" } -serde = { version = "1.0.216", features = ["derive"] } -serde_json = "1.0.133" -tokio = { version = "1.42.0", features = ["fs"] } -rfd = { version = "0.15.1", default-features = false, features = ["async-std", "gtk3"] } -rust-format = "0.3.4" -blob-uuid = "0.5.0" -thiserror = "2.0.6" - -[build-dependencies] -iced_fontello = "0.13.1" - -[target.'cfg(windows)'.build-dependencies] -embed-resource = "3.0.1" -windows_exe_info = "0.4" - -[[bin]] -name = "iced-builder" -path = "src/main.rs" - -[lints.rust] -missing_debug_implementations = "deny" -# missing_docs = "deny" -unsafe_code = "deny" -unused_results = "deny" - -[lints.clippy] -type-complexity = "allow" -semicolon_if_nothing_returned = "deny" -trivially-copy-pass-by-ref = "deny" -default_trait_access = "deny" -match-wildcard-for-single-variants = "deny" -redundant-closure-for-method-calls = "deny" -filter_map_next = "deny" -manual_let_else = "deny" -unused_async = "deny" -from_over_into = "deny" -needless_borrow = "deny" -new_without_default = "deny" -useless_conversion = "deny" - -[lints.rustdoc] -broken_intra_doc_links = "forbid" diff --git a/iced_builder/src/lib.rs b/iced_builder/src/lib.rs deleted file mode 100644 index f3165f5..0000000 --- a/iced_builder/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod dialogs; -pub mod error; -pub mod icon; -pub mod panes; -pub mod types; -pub mod widget; - -pub use error::Error; -pub type Result<T> = core::result::Result<T, Error>; diff --git a/iced_drop/.gitignore b/iced_drop/.gitignore deleted file mode 100644 index ff0d847..0000000 --- a/iced_drop/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target -/.vscode -Cargo.lock diff --git a/iced_drop/Cargo.toml b/iced_drop/Cargo.toml deleted file mode 100644 index 40beeb0..0000000 --- a/iced_drop/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "iced_drop" -version = "0.1.0" -edition = "2021" - -[dependencies.iced] -version = "0.13.1" -features = ["advanced"] diff --git a/iced_drop/LICENSE b/iced_drop/LICENSE deleted file mode 100644 index 89d9fee..0000000 --- a/iced_drop/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 jhannyj - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/iced_drop/README.md b/iced_drop/README.md deleted file mode 100644 index 41b637b..0000000 --- a/iced_drop/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# iced_drop - -A small library which provides a custom widget and operation to make drag and drop easier to implement in [iced](https://github.com/iced-rs/iced/tree/master) - -## Usage - -To add drag and drog functionality, first define two messages with the following format - -```rust -enum Message { - Drop(iced::Point, iced::Rectangle) - HandleZones(Vec<(iced::advanced::widget::Id, iced::Rectangle)>) -} -``` - -The `Drop` message will be sent when the droppable is being dragged, and the left mouse button is released. This message provides the mouse position and layout boundaries of the droppable at the release point. - -The `HandleZones` message will be sent after an operation that finds the drop zones under the mouse position. It provides the Id and bounds for each drop zone. - -Next, create create a droppable in the view method and assign the on_drop message. The dropopable function takes an `impl Into<Element>` object, so it's easy to make a droppable from any iced widget. - -```rust -iced_drop::droppable("Drop me!").on_drop(Message::Drop); -``` - -Next, create a "drop zone." A drop zone is any widget that operates like a container andhas some assigned Id. It's important that the widget is assigned some Id or it won't be recognized as a drop zone. - -```rust -iced::widget::container("Drop zone") - .id(iced::widget::container::Id::new("drop_zone")); -``` - -Finally, handle the updates of the drop messages - -```rust -match message { - Message::Drop(cursor_pos, _) => { - return iced_drop::zones_on_point( - Message::HandleZonesFound, - point, - None, - None, - ); - } - Message::HandleZones(zones) => { - println!("{:?}", zones) - } -} -``` - -On Drop, we return a widget operation that looks for drop zones under the cursor_pos. When this operation finishes, it returns the zones found and sends the `HandleZones` message. In this example, we only defined one zone, so the zones vector will either be empty if the droppable was not dropped on the zone, or it will contain the `drop_zone` - -## Examples - -There are two examples: color, todo. - -The color example is a very basic drag/drop showcase where the user can drag colors into zones and change the zone's color. I would start here. - -[Link to video](https://drive.google.com/file/d/1K1CCi2Lc90IUyDufsvoUBZmUCbeg6_Fi/view?usp=sharing) - -To run this examples: `cargo run -p color` - -The todo example is a basic todo board application similar to Trello. This is a much much more complex example as it handles custom highlighting and nested droppables, but it just shows you can make some pretty cool things with iced. - -[Link to video](https://drive.google.com/file/d/1MLOCk4Imd_oUnrTj_psbpYbwua976HmR/view?usp=sharing) - -To run this example try: `cargo run -p todo` - -Note: the todo example might also be a good example on how one can use operations. Check examples/todo/src/operation.rs. I didn't find any other examples of this in the iced repo except for the built in focus operations. - -## Future Development - -Right now it's a little annoying having to work with iced's Id type. At some point, I will work on a drop_zone widget that can take some generic clonable type as an id, and I will create a seperate find_zones operation that will return a list of this custom Id. This should make it easier to determine which drop zones were found. diff --git a/iced_drop/src/lib.rs b/iced_drop/src/lib.rs deleted file mode 100644 index 9906cbe..0000000 --- a/iced_drop/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -pub mod widget; - -use iced::{ - advanced::widget::{operate, Id}, - advanced::{graphics::futures::MaybeSend, renderer}, - task::Task, - Element, Point, Rectangle, -}; - -use widget::droppable::*; -use widget::operation::drop; - -pub fn droppable<'a, Message, Theme, Renderer>( - content: impl Into<Element<'a, Message, Theme, Renderer>>, -) -> Droppable<'a, Message, Theme, Renderer> -where - Message: Clone, - Renderer: renderer::Renderer, -{ - Droppable::new(content) -} - -pub fn zones_on_point<T, MF>( - msg: MF, - point: Point, - options: Option<Vec<Id>>, - depth: Option<usize>, -) -> Task<T> -where - T: Send + 'static, - MF: Fn(Vec<(Id, Rectangle)>) -> T + MaybeSend + Sync + Clone + 'static, -{ - operate(drop::find_zones( - move |bounds| bounds.contains(point), - options, - depth, - )) - .map(move |id| msg(id)) -} - -pub fn find_zones<Message, MF, F>( - msg: MF, - filter: F, - options: Option<Vec<Id>>, - depth: Option<usize>, -) -> Task<Message> -where - Message: Send + 'static, - MF: Fn(Vec<(Id, Rectangle)>) -> Message + MaybeSend + Sync + Clone + 'static, - F: Fn(&Rectangle) -> bool + Send + 'static, -{ - operate(drop::find_zones(filter, options, depth)).map(move |id| msg(id)) -} diff --git a/iced_drop/src/widget.rs b/iced_drop/src/widget.rs deleted file mode 100644 index 6b3fed2..0000000 --- a/iced_drop/src/widget.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod droppable; -pub mod operation; diff --git a/iced_drop/src/widget/droppable.rs b/iced_drop/src/widget/droppable.rs deleted file mode 100644 index 80d8600..0000000 --- a/iced_drop/src/widget/droppable.rs +++ /dev/null @@ -1,499 +0,0 @@ -//! Encapsulates a widget that can be dragged and dropped. -use std::fmt::Debug; -use std::vec; - -use iced::advanced::widget::{Operation, Tree, Widget}; -use iced::advanced::{self, layout, mouse, overlay, renderer, Layout}; -use iced::event::Status; -use iced::{Element, Point, Rectangle, Size, Vector}; - -/// An element that can be dragged and dropped on a [`DropZone`] -pub struct Droppable<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> -where - Message: Clone, - Renderer: renderer::Renderer, -{ - content: Element<'a, Message, Theme, Renderer>, - id: Option<iced::advanced::widget::Id>, - on_click: Option<Message>, - on_drop: Option<Box<dyn Fn(Point, Rectangle) -> Message + 'a>>, - on_drag: Option<Box<dyn Fn(Point, Rectangle) -> Message + 'a>>, - on_cancel: Option<Message>, - drag_mode: Option<(bool, bool)>, - drag_overlay: bool, - drag_hide: bool, - drag_center: bool, - drag_size: Option<Size>, - reset_delay: usize, -} - -impl<'a, Message, Theme, Renderer> Droppable<'a, Message, Theme, Renderer> -where - Message: Clone, - Renderer: renderer::Renderer, -{ - /// Creates a new [`Droppable`]. - pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self { - Self { - content: content.into(), - id: None, - on_click: None, - on_drop: None, - on_drag: None, - on_cancel: None, - drag_mode: Some((true, true)), - drag_overlay: true, - drag_hide: false, - drag_center: false, - drag_size: None, - reset_delay: 0, - } - } - - /// Sets the unique identifier of the [`Droppable`]. - pub fn id(mut self, id: iced::advanced::widget::Id) -> Self { - self.id = Some(id); - self - } - - /// Sets the message that will be produced when the [`Droppable`] is clicked. - pub fn on_click(mut self, message: Message) -> Self { - self.on_click = Some(message); - self - } - - /// Sets the message that will be produced when the [`Droppable`] is dropped on a [`DropZone`]. - /// - /// Unless this is set, the [`Droppable`] will be disabled. - pub fn on_drop<F>(mut self, message: F) -> Self - where - F: Fn(Point, Rectangle) -> Message + 'a, - { - self.on_drop = Some(Box::new(message)); - self - } - - /// Sets the message that will be produced when the [`Droppable`] is dragged. - pub fn on_drag<F>(mut self, message: F) -> Self - where - F: Fn(Point, Rectangle) -> Message + 'a, - { - self.on_drag = Some(Box::new(message)); - self - } - - /// Sets the message that will be produced when the user right clicks while dragging the [`Droppable`]. - pub fn on_cancel(mut self, message: Message) -> Self { - self.on_cancel = Some(message); - self - } - - /// Sets whether the [`Droppable`] should be drawn under the cursor while dragging. - pub fn drag_overlay(mut self, drag_overlay: bool) -> Self { - self.drag_overlay = drag_overlay; - self - } - - /// Sets whether the [`Droppable`] should be hidden while dragging. - pub fn drag_hide(mut self, drag_hide: bool) -> Self { - self.drag_hide = drag_hide; - self - } - - /// Sets whether the [`Droppable`] should be centered on the cursor while dragging. - pub fn drag_center(mut self, drag_center: bool) -> Self { - self.drag_center = drag_center; - self - } - - // Sets whether the [`Droppable`] can be dragged along individual axes. - pub fn drag_mode(mut self, drag_x: bool, drag_y: bool) -> Self { - self.drag_mode = Some((drag_x, drag_y)); - self - } - - /// Sets whether the [`Droppable`] should be be resized to a given size while dragging. - pub fn drag_size(mut self, hide_size: Size) -> Self { - self.drag_size = Some(hide_size); - self - } - - /// Sets the number of frames/layout calls to wait before resetting the size of the [`Droppable`] after dropping. - /// - /// This is useful for cases where the [`Droppable`] is being moved to a new location after some widget operation. - /// In this case, the [`Droppable`] will mainting the 'drag_size' for the given number of frames before resetting to its original size. - /// This prevents the [`Droppable`] from 'jumping' back to its original size before the new location is rendered which - /// prevents flickering. - /// - /// Warning: this should only be set if there's is some noticeble flickering when the [`Droppable`] is dropped. That is, if the - /// [`Droppable`] returns to its original size before it's moved to it's new location. - pub fn reset_delay(mut self, reset_delay: usize) -> Self { - self.reset_delay = reset_delay; - self - } -} - -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Droppable<'a, Message, Theme, Renderer> -where - Message: Clone, - Renderer: renderer::Renderer, -{ - fn state(&self) -> iced::advanced::widget::tree::State { - advanced::widget::tree::State::new(State::default()) - } - - fn tag(&self) -> iced::advanced::widget::tree::Tag { - advanced::widget::tree::Tag::of::<State>() - } - - fn children(&self) -> Vec<iced::advanced::widget::Tree> { - vec![advanced::widget::Tree::new(&self.content)] - } - - fn diff(&self, tree: &mut iced::advanced::widget::Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) - } - - fn size(&self) -> iced::Size<iced::Length> { - self.content.as_widget().size() - } - - fn on_event( - &mut self, - tree: &mut iced::advanced::widget::Tree, - event: iced::Event, - layout: iced::advanced::Layout<'_>, - cursor: iced::advanced::mouse::Cursor, - _renderer: &Renderer, - _clipboard: &mut dyn iced::advanced::Clipboard, - shell: &mut iced::advanced::Shell<'_, Message>, - _viewport: &iced::Rectangle, - ) -> iced::advanced::graphics::core::event::Status { - // handle the on event of the content first, in case that the droppable is nested - let status = self.content.as_widget_mut().on_event( - &mut tree.children[0], - event.clone(), - layout, - cursor, - _renderer, - _clipboard, - shell, - _viewport, - ); - // this should really only be captured if the droppable is nested or it contains some other - // widget that captures the event - if status == Status::Captured { - return status; - }; - - if let Some(on_drop) = self.on_drop.as_deref() { - let state = tree.state.downcast_mut::<State>(); - if let iced::Event::Mouse(mouse) = event { - match mouse { - mouse::Event::ButtonPressed(btn) => { - if btn == mouse::Button::Left && cursor.is_over(layout.bounds()) { - // select the droppable and store the position of the widget before dragging - state.action = Action::Select(cursor.position().unwrap()); - let bounds = layout.bounds(); - state.widget_pos = bounds.position(); - state.overlay_bounds.width = bounds.width; - state.overlay_bounds.height = bounds.height; - - if let Some(on_click) = self.on_click.clone() { - shell.publish(on_click); - } - return Status::Captured; - } else if btn == mouse::Button::Right { - if let Action::Drag(_, _) = state.action { - shell.invalidate_layout(); - state.action = Action::None; - if let Some(on_cancel) = self.on_cancel.clone() { - shell.publish(on_cancel); - } - } - } - } - mouse::Event::CursorMoved { mut position } => match state.action { - Action::Select(start) | Action::Drag(start, _) => { - // calculate the new position of the widget after dragging - - if let Some((drag_x, drag_y)) = self.drag_mode { - position = Point { - x: if drag_x { position.x } else { start.x }, - y: if drag_y { position.y } else { start.y }, - }; - } - - state.action = Action::Drag(start, position); - // update the position of the overlay since the cursor was moved - if self.drag_center { - state.overlay_bounds.x = - position.x - state.overlay_bounds.width / 2.0; - state.overlay_bounds.y = - position.y - state.overlay_bounds.height / 2.0; - } else { - state.overlay_bounds.x = state.widget_pos.x + position.x - start.x; - state.overlay_bounds.y = state.widget_pos.y + position.y - start.y; - } - // send on drag msg - if let Some(on_drag) = self.on_drag.as_deref() { - let message = (on_drag)(position, state.overlay_bounds); - shell.publish(message); - } - } - _ => (), - }, - mouse::Event::ButtonReleased(btn) => { - if btn == mouse::Button::Left { - match state.action { - Action::Select(_) => { - state.action = Action::None; - } - Action::Drag(_, current) => { - // send on drop msg - let message = (on_drop)(current, state.overlay_bounds); - shell.publish(message); - - if self.reset_delay == 0 { - state.action = Action::None; - } else { - state.action = Action::Wait(self.reset_delay); - } - } - _ => (), - } - } - } - _ => {} - } - } - } - Status::Ignored - } - - fn layout( - &self, - tree: &mut iced::advanced::widget::Tree, - renderer: &Renderer, - limits: &iced::advanced::layout::Limits, - ) -> iced::advanced::layout::Node { - let state: &mut State = tree.state.downcast_mut::<State>(); - let content_node = self - .content - .as_widget() - .layout(&mut tree.children[0], renderer, limits); - - // Adjust the size of the original widget if it's being dragged or we're wating to reset the size - if let Some(new_size) = self.drag_size { - match state.action { - Action::Drag(_, _) => { - return iced::advanced::layout::Node::with_children( - new_size, - content_node.children().to_vec(), - ); - } - Action::Wait(reveal_index) => { - if reveal_index <= 1 { - state.action = Action::None; - } else { - state.action = Action::Wait(reveal_index - 1); - } - - return iced::advanced::layout::Node::with_children( - new_size, - content_node.children().to_vec(), - ); - } - _ => (), - } - } - - content_node - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn Operation, - ) { - let state = tree.state.downcast_mut::<State>(); - operation.custom(state, self.id.as_ref()); - operation.container(self.id.as_ref(), layout.bounds(), &mut |operation| { - self.content - .as_widget() - .operate(&mut tree.children[0], layout, renderer, operation); - }); - } - - fn draw( - &self, - tree: &iced::advanced::widget::Tree, - renderer: &mut Renderer, - theme: &Theme, - style: &renderer::Style, - layout: iced::advanced::Layout<'_>, - cursor: iced::advanced::mouse::Cursor, - viewport: &iced::Rectangle, - ) { - let state: &State = tree.state.downcast_ref::<State>(); - if let Action::Drag(_, _) = state.action { - if self.drag_hide { - return; - } - } - - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - style, - layout, - cursor, - &viewport, - ); - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - _translation: Vector, - ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { - let state: &mut State = tree.state.downcast_mut::<State>(); - let mut children = tree.children.iter_mut(); - if self.drag_overlay { - if let Action::Drag(_, _) = state.action { - return Some(overlay::Element::new(Box::new(Overlay { - content: &self.content, - tree: children.next().unwrap(), - overlay_bounds: state.overlay_bounds, - }))); - } - } - self.content.as_widget_mut().overlay( - children.next().unwrap(), - layout, - renderer, - _translation, - ) - } - - fn mouse_interaction( - &self, - tree: &iced::advanced::widget::Tree, - layout: iced::advanced::Layout<'_>, - cursor: iced::advanced::mouse::Cursor, - _viewport: &iced::Rectangle, - _renderer: &Renderer, - ) -> iced::advanced::mouse::Interaction { - let child_interact = self.content.as_widget().mouse_interaction( - &tree.children[0], - layout, - cursor, - _viewport, - _renderer, - ); - if child_interact != mouse::Interaction::default() { - return child_interact; - } - - let state = tree.state.downcast_ref::<State>(); - - if self.on_drop.is_none() { - return mouse::Interaction::NotAllowed; - } - if let Action::Drag(_, _) = state.action { - return mouse::Interaction::Grabbing; - } - if cursor.is_over(layout.bounds()) { - return mouse::Interaction::Pointer; - } - mouse::Interaction::default() - } -} - -impl<'a, Message, Theme, Renderer> From<Droppable<'a, Message, Theme, Renderer>> - for Element<'a, Message, Theme, Renderer> -where - Message: 'a + Clone, - Theme: 'a, - Renderer: 'a + renderer::Renderer, -{ - fn from( - droppable: Droppable<'a, Message, Theme, Renderer>, - ) -> Element<'a, Message, Theme, Renderer> { - Element::new(droppable) - } -} - -#[derive(Default, Clone, Copy, PartialEq, Debug)] -pub struct State { - widget_pos: Point, - overlay_bounds: Rectangle, - action: Action, -} - -#[derive(Default, Clone, Copy, PartialEq, Debug)] -pub enum Action { - #[default] - None, - /// (point clicked) - Select(Point), - /// (start pos, current pos) - Drag(Point, Point), - /// (frames to wait) - Wait(usize), -} - -struct Overlay<'a, 'b, Message, Theme, Renderer> -where - Renderer: renderer::Renderer, -{ - content: &'b Element<'a, Message, Theme, Renderer>, - tree: &'b mut advanced::widget::Tree, - overlay_bounds: Rectangle, -} - -impl<'a, 'b, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer> - for Overlay<'a, 'b, Message, Theme, Renderer> -where - Renderer: renderer::Renderer, -{ - fn layout(&mut self, renderer: &Renderer, _bounds: Size) -> layout::Node { - Widget::<Message, Theme, Renderer>::layout( - self.content.as_widget(), - self.tree, - renderer, - &layout::Limits::new(Size::ZERO, self.overlay_bounds.size()), - ) - .move_to(self.overlay_bounds.position()) - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &Theme, - inherited_style: &renderer::Style, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - ) { - Widget::<Message, Theme, Renderer>::draw( - self.content.as_widget(), - self.tree, - renderer, - theme, - inherited_style, - layout, - cursor_position, - &Rectangle::with_size(Size::INFINITY), - ); - } - - fn is_over(&self, _layout: Layout<'_>, _renderer: &Renderer, _cursor_position: Point) -> bool { - false - } -} diff --git a/iced_drop/src/widget/operation.rs b/iced_drop/src/widget/operation.rs deleted file mode 100644 index 3d7dcff..0000000 --- a/iced_drop/src/widget/operation.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod drop; diff --git a/iced_drop/src/widget/operation/drop.rs b/iced_drop/src/widget/operation/drop.rs deleted file mode 100644 index a76181c..0000000 --- a/iced_drop/src/widget/operation/drop.rs +++ /dev/null @@ -1,90 +0,0 @@ -use iced::{ - advanced::widget::{ - operation::{Outcome, Scrollable}, - Id, Operation, - }, - Rectangle, Vector, -}; - -/// Produces an [`Operation`] that will find the drop zones that pass a filter on the zone's bounds. -/// For any drop zone to be considered, the Element must have some Id. -/// If `options` is `None`, all drop zones will be considered. -/// Depth determines how how deep into nested drop zones to go. -/// If 'depth' is `None`, nested dropzones will be fully explored -pub fn find_zones<F>( - filter: F, - options: Option<Vec<Id>>, - depth: Option<usize>, -) -> impl Operation<Vec<(Id, Rectangle)>> -where - F: Fn(&Rectangle) -> bool + Send + 'static, -{ - struct FindDropZone<F> { - filter: F, - options: Option<Vec<Id>>, - zones: Vec<(Id, Rectangle)>, - max_depth: Option<usize>, - c_depth: usize, - offset: Vector, - } - - impl<F> Operation<Vec<(Id, Rectangle)>> for FindDropZone<F> - where - F: Fn(&Rectangle) -> bool + Send + 'static, - { - fn container( - &mut self, - id: Option<&Id>, - bounds: iced::Rectangle, - operate_on_children: &mut dyn FnMut(&mut dyn Operation<Vec<(Id, Rectangle)>>), - ) { - match id { - Some(id) => { - let is_option = match &self.options { - Some(options) => options.contains(id), - None => true, - }; - let bounds = bounds - self.offset; - if is_option && (self.filter)(&bounds) { - self.c_depth += 1; - self.zones.push((id.clone(), bounds)); - } - } - None => (), - } - let goto_next = match &self.max_depth { - Some(m_depth) => self.c_depth < *m_depth, - None => true, - }; - if goto_next { - operate_on_children(self); - } - } - - fn finish(&self) -> Outcome<Vec<(Id, Rectangle)>> { - Outcome::Some(self.zones.clone()) - } - - fn scrollable( - &mut self, - _state: &mut dyn Scrollable, - _id: Option<&Id>, - bounds: Rectangle, - _content_bounds: Rectangle, - translation: Vector, - ) { - if (self.filter)(&bounds) { - self.offset = self.offset + translation; - } - } - } - - FindDropZone { - filter, - options, - zones: vec![], - max_depth: depth, - c_depth: 0, - offset: Vector { x: 0.0, y: 0.0 }, - } -} diff --git a/iced_builder/rustfmt.toml b/rustfmt.toml index 197262a..197262a 100644 --- a/iced_builder/rustfmt.toml +++ b/rustfmt.toml diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..9d29af7 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,121 @@ +use std::path::PathBuf; + +use serde::Deserialize; +use tokio_stream::wrappers::ReadDirStream; +use tokio_stream::StreamExt; + +use crate::theme::{theme_from_str, theme_index, Appearance, Theme}; +use crate::{environment, Error}; + +#[derive(Debug, Clone, Default)] +pub struct Config { + pub theme: Appearance, + pub last_project: Option<PathBuf>, +} + +impl Config { + pub fn selected_theme(&self) -> iced::Theme { + self.theme.selected.clone() + } + + pub fn config_dir() -> PathBuf { + let dir = environment::config_dir(); + + if !dir.exists() { + std::fs::create_dir_all(dir.as_path()) + .expect("expected permissions to create config folder"); + } + dir + } + + pub fn themes_dir() -> PathBuf { + let dir = Self::config_dir().join("themes"); + + if !dir.exists() { + std::fs::create_dir_all(dir.as_path()) + .expect("expected permissions to create themes folder"); + } + dir + } + + pub fn config_file_path() -> PathBuf { + Self::config_dir().join(environment::CONFIG_FILE_NAME) + } + + pub async fn load() -> Result<Self, Error> { + use tokio::fs; + + #[derive(Deserialize)] + pub struct Configuration { + #[serde(default)] + pub theme: String, + pub last_project: Option<PathBuf>, + } + + let path = Self::config_file_path(); + if !path.try_exists()? { + return Err(Error::ConfigMissing); + } + + let content = fs::read_to_string(path).await?; + + let Configuration { + theme, + last_project, + } = toml::from_str(content.as_ref())?; + + let theme = Self::load_theme(theme).await.unwrap_or_default(); + + Ok(Self { + theme, + last_project, + }) + } + + pub async fn load_theme(theme_name: String) -> Result<Appearance, Error> { + use tokio::fs; + + let read_entry = |entry: fs::DirEntry| async move { + let content = fs::read_to_string(entry.path()).await.ok()?; + + let theme: Theme = toml::from_str(content.as_ref()).ok()?; + let name = entry.path().file_stem()?.to_string_lossy().to_string(); + + Some(theme.into_iced_theme(name)) + }; + + let mut all = iced::Theme::ALL.to_owned(); + let mut selected = iced::Theme::default(); + + if theme_index(&theme_name, iced::Theme::ALL).is_some() { + selected = theme_from_str(None, &theme_name); + } + + let mut stream = + ReadDirStream::new(fs::read_dir(Self::themes_dir()).await?); + while let Some(entry) = stream.next().await { + let Ok(entry) = entry else { + continue; + }; + + let Some(file_name) = entry.file_name().to_str().map(String::from) + else { + continue; + }; + + if let Some(file_name) = file_name.strip_suffix(".toml") { + if let Some(theme) = read_entry(entry).await { + if file_name == theme_name { + selected = theme.clone(); + } + all.push(theme); + } + } + } + + Ok(Appearance { + selected, + all: all.into(), + }) + } +} diff --git a/iced_builder/src/dialogs.rs b/src/dialogs.rs index 047ffd2..2d916b1 100644 --- a/iced_builder/src/dialogs.rs +++ b/src/dialogs.rs @@ -9,13 +9,22 @@ pub fn error_dialog(description: impl Into<String>) { .show(); } -pub fn unsaved_changes_dialog( - description: impl Into<String>, -) -> MessageDialogResult { - MessageDialog::new() +pub fn warning_dialog(description: impl Into<String>) { + let _ = MessageDialog::new() + .set_level(MessageLevel::Warning) + .set_buttons(MessageButtons::Ok) + .set_title("Heads up!") + .set_description(description) + .show(); +} + +pub fn unsaved_changes_dialog(description: impl Into<String>) -> bool { + let result = MessageDialog::new() .set_level(MessageLevel::Warning) .set_buttons(MessageButtons::OkCancel) .set_title("Unsaved changes") .set_description(description) - .show() + .show(); + + matches!(result, MessageDialogResult::Ok) } diff --git a/src/environment.rs b/src/environment.rs new file mode 100644 index 0000000..3ecb790 --- /dev/null +++ b/src/environment.rs @@ -0,0 +1,43 @@ +use std::env; +use std::path::PathBuf; + +pub const CONFIG_FILE_NAME: &str = "config.toml"; + +pub fn config_dir() -> PathBuf { + portable_dir().unwrap_or_else(platform_specific_config_dir) +} + +fn portable_dir() -> Option<PathBuf> { + let exe = env::current_exe().ok()?; + let dir = exe.parent()?; + + dir.join(CONFIG_FILE_NAME) + .is_file() + .then(|| dir.to_path_buf()) +} + +fn platform_specific_config_dir() -> PathBuf { + #[cfg(target_os = "macos")] + { + xdg_config_dir().unwrap_or_else(|| { + dirs_next::config_dir() + .expect("expected valid config dir") + .join("iced-builder") + }) + } + #[cfg(not(target_os = "macos"))] + { + dirs_next::config_dir() + .expect("expected valid config dir") + .join("iced-builder") + } +} + +#[cfg(target_os = "macos")] +fn xdg_config_dir() -> Option<PathBuf> { + let config_dir = xdg::BaseDirectories::with_prefix("iced-builder") + .ok() + .and_then(|xdg| xdg.find_config_file(CONFIG_FILE_NAME))?; + + config_dir.parent().map(|p| p.to_path_buf()) +} diff --git a/iced_builder/src/error.rs b/src/error.rs index 8876016..f4011bd 100644 --- a/iced_builder/src/error.rs +++ b/src/error.rs @@ -6,13 +6,18 @@ use thiserror::Error; #[derive(Debug, Clone, Error)] #[error(transparent)] pub enum Error { - IOError(Arc<io::Error>), - SerdeError(Arc<serde_json::Error>), - FormatError(Arc<rust_format::Error>), - #[error("The element tree contains no matching element")] + IO(Arc<io::Error>), + #[error("config does not exist")] + ConfigMissing, + #[error("JSON parsing error: {0}")] + SerdeJSON(Arc<serde_json::Error>), + #[error("TOML parsing error: {0}")] + SerdeTOML(#[from] toml::de::Error), + RustFmt(Arc<rust_format::Error>), + #[error("the element tree contains no matching element")] NonExistentElement, #[error( - "The file dialog has been closed without selecting a valid option" + "the file dialog has been closed without selecting a valid option" )] DialogClosed, #[error("{0}")] @@ -21,19 +26,19 @@ pub enum Error { impl From<io::Error> for Error { fn from(value: io::Error) -> Self { - Self::IOError(Arc::new(value)) + Self::IO(Arc::new(value)) } } impl From<serde_json::Error> for Error { fn from(value: serde_json::Error) -> Self { - Self::SerdeError(Arc::new(value)) + Self::SerdeJSON(Arc::new(value)) } } impl From<rust_format::Error> for Error { fn from(value: rust_format::Error) -> Self { - Self::FormatError(Arc::new(value)) + Self::RustFmt(Arc::new(value)) } } @@ -42,3 +47,9 @@ impl From<&str> for Error { Self::Other(value.to_owned()) } } + +impl From<String> for Error { + fn from(value: String) -> Self { + Self::Other(value) + } +} diff --git a/iced_builder/src/icon.rs b/src/icon.rs index f6760d5..f6760d5 100644 --- a/iced_builder/src/icon.rs +++ b/src/icon.rs diff --git a/iced_builder/src/main.rs b/src/main.rs index a041c6f..5b95b94 100644 --- a/iced_builder/src/main.rs +++ b/src/main.rs @@ -1,26 +1,47 @@ +#![feature(test)] +mod config; +mod dialogs; +mod environment; +mod error; +mod icon; +mod panes; +mod theme; +mod types; +mod widget; + use std::path::PathBuf; +use config::Config; +use dialogs::{error_dialog, unsaved_changes_dialog, warning_dialog}; +use error::Error; use iced::advanced::widget::Id; use iced::widget::pane_grid::{self, Pane, PaneGrid}; use iced::widget::{container, pick_list, row, text_editor, Column}; use iced::{clipboard, keyboard, Alignment, Element, Length, Task, Theme}; -use iced_anim::{Animation, Spring}; -use iced_builder::dialogs::{error_dialog, unsaved_changes_dialog}; -use iced_builder::icon; -use iced_builder::panes::{code_view, designer_view, element_list}; -use iced_builder::types::{ - Action, DesignerPage, ElementName, Message, Project, -}; -use rfd::MessageDialogResult; +use iced_anim::transition::Easing; +use iced_anim::{Animated, Animation}; +use panes::{code_view, designer_view, element_list}; +use tokio::runtime; +use types::{Action, DesignerPage, ElementName, Message, Project}; + +//pub(crate) type Result<T> = core::result::Result<T, Error>; + +fn main() -> Result<(), Box<dyn std::error::Error>> { + let config_load = { + let rt = runtime::Builder::new_current_thread() + .enable_all() + .build()?; -const THEMES: &'static [Theme] = &[Theme::SolarizedDark, Theme::SolarizedLight]; + rt.block_on(Config::load()) + }; -fn main() -> iced::Result { iced::application(App::title, App::update, App::view) .font(icon::FONT) .theme(|state| state.theme.value().clone()) .subscription(App::subscription) - .run_with(App::new) + .run_with(move || App::new(config_load))?; + + Ok(()) } struct App { @@ -28,7 +49,8 @@ struct App { is_loading: bool, project_path: Option<PathBuf>, project: Project, - theme: Spring<Theme>, + config: Config, + theme: Animated<Theme>, pane_state: pane_grid::State<Panes>, focus: Option<Pane>, designer_page: DesignerPage, @@ -43,7 +65,7 @@ enum Panes { } impl App { - fn new() -> (Self, Task<Message>) { + fn new(config_load: Result<Config, Error>) -> (Self, Task<Message>) { let state = pane_grid::State::with_configuration( pane_grid::Configuration::Split { axis: pane_grid::Axis::Vertical, @@ -52,25 +74,46 @@ impl App { b: Box::new(pane_grid::Configuration::Pane(Panes::ElementList)), }, ); + + let config = config_load.unwrap_or_default(); + let theme = config.selected_theme(); + + let mut task = Task::none(); + + if let Some(path) = config.last_project.clone() { + if path.exists() && path.is_file() { + task = Task::perform( + Project::from_path(path, config.clone()), + Message::FileOpened, + ); + } else { + warning_dialog(format!( + "The file {} does not exist, or isn't a file.", + path.to_string_lossy() + )); + } + } + ( Self { is_dirty: false, is_loading: false, project_path: None, project: Project::new(), - theme: Spring::new(Theme::SolarizedDark), + config, + theme: Animated::new(theme, Easing::EASE_IN), pane_state: state, focus: None, designer_page: DesignerPage::DesignerView, element_list: ElementName::ALL, editor_content: text_editor::Content::new(), }, - Task::none(), + task, ) } fn title(&self) -> String { - let saved_state = if !self.is_dirty { "" } else { " *" }; + let saved_state = if self.is_dirty { " *" } else { "" }; let project_name = match &self.project.title { Some(n) => { @@ -83,7 +126,7 @@ impl App { } ) } - None => "".to_owned(), + None => String::new(), }; format!("iced Builder{project_name}{saved_state}") @@ -104,7 +147,7 @@ impl App { } } Message::RefreshEditorContent => { - match self.project.clone().app_code() { + match self.project.app_code(&self.config) { Ok(code) => { self.editor_content = text_editor::Content::with_text(&code); @@ -119,23 +162,19 @@ impl App { None, None, ) - .into() } Message::HandleNew(name, zones) => { let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect(); - if ids.len() > 0 { - let action = Action::new( - ids, - &mut self.project.element_tree.clone(), - None, - ); + if !ids.is_empty() { + let eltree_clone = self.project.element_tree.clone(); + let action = Action::new(&ids, &eltree_clone, None); let result = name.handle_action( self.project.element_tree.as_mut(), action, ); match result { Ok(Some(ref element)) => { - self.project.element_tree = Some(element.clone()) + self.project.element_tree = Some(element.clone()); } Err(error) => error_dialog(error.to_string()), _ => {} @@ -152,14 +191,14 @@ impl App { None, None, ) - .into() } Message::HandleMove(element, zones) => { let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect(); - if ids.len() > 0 { + if !ids.is_empty() { + let eltree_clone = self.project.element_tree.clone(); let action = Action::new( - ids, - &mut self.project.element_tree.clone(), + &ids, + &eltree_clone, Some(element.get_id()), ); let result = element.handle_action( @@ -193,13 +232,11 @@ impl App { self.project = Project::new(); self.project_path = None; self.editor_content = text_editor::Content::new(); - } else { - if let MessageDialogResult::Ok = unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and create a new project?") { + } else if unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and create a new project?") { self.is_dirty = false; self.project = Project::new(); self.project_path = None; self.editor_content = text_editor::Content::new(); - } } } } @@ -209,15 +246,13 @@ impl App { self.is_loading = true; return Task::perform( - Project::from_path(), + Project::from_file(self.config.clone()), Message::FileOpened, ); - } else { - if let MessageDialogResult::Ok = unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and open another project?") { + } else if unsaved_changes_dialog("You have unsaved changes. Do you wish to discard these and open another project?") { self.is_dirty = false; self.is_loading = true; - return Task::perform(Project::from_path(), Message::FileOpened); - } + return Task::perform(Project::from_file(self.config.clone()), Message::FileOpened); } } } @@ -227,11 +262,12 @@ impl App { match result { Ok((path, project)) => { - self.project = project.clone(); + self.project = project; self.project_path = Some(path); self.editor_content = text_editor::Content::with_text( - &project - .app_code() + &self + .project + .app_code(&self.config) .unwrap_or_else(|err| err.to_string()), ); } @@ -299,7 +335,7 @@ impl App { fn view(&self) -> Element<'_, Message> { let header = row![pick_list( - THEMES, + self.config.theme.all.clone(), Some(self.theme.target().clone()), |theme| { Message::ToggleTheme(theme.into()) } )] @@ -311,12 +347,12 @@ impl App { Panes::Designer => match &self.designer_page { DesignerPage::DesignerView => designer_view::view( &self.project.element_tree, - self.project.get_theme(), + self.project.get_theme(&self.config), is_focused, ), DesignerPage::CodeView => code_view::view( &self.editor_content, - self.theme.value().clone(), + self.theme.target().clone(), is_focused, ), }, diff --git a/iced_builder/src/panes.rs b/src/panes.rs index 387662a..387662a 100644 --- a/iced_builder/src/panes.rs +++ b/src/panes.rs diff --git a/iced_builder/src/panes/code_view.rs b/src/panes/code_view.rs index fe7801c..f545157 100644 --- a/iced_builder/src/panes/code_view.rs +++ b/src/panes/code_view.rs @@ -1,15 +1,15 @@ use iced::widget::{button, pane_grid, row, text, text_editor, Space}; -use iced::{Alignment, Length, Theme}; +use iced::{Alignment, Font, Length, Theme}; use super::style; use crate::icon::copy; use crate::types::{DesignerPage, Message}; use crate::widget::tip; -pub fn view<'a>( - editor_content: &'a text_editor::Content, +pub fn view( + editor_content: &text_editor::Content, theme: Theme, is_focused: bool, -) -> pane_grid::Content<'a, Message> { +) -> pane_grid::Content<'_, Message> { let title = row![ text("Generated Code"), Space::with_width(Length::Fill), @@ -36,6 +36,7 @@ pub fn view<'a>( } else { highlighter::Theme::InspiredGitHub }, + .font(Font::MONOSPACE) ) .height(Length::Fill) .padding(20), diff --git a/iced_builder/src/panes/designer_view.rs b/src/panes/designer_view.rs index 76456db..76456db 100644 --- a/iced_builder/src/panes/designer_view.rs +++ b/src/panes/designer_view.rs diff --git a/iced_builder/src/panes/element_list.rs b/src/panes/element_list.rs index 74188af..8a1c6eb 100644 --- a/iced_builder/src/panes/element_list.rs +++ b/src/panes/element_list.rs @@ -5,7 +5,7 @@ use iced_drop::droppable; use super::style; use crate::types::{ElementName, Message}; -fn items_list_view<'a>(items: &'a [ElementName]) -> Element<'a, Message> { +fn items_list_view(items: &[ElementName]) -> Element<'_, Message> { let mut column = Column::new() .spacing(20) .align_x(Alignment::Center) @@ -26,10 +26,10 @@ fn items_list_view<'a>(items: &'a [ElementName]) -> Element<'a, Message> { .into() } -pub fn view<'a>( - element_list: &'a [ElementName], +pub fn view( + element_list: &[ElementName], is_focused: bool, -) -> pane_grid::Content<'a, Message> { +) -> pane_grid::Content<'_, Message> { let items_list = items_list_view(element_list); let content = column![items_list] .align_x(Alignment::Center) diff --git a/iced_builder/src/panes/style.rs b/src/panes/style.rs index 1eefb2d..1eefb2d 100644 --- a/iced_builder/src/panes/style.rs +++ b/src/panes/style.rs diff --git a/src/theme.rs b/src/theme.rs new file mode 100644 index 0000000..7d18aa9 --- /dev/null +++ b/src/theme.rs @@ -0,0 +1,381 @@ +use std::sync::Arc; + +use iced::theme::palette::Extended; +use iced::Color; + +use crate::config::Config; + +pub fn theme_index(theme_name: &str, slice: &[iced::Theme]) -> Option<usize> { + slice + .iter() + .position(|theme| theme.to_string() == theme_name) +} + +pub fn theme_from_str( + config: Option<&Config>, + theme_name: &str, +) -> iced::Theme { + match theme_name { + "Light" => iced::Theme::Light, + "Dark" => iced::Theme::Dark, + "Dracula" => iced::Theme::Dracula, + "Nord" => iced::Theme::Nord, + "Solarized Light" => iced::Theme::SolarizedLight, + "Solarized Dark" => iced::Theme::SolarizedDark, + "Gruvbox Light" => iced::Theme::GruvboxLight, + "Gruvbox Dark" => iced::Theme::GruvboxDark, + "Catppuccin Latte" => iced::Theme::CatppuccinLatte, + "Catppuccin Frappé" => iced::Theme::CatppuccinFrappe, + "Catppuccin Macchiato" => iced::Theme::CatppuccinMacchiato, + "Catppuccin Mocha" => iced::Theme::CatppuccinMocha, + "Tokyo Night" => iced::Theme::TokyoNight, + "Tokyo Night Storm" => iced::Theme::TokyoNightStorm, + "Tokyo Night Light" => iced::Theme::TokyoNightLight, + "Kanagawa Wave" => iced::Theme::KanagawaWave, + "Kanagawa Dragon" => iced::Theme::KanagawaDragon, + "Kanagawa Lotus" => iced::Theme::KanagawaLotus, + "Moonfly" => iced::Theme::Moonfly, + "Nightfly" => iced::Theme::Nightfly, + "Oxocarbon" => iced::Theme::Oxocarbon, + "Ferra" => iced::Theme::Ferra, + _ => { + if let Some(config) = config { + if theme_name == config.theme.selected.to_string() { + config.theme.selected.clone() + } else if let Some(index) = + theme_index(theme_name, &config.theme.all) + { + config.theme.all[index].clone() + } else { + iced::Theme::default() + } + } else { + iced::Theme::default() + } + } + } +} + +fn palette_to_string(palette: &iced::theme::Palette) -> String { + format!( + r#"Palette {{ + background: color!(0x{}), + text: color!(0x{}), + primary: color!(0x{}), + success: color!(0x{}), + danger: color!(0x{}), + }}"#, + color_to_hex(palette.background), + color_to_hex(palette.text), + color_to_hex(palette.primary), + color_to_hex(palette.success), + color_to_hex(palette.danger), + ) +} + +fn extended_to_string(extended: &Extended) -> String { + format!( + r#" +Extended{{background:Background{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},primary:Primary{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},secondary:Secondary{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},success:Success{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},danger:Danger{{base:Pair{{color:color!(0x{}),text:color!(0x{}),}},weak:Pair{{color:color!(0x{}),text:color!(0x{}),}},strong:Pair{{color:color!(0x{}),text:color!(0x{}),}},}},is_dark:true,}}"#, + color_to_hex(extended.background.base.color), + color_to_hex(extended.background.base.text), + color_to_hex(extended.background.weak.color), + color_to_hex(extended.background.weak.text), + color_to_hex(extended.background.strong.color), + color_to_hex(extended.background.strong.text), + color_to_hex(extended.primary.base.color), + color_to_hex(extended.primary.base.text), + color_to_hex(extended.primary.weak.color), + color_to_hex(extended.primary.weak.text), + color_to_hex(extended.primary.strong.color), + color_to_hex(extended.primary.strong.text), + color_to_hex(extended.secondary.base.color), + color_to_hex(extended.secondary.base.text), + color_to_hex(extended.secondary.weak.color), + color_to_hex(extended.secondary.weak.text), + color_to_hex(extended.secondary.strong.color), + color_to_hex(extended.secondary.strong.text), + color_to_hex(extended.success.base.color), + color_to_hex(extended.success.base.text), + color_to_hex(extended.success.weak.color), + color_to_hex(extended.success.weak.text), + color_to_hex(extended.success.strong.color), + color_to_hex(extended.success.strong.text), + color_to_hex(extended.danger.base.color), + color_to_hex(extended.danger.base.text), + color_to_hex(extended.danger.weak.color), + color_to_hex(extended.danger.weak.text), + color_to_hex(extended.danger.strong.color), + color_to_hex(extended.danger.strong.text), + ) +} + +pub fn theme_to_string(theme: &iced::Theme) -> String { + let palette = theme.palette(); + let extended = theme.extended_palette(); + + let generated_extended = Extended::generate(palette); + + if &generated_extended == extended { + format!( + r#"custom( + "{}".to_string(), + {} + )"#, + theme, + palette_to_string(&palette) + ) + } else { + format!( + r#"custom_with_fn( + "{}".to_string(), + {}, + |_| {} + )"#, + theme, + palette_to_string(&palette), + extended_to_string(extended) + ) + } +} + +fn color_to_hex(color: Color) -> String { + use std::fmt::Write; + + let mut hex = String::with_capacity(12); + + let [r, g, b, a] = color.into_rgba8(); + + let _ = write!(&mut hex, "{:02X}", r); + let _ = write!(&mut hex, "{:02X}", g); + let _ = write!(&mut hex, "{:02X}", b); + + if a < u8::MAX { + let _ = write!(&mut hex, ", {:.2}", a as f32 / 255.0); + } + + hex +} + +#[derive(Debug, Clone)] +pub struct Appearance { + pub selected: iced::Theme, + pub all: Arc<[iced::Theme]>, +} + +impl Default for Appearance { + fn default() -> Self { + Self { + selected: iced::Theme::default(), + all: iced::Theme::ALL.into(), + } + } +} + +#[derive(Debug, Default, serde::Deserialize)] +pub struct Theme { + palette: ThemePalette, + is_dark: Option<bool>, + #[serde(flatten)] + extended: Option<ExtendedThemePalette>, +} + +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ThemePalette { + #[serde(with = "color_serde")] + background: Color, + #[serde(with = "color_serde")] + text: Color, + #[serde(with = "color_serde")] + primary: Color, + #[serde(with = "color_serde")] + success: Color, + #[serde(with = "color_serde")] + danger: Color, +} + +impl Theme { + pub fn into_iced_theme(self, name: String) -> iced::Theme { + iced::Theme::custom_with_fn(name, self.palette.clone().into(), |_| { + self.into() + }) + } +} + +impl Default for ThemePalette { + fn default() -> Self { + let palette = iced::Theme::default().palette(); + Self { + background: palette.background, + text: palette.text, + primary: palette.primary, + success: palette.success, + danger: palette.danger, + } + } +} + +impl From<ThemePalette> for iced::theme::Palette { + fn from(palette: ThemePalette) -> Self { + iced::theme::Palette { + background: palette.background, + text: palette.text, + primary: palette.primary, + success: palette.success, + danger: palette.danger, + } + } +} + +impl From<Theme> for Extended { + fn from(theme: Theme) -> Self { + let mut extended = Extended::generate(theme.palette.into()); + + if let Some(is_dark) = theme.is_dark { + extended.is_dark = is_dark; + } + + if let Some(extended_palette) = theme.extended { + if let Some(background) = extended_palette.background { + if let Some(base) = background.base { + extended.background.base = base.into(); + } + if let Some(weak) = background.weak { + extended.background.weak = weak.into(); + } + if let Some(strong) = background.strong { + extended.background.strong = strong.into(); + } + } + + // Handle primary + if let Some(primary) = extended_palette.primary { + if let Some(base) = primary.base { + extended.primary.base = base.into(); + } + if let Some(weak) = primary.weak { + extended.primary.weak = weak.into(); + } + if let Some(strong) = primary.strong { + extended.primary.strong = strong.into(); + } + } + + // Handle secondary + if let Some(secondary) = extended_palette.secondary { + if let Some(base) = secondary.base { + extended.secondary.base = base.into(); + } + if let Some(weak) = secondary.weak { + extended.secondary.weak = weak.into(); + } + if let Some(strong) = secondary.strong { + extended.secondary.strong = strong.into(); + } + } + + // Handle success + if let Some(success) = extended_palette.success { + if let Some(base) = success.base { + extended.success.base = base.into(); + } + if let Some(weak) = success.weak { + extended.success.weak = weak.into(); + } + if let Some(strong) = success.strong { + extended.success.strong = strong.into(); + } + } + + // Handle danger + if let Some(danger) = extended_palette.danger { + if let Some(base) = danger.base { + extended.danger.base = base.into(); + } + if let Some(weak) = danger.weak { + extended.danger.weak = weak.into(); + } + if let Some(strong) = danger.strong { + extended.danger.strong = strong.into(); + } + } + } + + extended + } +} + +#[derive(Debug, Default, serde::Deserialize)] +struct ExtendedThemePalette { + background: Option<ThemeBackground>, + primary: Option<ThemePrimary>, + secondary: Option<ThemeSecondary>, + success: Option<ThemeSuccess>, + danger: Option<ThemeDanger>, +} + +#[derive(Debug, Default, serde::Deserialize)] +struct ThemeBackground { + base: Option<ThemePair>, + weak: Option<ThemePair>, + strong: Option<ThemePair>, +} + +#[derive(Debug, Default, serde::Deserialize)] +struct ThemePrimary { + base: Option<ThemePair>, + weak: Option<ThemePair>, + strong: Option<ThemePair>, +} + +#[derive(Debug, Default, serde::Deserialize)] +struct ThemeSecondary { + base: Option<ThemePair>, + weak: Option<ThemePair>, + strong: Option<ThemePair>, +} + +#[derive(Debug, Default, serde::Deserialize)] +struct ThemeSuccess { + base: Option<ThemePair>, + weak: Option<ThemePair>, + strong: Option<ThemePair>, +} + +#[derive(Debug, Default, serde::Deserialize)] +struct ThemeDanger { + base: Option<ThemePair>, + weak: Option<ThemePair>, + strong: Option<ThemePair>, +} + +#[derive(Debug, Default, serde::Deserialize)] +struct ThemePair { + #[serde(with = "color_serde")] + color: Color, + #[serde(with = "color_serde")] + text: Color, +} + +impl From<ThemePair> for iced::theme::palette::Pair { + fn from(pair: ThemePair) -> Self { + Self { + color: pair.color, + text: pair.text, + } + } +} + +mod color_serde { + use iced::Color; + use serde::{Deserialize, Deserializer}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result<Color, D::Error> + where + D: Deserializer<'de>, + { + Ok(String::deserialize(deserializer) + .map(|hex| Color::parse(&hex))? + .unwrap_or(Color::TRANSPARENT)) + } +} diff --git a/iced_builder/src/types.rs b/src/types.rs index 161b5e1..ac9d039 100644 --- a/iced_builder/src/types.rs +++ b/src/types.rs @@ -7,15 +7,15 @@ use std::path::PathBuf; pub use element_name::ElementName; use iced::widget::{pane_grid, text_editor}; use iced::Theme; -use iced_anim::SpringEvent; +use iced_anim::Event; pub use project::Project; pub use rendered_element::*; -use crate::Result; +use crate::Error; #[derive(Debug, Clone)] pub enum Message { - ToggleTheme(SpringEvent<Theme>), + ToggleTheme(Event<Theme>), CopyCode, SwitchPage(DesignerPage), EditorAction(text_editor::Action), @@ -35,10 +35,10 @@ pub enum Message { PaneDragged(pane_grid::DragEvent), NewFile, OpenFile, - FileOpened(Result<(PathBuf, Project)>), + FileOpened(Result<(PathBuf, Project), Error>), SaveFile, SaveFileAs, - FileSaved(Result<PathBuf>), + FileSaved(Result<PathBuf, Error>), } #[derive(Debug, Clone)] diff --git a/iced_builder/src/types/element_name.rs b/src/types/element_name.rs index e172227..2687673 100644 --- a/iced_builder/src/types/element_name.rs +++ b/src/types/element_name.rs @@ -3,13 +3,13 @@ use serde::{Deserialize, Serialize}; use super::rendered_element::{ button, column, container, image, row, svg, text, Action, RenderedElement, }; -use crate::{Error, Result}; +use crate::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ElementName { Text(String), Button(String), - SVG(String), + Svg(String), Image(String), Container, Row, @@ -20,7 +20,7 @@ impl ElementName { pub const ALL: &'static [Self; 7] = &[ Self::Text(String::new()), Self::Button(String::new()), - Self::SVG(String::new()), + Self::Svg(String::new()), Self::Image(String::new()), Self::Container, Self::Row, @@ -31,23 +31,22 @@ impl ElementName { &self, element_tree: Option<&mut RenderedElement>, action: Action, - ) -> Result<Option<RenderedElement>> { + ) -> Result<Option<RenderedElement>, Error> { let element = match self { Self::Text(_) => text(""), Self::Button(_) => button(""), - Self::SVG(_) => svg(""), + Self::Svg(_) => svg(""), Self::Image(_) => image(""), Self::Container => container(None), Self::Row => row(None), Self::Column => column(None), }; match action { - Action::Stop => Ok(None), - Action::Drop => Ok(None), + Action::Stop | Action::Drop => Ok(None), Action::AddNew => Ok(Some(element)), Action::PushFront(id) => { element_tree - .ok_or("The action was of kind `PushFront`, but no element tree was provided.")? + .ok_or("the action was of kind `PushFront`, but no element tree was provided.")? .find_by_id(id) .ok_or(Error::NonExistentElement)? .push_front(&element); @@ -56,7 +55,7 @@ impl ElementName { Action::InsertAfter(parent_id, child_id) => { element_tree .ok_or( - "The action was of kind `InsertAfter`, but no element tree was provided.", + "the action was of kind `InsertAfter`, but no element tree was provided.", )? .find_by_id(parent_id) .ok_or(Error::NonExistentElement)? @@ -75,7 +74,7 @@ impl std::fmt::Display for ElementName { match self { Self::Text(_) => "Text", Self::Button(_) => "Button", - Self::SVG(_) => "SVG", + Self::Svg(_) => "SVG", Self::Image(_) => "Image", Self::Container => "Container", Self::Row => "Row", diff --git a/iced_builder/src/types/project.rs b/src/types/project.rs index f4dbcc4..27c576b 100644 --- a/iced_builder/src/types/project.rs +++ b/src/types/project.rs @@ -1,17 +1,29 @@ use std::path::{Path, PathBuf}; +extern crate fxhash; +use fxhash::FxHashMap; use iced::Theme; -use rust_format::{Config, Edition, Formatter, RustFmt}; +use rust_format::{Edition, Formatter, RustFmt}; use serde::{Deserialize, Serialize}; use super::rendered_element::RenderedElement; -use crate::{Error, Result}; +use crate::config::Config; +use crate::theme::{theme_from_str, theme_index, theme_to_string}; +use crate::Error; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Project { pub title: Option<String>, pub theme: Option<String>, pub element_tree: Option<RenderedElement>, + #[serde(skip)] + theme_cache: FxHashMap<String, String>, +} + +impl Default for Project { + fn default() -> Self { + Self::new() + } } impl Project { @@ -20,41 +32,43 @@ impl Project { title: None, theme: None, element_tree: None, + theme_cache: FxHashMap::default(), } } - pub fn get_theme(&self) -> Theme { + pub fn get_theme(&self, config: &Config) -> Theme { match &self.theme { - Some(theme) => match theme.as_str() { - "Light" => Theme::Light, - "Dark" => Theme::Dark, - "Dracula" => Theme::Dracula, - "Nord" => Theme::Nord, - "Solarized Light" => Theme::SolarizedLight, - "Solarized Dark" => Theme::SolarizedDark, - "Gruvbox Light" => Theme::GruvboxLight, - "Gruvbox Dark" => Theme::GruvboxDark, - "Catppuccin Latte" => Theme::CatppuccinLatte, - "Catppuccin Frappé" => Theme::CatppuccinFrappe, - "Catppuccin Macchiato" => Theme::CatppuccinMacchiato, - "Catppuccin Mocha" => Theme::CatppuccinMocha, - "Tokyo Night" => Theme::TokyoNight, - "Tokyo Night Storm" => Theme::TokyoNightStorm, - "Tokyo Night Light" => Theme::TokyoNightLight, - "Kanagawa Wave" => Theme::KanagawaWave, - "Kanagawa Dragon" => Theme::KanagawaDragon, - "Kanagawa Lotus" => Theme::KanagawaLotus, - "Moonfly" => Theme::Moonfly, - "Nightfly" => Theme::Nightfly, - "Oxocarbon" => Theme::Oxocarbon, - "Ferra" => Theme::Ferra, - _ => Theme::Dark, - }, - None => Theme::Dark, + Some(theme) => theme_from_str(Some(config), theme), + None => Theme::default(), + } + } + + fn theme_code(&mut self, theme: &Theme) -> String { + let theme_name = theme.to_string(); + if theme_index(&theme_name, Theme::ALL).is_none() { + (*self + .theme_cache + .entry(theme_name) + .or_insert(theme_to_string(theme))) + .to_string() + } else { + theme_name.replace(" ", "") } } - pub async fn from_path() -> Result<(PathBuf, Self)> { + pub async fn from_path( + path: PathBuf, + config: Config, + ) -> Result<(PathBuf, Self), Error> { + let contents = tokio::fs::read_to_string(&path).await?; + let mut project: Self = serde_json::from_str(&contents)?; + + let _ = project.theme_code(&project.get_theme(&config)); + + Ok((path, project)) + } + + pub async fn from_file(config: Config) -> Result<(PathBuf, Self), Error> { let picked_file = rfd::AsyncFileDialog::new() .set_title("Open a JSON file...") .add_filter("*.json, *.JSON", &["json", "JSON"]) @@ -64,13 +78,13 @@ impl Project { let path = picked_file.path().to_owned(); - let contents = tokio::fs::read_to_string(&path).await?; - let element: Self = serde_json::from_str(&contents)?; - - Ok((path, element)) + Self::from_path(path, config).await } - pub async fn write_to_file(self, path: Option<PathBuf>) -> Result<PathBuf> { + pub async fn write_to_file( + self, + path: Option<PathBuf>, + ) -> Result<PathBuf, Error> { let path = if let Some(p) = path { p } else { @@ -91,16 +105,25 @@ impl Project { Ok(path) } - pub fn app_code(&self) -> Result<String> { + pub fn app_code(&mut self, config: &Config) -> Result<String, Error> { match self.element_tree { Some(ref element_tree) => { let (imports, view) = element_tree.codegen(); - let mut app_code = - format!("use iced::{{widget::{{{imports}}},Element}};"); - - app_code = format!( + let theme = self.get_theme(config); + let theme_code = self.theme_code(&theme); + let mut theme_imports = ""; + if theme_index(&theme.to_string(), Theme::ALL).is_none() { + if theme_code.contains("Extended") { + theme_imports = "use iced::{{color,theme::{{Palette,palette::{{Extended,Background,Primary,Secondary,Success,Danger,Pair}}}}}};\n"; + } else { + theme_imports = "use iced::{{color,theme::Palette}};\n"; + } + } + + let app_code = format!( r#"// Automatically generated by iced Builder - {app_code} + use iced::{{widget::{{{imports}}},Element}}; + {theme_imports} fn main() -> iced::Result {{ iced::application("{}", State::update, State::view).theme(State::theme).run() @@ -127,9 +150,9 @@ impl Project { Some(ref t) => t, None => "New app", }, - self.get_theme().to_string().replace(" ", "") + theme_code ); - let config = Config::new_str() + let config = rust_format::Config::new_str() .edition(Edition::Rust2021) .option("trailing_comma", "Never") .option("imports_granularity", "Crate"); diff --git a/iced_builder/src/types/rendered_element.rs b/src/types/rendered_element.rs index d4d1a6c..b001556 100755 --- a/iced_builder/src/types/rendered_element.rs +++ b/src/types/rendered_element.rs @@ -1,17 +1,18 @@ use std::collections::BTreeMap; -use blob_uuid::random_blob; use iced::advanced::widget::Id; use iced::{widget, Element, Length}; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use super::ElementName; use crate::types::Message; -use crate::Result; +use crate::Error; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RenderedElement { - id: String, + #[serde(skip, default = "Uuid::new_v4")] + id: Uuid, child_elements: Option<Vec<RenderedElement>>, name: ElementName, options: BTreeMap<String, Option<String>>, @@ -20,7 +21,7 @@ pub struct RenderedElement { impl RenderedElement { fn new(name: ElementName) -> Self { Self { - id: random_blob(), + id: Uuid::new_v4(), child_elements: None, name, options: BTreeMap::new(), @@ -29,7 +30,7 @@ impl RenderedElement { fn with(name: ElementName, child_elements: Vec<RenderedElement>) -> Self { Self { - id: random_blob(), + id: Uuid::new_v4(), child_elements: Some(child_elements), name, options: BTreeMap::new(), @@ -37,22 +38,22 @@ impl RenderedElement { } pub fn get_id(&self) -> Id { - Id::new(self.id.clone()) + Id::new(self.id.to_string()) } - pub fn find_by_id(&mut self, id: Id) -> Option<&mut Self> { - if self.get_id() == id.clone() { - return Some(self); + pub fn find_by_id(&mut self, id: &Id) -> Option<&mut Self> { + if &self.get_id() == id { + Some(self) } else if let Some(child_elements) = self.child_elements.as_mut() { for element in child_elements { - let element = element.find_by_id(id.clone()); + let element = element.find_by_id(id); if element.is_some() { return element; } } - return None; + None } else { - return None; + None } } @@ -66,24 +67,21 @@ impl RenderedElement { if self .child_elements .clone() - .unwrap_or(vec![]) + .unwrap_or_default() .contains(child_element) { return Some(self); - } else { - if let Some(child_elements) = self.child_elements.as_mut() { - for element in child_elements { - let element = element.find_parent(child_element); - if element.is_some() { - return element; - } + } + if let Some(child_elements) = self.child_elements.as_mut() { + for element in child_elements { + let element = element.find_parent(child_element); + if element.is_some() { + return element; } } - return None; } - } else { - return None; } + None } pub fn is_parent(&self) -> bool { @@ -111,10 +109,10 @@ impl RenderedElement { } } - pub fn insert_after(&mut self, id: Id, element: &RenderedElement) { + pub fn insert_after(&mut self, id: &Id, element: &RenderedElement) { if let Some(child_elements) = self.child_elements.as_mut() { if let Some(index) = - child_elements.iter().position(|x| x.get_id() == id) + child_elements.iter().position(|x| &x.get_id() == id) { child_elements.insert(index + 1, element.clone()); } else { @@ -127,7 +125,7 @@ impl RenderedElement { &self, element_tree: Option<&mut RenderedElement>, action: Action, - ) -> Result<()> { + ) -> Result<(), Error> { let element_tree = element_tree.unwrap(); match action { @@ -159,7 +157,7 @@ impl RenderedElement { } } - fn preset_options<'a>(mut self, options: &[&'a str]) -> Self { + fn preset_options(mut self, options: &[&str]) -> Self { for opt in options { let _ = self.options.insert(opt.to_string(), None); } @@ -174,12 +172,12 @@ impl RenderedElement { self } - pub fn as_element<'a>(self) -> Element<'a, Message> { + pub fn into_element<'a>(self) -> Element<'a, Message> { let mut children = widget::column![]; if let Some(els) = self.child_elements.clone() { for el in els { - children = children.push(el.clone().as_element()); + children = children.push(el.clone().into_element()); } } iced_drop::droppable( @@ -219,7 +217,7 @@ impl RenderedElement { for element in els { let (c_imports, children) = element.codegen(); imports = format!("{imports}{c_imports}"); - elements = format!("{elements}{},", children); + elements = format!("{elements}{children},"); } } @@ -262,7 +260,7 @@ impl RenderedElement { imports = format!("{imports}image,"); view = format!("{view}\nimage(\"{path}\"){options}"); } - ElementName::SVG(path) => { + ElementName::Svg(path) => { imports = format!("{imports}svg,"); view = format!("{view}\nsvg(\"{path}\"){options}"); } @@ -336,7 +334,7 @@ impl<'a> From<RenderedElement> for Element<'a, Message> { widget::button(widget::text(s)).into() } } - ElementName::SVG(p) => widget::svg(p).into(), + ElementName::Svg(p) => widget::svg(p).into(), ElementName::Image(p) => widget::image(p).into(), ElementName::Container => { widget::container(if child_elements.len() == 1 { @@ -347,13 +345,13 @@ impl<'a> From<RenderedElement> for Element<'a, Message> { .padding(20) .into() } - ElementName::Row => widget::Row::from_iter( - child_elements.into_iter().map(|el| el.into()), + ElementName::Row => widget::Row::from_vec( + child_elements.into_iter().map(Into::into).collect(), ) .padding(20) .into(), - ElementName::Column => widget::Column::from_iter( - child_elements.into_iter().map(|el| el.into()), + ElementName::Column => widget::Column::from_vec( + child_elements.into_iter().map(Into::into).collect(), ) .padding(20) .into(), @@ -369,18 +367,18 @@ impl<'a> From<RenderedElement> for Element<'a, Message> { } #[derive(Debug, Clone)] -pub enum Action { +pub enum Action<'a> { AddNew, - PushFront(Id), - InsertAfter(Id, Id), + PushFront(&'a Id), + InsertAfter(&'a Id, &'a Id), Drop, Stop, } -impl Action { +impl<'a> Action<'a> { pub fn new( - ids: Vec<Id>, - element_tree: &mut Option<RenderedElement>, + ids: &'a [Id], + element_tree: &'a Option<RenderedElement>, source_id: Option<Id>, ) -> Self { let mut action = Self::Stop; @@ -391,51 +389,39 @@ impl Action { action = Self::Drop; } } else { - let id: Id = match source_id { + let id: &Id = match source_id { Some(id) if ids.contains(&id) => { let element_id = - ids[ids.iter().position(|x| *x == id).unwrap()].clone(); - if ids.len() > 2 && ids[ids.clone().len() - 1] == element_id - { + &ids[ids.iter().position(|x| *x == id).unwrap()]; + if ids.len() > 2 && &ids[ids.len() - 1] == element_id { return Self::Stop; } element_id } - _ => ids.last().cloned().unwrap(), + _ => ids.last().unwrap(), }; - let element = element_tree - .as_mut() - .unwrap() - .find_by_id(id.clone()) - .unwrap(); - - // Element IS a parent but ISN'T a non-empty container - match element.is_parent() - && !(element.name == ElementName::Container - && !element.is_empty()) + let mut element_tree = element_tree.clone().unwrap(); + let element = element_tree.find_by_id(id).unwrap(); + + // Element is a parent and isn't a non-empty container + if (element.is_empty() || !(element.name == ElementName::Container)) + && element.is_parent() { - true => { - action = Self::PushFront(id); - } - false if ids.len() > 2 => { - let parent = element_tree - .as_mut() - .unwrap() - .find_by_id(ids[&ids.len() - 2].clone()) - .unwrap(); - - if parent.name == ElementName::Container - && parent.child_elements != Some(vec![]) - { - action = Self::Stop; - } else { - action = Self::InsertAfter( - ids[&ids.len() - 2].clone(), - ids[&ids.len() - 1].clone(), - ); - } + action = Self::PushFront(id); + } else if ids.len() > 2 { + let parent = + element_tree.find_by_id(&ids[ids.len() - 2]).unwrap(); + + if parent.name == ElementName::Container + && parent.child_elements != Some(vec![]) + { + action = Self::Stop; + } else { + action = Self::InsertAfter( + &ids[ids.len() - 2], + &ids[ids.len() - 1], + ); } - _ => {} } } action @@ -456,7 +442,7 @@ pub fn button(text: &str) -> RenderedElement { } pub fn svg(path: &str) -> RenderedElement { - RenderedElement::new(ElementName::SVG(path.to_owned())) + RenderedElement::new(ElementName::Svg(path.to_owned())) } pub fn image(path: &str) -> RenderedElement { diff --git a/iced_builder/src/widget.rs b/src/widget.rs index ed2073a..ed2073a 100644 --- a/iced_builder/src/widget.rs +++ b/src/widget.rs |
