From c72ac1cde38dcae08c89457a5f8f44d3b5ecd380 Mon Sep 17 00:00:00 2001 From: Enrico Lumetti Date: Wed, 25 Nov 2020 01:11:03 +0100 Subject: [PATCH] Initial stage for arbitrary tool support --- Cargo.lock | 310 ++++++++++++++++++++++++---- Cargo.toml | 4 +- icons/eraser.svg | 1 + icons/pen.svg | 1 + src/lib.rs | 7 + src/main.rs | 108 ++++++++-- src/tool.rs | 62 ++++++ src/{widget.rs => widget/canvas.rs} | 8 +- src/widget/mod.rs | 5 + src/widget/tool_icon.rs | 74 +++++++ 10 files changed, 513 insertions(+), 67 deletions(-) create mode 100644 icons/eraser.svg create mode 100644 icons/pen.svg create mode 100644 src/tool.rs rename src/{widget.rs => widget/canvas.rs} (97%) create mode 100644 src/widget/mod.rs create mode 100644 src/widget/tool_icon.rs diff --git a/Cargo.lock b/Cargo.lock index 48b2189..abe61a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + [[package]] name = "anyhow" version = "1.0.34" @@ -61,6 +67,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "bitflags" version = "1.2.1" @@ -127,9 +139,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.62" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40" +checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" [[package]] name = "cfg-if" @@ -143,6 +155,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cmake" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.20.2" @@ -214,6 +235,24 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "data-url" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d33fe99ccedd6e84bc035f1931bb2e6be79739d6242bd895e7311c886c50dc9c" +dependencies = [ + "matches", +] + [[package]] name = "direct2d" version = "0.2.0" @@ -257,7 +296,7 @@ checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" [[package]] name = "druid" version = "0.6.0" -source = "git+https://github.com/doppioandante/druid?branch=stylus_events_0.6.0#b0f9dc0268409a56b5066f569ad245e5df016166" +source = "git+https://github.com/doppioandante/druid?branch=v0.6.0_stiletto#f8f8a566b77795cfa10caf7f2861f9240d159e90" dependencies = [ "console_log", "druid-derive", @@ -272,13 +311,14 @@ dependencies = [ "simple_logger", "unic-langid", "unicode-segmentation", + "usvg", "xi-unicode", ] [[package]] name = "druid-derive" version = "0.3.1" -source = "git+https://github.com/doppioandante/druid?branch=stylus_events_0.6.0#b0f9dc0268409a56b5066f569ad245e5df016166" +source = "git+https://github.com/doppioandante/druid?branch=v0.6.0_stiletto#f8f8a566b77795cfa10caf7f2861f9240d159e90" dependencies = [ "proc-macro2", "quote", @@ -288,7 +328,7 @@ dependencies = [ [[package]] name = "druid-shell" version = "0.6.0" -source = "git+https://github.com/doppioandante/druid?branch=stylus_events_0.6.0#b0f9dc0268409a56b5066f569ad245e5df016166" +source = "git+https://github.com/doppioandante/druid?branch=v0.6.0_stiletto#f8f8a566b77795cfa10caf7f2861f9240d159e90" dependencies = [ "anyhow", "bitflags", @@ -336,6 +376,24 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "flate2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" + [[package]] name = "fluent-bundle" version = "0.12.0" @@ -387,6 +445,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "freetype" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73222ab32d9ad65fe0e1c3258da8d614fd47cf19fce92b09eb520060c5c5ad5" +dependencies = [ + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48ac0ce366dd47a115ec8e598d7c51b4a974fc52ded5e53a56b31f55f34f3ea" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -395,24 +474,24 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "futures-channel" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" [[package]] name = "futures-executor" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" dependencies = [ "futures-core", "futures-task", @@ -421,15 +500,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" [[package]] name = "futures-macro" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -439,18 +518,18 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" dependencies = [ "once_cell", ] [[package]] name = "futures-util" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" dependencies = [ "futures-core", "futures-macro", @@ -650,6 +729,30 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "harfbuzz-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845f3e65ec4e30b0b1b6e1c055900871f3776d3492cc76744e3fc5943a6129a9" +dependencies = [ + "cc", + "core-graphics", + "core-text", + "foreign-types", + "freetype", + "pkg-config", +] + +[[package]] +name = "harfbuzz_rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5bba81dd6b356135f0b31c42447e49d45116adc2e02910070947a322c423aa5" +dependencies = [ + "bitflags", + "harfbuzz-sys", +] + [[package]] name = "hermit-abi" version = "0.1.17" @@ -675,9 +778,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -759,6 +862,31 @@ dependencies = [ "libc", ] +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memmap2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a" +dependencies = [ + "libc", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "num" version = "0.1.42" @@ -848,9 +976,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "pango" @@ -962,18 +1090,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" dependencies = [ "proc-macro2", "quote", @@ -1065,6 +1193,12 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rctree" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be9e29cb19c8fe84169fcb07f8f11e66bc9e6e0280efd4715c54818296f8a4a8" + [[package]] name = "rdrand" version = "0.4.0" @@ -1095,6 +1229,15 @@ dependencies = [ "syn", ] +[[package]] +name = "roxmltree" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5001f134077069d87f77c8b9452b690df2445f7a43f1c7ca4a1af8dd505789d" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustc-serialize" version = "0.3.24" @@ -1179,6 +1322,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "simplecss" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "596554e63596d556a0dbd681416342ca61c75f1a45203201e7e77d3fa2fa9014" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" + [[package]] name = "sized-chunks" version = "0.6.2" @@ -1197,9 +1355,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" +checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" [[package]] name = "stable_deref_trait" @@ -1209,9 +1367,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "standback" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e0831040d2cf2bdfd51b844be71885783d489898a192f254ae25d57cce725c" +checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8" dependencies = [ "version_check", ] @@ -1280,10 +1438,20 @@ dependencies = [ ] [[package]] -name = "syn" -version = "1.0.48" +name = "svgtypes" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" +dependencies = [ + "float-cmp", + "siphasher", +] + +[[package]] +name = "syn" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6" dependencies = [ "proc-macro2", "quote", @@ -1292,9 +1460,9 @@ dependencies = [ [[package]] name = "time" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b7151c9065e80917fbf285d9a5d1432f60db41d170ccafc749a136b41a93af" +checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" dependencies = [ "const_fn", "libc", @@ -1334,6 +1502,12 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" +[[package]] +name = "ttf-parser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" + [[package]] name = "type-map" version = "0.3.0" @@ -1368,10 +1542,31 @@ dependencies = [ ] [[package]] -name = "unicode-segmentation" -version = "1.6.0" +name = "unicode-bidi" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-script" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79bf4d5fc96546fdb73f9827097810bbda93b11a6770ff3a54e1f445d4135787" + +[[package]] +name = "unicode-segmentation" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8716a166f290ff49dabc18b44aa407cb7c6dbe1aa0971b44b8a24b0ca35aae" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-xid" @@ -1379,6 +1574,31 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "usvg" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d98fe4bbd8cfe811fb84dabebd670d26b1e633ecb4d3a4ef3a4b8c10252448d" +dependencies = [ + "base64", + "data-url", + "flate2", + "harfbuzz_rs", + "kurbo", + "log", + "memmap2", + "rctree", + "roxmltree", + "simplecss", + "siphasher", + "svgtypes", + "ttf-parser", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "version_check" version = "0.9.2" @@ -1485,3 +1705,15 @@ name = "xi-unicode" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e71b85d8b1b8bfaf4b5c834187554d201a8cd621c2bbfa33efd41a3ecabd48b2" + +[[package]] +name = "xmlparser" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" diff --git a/Cargo.toml b/Cargo.toml index dc2905d..6ca75fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,14 @@ edition = "2018" [dependencies] log = "0.4" -druid = { version = "0.6.0", features = ["im"] } +druid = { version = "0.6.0", features = ["im", "svg"] } im = { version = "*" } serde = { version = "1.0", features = ["derive"] } #serde_bare = "0.3.0" serde_json = "1.0" [patch.crates-io] -druid = { git = "https://github.com/doppioandante/druid", branch = "stylus_events_0.6.0", features = ["im"] } +druid = { git = "https://github.com/doppioandante/druid", branch = "v0.6.0_stiletto", features = ["im", "svg"] } [dependencies.gtk] version = "0.8.1" diff --git a/icons/eraser.svg b/icons/eraser.svg new file mode 100644 index 0000000..c49dd42 --- /dev/null +++ b/icons/eraser.svg @@ -0,0 +1 @@ + diff --git a/icons/pen.svg b/icons/pen.svg new file mode 100644 index 0000000..7ef6b16 --- /dev/null +++ b/icons/pen.svg @@ -0,0 +1 @@ + diff --git a/src/lib.rs b/src/lib.rs index a9e355d..2e325ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,8 +19,15 @@ use std::vec::Vec; pub mod canvas; pub mod history; +pub mod tool; pub mod widget; +pub mod commands { + use druid::Selector; + + pub const UPDATE_TOOL: Selector = Selector::new("stiletto_update_tool"); +} + #[derive(Serialize, Deserialize, Debug)] pub struct DocumentSnapshot { pub canvas_elements: Vec, diff --git a/src/main.rs b/src/main.rs index d23821f..7f5d697 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,14 +19,19 @@ use log::{info, warn}; use druid::commands; use druid::im::vector; use druid::widget::prelude::*; -use druid::widget::{Align, Button, CrossAxisAlignment, Flex, SizedBox}; +use druid::widget::{ + Align, Button, CrossAxisAlignment, Flex, List, ListGrowDirection, SizedBox, WidgetExt, +}; use druid::{ - AppDelegate, AppLauncher, Command, DelegateCtx, Env, FileDialogOptions, FileSpec, - LocalizedString, Target, WindowDesc, + AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, Env, FileDialogOptions, FileSpec, + Lens, LocalizedString, Target, WindowDesc, }; +use im::Vector; + use stiletto::history::VersionedCanvas; -use stiletto::widget::{CanvasState, CanvasWidget}; +use stiletto::tool::{CanvasToolCtx, CanvasToolParams}; +use stiletto::widget::{build_simple_tool_widget, CanvasState, CanvasToolIconState, CanvasWidget}; use stiletto::DocumentSnapshot; pub fn main() { @@ -35,9 +40,28 @@ pub fn main() { .title( LocalizedString::new("custom-widget-demo-window-title").with_placeholder("Stiletto"), ); - let canvas_data = CanvasState { - current_element: None, - elements: VersionedCanvas::new(vector![]), + + let default_pen_params = CanvasToolParams::Pen { + thickness: 2.0, + color: Color::rgb(0, 0, 0), + }; + let canvas_data = StilettoState { + canvas: CanvasState { + current_element: None, + elements: VersionedCanvas::new(vector![]), + }, + tool_icons: vector![ + CanvasToolIconState { + tool_params: default_pen_params.clone(), + selected: true, + }, + CanvasToolIconState { + tool_params: CanvasToolParams::Eraser, + selected: false, + } + ], + current_tool: 0, + tool_ctx: CanvasToolCtx::new(default_pen_params.clone()), }; AppLauncher::with_window(window) .use_simple_logger() @@ -46,16 +70,23 @@ pub fn main() { .expect("launch failed"); } -fn build_ui() -> impl Widget { - let history_buttons = - Flex::row() - .cross_axis_alignment(CrossAxisAlignment::Center) - .with_child(Button::new("Undo").on_click( - |_ctx: &mut EventCtx, data: &mut CanvasState, _env: &Env| data.perform_undo(), - )) - .with_child(Button::new("Redo").on_click( - |_ctx: &mut EventCtx, data: &mut CanvasState, _env: &Env| data.perform_redo(), - )); +#[derive(Clone, Data, Lens)] +struct StilettoState { + canvas: CanvasState, + tool_icons: Vector, + current_tool: usize, + tool_ctx: CanvasToolCtx, +} + +fn build_ui() -> impl Widget { + let history_buttons = Flex::row() + .cross_axis_alignment(CrossAxisAlignment::Center) + .with_child(Button::new("Undo").on_click( + |_ctx: &mut EventCtx, data: &mut StilettoState, _env: &Env| data.canvas.perform_undo(), + )) + .with_child(Button::new("Redo").on_click( + |_ctx: &mut EventCtx, data: &mut StilettoState, _env: &Env| data.canvas.perform_redo(), + )); let stlt = FileSpec::new("Stiletto notebook", &["stlt"]); let save_dialog_options = FileDialogOptions::new() @@ -84,10 +115,29 @@ fn build_ui() -> impl Widget { ) })); + let tool_buttons = Flex::row() + .cross_axis_alignment(CrossAxisAlignment::Center) + .with_flex_child( + List::new(|| { + build_simple_tool_widget(30.0, 30.0, 5.0) + .padding((8.0, 0.0)) + .on_click(|ctx, tool_state, _| { + tool_state.selected = true; + ctx.submit_command(stiletto::commands::UPDATE_TOOL, None); + }) + }) + .grow(ListGrowDirection::Right) + .lens(StilettoState::tool_icons), + 1.0, + ); + let toolbar = Flex::row() .cross_axis_alignment(CrossAxisAlignment::Center) .with_spacer(30.0) .with_flex_child(Align::left(history_buttons), 1.0) + .with_spacer(10.0) + .with_flex_child(Align::left(tool_buttons), 2.0) + .with_spacer(20.0) .with_flex_child(Align::right(save_buttons), 1.0) .with_spacer(30.0); @@ -95,18 +145,18 @@ fn build_ui() -> impl Widget { .cross_axis_alignment(CrossAxisAlignment::Center) .must_fill_main_axis(true) .with_child(SizedBox::new(Align::left(toolbar)).height(50.0)) - .with_flex_child(CanvasWidget {}, 1.0) + .with_flex_child((CanvasWidget {}).lens(StilettoState::canvas), 1.0) } struct Delegate; -impl AppDelegate for Delegate { +impl AppDelegate for Delegate { fn command( &mut self, _ctx: &mut DelegateCtx, _target: Target, cmd: &Command, - data: &mut CanvasState, + data: &mut StilettoState, _env: &Env, ) -> bool { use std::fs::File; @@ -114,7 +164,8 @@ impl AppDelegate for Delegate { if let Some(Some(file_info)) = cmd.get(commands::SAVE_FILE) { let res_file = File::create(file_info.path()); if let Ok(f) = res_file { - let write_res = serde_json::to_writer_pretty(f, &data.get_document_snapshot()); + let write_res = + serde_json::to_writer_pretty(f, &data.canvas.get_document_snapshot()); if write_res.is_err() { warn!("Error while saving: {:?}", write_res.err()); } else { @@ -130,7 +181,7 @@ impl AppDelegate for Delegate { let res_snapshot: Result = serde_json::from_reader(f); if let Ok(document_snapshot) = res_snapshot { - data.set_from_snapshot(document_snapshot); + data.canvas.set_from_snapshot(document_snapshot); info!("Loaded file {}", file_info.path().display()); } else { warn!("didn't work: {:?}", res_snapshot.err()); @@ -138,6 +189,19 @@ impl AppDelegate for Delegate { } return true; } + if let Some(_) = cmd.get(stiletto::commands::UPDATE_TOOL) { + let old_tool = data.current_tool; + if let Some(new_tool) = data + .tool_icons + .iter() + .enumerate() + .position(|(pos, x)| pos != old_tool && x.selected) + { + info!("Changing active tool to: tool #{}", new_tool); + data.tool_icons.get_mut(old_tool).unwrap().selected = false; + data.current_tool = new_tool; + } + } false } } diff --git a/src/tool.rs b/src/tool.rs new file mode 100644 index 0000000..24925fd --- /dev/null +++ b/src/tool.rs @@ -0,0 +1,62 @@ +// Stiletto +// Copyright (C) 2020 Stiletto Authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use druid::{Color, Data}; + +use crate::canvas::CanvasElement; +use crate::history::VersionedCanvas; + +#[derive(Clone, Data)] +pub enum CanvasToolParams { + Pen { thickness: f64, color: Color }, + Eraser, +} + +#[derive(Clone, PartialEq)] +pub enum CanvasToolType { + Pen, + Eraser, +} + +#[derive(Clone, Data)] +pub enum CanvasToolState { + Idle, + DrawingFreehand { current_path: CanvasElement }, +} + +#[derive(Clone, Data)] +pub struct CanvasToolCtx { + initial_params: CanvasToolParams, + state: CanvasToolState, +} + +impl CanvasToolParams { + pub fn tool_type(&self) -> CanvasToolType { + match self { + CanvasToolParams::Pen { .. } => CanvasToolType::Pen, + CanvasToolParams::Eraser => CanvasToolType::Eraser, + } + } +} + +impl CanvasToolCtx { + pub fn new(params: CanvasToolParams) -> Self { + CanvasToolCtx { + initial_params: params, + state: CanvasToolState::Idle, + } + } +} diff --git a/src/widget.rs b/src/widget/canvas.rs similarity index 97% rename from src/widget.rs rename to src/widget/canvas.rs index 180eca1..8053029 100644 --- a/src/widget.rs +++ b/src/widget/canvas.rs @@ -16,10 +16,10 @@ use im::Vector; -use super::canvas; -use super::canvas::{Canvas, CanvasElement}; -use super::history::VersionedCanvas; -use super::DocumentSnapshot; +use crate::canvas; +use crate::canvas::{Canvas, CanvasElement}; +use crate::history::VersionedCanvas; +use crate::DocumentSnapshot; use druid::kurbo::BezPath; use druid::widget::prelude::*; diff --git a/src/widget/mod.rs b/src/widget/mod.rs new file mode 100644 index 0000000..5d05f31 --- /dev/null +++ b/src/widget/mod.rs @@ -0,0 +1,5 @@ +pub mod canvas; +pub mod tool_icon; + +pub use canvas::*; +pub use tool_icon::*; diff --git a/src/widget/tool_icon.rs b/src/widget/tool_icon.rs new file mode 100644 index 0000000..b9b4a0c --- /dev/null +++ b/src/widget/tool_icon.rs @@ -0,0 +1,74 @@ +// Stiletto +// Copyright (C) 2020 Stiletto Authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use druid::widget::{Container, SizedBox, Svg, SvgData, ViewSwitcher, WidgetExt}; +use druid::{Color, Data, UnitPoint}; + +use crate::tool::{CanvasToolParams, CanvasToolType}; + +#[derive(Clone, Data)] +pub struct CanvasToolIconState { + pub tool_params: CanvasToolParams, + pub selected: bool, +} + +impl CanvasToolIconState { + fn tool_type(&self) -> CanvasToolType { + self.tool_params.tool_type() + } + + fn svg_data(&self) -> SvgData { + match self.tool_params { + CanvasToolParams::Pen { .. } => include_str!("../../icons/pen.svg") + .parse::() + .unwrap(), + CanvasToolParams::Eraser => include_str!("../../icons/eraser.svg") + .parse::() + .unwrap(), + } + } +} + +pub fn build_simple_tool_widget( + width: f64, + height: f64, + padding: f64, +) -> ViewSwitcher { + ViewSwitcher::::new( + |tool_state, _| (tool_state.tool_type(), tool_state.selected), + move |_, tool_state, _| { + Box::new( + Container::new( + SizedBox::new( + Svg::new(tool_state.svg_data()) + .align_horizontal(UnitPoint::CENTER) + .fix_width(width) + .fix_height(height), + ) + .padding(padding), + ) + .border( + if tool_state.selected { + Color::rgb(10, 10, 10) + } else { + Color::BLACK + }, + 1.0, + ), + ) + }, + ) +}