From b49b9e97640e8ac9abc08db94a248eb5be46bff6 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 2 Feb 2023 23:52:59 +0100 Subject: [PATCH] More stuff --- Cargo.lock | 761 ++++++++++++++++++++++++++++++++++---- Cargo.toml | 18 +- src/buffer.rs | 120 ++---- src/color.rs | 51 ++- src/draw.rs | 52 +-- src/event.rs | 61 +++ src/fonts.rs | 68 ++-- src/keyboard/mod.rs | 7 +- src/keyboard/state.rs | 3 + src/main.rs | 149 ++++---- src/state.rs | 126 +++++-- src/utils/desktop.rs | 96 ++--- src/utils/inish.rs | 134 +++++++ src/utils/mod.rs | 1 + src/widgets/audio.rs | 238 ++++++++++++ src/widgets/backlight.rs | 7 +- src/widgets/bar_widget.rs | 33 +- src/widgets/battery.rs | 171 ++++----- src/widgets/calendar.rs | 214 ++++++----- src/widgets/clock.rs | 13 +- src/widgets/date.rs | 23 +- src/widgets/launcher.rs | 109 ++++-- src/widgets/layout.rs | 174 +++++++++ src/widgets/line.rs | 14 +- src/widgets/mod.rs | 9 +- src/widgets/widget.rs | 173 +-------- 26 files changed, 2022 insertions(+), 803 deletions(-) create mode 100644 src/event.rs create mode 100644 src/utils/inish.rs create mode 100644 src/widgets/audio.rs create mode 100644 src/widgets/layout.rs diff --git a/Cargo.lock b/Cargo.lock index 6b33d5b..95766ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,14 +9,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6" [[package]] -name = "ahash" -version = "0.7.6" +name = "aho-corasick" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ - "getrandom", - "once_cell", - "version_check", + "memchr", ] [[package]] @@ -28,6 +26,89 @@ dependencies = [ "libc", ] +[[package]] +name = "async-broadcast" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b19760fa2b7301cf235360ffd6d3558b1ed4249edd16d6cca8d690cee265b95" +dependencies = [ + "event-listener", + "futures-core", + "parking_lot", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +dependencies = [ + "async-lock", + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "windows-sys", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] +name = "async-recursion" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + +[[package]] +name = "async-trait" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -55,6 +136,18 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + [[package]] name = "calloop" version = "0.10.5" @@ -105,6 +198,21 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-cstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -120,6 +228,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -175,14 +292,14 @@ dependencies = [ ] [[package]] -name = "dbus" -version = "0.9.6" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "libc", - "libdbus-sys", - "winapi", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -195,6 +312,26 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dlib" version = "0.5.0" @@ -204,12 +341,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "dlv-list" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" - [[package]] name = "downcast-rs" version = "1.2.0" @@ -223,10 +354,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" [[package]] -name = "either" -version = "1.8.0" +name = "enumflags2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" @@ -237,6 +389,68 @@ dependencies = [ "instant", ] +[[package]] +name = "fontconfig" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b0653582d9c82645f95bacd0dc87b49a3126f7a9633be8cafa9c6d17d525847" +dependencies = [ + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -268,13 +482,10 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "iana-time-zone" @@ -319,15 +530,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "js-sys" version = "0.3.60" @@ -349,15 +551,6 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" -[[package]] -name = "libdbus-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b" -dependencies = [ - "pkg-config", -] - [[package]] name = "libloading" version = "0.7.4" @@ -368,6 +561,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "libpulse-binding" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1745b20bfc194ac12ef828f144f0ec2d4a7fe993281fa3567a0bd4969aee6890" +dependencies = [ + "bitflags", + "libc", + "libpulse-sys", + "num-derive", + "num-traits", + "winapi", +] + +[[package]] +name = "libpulse-sys" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2191e6880818d1df4cf72eac8e91dce7a5a52ba0da4b2a5cdafabc22b937eadb" +dependencies = [ + "libc", + "num-derive", + "num-traits", + "pkg-config", + "winapi", +] + [[package]] name = "link-cplusplus" version = "1.0.7" @@ -377,6 +597,16 @@ dependencies = [ "cc", ] +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -429,6 +659,18 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + [[package]] name = "nix" version = "0.25.1" @@ -440,6 +682,7 @@ dependencies = [ "cfg-if", "libc", "memoffset 0.6.5", + "pin-utils", ] [[package]] @@ -477,6 +720,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -515,13 +769,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] -name = "ordered-multimap" -version = "0.4.3" +name = "ordered-stream" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +checksum = "d4eb9ba3f3e42dbdd3b7b122de5ca169c81e93d561eb900da3a8c99bcfcf381a" dependencies = [ - "dlv-list", - "hashbrown", + "futures-core", + "pin-project-lite", ] [[package]] @@ -533,6 +787,35 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "pest" version = "2.5.2" @@ -577,6 +860,12 @@ dependencies = [ "sha1", ] +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + [[package]] name = "pin-utils" version = "0.1.0" @@ -589,6 +878,37 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "polling" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "windows-sys", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro2" version = "1.0.47" @@ -616,6 +936,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rcalc_lib" version = "1.0.0" @@ -641,6 +991,34 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -650,16 +1028,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rust-ini" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - [[package]] name = "rusttype" version = "0.9.3" @@ -685,12 +1053,49 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "scratch" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_repr" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.5" @@ -708,6 +1113,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + [[package]] name = "slotmap" version = "1.0.6" @@ -723,6 +1137,16 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -803,6 +1227,75 @@ dependencies = [ "winapi", ] +[[package]] +name = "tokio" +version = "1.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "pin-project-lite", + "socket2", + "tracing", + "windows-sys", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + [[package]] name = "ttf-parser" version = "0.15.2" @@ -821,6 +1314,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + [[package]] name = "unicode-ident" version = "1.0.5" @@ -839,6 +1342,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "upower_dbus" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b1d77de98ab2e5187f5fa2b045b8c4eecfc49e5ccd07f16f4c89f008116f8c" +dependencies = [ + "serde", + "serde_repr", + "zbus", +] + [[package]] name = "vec_map" version = "0.8.2" @@ -851,6 +1365,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -991,6 +1511,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1086,21 +1615,23 @@ dependencies = [ "bitflags", "calloop", "chrono", - "dbus", + "fontconfig", "fuzzy-matcher", - "itertools", + "libpulse-binding", "memmap", "nix 0.26.1", "rcalc_lib", - "rust-ini", "rusttype", "shlex", - "tempfile", + "tokio", + "tokio-stream", "unicode-segmentation", + "upower_dbus", "walkdir", "wayland-client", "wayland-protocols", "xkbcommon", + "zbus", ] [[package]] @@ -1112,3 +1643,105 @@ dependencies = [ "libc", "memmap2", ] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec657fd32bbcbeaef5c7bc8e10b3db95b143fab8db0a50079773dbf936fd4f73" +dependencies = [ + "const-cstr", + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "zbus" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379d587c0ccb632d1179cf44082653f682842f0535f0fdfaefffc34849cc855e" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-recursion", + "async-task", + "async-trait", + "byteorder", + "derivative", + "dirs", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "lazy_static", + "nix 0.25.1", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "winapi", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66492a2e90c0df7190583eccb8424aa12eb7ff06edea415a4fff6688fae18cf8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "zbus_names" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zvariant" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576cc41e65c7f283e5460f5818073e68fb1f1631502b969ef228c2e03c862efb" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd4aafc0dee96ae7242a24249ce9babf21e1562822f03df650d4e68c20e41ed" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 372eb25..8bf8b4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,26 +4,25 @@ version = "0.1.0" edition = "2021" [profile.release] +#debug = true lto = "fat" panic = "abort" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +fontloading = ["fontconfig"] [dependencies] wayland-client = { version = "0.30", features = ["calloop", "log"] } wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] } -tempfile = "3.3" nix = "0.26" memmap = "0.7" -#fontconfig = "0.6.0" -rusttype = "^0.9" +fontconfig = { version = "0.6.0", optional = true } +rusttype = "0.9" calloop = { version = "0.10" } chrono = "0.4" walkdir = "2.3" -itertools = "0.10" -rust-ini = "0.18" - bitflags = "1.3" unicode-segmentation = "1.10" @@ -31,6 +30,11 @@ fuzzy-matcher = "0.3" shlex = "1.1" xkbcommon = { version = "0.5", features = ["wayland"] } -dbus = "0.9" +upower_dbus = "0.3" +zbus = { version = "3.7", default-features = false, features = ["tokio"] } +tokio = { version = "1.24", default-features = false, features = ["rt"] } +tokio-stream = "0.1" rcalc_lib = "1.0" + +libpulse-binding = "2.26" diff --git a/src/buffer.rs b/src/buffer.rs index 685583b..4be85fa 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -170,20 +170,27 @@ impl BufferManager { } pub struct BufferView<'a> { - buf: &'a mut MmapMut, + view: &'a mut [u32], dimensions: (u32, u32), subdimensions: Option<(u32, u32, u32, u32)>, } impl<'a> BufferView<'a> { pub fn new(buf: &'a mut MmapMut, dimensions: (u32, u32)) -> BufferView { + let view = unsafe { + std::slice::from_raw_parts_mut( + buf.as_mut_ptr() as *mut u32, + (dimensions.0 * dimensions.1) as usize, + ) + }; BufferView { - buf, + view, dimensions, subdimensions: None, } } + #[inline] pub fn get_bounds(&self) -> (u32, u32, u32, u32) { if let Some(subdim) = self.subdimensions { subdim @@ -195,22 +202,16 @@ impl<'a> BufferView<'a> { pub fn subdimensions( &mut self, subdimensions: (u32, u32, u32, u32), - ) -> Result { + ) -> BufferView { let bounds = self.get_bounds(); - if subdimensions.0 + subdimensions.2 > bounds.2 - || subdimensions.1 + subdimensions.3 > bounds.3 + if cfg!(debug_assertions) && (subdimensions.0 + subdimensions.2 > bounds.2 + || subdimensions.1 + subdimensions.3 > bounds.3) { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "cannot create subdimensions larger than buffer: {:?} > {:?}", - subdimensions, bounds - ), - )); + panic!("cannot create subdimensions larger than buffer: {:?} > {:?}", subdimensions, bounds); } - Ok(BufferView { - buf: self.buf, + BufferView { + view: self.view, dimensions: self.dimensions, subdimensions: Some(( subdimensions.0 + bounds.0, @@ -218,27 +219,21 @@ impl<'a> BufferView<'a> { subdimensions.2, subdimensions.3, )), - }) + } } - pub fn subgeometry(&mut self, geo: Geometry) -> Result { + pub fn subgeometry(&mut self, geo: Geometry) -> BufferView { self.subdimensions((geo.x, geo.y, geo.width, geo.height)) } - pub fn offset(&mut self, offset: (u32, u32)) -> Result { + pub fn offset(&mut self, offset: (u32, u32)) -> BufferView { let bounds = self.get_bounds(); - if offset.0 > bounds.2 || offset.1 > bounds.3 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "cannot create offset outside buffer: {:?} > {:?}", - offset, bounds - ), - )); + if cfg!(debug_assertions) && (offset.0 > bounds.2 || offset.1 > bounds.3) { + panic!("cannot create offset outside buffer: {:?} > {:?}", offset, bounds); } - Ok(BufferView { - buf: self.buf, + BufferView { + view: self.view, dimensions: self.dimensions, subdimensions: Some(( offset.0 + bounds.0, @@ -246,82 +241,47 @@ impl<'a> BufferView<'a> { bounds.2 - offset.0, bounds.3 - offset.1, )), - }) + } } - pub fn limit(&mut self, dimensions: (u32, u32)) -> Result { + pub fn limit(&mut self, dimensions: (u32, u32)) -> BufferView { let bounds = self.get_bounds(); - if dimensions.0 > bounds.2 || dimensions.1 > bounds.3 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "cannot create offset outside buffer: {:?} > {:?}", - dimensions, bounds - ), - )); + if cfg!(debug_assertions) && (dimensions.0 > bounds.2 || dimensions.1 > bounds.3) { + panic!("cannot create offset outside buffer: {:?} > {:?}", dimensions, bounds); } - Ok(BufferView { - buf: self.buf, + BufferView { + view: self.view, dimensions: self.dimensions, subdimensions: Some((bounds.0, bounds.1, dimensions.0, dimensions.1)), - }) + } } - pub fn memset(&mut self, c: &Color) { - let v = c.as_argb8888(); + pub fn memset(&mut self, c: Color) { if let Some(subdim) = self.subdimensions { - unsafe { - let ptr = self.buf.as_mut_ptr(); - for y in subdim.1..(subdim.1 + subdim.3) { - for x in subdim.0..(subdim.0 + subdim.2) { - *((ptr as *mut u32).offset((x + y * self.dimensions.0) as isize)) = v; - } + for y in subdim.1..(subdim.1 + subdim.3) { + for x in subdim.0..(subdim.0 + subdim.2) { + self.view[(x + y * self.dimensions.0) as usize] = c.0; } } } else { - unsafe { - let ptr = self.buf.as_mut_ptr(); - for p in 0..(self.dimensions.0 * self.dimensions.1) { - *((ptr as *mut u32).offset(p as isize)) = v; - } - } + self.view.fill(c.0); } } - pub fn put(&mut self, pos: (u32, u32), c: &Color) -> Result<(), ::std::io::Error> { + pub fn put(&mut self, pos: (u32, u32), c: Color) { let true_pos = if let Some(subdim) = self.subdimensions { - if pos.0 >= subdim.2 || pos.1 >= subdim.3 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "put({:?}) is not within subdimensions of buffer ({:?})", - pos, subdim - ), - )); + if cfg!(debug_assertions) && (pos.0 >= subdim.2 || pos.1 >= subdim.3) { + panic!("put({:?}) is not within subdimensions of buffer ({:?})", pos, subdim); } (pos.0 + subdim.0, pos.1 + subdim.1) } else { - if pos.0 >= self.dimensions.0 || pos.1 >= self.dimensions.1 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "put({:?}) is not within dimensions of buffer ({:?})", - pos, self.dimensions - ), - )); + if cfg!(debug_assertions) && (pos.0 >= self.dimensions.0 || pos.1 >= self.dimensions.1) { + panic!("put({:?}) is not within dimensions of buffer ({:?})", pos, self.dimensions); } pos }; - unsafe { - let ptr = self - .buf - .as_mut_ptr() - .offset(4 * (true_pos.0 + (true_pos.1 * self.dimensions.0)) as isize); - *(ptr as *mut u32) = c.as_argb8888(); - }; - - Ok(()) + self.view[(true_pos.0 + (true_pos.1 * self.dimensions.0)) as usize] = c.0; } } diff --git a/src/color.rs b/src/color.rs index 67675b2..f911120 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,5 +1,5 @@ //use serde::{Deserialize, Serialize}; - +/* //#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] pub struct Color { red: f32, @@ -37,6 +37,11 @@ impl Color { | ((255.0 * self.green) as u32 & 0xFF) << 8 | ((255.0 * self.blue) as u32 & 0xFF) } + + #[inline] + pub fn as_integer(&self) -> IntegerColor { + IntegerColor(self.as_argb8888()) + } } #[inline] @@ -69,3 +74,47 @@ fn clamp_f32(v: f32, a: f32, b: f32) -> f32 { fn blend_f32(a: f32, b: f32, r: f32) -> f32 { a + ((b - a) * r) } +*/ +#[inline] +fn blend_u8(a: u8, b: u8, r: f32) -> u32 { + let af = a as f32; + let bf = b as f32; + (af + ((bf - af) * r)) as u32 +} + +#[derive(Copy, Clone)] +pub struct Color(pub u32); + +impl Color { + pub const BLACK: Color = Color(0xFF000000); + pub const WHITE: Color = Color(0xFFFFFFFF); + pub const GREY50: Color = Color(0xFF7F7F7F); + pub const GREY75: Color = Color(0xFFBFBFBF); + pub const GREY80: Color = Color(0xFFCCCCCC); + pub const RED: Color = Color(0xFFFF0000); + pub const YELLOW: Color = Color(0xFFFFFF00); + pub const LIGHTGREEN: Color = Color(0xFF7FFF7F); + pub const LIGHTRED: Color = Color(0xFFFF7F7F); + pub const LIGHTORANGE: Color = Color(0xFFFFBF00); + pub const DARKORANGE: Color = Color(0xFFFF7F00); + pub const BUFF: Color = Color(0xFFFFBF7F); + + #[allow(unused)] + pub fn new(red: u8, green: u8, blue: u8, opacity: u8) -> Color { + Color((opacity as u32) << 24 | (red as u32) << 16 | (green as u32) << 8 | (blue as u32)) + } + + fn opacity(self) -> u8 {((self.0 >> 24) & 0xFF) as u8 } + fn red(self) -> u8 {((self.0 >> 16) & 0xFF) as u8 } + fn green(self) -> u8 {((self.0 >> 8) & 0xFF) as u8 } + fn blue(self) -> u8 {(self.0 & 0xFF) as u8 } + + pub fn blend(self, other: Color, ratio: f32) -> Color { + Color( + blend_u8(self.opacity(), other.opacity(), ratio) << 24 | + blend_u8(self.red(), other.red(), ratio) << 16 | + blend_u8(self.green(), other.green(), ratio) << 8 | + blend_u8(self.blue(), other.blue(), ratio) + ) + } +} diff --git a/src/draw.rs b/src/draw.rs index 747dc6c..3bafd4a 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -47,7 +47,7 @@ impl CachedGlyph { } } - fn draw(&self, buf: &mut BufferView, pos: (i32, i32), bg: &Color, c: &Color) { + fn draw(&self, buf: &mut BufferView, pos: (i32, i32), bg: Color, c: Color) { let mut x = 0; let mut y = 0; for v in &self.render { @@ -56,7 +56,7 @@ impl CachedGlyph { (x + pos.0 + self.origin.0) as u32, (y + pos.1 + self.origin.1) as u32, ), - &bg.blend(&c, *v), + bg.blend(c, *v), ); if x == self.dimensions.0 as i32 - 1 { @@ -84,6 +84,12 @@ impl<'a> Font<'a> { } } + pub fn height(&self) -> f32 { + let scale = Scale::uniform(self.size); + let m = self.font.v_metrics(scale); + m.ascent - m.descent + } + pub fn add_str_to_cache(&mut self, s: &str) { for ch in s.chars() { if self.glyphs.get(&ch).is_none() { @@ -96,8 +102,8 @@ impl<'a> Font<'a> { pub fn draw_text( &self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, s: &str, ) -> Result<(u32, u32), ::std::io::Error> { let mut x_off = 0; @@ -129,8 +135,8 @@ impl<'a> Font<'a> { pub fn draw_text_with_cursor( &self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, s: &str, cursor: usize, ) -> Result<(u32, u32), ::std::io::Error> { @@ -171,13 +177,13 @@ impl<'a> Font<'a> { pub fn draw_cursor( &self, buf: &mut BufferView, - c: &Color, + c: Color, offset: u32, height: u32, ) -> Result<(), ::std::io::Error> { // draw cursor for i in 1..height { - buf.put((offset, i), c)? + buf.put((offset, i), c); } Ok(()) } @@ -206,8 +212,8 @@ impl<'a> Font<'a> { pub fn auto_draw_text( &mut self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, s: &str, ) -> Result<(u32, u32), ::std::io::Error> { self.add_str_to_cache(s); @@ -217,8 +223,8 @@ impl<'a> Font<'a> { pub fn auto_draw_text_with_cursor( &mut self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, s: &str, cursor: usize, ) -> Result<(u32, u32), ::std::io::Error> { @@ -229,8 +235,8 @@ impl<'a> Font<'a> { pub fn draw_text_fixed_width( &self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, distances: &[u32], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -263,7 +269,7 @@ impl<'a> Font<'a> { pub fn draw_text_individual_colors( &self, buf: &mut BufferView, - bg: &Color, + bg: Color, color: &[Color], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -286,7 +292,7 @@ impl<'a> Font<'a> { } } for (idx, glyph) in glyphs.into_iter().enumerate() { - glyph.draw(buf, (x_off, -off), bg, &color[idx]); + glyph.draw(buf, (x_off, -off), bg, color[idx]); x_off += glyph.advance; } @@ -296,7 +302,7 @@ impl<'a> Font<'a> { pub fn auto_draw_text_individual_colors( &mut self, buf: &mut BufferView, - bg: &Color, + bg: Color, color: &[Color], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -305,14 +311,14 @@ impl<'a> Font<'a> { } } -pub fn draw_box(buf: &mut BufferView, c: &Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> { +pub fn draw_box(buf: &mut BufferView, c: Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> { for x in 0..dim.0 { - let _ = buf.put((x, 0), c); - let _ = buf.put((x, dim.1 - 1), c); + buf.put((x, 0), c); + buf.put((x, dim.1 - 1), c); } for y in 0..dim.1 { - buf.put((0, y), c)?; - buf.put((dim.0 - 1, y), c)?; + buf.put((0, y), c); + buf.put((dim.0 - 1, y), c); } Ok(()) @@ -320,7 +326,7 @@ pub fn draw_box(buf: &mut BufferView, c: &Color, dim: (u32, u32)) -> Result<(), pub fn draw_bar( buf: &mut BufferView, - color: &Color, + color: Color, length: u32, height: u32, fill: f32, diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..eb2bab0 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,61 @@ +use std::sync::{Arc, Mutex}; + +use calloop::ping::Ping; + +use crate::keyboard::KeyEvent; + +#[derive(Copy, Clone, Debug)] +pub enum PointerButton { + Left, + Right, + Middle, + ScrollVertical(f64), + ScrollHorizontal(f64), +} + +#[derive(Debug)] +pub struct PointerEvent { + pub button: PointerButton, + pub pos: (u32, u32), +} + +#[derive(Debug)] +pub enum Event { + NewMinute, + PowerUpdate, + LauncherUpdate, + AudioUpdate, + KeyEvent(KeyEvent), + PointerEvent(PointerEvent), + TokenUpdate(String), +} + +pub struct Events { + dirty: bool, + events: Vec, + ping: Ping, +} + +impl Events { + pub fn new(ping: Ping) -> Arc> { + Arc::new(Mutex::new(Events { + dirty: false, + events: Vec::new(), + ping, + })) + } + + pub fn add_event(&mut self, ev: Event) { + self.events.push(ev); + if !self.dirty { + self.dirty = true; + self.ping.ping(); + } + } + + pub fn flush(&mut self) -> Vec { + self.dirty = false; + self.events.drain(..).collect() + } +} + diff --git a/src/fonts.rs b/src/fonts.rs index da6c431..29c13a2 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -1,7 +1,6 @@ //! Utility module for fonts use crate::draw; -//use fontconfig::Fontconfig as FontConfig; use rusttype::Font; use std::{ cell::RefCell, @@ -10,7 +9,7 @@ use std::{ hash, io::Read, mem, - path::{Path, PathBuf}, + path::Path, rc::Rc, thread, }; @@ -18,19 +17,24 @@ use std::{ /// FontRef is used to store Fonts on widgets. pub type FontRef<'a> = &'a rusttype::Font<'a>; -/* -/// FontSeeker is a marker struct that is used to look up fonts -pub(crate) struct FontSeeker; - -impl FontSeeker { +#[cfg(feature = "fontloading")] +mod fc { + use fontconfig::Fontconfig as FontConfig; /// Acts like fc-match. /// Given a string, it matches it to a font file and returns its path. - pub(crate) fn from_string(name: &str) -> PathBuf { + pub(crate) fn from_string(name: &str) -> String { let fc = FontConfig::new().unwrap(); - fc.find(name, None).unwrap().path + fc.find(name, None).unwrap().path.to_str().unwrap().to_string() } } -*/ + +#[cfg(not(feature = "fontloading"))] +mod fc { + pub(crate) fn from_string(_name: &str) -> String { + panic!("fontloading feature not enabled"); + } +} + /// FontLoader is a marker struct that is used to load files pub(crate) struct FontLoader; @@ -77,51 +81,53 @@ impl Eq for ComparableF32 {} pub struct FontMap { fonts: HashMap<(&'static str, ComparableF32), draw::Font<'static>>, - required_fonts: HashMap<&'static str, (&'static str, Vec)>, + font_paths: HashMap<&'static str, String>, + required_fonts: HashMap<&'static str, (&'static str, Vec<(f32, &'static str)>)>, } impl FontMap { pub fn new() -> FontMap { FontMap { fonts: HashMap::new(), + font_paths: HashMap::new(), required_fonts: HashMap::new(), } } - pub fn queue_font_name(&mut self, font_name: &'static str, size: f32) { + pub fn queue_font(&mut self, font_name: &'static str, size: f32, preload: &'static str) { match self.required_fonts.get_mut(font_name) { - Some(v) => v.1.push(size), + Some(v) => v.1.push((size, preload)), None => { - self.required_fonts - .insert(font_name, (font_name, vec![size])); + self.required_fonts.insert(font_name, (font_name, vec![(size, preload)])); } } } - pub fn queue_font_path(&mut self, font_name: &'static str, font_path: &'static str, size: f32) { - match self.required_fonts.get_mut(font_name) { - Some(v) => v.1.push(size), - None => { - self.required_fonts - .insert(font_name, (font_path, vec![size])); - } - } + pub fn add_font_path(&mut self, font_name: &'static str, font_path: String) { + self.font_paths.insert(font_name, font_path); } pub fn load_fonts(&mut self) { for (font_name, v) in self.required_fonts.iter() { - let path = if v.0.starts_with("/") { - std::path::PathBuf::from(v.0) - } else { - //FontSeeker::from_string(v.0) - panic!("required FontSeeker"); + let path = match self.font_paths.get(font_name) { + Some(res) => res, + _ => { + let s = fc::from_string(font_name); + self.font_paths.insert(font_name, s); + self.font_paths.get(font_name).unwrap() + } }; let fontref = Box::leak(Box::new( FontLoader::from_path(path).expect("unable to load font"), )); - for size in &v.1 { - let font = draw::Font::new(fontref, *size); - self.fonts.insert((font_name, ComparableF32(*size)), font); + for (size, preload) in &v.1 { + if let Some(font) = self.fonts.get_mut(&(font_name, ComparableF32(*size))) { + font.add_str_to_cache(preload); + } else { + let mut font = draw::Font::new(fontref, *size); + font.add_str_to_cache(preload); + self.fonts.insert((font_name, ComparableF32(*size)), font); + } } } } diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 6b40547..3786d8a 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -12,8 +12,10 @@ //! calloop event loop. Not doing so will prevent key repetition to work //! (but the rest of the functionnality will not be affected). -use std::default::Default; -use std::os::unix::io::{AsRawFd, OwnedFd}; +use std::{ + default::Default, + os::unix::io::{AsRawFd, OwnedFd}, +}; use wayland_client::{protocol::wl_keyboard, WEnum}; use xkbcommon::xkb; @@ -24,6 +26,7 @@ mod state; use self::state::KbState; pub use self::state::ModifiersState; +#[derive(Debug)] pub struct KeyEvent { pub serial: u32, pub time: u32, diff --git a/src/keyboard/state.rs b/src/keyboard/state.rs index ba5ed64..300a5f6 100644 --- a/src/keyboard/state.rs +++ b/src/keyboard/state.rs @@ -50,6 +50,9 @@ impl ModifiersState { } } +// Safety: No. +unsafe impl Send for KbState {} + impl KbState { pub(crate) fn update_modifiers( &mut self, diff --git a/src/main.rs b/src/main.rs index a101746..09a8851 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod keyboard; mod state; mod utils; mod widgets; +mod event; use wayland_client::{Connection, QueueHandle, WaylandSource}; @@ -18,44 +19,17 @@ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; use color::Color; use fonts::{FontMap, MaybeFontMap}; -use state::{Event, Events, State}; +use state::State; +use event::{Event, Events}; use widgets::{ - Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, + Audio, Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, InvertedHorizontalLayout, Line, Margin, VerticalLayout, Widget, }; use std::{rc::Rc, thread}; fn main() { - let font_thread = thread::spawn(move || { - let mut fm = FontMap::new(); - // Hard-coding font paths make things a lot faster - fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 128.); - fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 48.); - fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 24.); - fm.queue_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - 32., - ); - fm.queue_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - 12., - ); - fm.queue_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - 12. * 1.5, - ); - fm.queue_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - 12. * 2.0, - ); - fm.load_fonts(); - fm - }); +// let now = Local::now(); let conn = Connection::connect_to_env().unwrap(); let event_queue = conn.new_event_queue(); @@ -66,32 +40,6 @@ fn main() { let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); - let events = Events::new(ping_sender); - - let clock = Box::new(Clock::new("sans", 128.)); -// let calendar = Box::new(Calendar::new("monospace", 12.0, 1, -1)); - let date = Box::new(Date::new("sans", 48.)); - let line = Box::new(Line::new()); - let launcher = Box::new(Interface::new("monospace", 32.)); - let battery = Box::new(Battery::new(events.clone(), "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", "sans", 24.)); - - let widgets: Vec> = - vec![clock, date, battery, backlight, line, launcher]; - let layout = Rc::new(VerticalLayout::new(vec![ - HorizontalLayout::new(vec![ - IndexedLayout::new(0), - Margin::new(IndexedLayout::new(1), (16, 8, 0, 0)), - VerticalLayout::new(vec![ - Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), - Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), - ]), - ]), - IndexedLayout::new(4), - IndexedLayout::new(5), -// InvertedHorizontalLayout::new(vec![IndexedLayout::new(5), IndexedLayout::new(6)]), - ])); - let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); @@ -136,6 +84,79 @@ fn main() { }) .expect("Failed to insert event source!"); + let events = Events::new(ping_sender); + + let mut fm = FontMap::new(); + + // Todo: Cache in a file + fm.add_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf".to_string()); + fm.add_font_path("monospace", "/usr/share/fonts/noto/NotoSansMono-Regular.ttf".to_string()); + + let clock = Box::new(Clock::new(&mut fm, "sans", 128.)); + let calendar = Box::new(Calendar::new(&mut fm, "monospace", 12.0, 1, -1)); + let date = Box::new(Date::new(&mut fm, "sans", 48.)); + let line = Box::new(Line::new(1)); + let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); + let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); + let audio = Box::new(Audio::new(events.clone(), &mut fm, "sans", 24.)); + + let widgets: Vec> = + vec![clock, date, battery, backlight, audio, line, calendar, launcher]; + + let layout = Rc::new(VerticalLayout::new(vec![ + HorizontalLayout::new(vec![ + IndexedLayout::new(0), + Margin::new(IndexedLayout::new(1), (16, 16, 0, 0)), + VerticalLayout::new(vec![ + Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), + Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), + Margin::new(IndexedLayout::new(4), (16, 8, 8, 0)), + ]), + ]), + IndexedLayout::new(5), + InvertedHorizontalLayout::new(vec![IndexedLayout::new(6), IndexedLayout::new(7)]), + ])); + + /* alt look + let clock = Box::new(Clock::new(&mut fm, "sans", 256.)); + let date = Box::new(Date::new(&mut fm, "sans", 64.)); + let calendar = Box::new(Calendar::new(&mut fm, "monospace", 16.0, 3, 1)); + let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); + let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); + + let widgets: Vec> = + vec![clock, date, battery, backlight, calendar]; + + let layout = Rc::new(Margin::new( + VerticalLayout::new(vec![ + HorizontalLayout::new(vec![ + VerticalLayout::new(vec![ + IndexedLayout::new(1), + IndexedLayout::new(0), + ]), + Margin::new( + VerticalLayout::new(vec![ + Margin::new(IndexedLayout::new(2), (0, 0, 0, 8)), + Margin::new(IndexedLayout::new(3), (0, 0, 0, 8)), + ]), + (88, 0, 0, 0), + ), + ]), + IndexedLayout::new(4), + //IndexedLayout::new(5), + ]), (20, 20, 20, 20)) + ); + */ + + let font_thread = thread::Builder::new() + .name("fontloader".to_string()) + .spawn(move || { + fm.load_fonts(); + fm + }).unwrap(); + let mut state = State::new(widgets, layout, MaybeFontMap::Waiting(font_thread), events); let mut damage = Vec::new(); @@ -174,16 +195,16 @@ fn main() { (state.dimensions.0 as u32, state.dimensions.1 as u32), ); - let bg = Color::new(0., 0., 0., 1.); +// eprintln!("time: {}", (Local::now() - now).num_milliseconds()); let surface = state.base_surface.as_ref().unwrap().clone(); if force { + // Other compositors probably need this. + //bufview.memset(Color::BLACK); for (idx, widget) in state.widgets.iter_mut().enumerate() { if force || widget.get_dirty() { let geo = widget.geometry(); - bufview.subgeometry(geo).unwrap().memset(&bg); - - let mut subview = bufview.subgeometry(geo).unwrap(); + let mut subview = bufview.subgeometry(geo); damage[idx] = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); } @@ -196,10 +217,10 @@ fn main() { drew = true; let old_damage = damage[idx]; - bufview.subgeometry(old_damage).unwrap().memset(&bg); + bufview.subgeometry(old_damage).memset(Color::BLACK); let geo = widget.geometry(); - let mut subview = bufview.subgeometry(geo).unwrap(); + let mut subview = bufview.subgeometry(geo); let new_damage = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); @@ -223,5 +244,7 @@ fn main() { surface.attach(Some(&buf.buffer), 0, 0); surface.commit(); +// eprintln!("draw time: {}", (Local::now() - now).num_milliseconds()); +// std::process::exit(0); } } diff --git a/src/state.rs b/src/state.rs index 506e29f..b6c1eb2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,12 +5,10 @@ use std::{ sync::{Arc, Mutex}, }; -use calloop::ping::Ping; - use wayland_client::{ protocol::{ wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, - wl_surface, + wl_surface, wl_pointer, }, Connection, Dispatch, QueueHandle, WEnum, }; @@ -23,46 +21,11 @@ use wayland_protocols::xdg::{ use crate::{ buffer::BufferManager, fonts::{FontMap, MaybeFontMap}, - keyboard::{KeyEvent, Keyboard}, + keyboard::Keyboard, widgets::{Geometry, Layout, Widget, WidgetUpdater}, + event::{Event, Events, PointerEvent, PointerButton}, }; -pub enum Event { - NewMinute, - PowerUpdate, - KeyEvent(KeyEvent), - TokenUpdate(String), -} - -pub struct Events { - dirty: bool, - events: Vec, - ping: Ping, -} - -impl Events { - pub fn new(ping: Ping) -> Arc> { - Arc::new(Mutex::new(Events { - dirty: false, - events: Vec::new(), - ping, - })) - } - - pub fn add_event(&mut self, ev: Event) { - self.events.push(ev); - if !self.dirty { - self.dirty = true; - self.ping.ping(); - } - } - - pub fn flush(&mut self) -> Vec { - self.dirty = false; - self.events.drain(..).collect() - } -} - pub struct State { pub running: bool, pub dirty: bool, @@ -77,6 +40,7 @@ pub struct State { pub bufmgr: BufferManager, pub widgets: Vec>, keyboard: Keyboard, + pointer: Option<(f64, f64)>, pub fonts: MaybeFontMap, pub events: Arc>, layout: Rc>, @@ -102,6 +66,7 @@ impl State { dimensions: (320, 240), bufmgr: BufferManager::new(), keyboard: Keyboard::new(), + pointer: None, widgets, fonts, events, @@ -380,6 +345,9 @@ impl Dispatch for State { if capabilities.contains(wl_seat::Capability::Keyboard) { seat.get_keyboard(qh, ()); } + if capabilities.contains(wl_seat::Capability::Pointer) { + seat.get_pointer(qh, ()); + } } } } @@ -431,6 +399,84 @@ impl Dispatch for State { } } +const BTN_LEFT: u32 = 0x110; +const BTN_RIGHT: u32 = 0x111; +const BTN_MIDDLE: u32 = 0x112; + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &wl_pointer::WlPointer, + event: wl_pointer::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_pointer::Event::Enter { surface_x, surface_y, .. } => { + state.pointer = Some((surface_x, surface_y)); + }, + wl_pointer::Event::Motion { surface_x, surface_y, .. } => { + state.pointer = Some((surface_x, surface_y)); + }, + wl_pointer::Event::Leave { .. } => { + state.pointer = None + }, + wl_pointer::Event::Axis { axis, value, .. } => match state.pointer { + Some((x, y)) => { + let axis = match axis { + WEnum::Value(wl_pointer::Axis::VerticalScroll) => PointerButton::ScrollVertical(value), + WEnum::Value(wl_pointer::Axis::HorizontalScroll) => PointerButton::ScrollHorizontal(value), + _ => return, + }; + let pos = (x as u32, y as u32); + for widget in state.widgets.iter_mut() { + let geo = widget.geometry(); + if geo.contains(pos) { + let pos = (pos.0 - geo.x, pos.1 - geo.y); + let ev = Event::PointerEvent(PointerEvent{ + button: axis, + pos: pos, + }); + widget.event(&ev); + state.dirty = true; + break; + } + } + }, + _ => (), + }, + wl_pointer::Event::Button { button, state: button_state, .. } => match (button, button_state, state.pointer) { + (button, WEnum::Value(wl_pointer::ButtonState::Pressed), Some((x, y))) => { + let button = match button { + BTN_LEFT => PointerButton::Left, + BTN_RIGHT => PointerButton::Right, + BTN_MIDDLE => PointerButton::Middle, + _ => return, + }; + let pos = (x as u32, y as u32); + for widget in state.widgets.iter_mut() { + let geo = widget.geometry(); + if geo.contains(pos) { + + let pos = (pos.0 - geo.x, pos.1 - geo.y); + let ev = Event::PointerEvent(PointerEvent{ + button: button, + pos: pos, + }); + widget.event(&ev); + state.dirty = true; + break; + } + } + }, + _ => (), + }, + _ => (), + } + } +} + impl Dispatch for State { fn event( _: &mut Self, diff --git a/src/utils/desktop.rs b/src/utils/desktop.rs index 8457685..9b14706 100644 --- a/src/utils/desktop.rs +++ b/src/utils/desktop.rs @@ -1,12 +1,16 @@ -use ini::{Ini, ParseOption}; -use itertools::Itertools; -use std::cmp::Ordering; -use std::env; -use std::error::Error; -use std::io::Error as io_error; -use std::io::ErrorKind; +use std::{ + cmp::Ordering, + collections::HashSet, + env, + error::Error, + fs::read_to_string, + io::{Error as io_error, ErrorKind}, +}; + use walkdir::WalkDir; +use crate::utils::inish; + #[derive(Clone, Debug, Eq, Hash)] pub struct Desktop { pub entry_type: String, @@ -21,23 +25,18 @@ pub struct Desktop { impl Desktop { fn parse(f: &str) -> Result> { - let file = Ini::load_from_file_opt( - f, - ParseOption { - enabled_quote: false, - enabled_escape: false, - }, - )?; - match file.section(Some("Desktop Entry")) { - Some(desktop) => Ok(Desktop { - entry_type: desktop.get("Type").unwrap_or(&"".to_string()).to_string(), - name: desktop.get("Name").unwrap_or(&"".to_string()).to_string(), - term: desktop.get("Terminal").unwrap_or(&"".to_string()) == "true", - no_display: desktop.get("NoDisplay").unwrap_or(&"".to_string()) == "true", - hidden: desktop.get("Hidden").unwrap_or(&"".to_string()) == "true", - exec: desktop.get("Exec").map(|x| x.to_string()), - url: desktop.get("URL").map(|x| x.to_string()), - keywords: desktop + let s = read_to_string(f)?; + let config = inish::parse(&s)?; + match config.get("Desktop Entry") { + Some(section) => Ok(Desktop { + entry_type: section.get("Type").unwrap_or(&"").to_string(), + name: section.get("Name").unwrap_or(&"").to_string(), + term: section.get("Terminal").unwrap_or(&"") == &"true", + no_display: section.get("NoDisplay").unwrap_or(&"") == &"true", + hidden: section.get("Hidden").unwrap_or(&"") == &"true", + exec: section.get("Exec").map(|x| x.to_string()), + url: section.get("URL").map(|x| x.to_string()), + keywords: section .get("Keywords") .map(|x| { x.split(';') @@ -53,20 +52,6 @@ impl Desktop { ))), } } - - fn parse_dir(d: &str) -> Result, Box> { - let mut files: Vec = Vec::with_capacity(16); - for entry in WalkDir::new(d) { - let entry = entry?; - let path = entry.path(); - - if let Ok(d) = Desktop::parse(path.to_str().unwrap()) { - files.push(d) - } - } - - Ok(files) - } } impl Ord for Desktop { @@ -99,14 +84,29 @@ pub fn load_desktop_files() -> Vec { None => "/usr/local/share:/usr/share".to_string(), }; - std::iter::once(xdg_data_home.as_str()) - .chain(xdg_data_dirs.split(':')) - .map(|p| Desktop::parse_dir(&format!("{}/applications", p))) - .filter_map(Result::ok) - .flatten() - .filter(|d| { - !d.hidden && !d.no_display && (d.entry_type == "Application" || d.entry_type == "Link") - }) - .unique_by(|x| x.name.clone()) - .collect() + let dirs = std::iter::once(xdg_data_home.as_str()).chain(xdg_data_dirs.split(':')); + + let mut desktop: Vec = Vec::new(); + let mut seen: HashSet = HashSet::new(); + + for dir in dirs { + for entry in WalkDir::new(format!("{}/applications", dir)) { + if let Ok(entry) = entry { + if let Ok(d) = Desktop::parse(entry.path().to_str().unwrap()) { + if d.hidden + || d.no_display + || (d.entry_type != "Application" && d.entry_type != "Link") + { + continue; + } + if !seen.contains(&d.name) { + seen.insert(d.name.to_string()); + desktop.push(d); + } + } + } + } + } + + desktop } diff --git a/src/utils/inish.rs b/src/utils/inish.rs new file mode 100644 index 0000000..13bb056 --- /dev/null +++ b/src/utils/inish.rs @@ -0,0 +1,134 @@ +use std::{collections::HashMap, error::Error}; + +fn parse_field<'a>(line: &'a str) -> Result<(&'a str, &'a str), Box> { + let split = match line.find('=') { + Some(v) => v, + None => return Err("expected equals sign on line, but found none".into()), + }; + + let (key, value) = line.split_at(split); + let (key, value) = (key.trim(), value.trim_matches('=').trim()); + + Ok((key, value)) +} + +type InishSection<'a> = HashMap<&'a str, &'a str>; +type Inish<'a> = HashMap<&'a str, InishSection<'a>>; + +pub fn parse<'a>(s: &'a str) -> Result, Box> { + let mut sections: Inish<'a> = HashMap::new(); + let mut current_section = HashMap::new(); + let mut current_section_name = ""; + + for line in s.lines() { + let line = line.trim(); + let mut chars = line.chars(); + let start = chars.next(); + let end = chars.last(); + match (start, end) { + (Some('#'), _) => continue, + (Some('['), Some(']')) => { + sections.insert(current_section_name, current_section); + current_section = HashMap::new(); + let len = line.bytes().count(); + current_section_name = &line[1..len - 1].trim(); + } + (Some('['), v) => { + return Err(format!( + "expected Some(']') to terminate section name, but got {:?}", + v + ) + .into()); + } + _ if line.is_empty() => continue, + _ => { + let (key, value) = parse_field(line)?; + current_section.insert(key, value); + } + } + } + + sections.insert(current_section_name, current_section); + Ok(sections) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sections() { + let config = parse( + " +[section_a] +[section_b] +", + ) + .expect("config didn't parse"); + + if !config.contains_key("section_a") { + panic!("named section did not exist"); + } + + if !config.contains_key("section_b") { + panic!("named section did not exist"); + } + + if !config.contains_key("") { + panic!("unnamed section did not exist"); + } + } + + #[test] + fn fields() { + let config = parse( + " +field_outside = 'hello' +[section_a] +field_a = 1234 +", + ) + .expect("config didn't parse"); + + let section_a = match config.get("section_a") { + Some(v) => v, + None => panic!("named section did not exist"), + }; + + assert_eq!(section_a.get("field_a"), Some(&"1234")); + + let section_unnamed = match config.get("") { + Some(v) => v, + None => panic!("unnamed section did not exist"), + }; + + assert_eq!(section_unnamed.get("field_outside"), Some(&"'hello'")); + } + + #[test] + fn whitespaces() { + let config = parse( + " + + field outside = 'hello' + +[ section a ] + field a = 1234 ", + ) + .expect("config didn't parse"); + + let section_a = match config.get("section a") { + Some(v) => v, + None => panic!("named section did not exist"), + }; + + assert_eq!(section_a.get("field a"), Some(&"1234")); + + let section_unnamed = match config.get("") { + Some(v) => v, + None => panic!("unnamed section did not exist"), + }; + + assert_eq!(section_unnamed.get("field outside"), Some(&"'hello'")); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6cf24c7..6dbe5d9 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ pub mod desktop; +mod inish; diff --git a/src/widgets/audio.rs b/src/widgets/audio.rs new file mode 100644 index 0000000..99c0f3c --- /dev/null +++ b/src/widgets/audio.rs @@ -0,0 +1,238 @@ + +use std::{ + sync::{Arc, Mutex}, + thread, + error::Error, + rc::Rc, + cell::RefCell, + os::fd::RawFd, +}; + +use nix::unistd::{ + pipe, write, read, +}; + +use crate::{ + color::Color, + event::{Event, Events, PointerButton}, + fonts::FontMap, + widgets::bar_widget::{BarWidget, BarWidgetImpl}, +}; + +use libpulse_binding::volume::ChannelVolumes; + +fn monitor(events: Arc>, inner: Arc>, pipe: RawFd) -> Result<(), Box> { + use libpulse_binding::{ + mainloop::{ + self, + standard::{ + IterateResult, + }, + api::Mainloop, + events::io::FlagSet as IoFlagSet, + }, + callbacks::ListResult, + context::{ + Context, + FlagSet, + subscribe::{InterestMaskSet, Facility}, + State, + }, + proplist::{properties, Proplist}, + volume::Volume, + }; + + let mut proplist = Proplist::new().unwrap(); + proplist + .set_str(properties::APPLICATION_NAME, "wldash") + .unwrap(); + + let mut mainloop = mainloop::standard::Mainloop::new().unwrap(); + let context = Rc::new(RefCell::new(Context::new_with_proplist(&mainloop, "wldash_context", &proplist).unwrap())); + context.borrow_mut().connect(None, FlagSet::NOFLAGS, None)?; + + loop { + mainloop.iterate(false); + match context.borrow().get_state() { + State::Ready => break, + State::Failed | State::Terminated => panic!("damn"), + _ => (), + } + } + + let local_context = context.clone(); + let local_inner = inner.clone(); + let local_events = events.clone(); + context + .borrow_mut() + .set_subscribe_callback(Some(Box::new(move |facility, _operation, index| { + match facility { + Some(Facility::Sink) => { + let introspector = local_context.borrow_mut().introspect(); + let inner = local_inner.clone(); + let events = local_events.clone(); + introspector.get_sink_info_by_index(index, move |res| { + match res { + ListResult::End | ListResult::Error => (), + ListResult::Item(sink_info) => if let Some(_) = &sink_info.name { + let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32; + let mut inner = inner.lock().unwrap(); + inner.volume = avg; + inner.mute = sink_info.mute; + inner.dirty = true; + drop(inner); + let mut events = events.lock().unwrap(); + events.add_event(Event::AudioUpdate); + }, + } + }); + }, + _ => (), + } + }))); + + context + .borrow_mut() + .subscribe(InterestMaskSet::SINK, |_| {}); + + let introspector = context.borrow_mut().introspect(); + let local_inner = inner.clone(); + introspector.get_sink_info_by_name("@DEFAULT_SINK@", move |res| { + match res { + ListResult::End | ListResult::Error => (), + ListResult::Item(sink_info) => if let Some(_) = &sink_info.name { + let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32; + let mut inner = local_inner.lock().unwrap(); + inner.volume = avg; + inner.channel_volume = Some(sink_info.volume); + inner.mute = sink_info.mute; + inner.dirty = true; + drop(inner); + let mut events = events.lock().unwrap(); + events.add_event(Event::AudioUpdate); + }, + } + }); + drop(introspector); + + let local_context = context.clone(); + let local_inner = inner.clone(); + let event_source = mainloop.new_io_event(pipe as i32, IoFlagSet::INPUT, Box::new(move |_, _, _| { + let mut buf = [0; 8]; + _ = read(pipe, &mut buf); + + let mut inner = local_inner.lock().unwrap(); + let mut introspector = local_context.borrow_mut().introspect(); + match inner.last_click.take() { + Some(Change::Volume(volume)) => { + // do something + let mut vol = inner.channel_volume.unwrap(); + vol.scale(Volume((volume * Volume::NORMAL.0 as f32).round() as u32)); + introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None); + }, + Some(Change::VolumeInc(value)) => { + let mut vol = inner.channel_volume.unwrap(); + // apply step to volumes + let step = (value * Volume::NORMAL.0 as f32).round() as i32; + if step > 0 { + vol.inc_clamp(Volume(step as u32), Volume::NORMAL); + } else { + vol.decrease(Volume(-step as u32)); + } + // HACK: It would be better to only update the volume from feedback, but axis + // events fire fast and require the last set volume immediately for reference. + inner.channel_volume = Some(vol); + introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None); + }, + Some(Change::ToggleMute) => { + introspector.set_sink_mute_by_name("@DEFAULT_SINK@", !inner.mute, None); + }, + None => (), + } + })); + + loop { + match mainloop.iterate(true) { + IterateResult::Quit(_) | IterateResult::Err(_) => break, + _ => (), + } + } + + // Need to keep the event source alive + _ = event_source; + + Ok(()) +} + +fn start_monitor(events: Arc>, inner: Arc>, pipe: RawFd) { + thread::Builder::new() + .name("audiomon".to_string()) + .spawn(move || { monitor(events, inner, pipe).unwrap() }) + .unwrap(); +} + +enum Change { + Volume(f32), + VolumeInc(f32), + ToggleMute, +} + +pub struct InnerAudio { + volume: f32, + channel_volume: Option, + mute: bool, + dirty: bool, + last_click: Option, + pipe: RawFd, +} + +pub struct Audio { + inner: Arc>, +} + +impl Audio { + pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + let (a, b) = pipe().unwrap(); + let audio = Audio { + inner: Arc::new(Mutex::new(InnerAudio{ + volume: 0.0, + channel_volume: None, + mute: false, + dirty: false, + last_click: None, + pipe: b, + })), + }; + start_monitor(events, audio.inner.clone(), a); + BarWidget::new(Box::new(audio), fm, font, size) + } +} + +impl BarWidgetImpl for Audio { + fn get_dirty(&self) -> bool { + self.inner.lock().unwrap().dirty + } + fn name(&self) -> &'static str { + "volume" + } + fn value(&self) -> f32 { + self.inner.lock().unwrap().volume + } + fn color(&self) -> Color { + match self.inner.lock().unwrap().mute { + true => Color::YELLOW, + false => Color::WHITE, + } + } + fn click(&mut self, pos: f32, btn: PointerButton) { + let mut inner = self.inner.lock().unwrap(); + inner.last_click = match btn { + PointerButton::Left => Some(Change::Volume(pos)), + PointerButton::Right => Some(Change::ToggleMute), + PointerButton::ScrollVertical(val) => Some(Change::VolumeInc((val / 512.) as f32)), + PointerButton::ScrollHorizontal(val) => Some(Change::VolumeInc((val / 100.) as f32)), + _ => None, + }; + _ = write(inner.pipe, &[0]); + } +} diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index 1226ce8..947134d 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -6,6 +6,7 @@ use std::{ use crate::{ color::Color, + fonts::FontMap, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; @@ -28,7 +29,7 @@ impl Backlight { self.max = read_file_as_u64(self.device_path.join("max_brightness").as_path()).unwrap(); } - pub fn new(path: &str, font: &'static str, size: f32) -> BarWidget { + pub fn new(path: &str, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { let mut dev = Backlight { device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), cur: 0, @@ -36,7 +37,7 @@ impl Backlight { }; dev.update(); - BarWidget::new(Box::new(dev), font, size) + BarWidget::new(Box::new(dev), fm, font, size) } } @@ -53,6 +54,6 @@ impl BarWidgetImpl for Backlight { self.cur as f32 / self.max as f32 } fn color(&self) -> Color { - Color::new(1.0, 1.0, 1.0, 1.0) + Color::WHITE } } diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index e10e23f..2338938 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -6,6 +6,7 @@ use crate::{ draw::{draw_bar, draw_box}, fonts::FontMap, widgets::{Geometry, Widget}, + event::{Event, PointerButton}, }; pub trait BarWidgetImpl { @@ -15,6 +16,7 @@ pub trait BarWidgetImpl { fn name(&self) -> &'static str; fn value(&self) -> f32; fn color(&self) -> Color; + fn click(&mut self, _pos: f32, _btn: PointerButton) {} } pub struct BarWidget { @@ -25,7 +27,8 @@ pub struct BarWidget { } impl BarWidget { - pub fn new(inner_widget: Box, font: &'static str, size: f32) -> BarWidget { + pub fn new(inner_widget: Box, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + fm.queue_font(font, size, inner_widget.name()); BarWidget { geometry: Default::default(), inner_widget, @@ -46,10 +49,10 @@ impl Widget for BarWidget { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let font = fonts.get_font(self.font, self.size); - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; - font.auto_draw_text(view, &bg, &fg, self.inner_widget.name()) + font.draw_text(view, bg, fg, self.inner_widget.name()) .unwrap(); let size = self.size.ceil() as u32; let bar_offset = 4 * size; @@ -57,8 +60,8 @@ impl Widget for BarWidget { let c = self.inner_widget.color(); draw_bar( - &mut view.offset((bar_offset, 0)).unwrap(), - &c, + &mut view.offset((bar_offset, 0)), + c, self.geometry.width - bar_offset, size, val, @@ -66,8 +69,8 @@ impl Widget for BarWidget { .unwrap(); draw_box( - &mut view.offset((bar_offset, 0)).unwrap(), - &c, + &mut view.offset((bar_offset, 0)), + c, (self.geometry.width - bar_offset, size), ) .unwrap(); @@ -88,4 +91,18 @@ impl Widget for BarWidget { }; self.geometry } + + fn event(&mut self, event: &Event) { + match event { + Event::PointerEvent(ev) => { + let offset = 4 * self.size.ceil() as u32; + if ev.pos.0 >= offset { + let val = (ev.pos.0 - offset) as f32 / (self.geometry.width - offset) as f32; + self.inner_widget.click(val, ev.button); + } + }, + _ => (), + } + } + } diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 4eeaea2..77209b9 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -1,110 +1,81 @@ use std::{ sync::{Arc, Mutex}, thread, - time::Duration, -}; - -use dbus::blocking::{ - stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged}, - LocalConnection, + error::Error, }; use crate::{ color::Color, - state::{Event, Events}, + event::{Event, Events}, + fonts::FontMap, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; -enum UpowerBatteryState { - Charging, - Discharging, - Empty, - Full, - NotCharging, - Unknown, -} +use upower_dbus::BatteryState; struct InnerBattery { value: f32, - state: UpowerBatteryState, + state: BatteryState, dirty: bool, } +async fn monitor(inner: Arc>, events: Arc>) -> Result<(), Box> { + use zbus::Connection; + use upower_dbus::UPowerProxy; + use tokio_stream::StreamExt; + + let conn = Connection::system().await?; + let proxy = UPowerProxy::new(&conn).await?; + + let device = proxy.get_display_device().await?; + + let local_inner = inner.clone(); + let local_events = events.clone(); + let mut percent_stream = device.receive_percentage_changed().await; + let a = tokio::spawn(async move { + while let Some(percent) = percent_stream.next().await { + let percent = percent.get().await.unwrap() as f32 / 100.; + let mut inner = local_inner.lock().unwrap(); + inner.value = percent; + inner.dirty = true; + drop(inner); + + let mut events = local_events.lock().unwrap(); + events.add_event(Event::PowerUpdate); + } + }); + + let local_inner = inner.clone(); + let local_events = events.clone(); + let mut state_stream = device.receive_state_changed().await; + let b = tokio::spawn(async move { + while let Some(state) = state_stream.next().await { + let state = state.get().await.unwrap(); + let mut inner = local_inner.lock().unwrap(); + inner.state = state; + inner.dirty = true; + drop(inner); + + let mut events = local_events.lock().unwrap(); + events.add_event(Event::PowerUpdate); + } + }); + a.await?; + b.await?; + + Ok(()) +} + fn start_monitor(inner: Arc>, events: Arc>) { + use tokio::runtime; thread::Builder::new() .name("battmon".to_string()) .spawn(move || { - let conn = LocalConnection::new_system().unwrap(); - let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; - let proxy = conn.with_proxy( - "org.freedesktop.UPower", - device_path, - Duration::from_millis(500), - ); - - let capacity: f64 = proxy - .get("org.freedesktop.UPower.Device", "Percentage") - .expect("unable to get property"); - let state: u32 = proxy - .get("org.freedesktop.UPower.Device", "State") - .expect("unable to get state"); - - { - let mut inner = inner.lock().unwrap(); - inner.value = capacity as f32 / 100.; - inner.state = match state { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - } - } - - proxy - .match_signal( - move |c: PropertiesPropertiesChanged, - _: &LocalConnection, - _: &dbus::Message| { - if c.interface_name != "org.freedesktop.UPower.Device" { - return true; - } - let mut inner = inner.lock().unwrap(); - for (key, value) in c.changed_properties { - match key.as_str() { - "State" => { - inner.state = match value.0.as_u64().unwrap() { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - }; - inner.dirty = true; - let mut events = events.lock().unwrap(); - events.add_event(Event::PowerUpdate); - } - "Percentage" => { - inner.value = value.0.as_f64().unwrap() as f32 / 100.; - inner.dirty = true; - let mut events = events.lock().unwrap(); - events.add_event(Event::PowerUpdate); - } - _ => (), - } - } - true - }, - ) + let rt = runtime::Builder::new_current_thread() + .enable_io() + .build() .unwrap(); - - loop { - conn.process(Duration::from_millis(60000)).unwrap(); - } + rt.block_on(async move { monitor(inner, events).await }).unwrap(); }) .unwrap(); } @@ -114,16 +85,16 @@ pub struct Battery { } impl Battery { - pub fn new(events: Arc>, font: &'static str, size: f32) -> BarWidget { + pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { let battery = Battery { inner: Arc::new(Mutex::new(InnerBattery { value: 0., - state: UpowerBatteryState::Unknown, + state: BatteryState::Unknown, dirty: false, })), }; start_monitor(battery.inner.clone(), events); - BarWidget::new(Box::new(battery), font, size) + BarWidget::new(Box::new(battery), fm, font, size) } } @@ -142,20 +113,14 @@ impl BarWidgetImpl for Battery { fn color(&self) -> Color { let inner = self.inner.lock().unwrap(); match inner.state { - UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => { - if inner.value > 0.25 { - Color::new(1.0, 1.0, 1.0, 1.0) - } else if inner.value > 0.1 { - Color::new(1.0, 0.5, 0.0, 1.0) - } else { - Color::new(1.0, 0.0, 0.0, 1.0) - } - } - UpowerBatteryState::Charging | UpowerBatteryState::Full => { - Color::new(0.5, 1.0, 0.5, 1.0) - } - UpowerBatteryState::NotCharging | UpowerBatteryState::Empty => { - Color::new(1.0, 0.5, 0.5, 1.0) + BatteryState::Charging | BatteryState::FullyCharged => Color::LIGHTGREEN, + BatteryState::PendingCharge => Color::LIGHTRED, + _ => if inner.value > 0.25 { + Color::WHITE + } else if inner.value > 0.1 { + Color::DARKORANGE + } else { + Color::RED } } } diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index f16f96f..68e3832 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -4,7 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, - state::Event, + event::Event, widgets::{Geometry, Widget}, }; @@ -14,18 +14,32 @@ pub struct Calendar { font: &'static str, sections_x: i32, sections_y: i32, + shown_sections_x: u32, + shown_sections_y: u32, size: f32, dirty: bool, geometry: Geometry, } +const DAY_FACTOR: f32 = 1.; +const YEAR_FACTOR: f32 = 1.5; +const MONTH_FACTOR: f32 = 2.; +const DATE_FACTOR: f32 = 2.; +const LINE_HEIGHT: f32 = 1.8; + impl Calendar { - pub fn new(font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar { + fm.queue_font(font, size, "ADEFHIMNORSTUW"); + fm.queue_font(font, size * 1.5, "0123456789"); + fm.queue_font(font, size * 2.0, "0123456789 ADFJMOSabcehgilmnoprstuvy"); + Calendar { font, size, sections_x, sections_y, + shown_sections_x: 0, + shown_sections_y: 0, dirty: true, geometry: Default::default(), } @@ -38,10 +52,62 @@ impl Calendar { orig: NaiveDate, mut time: NaiveDate, ) { - let black = Color::new(0., 0., 0., 1.); - let white = Color::new(1., 1., 1., 1.); - let dim = Color::new(0.8, 0.8, 0.8, 1.); - let dimmer = Color::new(0.75, 0.75, 0.75, 1.); + let black = Color::BLACK; + let white = Color::WHITE; + let dim = Color::GREY80; + let dimmer = Color::GREY75; + + let mut y_off = 1; + // TODO: Fix offsets: + // - Headline font is not correctly accounted for + + // + // Draw the week day + // + for idx in 1..8 { + let day_font = fonts.get_font(self.font, self.size * DAY_FACTOR); + let wk_chr = match idx { + 1 => "MON", + 2 => "TUE", + 3 => "WED", + 4 => "THU", + 5 => "FRI", + 6 => "SAT", + 7 => "SUN", + _ => panic!("impossible value"), + }; + + day_font + .draw_text( + &mut view + .offset(( + idx * (self.size * 2.5).ceil() as u32 + (self.size / 5.).ceil() as u32, + y_off * (self.size * LINE_HEIGHT).ceil() as u32 + + (self.size * 1.).ceil() as u32, + )), + black, + white, + &wk_chr, + ) + .unwrap(); + } + + // + // Draw the month + // + if time.year() != orig.year() { + let year_font = fonts.get_font(self.font, self.size * YEAR_FACTOR); + year_font + .draw_text( + &mut view.offset(((self.size * 17.).ceil() as u32, 0)), + black, + dim, + &format!("{:}", time.year()), + ) + .unwrap(); + } + + let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR); let month_str = match time.month() { 1 => "January", 2 => "February", @@ -57,62 +123,8 @@ impl Calendar { 12 => "December", _ => panic!("impossible value"), }; - - - let mut y_off = 1; - // TODO: Fix offsets: - // - Headline font is not correctly accounted for - - // - // Draw the week day - // - for idx in 1..8 { - let day_font = fonts.get_font(self.font, self.size); - let wk_chr = match idx { - 1 => "MON", - 2 => "TUE", - 3 => "WED", - 4 => "THU", - 5 => "FRI", - 6 => "SAT", - 7 => "SUN", - _ => panic!("impossible value"), - }; - - day_font - .auto_draw_text( - &mut view - .offset(( - idx * (self.size * 2.5).ceil() as u32 + (self.size / 5.).ceil() as u32, - y_off * (self.size * 1.8).ceil() as u32 - + (self.size * 1.).ceil() as u32, - )) - .unwrap(), - &black, - &white, - &wk_chr, - ) - .unwrap(); - } - - // - // Draw the month - // - if time.year() != orig.year() { - let year_font = fonts.get_font(self.font, self.size * 1.5); - year_font - .auto_draw_text( - &mut view.offset(((self.size * 17.).ceil() as u32, 0)).unwrap(), - &black, - &dim, - &format!("{:}", time.year()), - ) - .unwrap(); - } - - let cal_font = fonts.get_font(self.font, self.size * 2.); cal_font - .auto_draw_text(&mut view, &black, &white, month_str) + .draw_text(&mut view, black, white, month_str) .unwrap(); let mut done = false; @@ -133,16 +145,15 @@ impl Calendar { // let wk = time.iso_week(); cal_font - .auto_draw_text( + .draw_text( &mut view .offset(( 0, - y_off * (self.size * 1.8).ceil() as u32 - + (self.size * 2.).ceil() as u32, - )) - .unwrap(), - &black, - &dimmer, + y_off * (self.size * LINE_HEIGHT).ceil() as u32 + + (self.size * MONTH_FACTOR).ceil() as u32, + )), + black, + dimmer, &format!("{:02}", wk.week()), ) .unwrap(); @@ -153,21 +164,21 @@ impl Calendar { // while x_pos < 8 { let c = if time.day() == orig.day() && time.month() == orig.month() { - Color::new(1.0, 1.0, 1.0, 1.0) + Color::WHITE } else { - Color::new(0.5, 0.5, 0.5, 1.0) + Color::GREY50 }; cal_font - .auto_draw_text( + .draw_text( &mut view .offset(( x_pos * (self.size * 2.5).ceil() as u32, - y_off * (self.size * 1.8).ceil() as u32 + (self.size * 2.) as u32, - )) - .unwrap(), - &black, - &c, + y_off * (self.size * LINE_HEIGHT).ceil() as u32 + + (self.size * MONTH_FACTOR).ceil() as u32, + )), + black, + c, &format!("{:02}", time.day()), ) .unwrap(); @@ -205,35 +216,33 @@ impl<'a> Widget for Calendar { } fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + if self.shown_sections_y == 0 || self.shown_sections_x == 0 { + return Geometry { + x: self.geometry.x, + y: self.geometry.y, + width: 0, + height: 0, + }; + } + let time = Local::now().naive_local().date(); let cal_height = (self.size * 16.25).ceil() as u32; - let cal_width = (7. * self.size * 3. + self.size * 2.).ceil() as u32; + let cal_width = (7. * self.size * 2.5 + self.size * 2.).ceil() as u32; let cal_pad = (3. * self.size).ceil() as u32; - let sections_y = if self.sections_y > 0 { - self.sections_y as u32 - } else { - self.geometry.height / cal_height - }; - let sections_x = if self.sections_x > 0 { - self.sections_x as u32 - } else { - self.geometry.width / (cal_height + cal_pad) - }; let mut t = time.with_day(1).unwrap(); - let cals = sections_y * sections_x - 1; - if cals >=3 { + let cals = self.shown_sections_y * self.shown_sections_x; + if cals >= 3 { t = t.pred_opt().unwrap().with_day(1).unwrap(); } - for ydx in 0..sections_y { - for idx in 0..sections_x { + for ydx in 0..self.shown_sections_y { + for idx in 0..self.shown_sections_x { self.draw_month( fonts, &mut view - .offset(((cal_width + cal_pad) * idx, (cal_height) * ydx)) - .unwrap(), + .offset(((cal_width + cal_pad) * idx, (cal_height) * ydx)), time, t, ); @@ -249,17 +258,26 @@ impl<'a> Widget for Calendar { } fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { - let cal_width = (7. * self.size * 3. + self.size * 2.).ceil() as u32; - let cal_height = (self.size * 16.25).ceil() as u32; // TODO: Calculate from font sizes + let cal_width = 7 * (self.size * 2.5).ceil() as u32 + (self.size * 2.).ceil() as u32; + let cal_height = (self.size * MONTH_FACTOR).ceil() as u32 + + (self.size * LINE_HEIGHT * 8.).ceil() as u32; let cal_pad = (self.size * 3.).ceil() as u32; - let width = if self.sections_x > 0 { + + let possible_sections_x = geometry.width / (cal_width + cal_pad); + let possible_sections_y = geometry.height / cal_height; + + let width = if self.sections_x > 0 && self.sections_x < possible_sections_x as i32 { + self.shown_sections_x = self.sections_x as u32; cal_width * self.sections_x as u32 + cal_pad * (self.sections_x as u32 - 1) } else { - (geometry.width / (cal_width + cal_pad)) * (cal_width + cal_pad) + self.shown_sections_x = possible_sections_x; + (geometry.width / (cal_width + cal_pad)) * (cal_width + cal_pad) }; - let height = if self.sections_y > 0 { + let height = if self.sections_y > 0 && self.sections_y < possible_sections_y as i32 { + self.shown_sections_y = self.sections_y as u32; cal_height * self.sections_y as u32 } else { + self.shown_sections_y = possible_sections_y; (geometry.height / cal_height) * cal_height }; self.geometry = Geometry { diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 0fb807c..8af1941 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -4,7 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, - state::Event, + event::Event, widgets::{Geometry, Widget}, }; @@ -20,7 +20,8 @@ pub struct Clock { } impl<'a> Clock { - pub fn new(font: &'static str, size: f32) -> Clock { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Clock { + fm.queue_font(font, size, "0123456789: "); Clock { font, size, @@ -52,16 +53,16 @@ impl<'a> Widget for Clock { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let time = Local::now().naive_local(); - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; let digit = self.digit_width; let colon = self.colon_width; let font = fonts.get_font(self.font, self.size); font.draw_text_fixed_width( view, - &bg, - &fg, + bg, + fg, &[digit, digit, colon, digit, digit], &format!("{:02}:{:02}", time.hour(), time.minute()), ) diff --git a/src/widgets/date.rs b/src/widgets/date.rs index 1c9d806..cf67bc6 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -4,7 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, - state::Event, + event::Event, widgets::{Geometry, Widget}, }; @@ -18,7 +18,8 @@ pub struct Date { } impl Date { - pub fn new(font: &'static str, size: f32) -> Date { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Date { + fm.queue_font(font, size, "0123456789/, adehinortuFMSTW"); Date { font, size, @@ -48,14 +49,13 @@ impl<'a> Widget for Date { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let time = Local::now().naive_local(); - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); - let mut date_line = view.offset((0, 8)).unwrap(); + let fg = Color::WHITE; + let bg = Color::BLACK; let font = fonts.get_font(self.font, self.size); - font.auto_draw_text( - &mut date_line, - &bg, - &fg, + font.draw_text( + view, + bg, + fg, &format!( "{}, {:02}/{:02}/{:4}", time.weekday(), @@ -69,13 +69,14 @@ impl<'a> Widget for Date { self.geometry } - fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry { let width = (3. + 2. + 2. + 2. + 4.) * self.size / 2.; + let font = fonts.get_font(self.font, self.size); self.geometry = Geometry { x: geometry.x, y: geometry.y, width: width.ceil() as u32, - height: self.size.ceil() as u32, + height: font.height().ceil() as u32, }; self.geometry } diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 80c9e82..bcdadb5 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -12,7 +12,7 @@ use crate::{ color::Color, fonts::FontMap, keyboard::{keysyms, KeyEvent}, - state::Event, + event::{Event, Events, PointerEvent, PointerButton}, utils::desktop::{load_desktop_files, Desktop}, widgets::{Geometry, Widget}, }; @@ -205,14 +205,20 @@ struct Launcher { } impl Launcher { - fn new() -> Launcher { + fn new(events: Arc>) -> Launcher { let options = Arc::new(Mutex::new(Vec::new())); { let options = Arc::clone(&options); - thread::spawn(move || { - let mut options = options.lock().unwrap(); - *options = load_desktop_files(); - }); + thread::Builder::new() + .name("desktopini".to_string()) + .spawn(move || { + let mut options = options.lock().unwrap(); + *options = load_desktop_files(); + drop(options); + let mut events = events.lock().unwrap(); + events.add_event(Event::LauncherUpdate); + }) + .unwrap(); } Launcher { @@ -280,31 +286,31 @@ impl InterfaceWidget for Launcher { fonts: &mut FontMap, view: &mut BufferView, ) -> Geometry { - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; // Draw line let mut prompt_offset = intf.geometry.height - line_height; - let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_line = view.offset((0, prompt_offset)); let font = fonts.get_font(intf.font, intf.size); let mut x_max = font .auto_draw_text_with_cursor( &mut prompt_line, - &bg, - &fg, + bg, + fg, &format!(" {}", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, &bg, &fg, ">") + font.auto_draw_text(&mut prompt_line, bg, fg, ">") .unwrap(); // Draw entries - let dimfg = Color::new(0.5, 0.5, 0.5, 1.0); + let dimfg = Color::GREY50; prompt_offset -= 16; for (idx, m) in self.matches.iter().enumerate() { @@ -312,7 +318,7 @@ impl InterfaceWidget for Launcher { break; } prompt_offset -= line_height; - let mut line = view.offset((0, prompt_offset)).unwrap(); + let mut line = view.offset((0, prompt_offset)); if idx == intf.selection { let fuzzy_matcher = SkimMatcherV2::default(); @@ -323,20 +329,20 @@ impl InterfaceWidget for Launcher { let mut colors = Vec::with_capacity(m.name.len()); for pos in 0..m.name.len() { if indices.contains(&pos) { - colors.push(Color::new(1.0, 0.65, 0., 1.0)); + colors.push(Color::LIGHTORANGE); } else { - colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); + colors.push(Color::GREY75); } } x_max = max( x_max, - font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name) + font.auto_draw_text_individual_colors(&mut line, bg, &colors, &m.name) .unwrap(), ); } else { x_max = max( x_max, - font.auto_draw_text(&mut line, &bg, &dimfg, &m.name) + font.auto_draw_text(&mut line, bg, dimfg, &m.name) .unwrap(), ); } @@ -379,27 +385,27 @@ impl InterfaceWidget for Shell { fonts: &mut FontMap, view: &mut BufferView, ) -> Geometry { - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; // Draw line let prompt_offset = intf.geometry.height - line_height; - let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_line = view.offset((0, prompt_offset)); // Draw prompt let font = fonts.get_font(intf.font, intf.size); let x_max = font .auto_draw_text_with_cursor( &mut prompt_line, - &bg, - &fg, + bg, + fg, &format!(" {}", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "!") + font.auto_draw_text(&mut prompt_line, bg, Color::BUFF, "!") .unwrap(); Geometry { @@ -440,45 +446,45 @@ impl InterfaceWidget for Calc { fonts: &mut FontMap, view: &mut BufferView, ) -> Geometry { - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; // Draw line let mut prompt_offset = intf.geometry.height - (line_height); - let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_line = view.offset((0, prompt_offset)); // Draw prompt let font = fonts.get_font(intf.font, intf.size); let res_off = font .auto_draw_text_with_cursor( &mut prompt_line, - &bg, - &fg, + bg, + fg, &format!(" {} ", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "=") + font.auto_draw_text(&mut prompt_line, bg, Color::BUFF, "=") .unwrap(); if let Some(res) = &self.result { - let resfg = Color::new(1.0, 0.65, 0., 1.0); - let mut result_line = view.offset((res_off.0, prompt_offset)).unwrap(); - font.auto_draw_text(&mut result_line, &bg, &resfg, &format!(" = {}", &res)) + let resfg = Color::LIGHTORANGE; + let mut result_line = view.offset((res_off.0, prompt_offset)); + font.auto_draw_text(&mut result_line, bg, resfg, &format!(" = {}", &res)) .unwrap(); } for m in self.old.iter().rev() { - let dimfg = Color::new(0.5, 0.5, 0.5, 1.0); + let dimfg = Color::GREY50; if prompt_offset < line_height { break; } prompt_offset -= line_height; - let mut result_line = view.offset((0, prompt_offset)).unwrap(); - font.auto_draw_text(&mut result_line, &bg, &dimfg, &m) + let mut result_line = view.offset((0, prompt_offset)); + font.auto_draw_text(&mut result_line, bg, dimfg, &m) .unwrap(); } @@ -512,9 +518,14 @@ pub struct Interface { } impl Interface { - pub fn new(font: &'static str, size: f32) -> Interface { + pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> Interface { + fm.queue_font( + font, size, + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 >=!,.-/()", + ); + Interface { - launcher: Launcher::new(), + launcher: Launcher::new(events), shell: Shell {}, calc: Calc { old: Vec::new(), @@ -531,6 +542,23 @@ impl Interface { } } + fn pointer_input(&mut self, event: &PointerEvent) { + if let PointerButton::Left = event.button { + let line_height = self.inner.size.ceil() as u32; + let height = self.inner.geometry.height - line_height - 16; + let lines = height / line_height; + + let offset = height % line_height; + let pos = if event.pos.1 >= offset { + event.pos.1 - offset + } else { + return; + }; + self.inner.selection = (lines - pos / line_height - 1) as usize; + self.inner.dirty = true; + } + } + fn keyboard_input(&mut self, event: &KeyEvent) { if event.state != WEnum::Value(wl_keyboard::KeyState::Pressed) { return; @@ -616,6 +644,11 @@ impl Widget for Interface { fn event(&mut self, event: &Event) { match event { Event::KeyEvent(e) => self.keyboard_input(e), + Event::PointerEvent(e) => self.pointer_input(e), + Event::LauncherUpdate => { + self.inner.dirty = true; + self.launcher.update(&self.inner); + }, Event::TokenUpdate(t) => self.launcher.next_token = Some(t.to_string()), _ => (), } diff --git a/src/widgets/layout.rs b/src/widgets/layout.rs new file mode 100644 index 0000000..c952a97 --- /dev/null +++ b/src/widgets/layout.rs @@ -0,0 +1,174 @@ +use std::cmp::max; + +use crate::{ + fonts::FontMap, + state::State, + widgets::{Geometry}, +}; + +pub trait Layout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry; +} + +pub struct HorizontalLayout { + pub widgets: Vec>, +} + +impl HorizontalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(HorizontalLayout { widgets }) + } +} + +impl Layout for HorizontalLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter() { + let result = w.geometry_update(fonts, &geo, state); + geo.x = result.x + result.width; + geo.width -= result.width; + max_width += result.width; + max_height = max(result.height, max_height); + } + geo.width = max_width; + geo.height = max_height; + geo + } +} + +pub struct VerticalLayout { + pub widgets: Vec>, +} + +impl VerticalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(VerticalLayout { widgets }) + } +} + +impl Layout for VerticalLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter() { + let result = w.geometry_update(fonts, &geo, state); + geo.y = result.y + result.height; + geo.height -= result.height; + max_width = max(result.width, max_width); + max_height += result.height; + } + geo.width = max_width; + geo.height = max_height; + geo + } +} + +pub trait WidgetUpdater { + fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry) + -> Geometry; +} + +pub struct IndexedLayout { + pub widget_idx: usize, +} + +impl IndexedLayout { + pub fn new(widget_idx: usize) -> Box { + Box::new(IndexedLayout { widget_idx }) + } +} + +impl Layout for IndexedLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + state.geometry_update(self.widget_idx, fonts, geometry) + } +} + +pub struct Margin { + pub widget: Box, + pub margin: (u32, u32, u32, u32), +} + +impl Margin { + pub fn new(widget: Box, margin: (u32, u32, u32, u32)) -> Box { + Box::new(Margin { widget, margin }) + } +} + +impl Layout for Margin { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let geo = Geometry { + x: geometry.x + self.margin.0, + y: geometry.y + self.margin.1, + width: geometry.width - self.margin.0 - self.margin.2, + height: geometry.height - self.margin.1 - self.margin.3, + }; + let out = self.widget.geometry_update(fonts, &geo, state); + Geometry { + x: out.x - self.margin.0, + y: out.y - self.margin.1, + width: out.width + self.margin.0 + self.margin.2, + height: out.height + self.margin.1 + self.margin.3, + } + } +} + +pub struct InvertedHorizontalLayout { + pub widgets: Vec>, +} + +impl InvertedHorizontalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(InvertedHorizontalLayout { widgets }) + } +} + +impl Layout for InvertedHorizontalLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_height = 0; + for w in self.widgets.iter() { + let mut temp_geo = geo.clone(); + let temp_result = w.geometry_update(fonts, &temp_geo, state); + temp_geo.x = geo.x + (geo.width - temp_result.width); + let result = w.geometry_update(fonts, &temp_geo, state); + geo.width -= result.width; + max_height = max(result.height, max_height); + } + geo.height = max_height; + geo + } +} diff --git a/src/widgets/line.rs b/src/widgets/line.rs index 705097b..faa9750 100644 --- a/src/widgets/line.rs +++ b/src/widgets/line.rs @@ -7,14 +7,17 @@ use crate::{ widgets::{Geometry, Widget}, }; -#[derive(Default)] pub struct Line { geometry: Geometry, + thickness: u32, } impl Line { - pub fn new() -> Line { - Default::default() + pub fn new(thickness: u32) -> Line { + Line{ + geometry: Default::default(), + thickness, + } } } @@ -23,16 +26,15 @@ impl Widget for Line { self.geometry } fn draw(&mut self, _fonts: &mut FontMap, view: &mut BufferView) -> Geometry { - view.memset(&Color::new(1., 1., 1., 1.)); + view.memset(Color::WHITE); self.geometry } - fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { self.geometry = Geometry { x: geometry.x, y: geometry.y, width: geometry.width, - height: 1, + height: self.thickness, }; self.geometry } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index ac08074..eb74676 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,3 +1,4 @@ +mod audio; mod backlight; mod bar_widget; mod battery; @@ -7,7 +8,9 @@ mod date; mod launcher; mod line; mod widget; +mod layout; +pub use audio::Audio; pub use backlight::Backlight; pub use battery::Battery; pub use calendar::Calendar; @@ -15,7 +18,5 @@ pub use clock::Clock; pub use date::Date; pub use launcher::Interface; pub use line::Line; -pub use widget::{ - Geometry, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, - VerticalLayout, Widget, WidgetUpdater, -}; +pub use layout::{VerticalLayout, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, WidgetUpdater}; +pub use widget::{Geometry, Widget}; diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs index c63f8e6..b097854 100644 --- a/src/widgets/widget.rs +++ b/src/widgets/widget.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ buffer::BufferView, fonts::FontMap, - state::{Event, State}, + event::Event, }; #[derive(Debug, Default, Clone, Copy)] @@ -47,6 +47,11 @@ impl Geometry { height: max(self.height + self.y, other.height + other.height) - y, } } + + pub fn contains(&self, position: (u32, u32)) -> bool { + position.0 >= self.x && position.0 < self.x + self.width && + position.1 >= self.y && position.1 < self.y + self.height + } } pub trait Widget { @@ -59,169 +64,3 @@ pub trait Widget { fn event(&mut self, _event: &Event) {} } -pub trait Layout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry; -} -pub struct HorizontalLayout { - pub widgets: Vec>, -} - -impl HorizontalLayout { - pub fn new(widgets: Vec>) -> Box { - Box::new(HorizontalLayout { widgets }) - } -} - -impl Layout for HorizontalLayout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let mut geo = geometry.clone(); - let mut max_width = 0; - let mut max_height = 0; - for w in self.widgets.iter() { - let result = w.geometry_update(fonts, &geo, state); - geo.x = result.x + result.width; - geo.width -= result.width; - max_width += result.width; - max_height = max(result.height, max_height); - } - geo.width = max_width; - geo.height = max_height; - geo - } -} - -pub struct VerticalLayout { - pub widgets: Vec>, -} - -impl VerticalLayout { - pub fn new(widgets: Vec>) -> Box { - Box::new(VerticalLayout { widgets }) - } -} - -impl Layout for VerticalLayout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let mut geo = geometry.clone(); - let mut max_width = 0; - let mut max_height = 0; - for w in self.widgets.iter() { - let result = w.geometry_update(fonts, &geo, state); - geo.y = result.y + result.height; - geo.height -= result.height; - max_width = max(result.width, max_width); - max_height += result.height; - } - geo.width = max_width; - geo.height = max_height; - geo - } -} - -pub trait WidgetUpdater { - fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry) - -> Geometry; -} - -pub struct IndexedLayout { - pub widget_idx: usize, -} - -impl IndexedLayout { - pub fn new(widget_idx: usize) -> Box { - Box::new(IndexedLayout { widget_idx }) - } -} - -impl Layout for IndexedLayout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - state.geometry_update(self.widget_idx, fonts, geometry) - } -} - -pub struct Margin { - pub widget: Box, - pub margin: (u32, u32, u32, u32), -} - -impl Margin { - pub fn new(widget: Box, margin: (u32, u32, u32, u32)) -> Box { - Box::new(Margin { widget, margin }) - } -} - -impl Layout for Margin { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let geo = Geometry { - x: geometry.x + self.margin.0, - y: geometry.y + self.margin.1, - width: geometry.width - self.margin.0 - self.margin.2, - height: geometry.height - self.margin.1 - self.margin.3, - }; - - let out = self.widget.geometry_update(fonts, &geo, state); - Geometry { - x: out.x - self.margin.0, - y: out.y - self.margin.1, - width: out.width + self.margin.0 + self.margin.2, - height: out.height + self.margin.1 + self.margin.3, - } - } -} - -pub struct InvertedHorizontalLayout { - pub widgets: Vec>, -} - -impl InvertedHorizontalLayout { - pub fn new(widgets: Vec>) -> Box { - Box::new(InvertedHorizontalLayout { widgets }) - } -} - -impl Layout for InvertedHorizontalLayout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let mut geo = geometry.clone(); - let mut max_height = 0; - for w in self.widgets.iter() { - let mut temp_geo = geo.clone(); - let temp_result = w.geometry_update(fonts, &temp_geo, state); - temp_geo.x = geo.x + (geo.width - temp_result.width); - let result = w.geometry_update(fonts, &temp_geo, state); - geo.width -= result.width; - max_height = max(result.height, max_height); - } - geo.height = max_height; - geo - } -}