From 6f68bd7dd7a5af39336f7c41bd59f483c25eb40a Mon Sep 17 00:00:00 2001 From: Matthias Gabriel Date: Tue, 13 Feb 2024 07:05:14 +0100 Subject: [PATCH 01/10] add initial sample add first version fix scalings add more rendering rework structure add undo/redo add brush tool add arrow gl add rect gl add text rendering results add text rendering add marker add crop add blurring remove renderer remove draw functions clean refactor clean add basic rendering support add scale_factor add saving and clipboard fix crash clean refacto clean clean up, refactor refactor refactor improve handle sizing rework scale remove pangocairo calc everything in f32 refactor more refactor celan up --- .vscode/launch.json | 2 +- .vscode/tasks.json | 7 +- Cargo.lock | 622 +++++++++++++++++++++++++++++----------- Cargo.toml | 9 +- flake.nix | 3 +- src/command_line.rs | 2 +- src/configuration.rs | 8 +- src/femtovg_area/imp.rs | 391 +++++++++++++++++++++++++ src/femtovg_area/mod.rs | 79 +++++ src/main.rs | 29 +- src/math.rs | 16 +- src/renderer.rs | 174 ----------- src/sketch_board.rs | 166 ++++++----- src/style.rs | 45 ++- src/tools/arrow.rs | 40 ++- src/tools/blur.rs | 117 ++++---- src/tools/brush.rs | 38 +-- src/tools/crop.rs | 97 ++++--- src/tools/line.rs | 22 +- src/tools/marker.rs | 85 +++--- src/tools/mod.rs | 14 +- src/tools/rectangle.rs | 27 +- src/tools/text.rs | 117 ++++++-- 23 files changed, 1429 insertions(+), 681 deletions(-) create mode 100644 src/femtovg_area/imp.rs create mode 100644 src/femtovg_area/mod.rs delete mode 100644 src/renderer.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index fc209c7..50032d9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,7 +21,7 @@ }, "args": [ "--filename", - "/home/gabm/Pictures/Screenshots/swappy-20230921-054340.png", + "/home/gabm/Pictures/Screenshots/satty-20240109-22:19:08.png", //"/home/gabm/Pictures/Wallpaper/torres_1.jpg" "--output-filename", "/tmp/out.png" diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 06cff6e..c32c22b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,10 +8,11 @@ "run", "--", "--filename", - "/home/gabm/Pictures/Screenshots/satty-20240109-22:19:08.png", + "/home/gabm/Pictures/Screenshots/satty-20240219-14:19:29.png", // small + //"/home/gabm/Pictures/Screenshots/satty-20240109-22:19:08.png", // big //"--fullscreen", - //"--output-filename", - //"/tmp/out.png", + "--output-filename", + "/tmp/out.png", "--copy-command", "wl-copy", ], diff --git a/Cargo.lock b/Cargo.lock index 53e014e..ece26d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,9 +34,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.12" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "arrayvec" @@ -100,7 +100,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", ] [[package]] @@ -130,11 +130,23 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytemuck" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" [[package]] name = "byteorder" @@ -154,9 +166,9 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3603c4028a5e368d09b51c8b624b9a46edcd7c3778284077a6125af73c9f0a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-sys-rs", - "glib", + "glib 0.17.10", "libc", "once_cell", "thiserror", @@ -168,16 +180,16 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "691d0c66b1fb4881be80a760cb8fe76ea97218312f9dfe2c9cc0f496ca279cb1" dependencies = [ - "glib-sys", + "glib-sys 0.17.10", "libc", "system-deps", ] [[package]] name = "cc" -version = "1.0.86" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" +checksum = "9b918671670962b48bc23753aef0c51d072dca6f52f01f800854ada6ddb7f7d3" [[package]] name = "cfg-expr" @@ -206,14 +218,14 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.3", + "windows-targets 0.52.0", ] [[package]] name = "clap" -version = "4.5.1" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", "clap_derive", @@ -221,9 +233,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" dependencies = [ "anstream", "anstyle", @@ -233,9 +245,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.1" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +checksum = "299353be8209bd133b049bf1c63582d184a8b39fd9c04f15fe65f50f88bdfe6c" dependencies = [ "clap", ] @@ -252,9 +264,9 @@ dependencies = [ [[package]] name = "clap_complete_nushell" -version = "4.5.1" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0e48e026ce7df2040239117d25e4e79714907420c70294a5ce4b6bbe6a7b6" +checksum = "92cd8ac7c321605310e4b3bdc047daf27c6ca1b50f6ae59b22aa0661ab45e295" dependencies = [ "clap", "clap_complete", @@ -269,7 +281,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", ] [[package]] @@ -278,6 +290,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.0" @@ -299,12 +317,46 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "epoxy" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b96028ce3ff03972312fd8243281858e80fc0f9838b1f035676b6c199214d9e" +dependencies = [ + "gl_generator", + "libc", + "pkg-config", + "shared_library", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "femtovg" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18ab822e58e8bc2b89840dc5dde49afe39302e129c60d39c8520200c085404a7" +dependencies = [ + "bitflags 2.4.2", + "fnv", + "generational-arena", + "glow", + "image", + "imgref", + "log", + "lru", + "rgb", + "rustybuzz", + "unicode-bidi", + "unicode-segmentation", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "field-offset" version = "0.3.6" @@ -338,6 +390,12 @@ dependencies = [ "spin", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fragile" version = "2.0.0" @@ -400,7 +458,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", ] [[package]] @@ -439,10 +497,10 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "695d6bc846438c5708b07007537b9274d883373dd30858ca881d7d71b5540717" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gdk-pixbuf-sys", "gio", - "glib", + "glib 0.17.10", "libc", "once_cell", ] @@ -453,9 +511,9 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9285ec3c113c66d7d0ab5676599176f1f42f4944ca1b581852215bf5694870cb" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.17.10", + "glib-sys 0.17.10", + "gobject-sys 0.17.10", "libc", "system-deps", ] @@ -466,12 +524,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3abf96408a26e3eddf881a7f893a1e111767137136e347745e8ea6ed12731ff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "gdk-pixbuf", "gdk4-sys", "gio", - "glib", + "glib 0.17.10", "libc", "pango", ] @@ -484,15 +542,24 @@ checksum = "1bc92aa1608c089c49393d014c38ac0390d01e4841e1fedaa75dbcef77aaed64" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.17.10", + "glib-sys 0.17.10", + "gobject-sys 0.17.10", "libc", "pango-sys", "pkg-config", "system-deps", ] +[[package]] +name = "generational-arena" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e94aff08e743b651baaea359664321055749b398adff8740a7399af7796e7" +dependencies = [ + "cfg-if", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -518,13 +585,13 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6973e92937cf98689b6a054a9e56c657ed4ff76de925e36fc331a15f0c5d30a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "futures-channel", "futures-core", "futures-io", "futures-util", - "gio-sys", - "glib", + "gio-sys 0.17.10", + "glib 0.17.10", "libc", "once_cell", "pin-project-lite", @@ -538,29 +605,53 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ccf87c30a12c469b6d958950f6a9c09f2be20b7773f7e70d20b867fdf2628c3" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.17.10", + "gobject-sys 0.17.10", "libc", "system-deps", "winapi", ] +[[package]] +name = "gio-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf8e1d9219bb294636753d307b030c1e8a032062cba74f493c431a5c8b81ce4" +dependencies = [ + "glib-sys 0.19.0", + "gobject-sys 0.19.0", + "libc", + "system-deps", + "windows-sys 0.52.0", +] + +[[package]] +name = "gl_generator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a795170cbd85b5a7baa58d6d7525cae6a03e486859860c220f7ebbbdd379d0a" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + [[package]] name = "glib" version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fad45ba8d4d2cea612b432717e834f48031cd8853c8aaf43b2c79fec8d144b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", + "gio-sys 0.17.10", + "glib-macros 0.17.10", + "glib-sys 0.17.10", + "gobject-sys 0.17.10", "libc", "memchr", "once_cell", @@ -568,6 +659,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "glib" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9e86540b5d8402e905ad4ce7d6aa544092131ab564f3102175af176b90a053" +dependencies = [ + "bitflags 2.4.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys 0.19.0", + "glib-macros 0.19.2", + "glib-sys 0.19.0", + "gobject-sys 0.19.0", + "libc", + "memchr", + "smallvec", + "thiserror", +] + [[package]] name = "glib-macros" version = "0.17.10" @@ -576,13 +689,26 @@ checksum = "eca5c79337338391f1ab8058d6698125034ce8ef31b72a442437fa6c8580de26" dependencies = [ "anyhow", "heck", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro-error", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "glib-macros" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f5897ca27a83e4cdc7b4666850bade0a2e73e17689aabafcc9acddad9d823b8" +dependencies = [ + "heck", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "glib-sys" version = "0.17.10" @@ -593,13 +719,46 @@ dependencies = [ "system-deps", ] +[[package]] +name = "glib-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630f097773d7c7a0bb3258df4e8157b47dc98bbfa0e60ad9ab56174813feced4" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gobject-sys" version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd34c3317740a6358ec04572c1bcfd3ac0b5b6529275fae255b237b314bb8062" dependencies = [ - "glib-sys", + "glib-sys 0.17.10", + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e2b1080b9418dd0c58b498da3a5c826030343e0ef07bde6a955d28de54979" +dependencies = [ + "glib-sys 0.19.0", "libc", "system-deps", ] @@ -610,7 +769,7 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "def4bb01265b59ed548b05455040d272d989b3012c42d4c1bbd39083cb9b40d9" dependencies = [ - "glib", + "glib 0.17.10", "graphene-sys", "libc", ] @@ -621,7 +780,7 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1856fc817e6a6675e36cea0bd9a3afe296f5d9709d1e2d3182803ac77f0ab21d" dependencies = [ - "glib-sys", + "glib-sys 0.17.10", "libc", "pkg-config", "system-deps", @@ -633,10 +792,10 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f01ef44fa7cac15e2da9978529383e6bee03e570ba5bf7036b4c10a15cc3a3c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "gdk4", - "glib", + "glib 0.17.10", "graphene-rs", "gsk4-sys", "libc", @@ -651,8 +810,8 @@ checksum = "c07a84fb4dcf1323d29435aa85e2f5f58bef564342bef06775ec7bd0da1f01b0" dependencies = [ "cairo-sys-rs", "gdk4-sys", - "glib-sys", - "gobject-sys", + "glib-sys 0.17.10", + "gobject-sys 0.17.10", "graphene-sys", "libc", "pango-sys", @@ -665,14 +824,14 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b28a32a04cd75cef14a0983f8b0c669e0fe152a0a7725accdeb594e2c764c88b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cairo-rs", "field-offset", "futures-channel", "gdk-pixbuf", "gdk4", "gio", - "glib", + "glib 0.17.10", "graphene-rs", "gsk4", "gtk4-macros", @@ -689,7 +848,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a4d6b61570f76d3ee542d984da443b1cd69b6105264c61afec3abed08c2500f" dependencies = [ "anyhow", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro-error", "proc-macro2", "quote", @@ -705,9 +864,9 @@ dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk4-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.17.10", + "glib-sys 0.17.10", + "gobject-sys 0.17.10", "graphene-sys", "gsk4-sys", "libc", @@ -747,9 +906,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" [[package]] name = "hex_color" @@ -785,6 +944,24 @@ dependencies = [ "cc", ] +[[package]] +name = "image" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", +] + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "indexmap" version = "2.2.3" @@ -810,6 +987,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "khronos_api" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037ab472c33f67b5fbd3e9163a2645319e5356fcd355efa6d4eb7fff4bbcb554" + [[package]] name = "lazy_static" version = "1.4.0" @@ -822,11 +1005,11 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ab9c0843f9f23ff25634df2743690c3a1faffe0a190e60c490878517eb81abf" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gdk-pixbuf", "gdk4", "gio", - "glib", + "glib 0.17.10", "gtk4", "libadwaita-sys", "libc", @@ -840,9 +1023,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4231cb2499a9f0c4cdfa4885414b33e39901ddcac61150bc0bb4ff8a57ede404" dependencies = [ "gdk4-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.17.10", + "glib-sys 0.17.10", + "gobject-sys 0.17.10", "gtk4-sys", "libc", "pango-sys", @@ -855,6 +1038,16 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "lock_api" version = "0.4.11" @@ -871,6 +1064,12 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" + [[package]] name = "memchr" version = "2.7.1" @@ -964,9 +1163,9 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35be456fc620e61f62dff7ff70fbd54dcbaf0a4b920c0f16de1107c47d921d48" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gio", - "glib", + "glib 0.17.10", "libc", "once_cell", "pango-sys", @@ -978,39 +1177,12 @@ version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da69f9f3850b0d8990d462f8c709561975e95f689c1cdf0fecdebde78b35195" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.17.10", + "gobject-sys 0.17.10", "libc", "system-deps", ] -[[package]] -name = "pangocairo" -version = "0.17.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bf29cb1c2e73817944f66011fb12135e1c6d268e8e4c5cfc689101c25822cf" -dependencies = [ - "bitflags", - "cairo-rs", - "glib", - "libc", - "pango", - "pangocairo-sys", -] - -[[package]] -name = "pangocairo-sys" -version = "0.17.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94dfd38d9bf8ff5f881be2107ba49fcb22090d247aa00133f8dadf96b122b97a" -dependencies = [ - "cairo-sys-rs", - "glib-sys", - "libc", - "pango-sys", - "system-deps", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -1051,7 +1223,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", ] [[package]] @@ -1068,9 +1240,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "ppv-lite86" @@ -1088,6 +1260,15 @@ dependencies = [ "toml_edit 0.19.15", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1176,7 +1357,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1215,7 +1396,22 @@ checksum = "9340e2553c0a184a80a0bfa1dcf73c47f3d48933aa6be90724b202f9fbd24735" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", +] + +[[package]] +name = "resource" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a7f6703c396037a02da99195e49138c37f3cc5146cb95f2f7d26debc0c5622" + +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", ] [[package]] @@ -1233,11 +1429,27 @@ dependencies = [ "semver", ] +[[package]] +name = "rustybuzz" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee8fe2a8461a0854a37101fe7a1b13998d0cfa987e43248e81d2a5f4570f6fa" +dependencies = [ + "bitflags 1.3.2", + "bytemuck", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "safe-transmute" @@ -1264,11 +1476,17 @@ dependencies = [ "clap_complete", "clap_complete_fig", "clap_complete_nushell", + "epoxy", + "femtovg", "gdk-pixbuf", + "glib 0.19.2", + "glib-macros 0.19.2", + "glow", "hex_color", - "pangocairo", + "libloading", "relm4", "relm4-icons", + "resource", "serde", "serde_derive", "thiserror", @@ -1285,35 +1503,35 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.22" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -1329,6 +1547,16 @@ dependencies = [ "serde", ] +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1347,6 +1575,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.13.1" @@ -1397,9 +1634,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -1421,9 +1658,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" [[package]] name = "thiserror" @@ -1442,7 +1679,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", ] [[package]] @@ -1472,7 +1709,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", ] [[package]] @@ -1484,7 +1721,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.6", + "toml_edit 0.22.4", ] [[package]] @@ -1504,20 +1741,31 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", - "winnow 0.5.40", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", ] [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.2", + "winnow", ] [[package]] @@ -1539,7 +1787,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", ] [[package]] @@ -1551,12 +1799,54 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-script" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "utf8parse" version = "0.2.1" @@ -1612,7 +1902,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -1634,7 +1924,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1645,6 +1935,16 @@ version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1682,7 +1982,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.0", ] [[package]] @@ -1700,7 +2000,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.0", ] [[package]] @@ -1720,17 +2020,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1741,9 +2041,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -1753,9 +2053,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -1765,9 +2065,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -1777,9 +2077,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -1789,9 +2089,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -1801,9 +2101,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" @@ -1813,9 +2113,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" @@ -1826,21 +2126,21 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" -dependencies = [ - "memchr", -] - [[package]] name = "xdg" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" +[[package]] +name = "xml-rs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "zvariant" version = "3.15.0" @@ -1860,7 +2160,7 @@ version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", diff --git a/Cargo.toml b/Cargo.toml index f20dcf6..5284566 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ include = [ [dependencies] relm4 = { version = "0.6.2", features = ["macros", "libadwaita", "gnome_42"] } -pangocairo = "0.17.10" tokio = { version = "1.32.0", features = ["full"] } gdk-pixbuf = "0.17.2" @@ -38,6 +37,14 @@ serde_derive = "1.0" hex_color = {version = "3", features = ["serde"]} chrono = "0.4.31" +# opengl rendering backend +femtovg = "0.8" +libloading = "0.8" +epoxy = "0.1.0" +glow = "0.13.1" +glib-macros = "0.19.2" +glib = "0.19.2" +resource = "0.5.0" # font emedding [dependencies.relm4-icons] version = "0.6.0" diff --git a/flake.nix b/flake.nix index cc5d463..29726ff 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,8 @@ devShells.default = mkShell { buildInputs = [ pkg-config - + libGL + libepoxy gtk4 wrapGAppsHook4 # this is needed for relm4-icons to properly load after gtk::init() libadwaita diff --git a/src/command_line.rs b/src/command_line.rs index b0f6300..9774bb5 100644 --- a/src/command_line.rs +++ b/src/command_line.rs @@ -34,7 +34,7 @@ pub struct CommandLine { /// Increase or decrease the size of the annotations #[arg(long)] - pub annotation_size_factor: Option, + pub annotation_size_factor: Option, /// After copying the screenshot, save it to a file as well #[arg(long)] diff --git a/src/configuration.rs b/src/configuration.rs index 28f8f09..4d60c04 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -34,7 +34,7 @@ pub struct Configuration { early_exit: bool, initial_tool: Tools, copy_command: Option, - annotation_size_factor: f64, + annotation_size_factor: f32, save_after_copy: bool, color_palette: ColorPalette, default_hide_toolbars: bool, @@ -212,7 +212,7 @@ impl Configuration { self.input_filename.as_ref() } - pub fn annotation_size_factor(&self) -> f64 { + pub fn annotation_size_factor(&self) -> f32 { self.annotation_size_factor } @@ -238,7 +238,7 @@ impl Default for Configuration { early_exit: false, initial_tool: Tools::Pointer, copy_command: None, - annotation_size_factor: 1.0f64, + annotation_size_factor: 1.0, save_after_copy: false, color_palette: ColorPalette::default(), default_hide_toolbars: false, @@ -273,7 +273,7 @@ struct ConfiguationFileGeneral { early_exit: Option, initial_tool: Option, copy_command: Option, - annotation_size_factor: Option, + annotation_size_factor: Option, output_filename: Option, save_after_copy: Option, default_hide_toolbars: Option, diff --git a/src/femtovg_area/imp.rs b/src/femtovg_area/imp.rs new file mode 100644 index 0000000..85b53ce --- /dev/null +++ b/src/femtovg_area/imp.rs @@ -0,0 +1,391 @@ +use anyhow::Result; +use glow::HasContext; +use std::{ + cell::{RefCell, RefMut}, + num::NonZeroU32, + rc::Rc, +}; + +use femtovg::{ + imgref::{Img, ImgVec}, + renderer, + rgb::{RGB, RGBA, RGBA8}, + Canvas, FontId, ImageFlags, ImageId, ImageSource, Paint, Path, PixelFormat, Transform2D, +}; +use gdk_pixbuf::Pixbuf; +use gtk::{glib, prelude::*, subclass::prelude::*}; +use relm4::{gtk, Sender}; +use resource::resource; + +use crate::{ + sketch_board::{Action, SketchBoardInput}, + tools::{CropTool, Drawable, Tool}, +}; + +#[derive(Default)] +pub struct FemtoVGArea { + canvas: RefCell>>, + font: RefCell>, + inner: RefCell>, + request_render: RefCell>, + sender: RefCell>>, +} + +pub struct FemtoVgAreaMut { + background_image: Pixbuf, + background_image_id: Option, + active_tool: Rc>, + crop_tool: Rc>, + scale_factor: f32, + drawables: Vec>, + redo_stack: Vec>, +} + +#[glib::object_subclass] +impl ObjectSubclass for FemtoVGArea { + const NAME: &'static str = "FemtoVGArea"; + type Type = super::FemtoVGArea; + type ParentType = gtk::GLArea; +} + +impl ObjectImpl for FemtoVGArea { + fn constructed(&self) { + self.parent_constructed(); + let area = self.obj(); + area.set_has_stencil_buffer(true); + area.queue_render(); + } +} + +impl WidgetImpl for FemtoVGArea { + fn realize(&self) { + self.parent_realize(); + } + fn unrealize(&self) { + self.obj().make_current(); + self.canvas.borrow_mut().take(); + self.parent_unrealize(); + } +} + +impl GLAreaImpl for FemtoVGArea { + fn resize(&self, width: i32, height: i32) { + self.ensure_canvas(); + let mut bc = self.canvas.borrow_mut(); + let canvas = bc.as_mut().unwrap(); // this unwrap is safe as long as we call "ensure_canvas" before + + canvas.set_size( + width as u32, + height as u32, + self.obj().scale_factor() as f32, + ); + } + fn render(&self, _context: >k::gdk::GLContext) -> bool { + self.ensure_canvas(); + + let mut bc = self.canvas.borrow_mut(); + let canvas = bc.as_mut().unwrap(); // this unwrap is safe as long as we call "ensure_canvas" before + let font = self.font.borrow().unwrap(); // this unwrap is safe as long as we call "ensure_canvas" before + let mut action = self.request_render.borrow_mut(); + + // if we got requested to render a frame + if let Some(a) = action.as_ref() { + // render image + let image = match self + .inner() + .as_mut() + .expect("Did you call init before using FemtoVgArea?") + .render_native_resolution(canvas, font) + { + Ok(t) => t, + Err(e) => { + println!("Error while rendering image: {e}"); + return false; + } + }; + + // send result + self.sender + .borrow() + .as_ref() + .expect("Did you call init before using FemtoVgArea?") + .emit(SketchBoardInput::RenderResult(image, *a)); + + // reset request + *action = None; + } + if let Err(e) = self + .inner() + .as_mut() + .expect("Did you call init before using FemtoVgArea?") + .render_framebuffer(canvas, font) + { + println!("Error rendering to framebuffer: {e}"); + } + false + } +} +impl FemtoVGArea { + pub fn init( + &self, + sender: Sender, + crop_tool: Rc>, + active_tool: Rc>, + background_image: Pixbuf, + ) { + self.inner().replace(FemtoVgAreaMut { + background_image, + background_image_id: None, + active_tool, + crop_tool, + scale_factor: 1.0, + drawables: Vec::new(), + redo_stack: Vec::new(), + }); + self.sender.borrow_mut().replace(sender); + } + fn ensure_canvas(&self) { + if self.canvas.borrow().is_none() { + let c = self + .setup_canvas() + .expect("Cannot setup renderer and canvas"); + self.canvas.borrow_mut().replace(c); + } + + self.font.borrow_mut().replace( + self.canvas + .borrow_mut() + .as_mut() + .unwrap() // this unwrap is safe because it gets placed above + .add_font_mem(&resource!("src/assets/Roboto-Regular.ttf")) + .expect("Cannot add font"), + ); + } + + fn setup_canvas(&self) -> Result> { + let widget = self.obj(); + widget.attach_buffers(); + + static LOAD_FN: fn(&str) -> *const std::ffi::c_void = + |s| epoxy::get_proc_addr(s) as *const _; + // SAFETY: Need to get the framebuffer id that gtk expects us to draw into, so + // femtovg knows which framebuffer to bind. This is safe as long as we + // call attach_buffers beforehand. Also unbind it here just in case, + // since this can be called outside render. + let (mut renderer, fbo) = unsafe { + let renderer = + renderer::OpenGl::new_from_function(LOAD_FN).expect("Cannot create renderer"); + let ctx = glow::Context::from_loader_function(LOAD_FN); + let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32) + .expect("No GTK provided framebuffer binding"); + ctx.bind_framebuffer(glow::FRAMEBUFFER, None); + (renderer, glow::NativeFramebuffer(id)) + }; + renderer.set_screen_target(Some(fbo)); + Ok(Canvas::new(renderer)?) + } + + pub fn inner(&self) -> RefMut<'_, Option> { + self.inner.borrow_mut() + } + pub fn request_render(&self, action: Action) { + self.request_render.borrow_mut().replace(action); + self.obj().queue_render(); + } + pub fn set_parent_sender(&self, sender: Sender) { + self.sender.borrow_mut().replace(sender); + } +} + +impl FemtoVgAreaMut { + pub fn commit(&mut self, drawable: Box) { + self.drawables.push(drawable); + self.redo_stack.clear(); + } + + pub fn undo(&mut self) -> bool { + match self.drawables.pop() { + Some(mut d) => { + // notify of the undo action + d.handle_undo(); + + // push to redo stack + self.redo_stack.push(d); + true + } + None => false, + } + } + pub fn redo(&mut self) -> bool { + match self.redo_stack.pop() { + Some(mut d) => { + // notify of the redo action + d.handle_redo(); + + // push to drawable stack + self.drawables.push(d); + + true + } + None => false, + } + } + + pub fn set_active_tool(&mut self, active_tool: Rc>) { + self.active_tool = active_tool; + } + + pub fn set_scale_factor(&mut self, scale_factor: f32) { + self.scale_factor = scale_factor; + } + + pub fn render_native_resolution( + &mut self, + canvas: &mut femtovg::Canvas, + font: FontId, + ) -> anyhow::Result> { + let image_id = canvas.create_image_empty( + self.background_image.width() as usize, + self.background_image.height() as usize, + PixelFormat::Rgba8, + ImageFlags::empty(), + )?; + + canvas.set_render_target(femtovg::RenderTarget::Image(image_id)); + canvas.reset_transform(); + + self.render(canvas, font)?; + + Ok(canvas.screenshot()?) + } + + pub fn render_framebuffer( + &mut self, + canvas: &mut femtovg::Canvas, + font: FontId, + ) -> Result<()> { + canvas.set_render_target(femtovg::RenderTarget::Screen); + + // setup transform to image coordinates + let mut transform = Transform2D::identity(); + transform.scale(self.scale_factor, self.scale_factor); + canvas.reset_transform(); + canvas.set_transform(&transform); + + self.render(canvas, font)?; + + Ok(()) + } + + fn render( + &mut self, + canvas: &mut femtovg::Canvas, + font: FontId, + ) -> Result<()> { + // clear canvas + canvas.clear_rect( + 0, + 0, + canvas.width(), + canvas.height(), + femtovg::Color::black(), + ); + + // render background + self.render_background_image(canvas)?; + + // render the whole stack + for d in &mut self.drawables { + d.draw(canvas, font)?; + } + + // render active tool + if let Some(d) = self.active_tool.borrow().get_drawable() { + d.draw(canvas, font)?; + } + + // render crop tool + if let Some(c) = self.crop_tool.borrow().get_crop() { + c.draw(canvas, font)?; + } + + canvas.flush(); + Ok(()) + } + + fn render_background_image( + &mut self, + canvas: &mut femtovg::Canvas, + ) -> Result<()> { + let background_image_id = match self.background_image_id { + Some(id) => id, + None => { + let id = Self::upload_background_image(canvas, &self.background_image)?; + self.background_image_id.replace(id); + id + } + }; + + // render the image + let mut path = Path::new(); + path.rect( + 0.0, + 0.0, + self.background_image.width() as f32, + self.background_image.height() as f32, + ); + + canvas.fill_path( + &path, + &Paint::image( + background_image_id, + 0f32, + 0f32, + self.background_image.width() as f32, + self.background_image.height() as f32, + 0f32, + 1f32, + ), + ); + + Ok(()) + } + + fn upload_background_image( + canvas: &mut femtovg::Canvas, + image: &Pixbuf, + ) -> Result { + let format = if image.has_alpha() { + PixelFormat::Rgba8 + } else { + PixelFormat::Rgb8 + }; + + let background_image_id = canvas.create_image_empty( + image.width() as usize, + image.height() as usize, + format, + ImageFlags::empty(), + )?; + + let img = unsafe { + if image.has_alpha() { + let img = Img::new( + image.pixels().align_to::>().1, + image.width() as usize, + image.height() as usize, + ); + ImageSource::Rgba(img) + } else { + let img = Img::new( + image.pixels().align_to::>().1, + image.width() as usize, + image.height() as usize, + ); + ImageSource::Rgb(img) + } + }; + canvas.update_image(background_image_id, img, 0, 0)?; + Ok(background_image_id) + } +} diff --git a/src/femtovg_area/mod.rs b/src/femtovg_area/mod.rs new file mode 100644 index 0000000..c48e338 --- /dev/null +++ b/src/femtovg_area/mod.rs @@ -0,0 +1,79 @@ +mod imp; + +use std::{cell::RefCell, rc::Rc}; + +use gdk_pixbuf::{glib::subclass::types::ObjectSubclassIsExt, Pixbuf}; +use gtk::glib; +use relm4::{ + gtk::{self}, + Sender, +}; + +use crate::{ + sketch_board::{Action, SketchBoardInput}, + tools::{CropTool, Drawable, Tool}, +}; + +glib::wrapper! { + pub struct FemtoVGArea(ObjectSubclass) + @extends gtk::Widget, gtk::GLArea; +} + +impl Default for FemtoVGArea { + fn default() -> Self { + glib::Object::new() + } +} + +impl FemtoVGArea { + pub fn set_scale_factor(&mut self, scale_factor: f32) { + self.imp() + .inner() + .as_mut() + .expect("Did you call init before usind FemtoVgArea?") + .set_scale_factor(scale_factor); + } + pub fn set_active_tool(&mut self, active_tool: Rc>) { + self.imp() + .inner() + .as_mut() + .expect("Did you call init before using FemtoVgArea?") + .set_active_tool(active_tool); + } + + pub fn commit(&mut self, drawable: Box) { + self.imp() + .inner() + .as_mut() + .expect("Did you call init before using FemtoVgArea?") + .commit(drawable); + } + pub fn undo(&mut self) -> bool { + self.imp() + .inner() + .as_mut() + .expect("Did you call init before using FemtoVgArea?") + .undo() + } + pub fn redo(&mut self) -> bool { + self.imp() + .inner() + .as_mut() + .expect("Did you call init before using FemtoVgArea?") + .redo() + } + pub fn request_render(&self, action: Action) { + self.imp().request_render(action); + } + + pub fn init( + &mut self, + sender: Sender, + crop_tool: Rc>, + active_tool: Rc>, + background_image: Pixbuf, + ) { + self.imp() + .init(sender, crop_tool, active_tool, background_image); + } +} diff --git a/src/main.rs b/src/main.rs index b17ead5..06e36c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use std::io::Read; +use std::ptr; use std::{io, time::Duration}; use configuration::{Configuration, APP_CONFIG}; @@ -19,8 +20,8 @@ use ui::toolbars::{StyleToolbar, StyleToolbarInput, ToolsToolbar, ToolsToolbarIn mod command_line; mod configuration; +mod femtovg_area; mod math; -mod renderer; mod sketch_board; mod style; mod tools; @@ -263,8 +264,33 @@ impl Component for App { } } +fn load_gl() -> Result<()> { + // Load GL pointers from epoxy (GL context management library used by GTK). + #[cfg(target_os = "macos")] + let library = unsafe { libloading::os::unix::Library::new("libepoxy.0.dylib") }?; + #[cfg(all(unix, not(target_os = "macos")))] + let library = unsafe { libloading::os::unix::Library::new("libepoxy.so.0") }?; + #[cfg(windows)] + let library = libloading::os::windows::Library::open_already_loaded("libepoxy-0.dll") + .or_else(|_| libloading::os::windows::Library::open_already_loaded("epoxy-0.dll"))?; + + epoxy::load_with(|name| { + unsafe { library.get::<_>(name.as_bytes()) } + .map(|symbol| *symbol) + .unwrap_or(ptr::null()) + }); + + Ok(()) +} + fn run_satty() -> Result<()> { + // load OpenGL + load_gl()?; + + // load app config let config = APP_CONFIG.read(); + + // load input image let image = if config.input_filename() == "-" { let mut buf = Vec::::new(); io::stdin().lock().read_to_end(&mut buf)?; @@ -278,6 +304,7 @@ fn run_satty() -> Result<()> { Pixbuf::from_file(config.input_filename()).context("couldn't load image")? }; + // start GUI let app = RelmApp::new("com.gabm.satty").with_args(vec![]); relm4_icons::initialize_icons(); app.run::(image); diff --git a/src/math.rs b/src/math.rs index 800cfb3..8b0f9aa 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,36 +1,36 @@ use std::{ - f64::consts::PI, + f32::consts::PI, fmt::Display, ops::{Add, AddAssign, Sub, SubAssign}, }; #[derive(Default, Debug, Copy, Clone, PartialEq)] pub struct Vec2D { - pub x: f64, - pub y: f64, + pub x: f32, + pub y: f32, } impl Vec2D { pub fn zero() -> Self { - Self { x: 0f64, y: 0f64 } + Self { x: 0.0, y: 0.0 } } - pub fn new(x: f64, y: f64) -> Self { + pub fn new(x: f32, y: f32) -> Self { Self { x, y } } - pub fn norm(&self) -> f64 { + pub fn norm(&self) -> f32 { (self.x * self.x + self.y * self.y).sqrt() } - pub fn norm2(&self) -> f64 { + pub fn norm2(&self) -> f32 { self.x * self.x + self.y * self.y } pub fn snapped_vector_15deg(&self) -> Vec2D { let current_angle = (self.y / self.x).atan(); let current_norm2 = self.norm2(); - let new_angle = (current_angle / 0.26179938782).round() * 0.2617993878; + let new_angle = (current_angle / 0.261_799_4).round() * 0.261_799_4; let (a, b) = if new_angle.abs() < PI / 4.0 // 45° diff --git a/src/renderer.rs b/src/renderer.rs deleted file mode 100644 index be523aa..0000000 --- a/src/renderer.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use anyhow::Result; -use gdk_pixbuf::glib::Bytes; -use gdk_pixbuf::Pixbuf; -use pangocairo::cairo::{Context, Format, ImageSurface, Operator}; - -use relm4::gtk::gdk::{MemoryFormat, MemoryTexture}; -use relm4::gtk::prelude::*; - -use crate::tools::CropTool; -use crate::tools::Drawable; -use crate::tools::Tool; - -pub struct Renderer { - original_image: ImageSurface, - crop_tool: Rc>, - drawables: Vec>, - redo_stack: Vec>, -} - -impl Renderer { - pub fn new(original_image: Pixbuf, crop_tool: Rc>) -> Result { - let original_image_surface = ImageSurface::create( - Format::ARgb32, - original_image.width(), - original_image.height(), - )?; - - // render background image - let cx: Context = Context::new(original_image_surface.clone())?; - cx.set_operator(Operator::Over); - - cx.set_source_pixbuf(&original_image, 0.0, 0.0); - cx.paint()?; - - Ok(Self { - original_image: original_image_surface, - drawables: Vec::new(), - redo_stack: Vec::new(), - crop_tool, - }) - } - - pub fn render_full_size( - &self, - render_crop: bool, - active_tool: &Rc>, - ) -> Result { - let surface = ImageSurface::create( - Format::ARgb32, - self.original_image.width(), - self.original_image.height(), - )?; - - let cx: Context = Context::new(surface.clone())?; - cx.set_operator(Operator::Over); - - // render background image - cx.set_source_surface(&self.original_image, 0.0, 0.0)?; - cx.paint()?; - - // render comitted drawables - for da in &self.drawables { - da.draw(&cx, &surface)?; - } - - // render drawable of active tool, if any - if let Some(d) = &active_tool.borrow().get_drawable() { - d.draw(&cx, &surface)?; - } - - if render_crop { - // render crop (even if tool not active) - if let Some(c) = self.crop_tool.borrow().get_crop() { - c.draw(&cx, &surface)?; - } - } - - Ok(surface) - } - - pub fn render_to_window( - &self, - cx: &Context, - scale_factor: f64, - active_tool: &Rc>, - ) -> Result<()> { - let surface = self.render_full_size(true, active_tool)?; - - // render to window - cx.scale(scale_factor, scale_factor); - cx.set_source_surface(surface, 0.0, 0.0)?; - cx.paint()?; - - Ok(()) - } - - pub fn render_with_crop(&self, active_tool: &Rc>) -> Result { - // render final image - let mut surface = self.render_full_size(false, active_tool)?; - - if let Some((pos, size)) = self - .crop_tool - .borrow() - .get_crop() - .and_then(|c| c.get_rectangle()) - { - // crop the full size render to target values - let cropped_surface = - ImageSurface::create(Format::ARgb32, size.x as i32, size.y as i32)?; - let cropped_cx = Context::new(cropped_surface.clone())?; - cropped_cx.set_source_surface(surface, -pos.x, -pos.y)?; - cropped_cx.paint()?; - - surface = cropped_surface.clone(); - } - - Ok(surface) - } - - pub fn render_to_texture(&self, active_tool: &Rc>) -> Result { - let mut surface = self.render_with_crop(active_tool)?; - - let height = surface.height(); - let width = surface.width(); - let stride = surface.stride() as usize; - let data = surface.data()?; - - let texture = MemoryTexture::new( - width, - height, - MemoryFormat::B8g8r8a8Premultiplied, - &Bytes::from(&*data), - stride, - ); - - Ok(texture) - } - - pub fn commit(&mut self, drawable: Box) { - self.drawables.push(drawable); - self.redo_stack.clear(); - } - - pub fn undo(&mut self) -> bool { - match self.drawables.pop() { - Some(mut d) => { - // notify of the undo action - d.handle_undo(); - - // push to redo stack - self.redo_stack.push(d); - true - } - None => false, - } - } - pub fn redo(&mut self) -> bool { - match self.redo_stack.pop() { - Some(mut d) => { - // notify of the redo action - d.handle_redo(); - - // push to drawable stack - self.drawables.push(d); - - true - } - None => false, - } - } -} diff --git a/src/sketch_board.rs b/src/sketch_board.rs index c5aec3c..75f44bd 100644 --- a/src/sketch_board.rs +++ b/src/sketch_board.rs @@ -1,5 +1,8 @@ use anyhow::anyhow; +use femtovg::imgref::Img; +use femtovg::rgb::{ComponentBytes, RGBA}; +use gdk_pixbuf::glib::Bytes; use gdk_pixbuf::Pixbuf; use std::cell::RefCell; use std::fs; @@ -9,22 +12,30 @@ use std::rc::Rc; use gtk::prelude::*; -use relm4::drawing::DrawHandler; -use relm4::gtk::gdk::{DisplayManager, Key, MemoryTexture, ModifierType}; +use relm4::gtk::gdk::{DisplayManager, Key, ModifierType, Texture}; use relm4::{gtk, Component, ComponentParts, ComponentSender}; use crate::configuration::APP_CONFIG; +use crate::femtovg_area::FemtoVGArea; use crate::math::Vec2D; -use crate::renderer::Renderer; use crate::style::Style; use crate::tools::{Tool, ToolEvent, ToolUpdateResult, ToolsManager}; use crate::ui::toolbars::ToolbarEvent; +type RenderedImage = Img>>; + #[derive(Debug, Clone)] pub enum SketchBoardInput { InputEvent(InputEvent), Resize(Vec2D), ToolbarEvent(ToolbarEvent), + RenderResult(RenderedImage, Action), +} + +#[derive(Debug, Clone, Copy)] +pub enum Action { + SaveToClipboard, + SaveToFile, } #[derive(Debug, Clone)] @@ -112,26 +123,25 @@ impl From for MouseButton { } impl InputEvent { - fn screen2image(p: &mut Vec2D, scale: f64) { + fn screen2image(p: &mut Vec2D, scale: f32) { p.x /= scale; p.y /= scale; } - fn remap_event_coordinates(&mut self, scale: f64) { - if let InputEvent::Mouse(me) = self { + fn remap_event_coordinates(&mut self, scale: f32) { + if let InputEvent::MouseEvent(me) = self { Self::screen2image(&mut me.pos, scale) }; } } pub struct SketchBoard { - handler: DrawHandler, + renderer: FemtoVGArea, active_tool: Rc>, tools: ToolsManager, style: Style, - renderer: Renderer, image_dimensions: Vec2D, - scale_factor: f64, + scale_factor: f32, } impl SketchBoard { @@ -142,30 +152,45 @@ impl SketchBoard { } else { new_dimensions.y * aspect_ratio / self.image_dimensions.x }; + + self.renderer.set_scale_factor(self.scale_factor); } fn refresh_screen(&mut self) { - let cx = self.handler.get_context(); - if let Err(e) = self - .renderer - .render_to_window(&cx, self.scale_factor, &self.active_tool) - { - println!("Error drawing: {:?}", e); - } + self.renderer.queue_render(); } - fn handle_save(&self, sender: ComponentSender) { - let texture = match self.renderer.render_to_texture(&self.active_tool) { - Ok(t) => t, - Err(e) => { - println!("Error while creating texture: {e}"); - return; + fn image_to_pixbuf(image: RenderedImage) -> Pixbuf { + let (buf, w, h) = image.into_contiguous_buf(); + + Pixbuf::from_bytes( + &Bytes::from(buf.as_bytes()), + gdk_pixbuf::Colorspace::Rgb, + true, + 8, + w as i32, + h as i32, + w as i32 * 4, + ) + } + + fn handle_render_result( + &self, + sender: ComponentSender, + image: RenderedImage, + action: Action, + ) { + match action { + Action::SaveToClipboard => { + self.handle_copy_clipboard(sender, Self::image_to_pixbuf(image)) } + Action::SaveToFile => self.handle_save(sender, Self::image_to_pixbuf(image)), }; - - self.handle_save_texture(sender, &texture); + if APP_CONFIG.read().early_exit() { + relm4::main_application().quit(); + } } - fn handle_save_texture(&self, sender: ComponentSender, texture: &MemoryTexture) { + fn handle_save(&self, sender: ComponentSender, image: Pixbuf) { let output_filename = match APP_CONFIG.read().output_filename() { None => { println!("No Output filename specified!"); @@ -177,6 +202,7 @@ impl SketchBoard { // run the output filename by "chrono date format" let output_filename = format!("{}", chrono::Local::now().format(&output_filename)); + // TODO: we could support more data types if !output_filename.ends_with(".png") { let msg = "The only supported format is png, but the filename does not end in png"; println!("{msg}"); @@ -186,7 +212,13 @@ impl SketchBoard { return; } - let data = texture.save_to_png_bytes(); + let data = match image.save_to_bufferv("png", &Vec::new()) { + Ok(d) => d, + Err(e) => { + println!("Error serializing image: {e}"); + return; + } + }; let msg = match fs::write(&output_filename, data) { Err(e) => format!("Error while saving file: {e}"), @@ -198,7 +230,7 @@ impl SketchBoard { .emit(SketchBoardOutput::ShowToast(msg)); } - fn save_to_clipboard(&self, texture: &MemoryTexture) -> anyhow::Result<()> { + fn save_to_clipboard(&self, texture: &impl IsA) -> anyhow::Result<()> { let display = DisplayManager::get() .default_display() .ok_or(anyhow!("Cannot open default display for clipboard."))?; @@ -209,7 +241,7 @@ impl SketchBoard { fn save_to_external_process( &self, - texture: &MemoryTexture, + texture: &impl IsA, command: &str, ) -> anyhow::Result<()> { let mut child = Command::new(command) @@ -227,14 +259,8 @@ impl SketchBoard { Ok(()) } - fn handle_copy_clipboard(&self, sender: ComponentSender) { - let texture = match self.renderer.render_to_texture(&self.active_tool) { - Ok(t) => t, - Err(e) => { - println!("Error while creating texture: {e}"); - return; - } - }; + fn handle_copy_clipboard(&self, sender: ComponentSender, image: Pixbuf) { + let texture = Texture::for_pixbuf(&image); let result = if let Some(command) = APP_CONFIG.read().copy_command() { self.save_to_external_process(&texture, command) @@ -249,8 +275,9 @@ impl SketchBoard { "Copied to clipboard.".to_string(), )); + // TODO: rethink order and messaging patterns if APP_CONFIG.read().save_after_copy() { - self.handle_save_texture(sender, &texture); + self.handle_save(sender, image); }; } } @@ -283,11 +310,7 @@ impl SketchBoard { ToolUpdateResult::Unmodified } - fn handle_toolbar_event( - &mut self, - toolbar_event: ToolbarEvent, - sender: ComponentSender, - ) -> ToolUpdateResult { + fn handle_toolbar_event(&mut self, toolbar_event: ToolbarEvent) -> ToolUpdateResult { match toolbar_event { ToolbarEvent::ToolSelected(tool) => { // deactivate old tool and save drawable, if any @@ -304,6 +327,7 @@ impl SketchBoard { // change active tool self.active_tool = self.tools.get(&tool); + self.renderer.set_active_tool(self.active_tool.clone()); // send style event self.active_tool @@ -334,17 +358,11 @@ impl SketchBoard { .handle_event(ToolEvent::StyleChanged(self.style)) } ToolbarEvent::SaveFile => { - self.handle_save(sender); - if APP_CONFIG.read().early_exit() { - relm4::main_application().quit(); - } + self.renderer.request_render(Action::SaveToFile); ToolUpdateResult::Unmodified } ToolbarEvent::CopyClipboard => { - self.handle_copy_clipboard(sender); - if APP_CONFIG.read().early_exit() { - relm4::main_application().quit(); - } + self.renderer.request_render(Action::SaveToClipboard); ToolUpdateResult::Unmodified } ToolbarEvent::Undo => self.handle_undo(), @@ -363,7 +381,7 @@ impl Component for SketchBoard { view! { gtk::Box { #[local_ref] - area -> gtk::DrawingArea { + area -> FemtoVGArea { set_vexpand: true, set_hexpand: true, grab_focus: (), @@ -375,7 +393,7 @@ impl Component for SketchBoard { MouseEventType::BeginDrag, controller.current_button(), controller.current_event_state(), - Vec2D::new(x, y))); + Vec2D::new(x as f32, y as f32))); }, connect_drag_update[sender] => move |controller, x, y| { @@ -383,14 +401,14 @@ impl Component for SketchBoard { MouseEventType::UpdateDrag, controller.current_button(), controller.current_event_state(), - Vec2D::new(x, y))); + Vec2D::new(x as f32, y as f32))); }, connect_drag_end[sender] => move |controller, x, y| { sender.input(SketchBoardInput::new_mouse_event( MouseEventType::EndDrag, controller.current_button(), controller.current_event_state(), - Vec2D::new(x, y) + Vec2D::new(x as f32, y as f32) )); } }, @@ -401,12 +419,12 @@ impl Component for SketchBoard { MouseEventType::Click, controller.current_button(), controller.current_event_state(), - Vec2D::new(x, y))); + Vec2D::new(x as f32, y as f32))); } }, connect_resize[sender] => move |_, x, y| { - sender.input(SketchBoardInput::Resize(Vec2D::new(x as f64,y as f64))); + sender.input(SketchBoardInput::Resize(Vec2D::new(x as f32,y as f32))); } } }, @@ -429,16 +447,10 @@ impl Component for SketchBoard { } else if ke.key == Key::t && ke.modifier == ModifierType::CONTROL_MASK { self.handle_toggle_toolbars_display(sender) } else if ke.key == Key::s && ke.modifier == ModifierType::CONTROL_MASK { - self.handle_save(sender); - if APP_CONFIG.read().early_exit() { - relm4::main_application().quit(); - } + self.renderer.request_render(Action::SaveToFile); ToolUpdateResult::Unmodified } else if ke.key == Key::c && ke.modifier == ModifierType::CONTROL_MASK { - self.handle_copy_clipboard(sender); - if APP_CONFIG.read().early_exit() { - relm4::main_application().quit(); - } + self.renderer.request_render(Action::SaveToClipboard); ToolUpdateResult::Unmodified } else if ke.key == Key::Escape { relm4::main_application().quit(); @@ -450,14 +462,20 @@ impl Component for SketchBoard { .handle_event(ToolEvent::Input(ie)) } } else { - ie.remap_event_coordinates(self.scale_factor); + ie.remap_event_coordinates( + self.scale_factor / self.renderer.scale_factor() as f32, + ); self.active_tool .borrow_mut() .handle_event(ToolEvent::Input(ie)) } } SketchBoardInput::ToolbarEvent(toolbar_event) => { - self.handle_toolbar_event(toolbar_event, sender) + self.handle_toolbar_event(toolbar_event) + } + SketchBoardInput::RenderResult(img, action) => { + self.handle_render_result(sender, img, action); + ToolUpdateResult::Unmodified } }; @@ -480,17 +498,23 @@ impl Component for SketchBoard { let config = APP_CONFIG.read(); let tools = ToolsManager::new(); - let model = Self { - image_dimensions: Vec2D::new(image.width() as f64, image.height() as f64), - handler: DrawHandler::new(), + let mut model = Self { + image_dimensions: Vec2D::new(image.width() as f32, image.height() as f32), + renderer: FemtoVGArea::default(), active_tool: tools.get(&config.initial_tool()), style: Style::default(), - renderer: Renderer::new(image, tools.get_crop_tool()).expect("Can't create renderer"), scale_factor: 1.0, tools, }; - let area = model.handler.drawing_area(); + let area = &mut model.renderer; + area.init( + sender.input_sender().clone(), + model.tools.get_crop_tool(), + model.active_tool.clone(), + image, + ); + let widgets = view_output!(); ComponentParts { model, widgets } diff --git a/src/style.rs b/src/style.rs index ab30c9c..aad53a1 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,11 +1,11 @@ use std::borrow::Cow; +use femtovg::Paint; use gdk_pixbuf::{ glib::{FromVariant, Variant, VariantTy}, prelude::{StaticVariantType, ToVariant}, }; use hex_color::HexColor; -use pangocairo::pango::SCALE; use relm4::gtk::gdk::RGBA; use crate::configuration::APP_CONFIG; @@ -124,12 +124,33 @@ impl From for RGBA { } } +impl From for femtovg::Color { + fn from(value: Color) -> Self { + femtovg::Color { + r: value.r as f32 / 255.0, + g: value.g as f32 / 255.0, + b: value.b as f32 / 255.0, + a: value.a as f32 / 255.0, + } + } +} + impl From for Color { fn from(value: HexColor) -> Self { Self::new(value.r, value.g, value.b, value.a) } } +impl From