From f5c35e48c480355036778d26aacde498e5c15e68 Mon Sep 17 00:00:00 2001 From: pml68 Date: Sun, 22 Sep 2024 23:53:02 +0200 Subject: feat: restructure project, start drag and drop --- .gitignore | 2 +- Cargo.lock | 328 ++++++++++--------- Cargo.toml | 22 +- fonts/icons.ttf | Bin 6352 -> 0 bytes iced_builder/Cargo.toml | 21 ++ iced_builder/fonts/icons.ttf | Bin 0 -> 6352 bytes iced_builder/src/codegen/mod.rs | 158 +++++++++ iced_builder/src/lib.rs | 0 iced_builder/src/main.rs | 326 +++++++++++++++++++ iced_builder/src/types/mod.rs | 54 ++++ iced_builder/src/types/rendered_element.rs | 47 +++ iced_drop/.gitignore | 3 + iced_drop/Cargo.toml | 8 + iced_drop/README.md | 73 +++++ iced_drop/src/lib.rs | 51 +++ iced_drop/src/widget.rs | 2 + iced_drop/src/widget/droppable.rs | 497 +++++++++++++++++++++++++++++ iced_drop/src/widget/operation.rs | 1 + iced_drop/src/widget/operation/drop.rs | 89 ++++++ src/codegen/mod.rs | 160 ---------- src/lib.rs | 0 src/main.rs | 301 ----------------- src/types/mod.rs | 25 -- src/types/rendered_element.rs | 47 --- 24 files changed, 1511 insertions(+), 704 deletions(-) delete mode 100644 fonts/icons.ttf create mode 100644 iced_builder/Cargo.toml create mode 100644 iced_builder/fonts/icons.ttf create mode 100644 iced_builder/src/codegen/mod.rs create mode 100644 iced_builder/src/lib.rs create mode 100644 iced_builder/src/main.rs create mode 100644 iced_builder/src/types/mod.rs create mode 100644 iced_builder/src/types/rendered_element.rs create mode 100644 iced_drop/.gitignore create mode 100644 iced_drop/Cargo.toml create mode 100644 iced_drop/README.md create mode 100644 iced_drop/src/lib.rs create mode 100644 iced_drop/src/widget.rs create mode 100644 iced_drop/src/widget/droppable.rs create mode 100644 iced_drop/src/widget/operation.rs create mode 100644 iced_drop/src/widget/operation/drop.rs delete mode 100644 src/codegen/mod.rs delete mode 100644 src/lib.rs delete mode 100644 src/main.rs delete mode 100644 src/types/mod.rs delete mode 100644 src/types/rendered_element.rs diff --git a/.gitignore b/.gitignore index 2f7896d..f4ceea7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -target/ +**/target/ diff --git a/Cargo.lock b/Cargo.lock index b519b92..d12997d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -144,17 +144,17 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide 0.8.0", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -275,9 +275,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" dependencies = [ "bytemuck_derive", ] @@ -290,7 +290,7 @@ checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -301,9 +301,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "calloop" @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.14" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", @@ -403,13 +403,13 @@ dependencies = [ [[package]] name = "clipboard_macos" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145a7f9e9b89453bc0a5e32d166456405d389cea5b578f57f1274b1397588a95" +checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" dependencies = [ - "objc", - "objc-foundation", - "objc_id", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", ] [[package]] @@ -507,6 +507,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -520,8 +530,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", "foreign-types", "libc", ] @@ -533,7 +556,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", "libc", ] @@ -720,9 +754,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.2.0" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" [[package]] name = "etagere" @@ -736,9 +770,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.10" +version = "0.22.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f0eb73b934648cd7a4a61f1b15391cd95dab0b4da6e2e66c2a072c144b4a20" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" dependencies = [ "num-traits", ] @@ -767,9 +801,9 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" @@ -782,9 +816,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -867,7 +901,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -933,7 +967,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1009,9 +1043,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "gl_generator" @@ -1194,17 +1228,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "iced-builder" -version = "0.1.0" -dependencies = [ - "iced", - "iced_aw", - "rust-format", - "tokio", - "unique_id", -] - [[package]] name = "iced_aw" version = "0.9.3" @@ -1215,6 +1238,18 @@ dependencies = [ "iced", ] +[[package]] +name = "iced_builder" +version = "0.1.0" +dependencies = [ + "iced", + "iced_aw", + "iced_drop", + "rust-format", + "tokio", + "unique_id", +] + [[package]] name = "iced_core" version = "0.12.3" @@ -1233,6 +1268,13 @@ dependencies = [ "xxhash-rust", ] +[[package]] +name = "iced_drop" +version = "0.1.0" +dependencies = [ + "iced", +] + [[package]] name = "iced_futures" version = "0.12.0" @@ -1426,9 +1468,9 @@ checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -1719,9 +1761,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -1734,7 +1776,7 @@ checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ "bitflags 2.6.0", "block", - "core-graphics-types", + "core-graphics-types 0.1.3", "foreign-types", "log", "objc", @@ -1860,7 +1902,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1873,17 +1915,6 @@ dependencies = [ "objc_exception", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -1970,7 +2001,6 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.6.0", "block2 0.5.1", - "dispatch", "libc", "objc2 0.5.2", ] @@ -2009,20 +2039,11 @@ dependencies = [ "cc", ] -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "object" -version = "0.36.3" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -2094,7 +2115,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -2140,7 +2161,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.4", "smallvec", "windows-targets 0.52.6", ] @@ -2187,7 +2208,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -2278,9 +2299,9 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] @@ -2326,9 +2347,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.34.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] @@ -2440,9 +2461,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] @@ -2478,9 +2499,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] @@ -2520,9 +2541,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -2599,7 +2620,7 @@ checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" dependencies = [ "ab_glyph", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "smithay-client-toolkit 0.18.1", "tiny-skia", ] @@ -2612,29 +2633,29 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -2715,7 +2736,7 @@ dependencies = [ "cursor-icon", "libc", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "rustix", "thiserror", "wayland-backend", @@ -2740,15 +2761,15 @@ dependencies = [ "cursor-icon", "libc", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "rustix", "thiserror", "wayland-backend", "wayland-client", "wayland-csd-frame", "wayland-cursor", - "wayland-protocols 0.32.3", - "wayland-protocols-wlr 0.3.3", + "wayland-protocols 0.32.4", + "wayland-protocols-wlr 0.3.4", "wayland-scanner", "xkeysym", ] @@ -2775,26 +2796,25 @@ dependencies = [ [[package]] name = "softbuffer" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d623bff5d06f60d738990980d782c8c866997d9194cfe79ecad00aa2f76826dd" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "as-raw-xcb-connection", "bytemuck", "cfg_aliases 0.2.1", - "core-graphics", + "core-graphics 0.24.0", "drm", "fastrand", "foreign-types", "js-sys", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "objc2 0.5.2", - "objc2-app-kit", "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall 0.5.3", + "redox_syscall 0.5.4", "rustix", "tiny-xlib", "wasm-bindgen", @@ -2802,7 +2822,7 @@ dependencies = [ "wayland-client", "wayland-sys", "web-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "x11rb", ] @@ -2879,9 +2899,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -2930,22 +2950,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -3062,9 +3082,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap", "toml_datetime", @@ -3090,7 +3110,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -3140,9 +3160,9 @@ checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-linebreak" @@ -3158,15 +3178,15 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-script" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-vo" @@ -3176,15 +3196,15 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unique_id" @@ -3311,7 +3331,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -3345,7 +3365,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3373,9 +3393,9 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", @@ -3387,9 +3407,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" +checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" dependencies = [ "bitflags 2.6.0", "rustix", @@ -3410,9 +3430,9 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" +checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" dependencies = [ "rustix", "wayland-client", @@ -3433,9 +3453,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.3" +version = "0.32.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" +checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -3471,33 +3491,33 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" +checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" dependencies = [ "bitflags 2.6.0", "wayland-backend", "wayland-client", - "wayland-protocols 0.32.3", + "wayland-protocols 0.32.4", "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml 0.34.0", + "quick-xml 0.36.2", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", "log", @@ -3595,7 +3615,7 @@ dependencies = [ "bitflags 2.6.0", "block", "cfg_aliases 0.1.1", - "core-graphics-types", + "core-graphics-types 0.1.3", "d3d12", "glow", "glutin_wgl_sys", @@ -3935,14 +3955,14 @@ dependencies = [ "bytemuck", "calloop 0.12.4", "cfg_aliases 0.1.1", - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.23.2", "cursor-icon", "icrate", "js-sys", "libc", "log", - "memmap2 0.9.4", + "memmap2 0.9.5", "ndk", "ndk-sys", "objc2 0.4.1", @@ -3972,9 +3992,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -4038,9 +4058,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601" +checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" [[package]] name = "xmlparser" @@ -4098,7 +4118,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 301ecfe..64f9037 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,6 @@ -[package] -name = "iced-builder" -description = "GUI builder for iced, built with iced." -version = "0.1.0" -edition = "2021" -authors = ["pml68 "] -repository = "https://github.com/pml68/iced-builder" -license = "GPL-3.0-or-later" -keywords = ["gui", "iced"] - -[dependencies] -iced = { version = "0.12.1", features = [ "image","svg","canvas","qr_code","advanced","tokio","highlighter"] } -iced_aw = { version = "0.9.3", default-features = false, features = ["menu","color_picker"] } -tokio = { version = "1.40.0", features = ["fs"] } -rust-format = "0.3.4" -unique_id = "0.1.5" +[workspace] +resolver = "2" +members = [ + "iced_drop", + "iced_builder" +] diff --git a/fonts/icons.ttf b/fonts/icons.ttf deleted file mode 100644 index 393c692..0000000 Binary files a/fonts/icons.ttf and /dev/null differ diff --git a/iced_builder/Cargo.toml b/iced_builder/Cargo.toml new file mode 100644 index 0000000..febff28 --- /dev/null +++ b/iced_builder/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "iced_builder" +description = "GUI builder for iced, built with iced." +version = "0.1.0" +edition = "2021" +authors = ["pml68 "] +repository = "https://github.com/pml68/iced-builder" +license = "GPL-3.0-or-later" +keywords = ["gui", "iced"] + +[dependencies] +iced = { version = "0.12.1", features = [ "image","svg","canvas","qr_code","advanced","tokio","highlighter"] } +iced_aw = { version = "0.9.3", default-features = false, features = ["menu","color_picker"] } +iced_drop = { path = "../iced_drop" } +tokio = { version = "1.40.0", features = ["fs"] } +rust-format = "0.3.4" +unique_id = "0.1.5" + +[[bin]] +name = "iced-builder" +path = "src/main.rs" diff --git a/iced_builder/fonts/icons.ttf b/iced_builder/fonts/icons.ttf new file mode 100644 index 0000000..393c692 Binary files /dev/null and b/iced_builder/fonts/icons.ttf differ diff --git a/iced_builder/src/codegen/mod.rs b/iced_builder/src/codegen/mod.rs new file mode 100644 index 0000000..88c0c61 --- /dev/null +++ b/iced_builder/src/codegen/mod.rs @@ -0,0 +1,158 @@ +use rust_format::{Config, Edition, Formatter, RustFmt}; + +use crate::types::{rendered_element::RenderedElement, ElementName}; + +impl RenderedElement { + fn props_codegen(&self) -> String { + let mut props_string = String::new(); + + for (k, v) in self.props.clone() { + props_string = format!("{props_string}.{k}({v})"); + } + + props_string + } + + fn codegen(&self) -> (String, String) { + let mut imports = String::new(); + let mut view = String::new(); + let props = self.props_codegen(); + + let mut elements = String::new(); + + match self.name { + ElementName::Column | ElementName::Row | ElementName::Container => { + for element in &self.child_elements { + let (c_imports, children) = element.codegen(); + imports = format!("{imports}{c_imports}"); + elements = format!("{elements}{},", children); + } + } + _ => {} + } + + match &self.name { + ElementName::Container => { + imports = format!("{imports}container,"); + view = if self.child_elements.len() < 2 { + format!("{view}\ncontainer({elements}){props}") + } else { + format!("{view}\ncontainer(){props}") + }; + } + ElementName::Row => { + imports = format!("{imports}row,"); + view = format!("{view}\nrow![{elements}]{props}"); + } + ElementName::Column => { + imports = format!("{imports}column,"); + view = format!("{view}\ncolumn![{elements}]{props}"); + } + ElementName::Text(string) => { + imports = format!("{imports}text,"); + view = format!( + "{view}\ntext(\"{}\"){props}", + if *string == String::new() { + "New Text" + } else { + string + } + ); + } + ElementName::Button(string) => { + imports = format!("{imports}button,"); + view = format!( + "{view}\nbutton(\"{}\"){props}", + if *string == String::new() { + "New Button" + } else { + string + } + ); + } + ElementName::Image(path) => { + imports = format!("{imports}image,"); + view = format!("{view}\nimage(\"{path}\"){props}"); + } + ElementName::SVG(path) => { + imports = format!("{imports}svg,"); + view = format!("{view}\nsvg(\"{path}\"){props}"); + } + } + + (imports, view) + } + + pub fn app_code( + &self, + title: &str, + theme: Option, + ) -> Result> { + let (imports, view) = self.codegen(); + let mut app_code = format!("use iced::{{widget::{{{imports}}},Sandbox,Settings,Element}};"); + + app_code = format!( + r#"{app_code} + + fn main() -> iced::Result {{ + App::run(Settings::default()) + }} + + struct App; + + impl Sandbox for App {{ + type Message = (); + + fn new() -> Self {{ + Self {{}} + }} + + fn title(&self) -> String {{ + "{title}".into() + }} + + fn theme(&self) -> iced::Theme {{ + iced::Theme::{} + }} + + fn update(&mut self, message: Self::Message) {{ + + }} + + fn view(&self) -> Element {{ + {view}.into() + }} + }}"#, + if let Some(c) = theme { + c.to_string().replace(' ', "") + } else { + "default()".to_owned() + } + ); + let config = Config::new_str() + .edition(Edition::Rust2021) + .option("trailing_comma", "Never") + .option("imports_granularity", "Crate"); + let rustfmt = RustFmt::from_config(config); + Ok(rustfmt.format_str(app_code)?) + } + + pub fn test() -> String { + let mut text1 = RenderedElement::new(ElementName::Text("wow")); + text1.set_property("height", "120.5"); + text1.set_property("width", "230"); + + let element = RenderedElement::new(ElementName::Container).push(RenderedElement::from_vec( + ElementName::Row, + vec![ + text1, + RenderedElement::new(ElementName::Text("heh")), + RenderedElement::new(ElementName::SVG( + "/mnt/drive_d/git/obs-website/src/lib/assets/bars-solid.svg", + )), + ], + )); + + element.app_code("new app", None).unwrap() + } +} diff --git a/iced_builder/src/lib.rs b/iced_builder/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/iced_builder/src/main.rs b/iced_builder/src/main.rs new file mode 100644 index 0000000..e966614 --- /dev/null +++ b/iced_builder/src/main.rs @@ -0,0 +1,326 @@ +mod codegen; +mod types; + +use iced::{ + clipboard, executor, + highlighter::{self, Highlighter}, + theme, + widget::{ + button, column, container, + pane_grid::{self, Pane, PaneGrid}, + row, text, text_editor, tooltip, Column, Space, + }, + Alignment, Application, Color, Command, Element, Font, Length, Settings, +}; +use iced_drop::droppable; +use types::{rendered_element::RenderedElement, DesignerPage, DesignerState}; + +fn main() -> iced::Result { + App::run(Settings { + fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()], + ..Settings::default() + }) +} + +struct App { + is_saved: bool, + current_project: Option, + dark_theme: bool, + pane_state: pane_grid::State, + focus: Option, + designer_state: DesignerState, + element_list: Vec, + editor_content: text_editor::Content, +} + +#[derive(Debug, Clone)] +enum Message { + ToggleTheme, + CopyCode, + Drop(types::ElementName, iced::Point, iced::Rectangle), + HandleZones( + types::ElementName, + Vec<(iced::advanced::widget::Id, iced::Rectangle)>, + ), + Resized(pane_grid::ResizeEvent), + Clicked(pane_grid::Pane), + PaneDragged(pane_grid::DragEvent), +} + +#[derive(Clone, Debug)] +enum Panes { + Designer, + ElementList, +} + +impl Application for App { + type Message = Message; + type Theme = theme::Theme; + type Executor = executor::Default; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command) { + let state = pane_grid::State::with_configuration(pane_grid::Configuration::Split { + axis: pane_grid::Axis::Vertical, + ratio: 0.8, + a: Box::new(pane_grid::Configuration::Pane(Panes::Designer)), + b: Box::new(pane_grid::Configuration::Pane(Panes::ElementList)), + }); + ( + Self { + is_saved: true, + current_project: None, + dark_theme: true, + pane_state: state, + focus: None, + designer_state: DesignerState { + designer_content: vec![], + designer_page: DesignerPage::Designer, + }, + element_list: types::ElementName::ALL.to_vec(), + editor_content: text_editor::Content::with_text(&RenderedElement::test()), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + let saved_state = if self.is_saved { "" } else { " *" }; + + let project_name = match &self.current_project { + Some(n) => format!(" - {n}"), + None => "".to_owned(), + }; + + format!("iced Builder{project_name}{saved_state}") + } + + fn theme(&self) -> iced::Theme { + if self.dark_theme { + theme::Theme::CatppuccinMocha + } else { + theme::Theme::CatppuccinLatte + } + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::ToggleTheme => self.dark_theme = !self.dark_theme, + Message::CopyCode => return clipboard::write(self.editor_content.text()), + Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { + self.pane_state.resize(split, ratio); + } + Message::Clicked(pane) => { + self.focus = Some(pane); + } + Message::Drop(name, point, _) => { + return iced_drop::zones_on_point( + move |zones| Message::HandleZones(name, zones), + point, + None, + None, + ) + .into() + } + Message::HandleZones(name, zones) => { + println!("{:?}\n{name}", zones); + } + Message::PaneDragged(pane_grid::DragEvent::Dropped { pane, target }) => { + self.pane_state.drop(pane, target); + } + Message::PaneDragged(_) => {} + } + + Command::none() + } + + fn view(&self) -> Element { + let header = row![button("Toggle Theme") + .on_press(Message::ToggleTheme) + .padding(5)] + .width(200); + let pane_grid = PaneGrid::new(&self.pane_state, |id, pane, _is_maximized| { + let is_focused = Some(id) == self.focus; + match pane { + Panes::Designer => match self.designer_state.designer_page { + DesignerPage::Designer => { + let content = container("") + .id(iced::widget::container::Id::new("drop_zone")) + .height(Length::Fill) + .width(Length::Fill); + let title = text("Designer").style(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }); + let title_bar = pane_grid::TitleBar::new(title) + .padding(10) + .style(style::title_bar); + pane_grid::Content::new(content) + .title_bar(title_bar) + .style(if is_focused { + style::pane_focused + } else { + style::pane_active + }) + } + DesignerPage::CodeView => { + let title = row![ + text("Generated Code").style(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }), + Space::with_width(Length::Fill), + tooltip( + button( + container( + text('\u{0e801}').font(Font::with_name("editor-icons")) + ) + .width(30) + .center_x() + ) + .on_press(Message::CopyCode), + "Copy code to clipboard", + tooltip::Position::Left + ) + ]; + let title_bar = pane_grid::TitleBar::new(title) + .padding(10) + .style(style::title_bar); + pane_grid::Content::new( + text_editor(&self.editor_content) + .highlight::( + highlighter::Settings { + theme: highlighter::Theme::Base16Mocha, + extension: "rs".to_string(), + }, + |highlight, _theme| highlight.to_format(), + ) + .height(Length::Fill) + .padding(20), + ) + .title_bar(title_bar) + .style(if is_focused { + style::pane_focused + } else { + style::pane_active + }) + } + }, + Panes::ElementList => { + let items_list = items_list_view(self.element_list.clone()); + let content = column![items_list] + .align_items(Alignment::Center) + .height(Length::Fill) + .width(Length::Fill); + let title = text("Element List").style(if is_focused { + PANE_ID_COLOR_FOCUSED + } else { + PANE_ID_COLOR_UNFOCUSED + }); + let title_bar = pane_grid::TitleBar::new(title) + .padding(10) + .style(style::title_bar); + pane_grid::Content::new(content) + .title_bar(title_bar) + .style(if is_focused { + style::pane_focused + } else { + style::pane_active + }) + } + } + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .on_resize(10, Message::Resized) + .on_click(Message::Clicked) + .on_drag(Message::PaneDragged); + + let content = Column::new() + .push(header) + .push(pane_grid) + .spacing(5) + .align_items(Alignment::Center) + .width(Length::Fill); + + container(content).height(Length::Fill).into() + } +} + +const fn from_grayscale(grayscale: f32) -> Color { + Color { + r: grayscale, + g: grayscale, + b: grayscale, + a: 1.0, + } +} + +// #ffffff +const PANE_ID_COLOR_FOCUSED: Color = from_grayscale(1.0); + +// #e8e8e8 +const PANE_ID_COLOR_UNFOCUSED: Color = from_grayscale(0xE8 as f32 / 255.0); + +fn items_list_view(items: Vec) -> Element<'static, Message> { + let mut column = Column::new() + .spacing(20) + .align_items(Alignment::Center) + .width(Length::Fill); + + for item in items { + let value = item.clone(); + column = column.push( + droppable(text(value.to_string())) + .on_drop(move |point, rect| Message::Drop(value, point, rect)), + ); + } + + container(column).height(250.0).width(300).into() +} + +mod style { + use iced::widget::container; + use iced::{Border, Theme}; + + pub fn title_bar(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + text_color: Some(palette.background.strong.text), + background: Some(palette.background.strong.color.into()), + ..Default::default() + } + } + + pub fn pane_active(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + background: Some(palette.background.weak.color.into()), + border: Border { + width: 1.0, + color: palette.background.strong.color, + ..Border::default() + }, + ..Default::default() + } + } + + pub fn pane_focused(theme: &Theme) -> container::Appearance { + let palette = theme.extended_palette(); + + container::Appearance { + background: Some(palette.background.weak.color.into()), + border: Border { + width: 4.0, + color: palette.background.strong.color, + ..Border::default() + }, + ..Default::default() + } + } +} diff --git a/iced_builder/src/types/mod.rs b/iced_builder/src/types/mod.rs new file mode 100644 index 0000000..db06ffa --- /dev/null +++ b/iced_builder/src/types/mod.rs @@ -0,0 +1,54 @@ +pub mod rendered_element; + +use rendered_element::RenderedElement; + +pub struct DesignerState { + pub designer_content: Vec, + pub designer_page: DesignerPage, +} + +#[derive(Debug, Clone, Copy)] +pub enum ElementName { + Text(&'static str), + Button(&'static str), + SVG(&'static str), + Image(&'static str), + Container, + Row, + Column, +} + +impl ElementName { + pub const ALL: [Self; 7] = [ + Self::Text(""), + Self::Button(""), + Self::SVG(""), + Self::Image(""), + Self::Container, + Self::Row, + Self::Column, + ]; +} + +impl std::fmt::Display for ElementName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Text(_) => "Text", + Self::Button(_) => "Button", + Self::SVG(_) => "SVG", + Self::Image(_) => "Image", + Self::Container => "Container", + Self::Row => "Row", + Self::Column => "Column", + } + ) + } +} + +pub enum DesignerPage { + Designer, + CodeView, +} diff --git a/iced_builder/src/types/rendered_element.rs b/iced_builder/src/types/rendered_element.rs new file mode 100644 index 0000000..f05594d --- /dev/null +++ b/iced_builder/src/types/rendered_element.rs @@ -0,0 +1,47 @@ +use std::collections::HashMap; + +use unique_id::{string::StringGenerator, Generator}; + +use iced::advanced::widget::Id; + +use super::ElementName; + +#[derive(Debug)] +pub struct RenderedElement { + pub id: Id, + pub child_elements: Vec, + pub name: ElementName, + pub props: HashMap<&'static str, &'static str>, +} + +impl RenderedElement { + pub fn new(name: ElementName) -> Self { + let gen = StringGenerator::default(); + Self { + id: Id::new(gen.next_id()), + child_elements: vec![], + name, + props: HashMap::new(), + } + } + + pub fn from_vec(name: ElementName, child_elements: Vec) -> Self { + let gen = StringGenerator::default(); + Self { + id: Id::new(gen.next_id()), + child_elements, + name, + props: HashMap::new(), + } + } + + pub fn push(mut self, element: RenderedElement) -> Self { + self.child_elements.push(element); + self + } + + pub fn set_property(&mut self, prop: &'static str, value: &'static str) { + let prop_ref = self.props.entry(prop).or_insert(value); + *prop_ref = value; + } +} diff --git a/iced_drop/.gitignore b/iced_drop/.gitignore new file mode 100644 index 0000000..ff0d847 --- /dev/null +++ b/iced_drop/.gitignore @@ -0,0 +1,3 @@ +/target +/.vscode +Cargo.lock diff --git a/iced_drop/Cargo.toml b/iced_drop/Cargo.toml new file mode 100644 index 0000000..8b9d88c --- /dev/null +++ b/iced_drop/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "iced_drop" +version = "0.1.0" +edition = "2021" + +[dependencies.iced] +version = "0.12.1" +features = ["advanced"] diff --git a/iced_drop/README.md b/iced_drop/README.md new file mode 100644 index 0000000..41b637b --- /dev/null +++ b/iced_drop/README.md @@ -0,0 +1,73 @@ +# 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` 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 new file mode 100644 index 0000000..fc559dc --- /dev/null +++ b/iced_drop/src/lib.rs @@ -0,0 +1,51 @@ +pub mod widget; + +use iced::{ + advanced::{graphics::futures::MaybeSend, renderer, widget::Id}, + Command, Element, Point, Rectangle, +}; + +use widget::droppable::*; +use widget::operation::drop; + +pub fn droppable<'a, Message, Theme, Renderer>( + content: impl Into>, +) -> Droppable<'a, Message, Theme, Renderer> +where + Message: Clone, + Renderer: renderer::Renderer, +{ + Droppable::new(content) +} + +pub fn zones_on_point( + msg: MF, + point: Point, + options: Option>, + depth: Option, +) -> Command +where + Message: 'static, + MF: Fn(Vec<(Id, Rectangle)>) -> Message + MaybeSend + Sync + Clone + 'static, +{ + Command::widget(drop::find_zones( + move |bounds| bounds.contains(point), + options, + depth, + )) + .map(move |id| msg(id)) +} + +pub fn find_zones( + msg: MF, + filter: F, + options: Option>, + depth: Option, +) -> Command +where + Message: 'static, + MF: Fn(Vec<(Id, Rectangle)>) -> Message + MaybeSend + Sync + Clone + 'static, + F: Fn(&Rectangle) -> bool + 'static, +{ + Command::widget(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 new file mode 100644 index 0000000..6b3fed2 --- /dev/null +++ b/iced_drop/src/widget.rs @@ -0,0 +1,2 @@ +pub mod droppable; +pub mod operation; diff --git a/iced_drop/src/widget/droppable.rs b/iced_drop/src/widget/droppable.rs new file mode 100644 index 0000000..ed7dcbd --- /dev/null +++ b/iced_drop/src/widget/droppable.rs @@ -0,0 +1,497 @@ +//! 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, + on_click: Option, + on_drop: Option Message + 'a>>, + on_drag: Option Message + 'a>>, + on_cancel: Option, + drag_mode: Option<(bool, bool)>, + drag_overlay: bool, + drag_hide: bool, + drag_center: bool, + drag_size: Option, + 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>) -> 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(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(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 + 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::() + } + + fn children(&self) -> Vec { + 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 { + 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::(); + 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::(); + 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::(); + 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::(); + 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> { + let state: &mut State = tree.state.downcast_mut::(); + 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::(); + + 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> + 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 + for Overlay<'a, 'b, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, +{ + fn layout(&mut self, renderer: &Renderer, _bounds: Size) -> layout::Node { + Widget::::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::::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 new file mode 100644 index 0000000..3d7dcff --- /dev/null +++ b/iced_drop/src/widget/operation.rs @@ -0,0 +1 @@ +pub mod drop; diff --git a/iced_drop/src/widget/operation/drop.rs b/iced_drop/src/widget/operation/drop.rs new file mode 100644 index 0000000..12a2e30 --- /dev/null +++ b/iced_drop/src/widget/operation/drop.rs @@ -0,0 +1,89 @@ +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( + filter: F, + options: Option>, + depth: Option, +) -> impl Operation> +where + F: Fn(&Rectangle) -> bool + 'static, +{ + struct FindDropZone { + filter: F, + options: Option>, + zones: Vec<(Id, Rectangle)>, + max_depth: Option, + c_depth: usize, + offset: Vector, + } + + impl Operation> for FindDropZone + where + F: Fn(&Rectangle) -> bool + 'static, + { + fn container( + &mut self, + id: Option<&Id>, + bounds: iced::Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation>), + ) { + 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> { + Outcome::Some(self.zones.clone()) + } + + fn scrollable( + &mut self, + _state: &mut dyn Scrollable, + _id: Option<&Id>, + 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/src/codegen/mod.rs b/src/codegen/mod.rs deleted file mode 100644 index 2dd9cff..0000000 --- a/src/codegen/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::path::PathBuf; - -use rust_format::{Config, Edition, Formatter, RustFmt}; - -use crate::types::{rendered_element::RenderedElement, ElementName}; - -impl RenderedElement { - fn props_codegen(&self) -> String { - let mut props_string = String::new(); - - for (k, v) in self.props.clone() { - props_string = format!("{props_string}.{k}({v})"); - } - - props_string - } - - fn codegen(&self) -> (String, String) { - let mut imports = String::new(); - let mut view = String::new(); - let props = self.props_codegen(); - - let mut elements = String::new(); - - match self.name { - ElementName::Column | ElementName::Row | ElementName::Container => { - for element in &self.child_elements { - let (c_imports, children) = element.codegen(); - imports = format!("{imports}{c_imports}"); - elements = format!("{elements}{},", children); - } - } - _ => {} - } - - match &self.name { - ElementName::Container => { - imports = format!("{imports}container,"); - view = if self.child_elements.len() < 2 { - format!("{view}\ncontainer({elements}){props}") - } else { - format!("{view}\ncontainer(){props}") - }; - } - ElementName::Row => { - imports = format!("{imports}row,"); - view = format!("{view}\nrow![{elements}]{props}"); - } - ElementName::Column => { - imports = format!("{imports}column,"); - view = format!("{view}\ncolumn![{elements}]{props}"); - } - ElementName::Text(string) => { - imports = format!("{imports}text,"); - view = format!( - "{view}\ntext(\"{}\"){props}", - if *string == String::new() { - "New Text" - } else { - string - } - ); - } - ElementName::Button(string) => { - imports = format!("{imports}button,"); - view = format!( - "{view}\nbutton(\"{}\"){props}", - if *string == String::new() { - "New Button" - } else { - string - } - ); - } - ElementName::Image(path) => { - imports = format!("{imports}image,"); - view = format!("{view}\nimage(\"{}\"){props}", path.display().to_string()); - } - ElementName::SVG(path) => { - imports = format!("{imports}svg,"); - view = format!("{view}\nsvg(\"{}\"){props}", path.display().to_string()); - } - } - - (imports, view) - } - - pub fn app_code( - &self, - title: &str, - theme: Option, - ) -> Result> { - let (imports, view) = self.codegen(); - let mut app_code = format!("use iced::{{widget::{{{imports}}},Sandbox,Settings,Element}};"); - - app_code = format!( - r#"{app_code} - - fn main() -> iced::Result {{ - App::run(Settings::default()) - }} - - struct App; - - impl Sandbox for App {{ - type Message = (); - - fn new() -> Self {{ - Self {{}} - }} - - fn title(&self) -> String {{ - "{title}".into() - }} - - fn theme(&self) -> iced::Theme {{ - iced::Theme::{} - }} - - fn update(&mut self, message: Self::Message) {{ - - }} - - fn view(&self) -> Element {{ - {view}.into() - }} - }}"#, - if let Some(c) = theme { - c.to_string().replace(' ', "") - } else { - "default()".to_owned() - } - ); - let config = Config::new_str() - .edition(Edition::Rust2021) - .option("trailing_comma", "Never") - .option("imports_granularity", "Crate"); - let rustfmt = RustFmt::from_config(config); - Ok(rustfmt.format_str(app_code)?) - } - - pub fn test() -> String { - let mut text1 = RenderedElement::new(ElementName::Text("wow")); - text1.set_property("height", "120.5"); - text1.set_property("width", "230"); - - let element = RenderedElement::new(ElementName::Container).push(RenderedElement::from_vec( - ElementName::Row, - vec![ - text1, - RenderedElement::new(ElementName::Text("heh")), - RenderedElement::new(ElementName::SVG(PathBuf::from( - "/mnt/drive_d/git/obs-website/src/lib/assets/bars-solid.svg", - ))), - ], - )); - - element.app_code("new app", None).unwrap() - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index d3fafc6..0000000 --- a/src/main.rs +++ /dev/null @@ -1,301 +0,0 @@ -mod codegen; -mod types; - -use iced::{ - clipboard, executor, - highlighter::{self, Highlighter}, - theme, - widget::{ - button, column, container, - pane_grid::{self, Pane, PaneGrid}, - row, text, text_editor, tooltip, Column, Space, - }, - Alignment, Application, Color, Command, Element, Font, Length, Settings, -}; -use types::{rendered_element::RenderedElement, DesignerPage, DesignerState}; - -fn main() -> iced::Result { - App::run(Settings { - fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()], - ..Settings::default() - }) -} - -struct App { - is_saved: bool, - current_project: Option, - dark_theme: bool, - pane_state: pane_grid::State, - focus: Option, - designer_state: DesignerState, - element_list: Vec, - editor_content: text_editor::Content, -} - -#[derive(Debug, Clone)] -enum Message { - ToggleTheme, - CopyCode, - Resized(pane_grid::ResizeEvent), - Clicked(pane_grid::Pane), -} - -#[derive(Clone, Debug)] -enum Panes { - Designer, - ElementList, -} - -impl Application for App { - type Message = Message; - type Theme = theme::Theme; - type Executor = executor::Default; - type Flags = (); - - fn new(_flags: ()) -> (Self, Command) { - let state = pane_grid::State::with_configuration(pane_grid::Configuration::Split { - axis: pane_grid::Axis::Vertical, - ratio: 0.8, - a: Box::new(pane_grid::Configuration::Pane(Panes::Designer)), - b: Box::new(pane_grid::Configuration::Pane(Panes::ElementList)), - }); - ( - Self { - is_saved: true, - current_project: None, - dark_theme: true, - pane_state: state, - focus: None, - designer_state: DesignerState { - designer_content: vec![], - designer_page: DesignerPage::CodeView, - }, - element_list: vec!["Column", "Row", "PickList", "PaneGrid", "Button", "Text"] - .into_iter() - .map(|c| c.to_owned()) - .collect(), - editor_content: text_editor::Content::with_text(&RenderedElement::test()), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - let saved_state = if self.is_saved { "" } else { " *" }; - - let project_name = match &self.current_project { - Some(n) => format!(" - {n}"), - None => "".to_owned(), - }; - - format!("iced Builder{project_name}{saved_state}") - } - - fn theme(&self) -> iced::Theme { - if self.dark_theme { - theme::Theme::CatppuccinMocha - } else { - theme::Theme::CatppuccinLatte - } - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::ToggleTheme => self.dark_theme = !self.dark_theme, - Message::CopyCode => return clipboard::write(self.editor_content.text()), - Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { - self.pane_state.resize(split, ratio); - } - Message::Clicked(pane) => { - self.focus = Some(pane); - } - } - - Command::none() - } - - fn view(&self) -> Element { - let header = row![button("Toggle Theme") - .on_press(Message::ToggleTheme) - .padding(5)] - .width(200); - let pane_grid = PaneGrid::new(&self.pane_state, |id, pane, _is_maximized| { - let is_focused = Some(id) == self.focus; - match pane { - Panes::Designer => match self.designer_state.designer_page { - DesignerPage::Designer => { - let content = column![text("Designer"),] - .align_items(Alignment::Center) - .height(Length::Fill) - .width(Length::Fill); - let title = text("Designer").style(if is_focused { - PANE_ID_COLOR_FOCUSED - } else { - PANE_ID_COLOR_UNFOCUSED - }); - let title_bar = pane_grid::TitleBar::new(title) - .padding(10) - .style(style::title_bar); - pane_grid::Content::new(content) - .title_bar(title_bar) - .style(if is_focused { - style::pane_focused - } else { - style::pane_active - }) - } - DesignerPage::CodeView => { - let title = row![ - text("Generated Code").style(if is_focused { - PANE_ID_COLOR_FOCUSED - } else { - PANE_ID_COLOR_UNFOCUSED - }), - Space::with_width(Length::Fill), - tooltip( - button( - container( - text('\u{0e801}').font(Font::with_name("editor-icons")) - ) - .width(30) - .center_x() - ) - .on_press(Message::CopyCode), - "Copy code to clipboard", - tooltip::Position::Left - ) - ]; - let title_bar = pane_grid::TitleBar::new(title) - .padding(10) - .style(style::title_bar); - pane_grid::Content::new( - text_editor(&self.editor_content) - .highlight::( - highlighter::Settings { - theme: highlighter::Theme::Base16Mocha, - extension: "rs".to_string(), - }, - |highlight, _theme| highlight.to_format(), - ) - .height(Length::Fill) - .padding(20), - ) - .title_bar(title_bar) - .style(if is_focused { - style::pane_focused - } else { - style::pane_active - }) - } - }, - Panes::ElementList => { - let items_list = items_list_view(&self.element_list); - let content = column![items_list] - .align_items(Alignment::Center) - .height(Length::Fill) - .width(Length::Fill); - let title = text("Element List").style(if is_focused { - PANE_ID_COLOR_FOCUSED - } else { - PANE_ID_COLOR_UNFOCUSED - }); - let title_bar = pane_grid::TitleBar::new(title) - .padding(10) - .style(style::title_bar); - pane_grid::Content::new(content) - .title_bar(title_bar) - .style(if is_focused { - style::pane_focused - } else { - style::pane_active - }) - } - } - }) - .width(Length::Fill) - .height(Length::Fill) - .spacing(10) - .on_resize(10, Message::Resized) - .on_click(Message::Clicked); - - let content = Column::new() - .push(header) - .push(pane_grid) - .spacing(5) - .align_items(Alignment::Center) - .width(Length::Fill); - - container(content).height(Length::Fill).into() - } -} - -const fn from_grayscale(grayscale: f32) -> Color { - Color { - r: grayscale, - g: grayscale, - b: grayscale, - a: 1.0, - } -} - -// #ffffff -const PANE_ID_COLOR_FOCUSED: Color = from_grayscale(1.0); - -// #e8e8e8 -const PANE_ID_COLOR_UNFOCUSED: Color = from_grayscale(0xE8 as f32 / 255.0); - -fn items_list_view(items: &Vec) -> Element<'static, Message> { - let mut column = Column::new() - .spacing(20) - .align_items(Alignment::Center) - .width(Length::Fill); - - for value in items { - column = column.push(text(value)); - } - - container(column).height(250.0).width(300).into() -} - -mod style { - use iced::widget::container; - use iced::{Border, Theme}; - - pub fn title_bar(theme: &Theme) -> container::Appearance { - let palette = theme.extended_palette(); - - container::Appearance { - text_color: Some(palette.background.strong.text), - background: Some(palette.background.strong.color.into()), - ..Default::default() - } - } - - pub fn pane_active(theme: &Theme) -> container::Appearance { - let palette = theme.extended_palette(); - - container::Appearance { - background: Some(palette.background.weak.color.into()), - border: Border { - width: 1.0, - color: palette.background.strong.color, - ..Border::default() - }, - ..Default::default() - } - } - - pub fn pane_focused(theme: &Theme) -> container::Appearance { - let palette = theme.extended_palette(); - - container::Appearance { - background: Some(palette.background.weak.color.into()), - border: Border { - width: 4.0, - color: palette.background.strong.color, - ..Border::default() - }, - ..Default::default() - } - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index 7a04d79..0000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -pub mod rendered_element; - -use rendered_element::RenderedElement; -use std::path::PathBuf; - -pub struct DesignerState { - pub designer_content: Vec, - pub designer_page: DesignerPage, -} - -#[derive(Debug)] -pub enum ElementName { - Text(&'static str), - Button(&'static str), - SVG(PathBuf), - Image(PathBuf), - Container, - Row, - Column, -} - -pub enum DesignerPage { - Designer, - CodeView, -} diff --git a/src/types/rendered_element.rs b/src/types/rendered_element.rs deleted file mode 100644 index f05594d..0000000 --- a/src/types/rendered_element.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::collections::HashMap; - -use unique_id::{string::StringGenerator, Generator}; - -use iced::advanced::widget::Id; - -use super::ElementName; - -#[derive(Debug)] -pub struct RenderedElement { - pub id: Id, - pub child_elements: Vec, - pub name: ElementName, - pub props: HashMap<&'static str, &'static str>, -} - -impl RenderedElement { - pub fn new(name: ElementName) -> Self { - let gen = StringGenerator::default(); - Self { - id: Id::new(gen.next_id()), - child_elements: vec![], - name, - props: HashMap::new(), - } - } - - pub fn from_vec(name: ElementName, child_elements: Vec) -> Self { - let gen = StringGenerator::default(); - Self { - id: Id::new(gen.next_id()), - child_elements, - name, - props: HashMap::new(), - } - } - - pub fn push(mut self, element: RenderedElement) -> Self { - self.child_elements.push(element); - self - } - - pub fn set_property(&mut self, prop: &'static str, value: &'static str) { - let prop_ref = self.props.entry(prop).or_insert(value); - *prop_ref = value; - } -} -- cgit v1.2.3