system settings
git clone https://git.lucas.co/cce-system-interface.git
Migrate from winit to smithay-client-toolkit and calloop
Cargo.lock | 809 +--------------------
Cargo.toml | 7 +-
src/app.rs | 10 +-
src/main.rs | 1793 ++++++++++++++++++++++++++++++-----------------
src/pages/display.rs | 4 +-
src/pages/processors.rs | 264 ++++++-
src/pages/typeface.rs | 499 +++++++++++--
7 files changed, 1885 insertions(+), 1501 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 37ca0db..812100a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,60 +2,6 @@
# It is not intended for manual editing.
version = 4
-[[package]]
-name = "ab_glyph"
-version = "0.2.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
-dependencies = [
- "ab_glyph_rasterizer",
- "owned_ttf_parser",
-]
-
-[[package]]
-name = "ab_glyph_rasterizer"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
-
-[[package]]
-name = "ahash"
-version = "0.8.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
-dependencies = [
- "cfg-if",
- "getrandom 0.3.4",
- "once_cell",
- "version_check",
- "zerocopy",
-]
-
-[[package]]
-name = "android-activity"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd"
-dependencies = [
- "android-properties",
- "bitflags 2.11.1",
- "cc",
- "jni",
- "libc",
- "log",
- "ndk",
- "ndk-context",
- "ndk-sys 0.6.0+11769913",
- "num_enum",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "android-properties"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
-
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -71,24 +17,12 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
-[[package]]
-name = "arrayref"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
-
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
-[[package]]
-name = "as-raw-xcb-connection"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
-
[[package]]
name = "ash"
version = "0.38.0+1.3.281"
@@ -277,15 +211,6 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
-[[package]]
-name = "block2"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
-dependencies = [
- "objc2",
-]
-
[[package]]
name = "blocking"
version = "1.6.2"
@@ -364,8 +289,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
dependencies = [
"find-msvc-tools",
- "jobserver",
- "libc",
"shlex",
]
@@ -386,14 +309,19 @@ name = "clear-system-interface"
version = "0.1.0"
dependencies = [
"bytemuck",
+ "calloop",
+ "calloop-wayland-source",
"clear-ui",
"futures",
"glyphon",
"pollster",
+ "raw-window-handle",
"serde",
+ "smithay-client-toolkit",
"tokio",
+ "wayland-client",
"wgpu",
- "winit",
+ "xkeysym",
"zbus",
]
@@ -402,11 +330,16 @@ name = "clear-ui"
version = "0.1.0"
dependencies = [
"bytemuck",
+ "calloop",
+ "calloop-wayland-source",
"glyphon",
"pollster",
+ "raw-window-handle",
+ "smithay-client-toolkit",
"tokio",
+ "wayland-client",
"wgpu",
- "winit",
+ "xkeysym",
]
[[package]]
@@ -419,16 +352,6 @@ dependencies = [
"unicode-width",
]
-[[package]]
-name = "combine"
-version = "4.6.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
-dependencies = [
- "bytes",
- "memchr",
-]
-
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@@ -454,19 +377,6 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-[[package]]
-name = "core-graphics"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
-dependencies = [
- "bitflags 1.3.2",
- "core-foundation",
- "core-graphics-types",
- "foreign-types",
- "libc",
-]
-
[[package]]
name = "core-graphics-types"
version = "0.1.3"
@@ -532,12 +442,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
-[[package]]
-name = "dispatch"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
-
[[package]]
name = "dlib"
version = "0.5.3"
@@ -562,12 +466,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
-[[package]]
-name = "dpi"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
-
[[package]]
name = "either"
version = "1.15.0"
@@ -701,7 +599,7 @@ checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3"
dependencies = [
"fontconfig-parser",
"log",
- "memmap2",
+ "memmap2 0.9.10",
"slotmap",
"tinyvec",
"ttf-parser 0.20.0",
@@ -835,28 +733,6 @@ dependencies = [
"slab",
]
-[[package]]
-name = "gethostname"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
-dependencies = [
- "rustix 1.1.4",
- "windows-link",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi 5.3.0",
- "wasip2",
-]
-
[[package]]
name = "getrandom"
version = "0.4.2"
@@ -865,7 +741,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
- "r-efi 6.0.0",
+ "r-efi",
"wasip2",
"wasip3",
]
@@ -1029,36 +905,6 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
-[[package]]
-name = "jni"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
-dependencies = [
- "cfg-if",
- "combine",
- "jni-macros",
- "jni-sys 0.4.1",
- "log",
- "simd_cesu8",
- "thiserror 2.0.18",
- "walkdir",
- "windows-link",
-]
-
-[[package]]
-name = "jni-macros"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
-dependencies = [
- "proc-macro2",
- "quote",
- "rustc_version",
- "simd_cesu8",
- "syn",
-]
-
[[package]]
name = "jni-sys"
version = "0.3.1"
@@ -1087,16 +933,6 @@ dependencies = [
"syn",
]
-[[package]]
-name = "jobserver"
-version = "0.1.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
-dependencies = [
- "getrandom 0.3.4",
- "libc",
-]
-
[[package]]
name = "js-sys"
version = "0.3.98"
@@ -1154,18 +990,6 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
-[[package]]
-name = "libredox"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
-dependencies = [
- "bitflags 2.11.1",
- "libc",
- "plain",
- "redox_syscall 0.7.5",
-]
-
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -1220,6 +1044,15 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+[[package]]
+name = "memmap2"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "memmap2"
version = "0.9.10"
@@ -1286,27 +1119,6 @@ dependencies = [
"unicode-xid",
]
-[[package]]
-name = "ndk"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
-dependencies = [
- "bitflags 2.11.1",
- "jni-sys 0.3.1",
- "log",
- "ndk-sys 0.6.0+11769913",
- "num_enum",
- "raw-window-handle",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "ndk-context"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
-
[[package]]
name = "ndk-sys"
version = "0.5.0+25.2.9519653"
@@ -1316,15 +1128,6 @@ dependencies = [
"jni-sys 0.3.1",
]
-[[package]]
-name = "ndk-sys"
-version = "0.6.0+11769913"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
-dependencies = [
- "jni-sys 0.3.1",
-]
-
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -1334,28 +1137,6 @@ dependencies = [
"autocfg",
]
-[[package]]
-name = "num_enum"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
-dependencies = [
- "num_enum_derive",
- "rustversion",
-]
-
-[[package]]
-name = "num_enum_derive"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "objc"
version = "0.2.7"
@@ -1365,225 +1146,12 @@ dependencies = [
"malloc_buf",
]
-[[package]]
-name = "objc-sys"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
-
-[[package]]
-name = "objc2"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
-dependencies = [
- "objc-sys",
- "objc2-encode",
-]
-
-[[package]]
-name = "objc2-app-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "libc",
- "objc2",
- "objc2-core-data",
- "objc2-core-image",
- "objc2-foundation",
- "objc2-quartz-core",
-]
-
-[[package]]
-name = "objc2-cloud-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-core-location",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-contacts"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-core-data"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-core-image"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
- "objc2-metal",
-]
-
-[[package]]
-name = "objc2-core-location"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
-dependencies = [
- "block2",
- "objc2",
- "objc2-contacts",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-encode"
-version = "4.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
-
-[[package]]
-name = "objc2-foundation"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "dispatch",
- "libc",
- "objc2",
-]
-
-[[package]]
-name = "objc2-link-presentation"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
-dependencies = [
- "block2",
- "objc2",
- "objc2-app-kit",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-metal"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-quartz-core"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-foundation",
- "objc2-metal",
-]
-
-[[package]]
-name = "objc2-symbols"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
-dependencies = [
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-ui-kit"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-cloud-kit",
- "objc2-core-data",
- "objc2-core-image",
- "objc2-core-location",
- "objc2-foundation",
- "objc2-link-presentation",
- "objc2-quartz-core",
- "objc2-symbols",
- "objc2-uniform-type-identifiers",
- "objc2-user-notifications",
-]
-
-[[package]]
-name = "objc2-uniform-type-identifiers"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
-dependencies = [
- "block2",
- "objc2",
- "objc2-foundation",
-]
-
-[[package]]
-name = "objc2-user-notifications"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
-dependencies = [
- "bitflags 2.11.1",
- "block2",
- "objc2",
- "objc2-core-location",
- "objc2-foundation",
-]
-
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
-[[package]]
-name = "orbclient"
-version = "0.3.54"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a570f6bca41d29acb2139229a7c873ec99bc9a313bd10804081d89bfac8ff329"
-dependencies = [
- "libc",
- "libredox",
-]
-
[[package]]
name = "ordered-float"
version = "4.6.0"
@@ -1603,15 +1171,6 @@ dependencies = [
"pin-project-lite",
]
-[[package]]
-name = "owned_ttf_parser"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
-dependencies = [
- "ttf-parser 0.25.1",
-]
-
[[package]]
name = "parking"
version = "2.2.1"
@@ -1636,7 +1195,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall 0.5.18",
+ "redox_syscall",
"smallvec",
"windows-link",
]
@@ -1647,32 +1206,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
-[[package]]
-name = "pin-project"
-version = "1.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -1696,12 +1229,6 @@ version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
-[[package]]
-name = "plain"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
-
[[package]]
name = "polling"
version = "3.11.0"
@@ -1780,12 +1307,6 @@ dependencies = [
"proc-macro2",
]
-[[package]]
-name = "r-efi"
-version = "5.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-
[[package]]
name = "r-efi"
version = "6.0.0"
@@ -1840,15 +1361,6 @@ dependencies = [
"font-types",
]
-[[package]]
-name = "redox_syscall"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
-dependencies = [
- "bitflags 1.3.2",
-]
-
[[package]]
name = "redox_syscall"
version = "0.5.18"
@@ -1858,15 +1370,6 @@ dependencies = [
"bitflags 2.11.1",
]
-[[package]]
-name = "redox_syscall"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b"
-dependencies = [
- "bitflags 2.11.1",
-]
-
[[package]]
name = "renderdoc-sys"
version = "1.1.0"
@@ -1891,15 +1394,6 @@ version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
-[[package]]
-name = "rustc_version"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
-dependencies = [
- "semver",
-]
-
[[package]]
name = "rustix"
version = "0.38.44"
@@ -1949,15 +1443,6 @@ dependencies = [
"unicode-script",
]
-[[package]]
-name = "same-file"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
-dependencies = [
- "winapi-util",
-]
-
[[package]]
name = "scoped-tls"
version = "1.0.1"
@@ -1970,19 +1455,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-[[package]]
-name = "sctk-adwaita"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
-dependencies = [
- "ab_glyph",
- "log",
- "memmap2",
- "smithay-client-toolkit",
- "tiny-skia",
-]
-
[[package]]
name = "self_cell"
version = "1.2.2"
@@ -2065,22 +1537,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "simd_cesu8"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
-dependencies = [
- "rustc_version",
- "simdutf8",
-]
-
-[[package]]
-name = "simdutf8"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
-
[[package]]
name = "skrifa"
version = "0.22.3"
@@ -2119,12 +1575,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
dependencies = [
"bitflags 2.11.1",
+ "bytemuck",
"calloop",
"calloop-wayland-source",
"cursor-icon",
"libc",
"log",
- "memmap2",
+ "memmap2 0.9.10",
+ "pkg-config",
"rustix 0.38.44",
"thiserror 1.0.69",
"wayland-backend",
@@ -2134,18 +1592,10 @@ dependencies = [
"wayland-protocols",
"wayland-protocols-wlr",
"wayland-scanner",
+ "xkbcommon",
"xkeysym",
]
-[[package]]
-name = "smol_str"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "socket2"
version = "0.6.3"
@@ -2171,12 +1621,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-[[package]]
-name = "strict-num"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
-
[[package]]
name = "strum"
version = "0.26.3"
@@ -2243,7 +1687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
- "getrandom 0.4.2",
+ "getrandom",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
@@ -2298,31 +1742,6 @@ dependencies = [
"syn",
]
-[[package]]
-name = "tiny-skia"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
-dependencies = [
- "arrayref",
- "arrayvec",
- "bytemuck",
- "cfg-if",
- "log",
- "tiny-skia-path",
-]
-
-[[package]]
-name = "tiny-skia-path"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
-dependencies = [
- "arrayref",
- "bytemuck",
- "strict-num",
-]
-
[[package]]
name = "tinyvec"
version = "1.11.0"
@@ -2439,12 +1858,6 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
-[[package]]
-name = "ttf-parser"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
-
[[package]]
name = "uds_windows"
version = "1.2.1"
@@ -2533,16 +1946,6 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-[[package]]
-name = "walkdir"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
-dependencies = [
- "same-file",
- "winapi-util",
-]
-
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@@ -2716,19 +2119,6 @@ dependencies = [
"wayland-scanner",
]
-[[package]]
-name = "wayland-protocols-plasma"
-version = "0.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
-dependencies = [
- "bitflags 2.11.1",
- "wayland-backend",
- "wayland-client",
- "wayland-protocols",
- "wayland-scanner",
-]
-
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.12"
@@ -2761,7 +2151,6 @@ checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
dependencies = [
"dlib",
"log",
- "once_cell",
"pkg-config",
]
@@ -2775,16 +2164,6 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "web-time"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
[[package]]
name = "wgpu"
version = "24.0.5"
@@ -2863,7 +2242,7 @@ dependencies = [
"log",
"metal",
"naga",
- "ndk-sys 0.5.0+25.2.9519653",
+ "ndk-sys",
"objc",
"once_cell",
"ordered-float",
@@ -2973,15 +2352,6 @@ dependencies = [
"windows-targets",
]
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets",
-]
-
[[package]]
name = "windows-sys"
version = "0.59.0"
@@ -3064,58 +2434,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-[[package]]
-name = "winit"
-version = "0.30.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
-dependencies = [
- "ahash",
- "android-activity",
- "atomic-waker",
- "bitflags 2.11.1",
- "block2",
- "bytemuck",
- "calloop",
- "cfg_aliases",
- "concurrent-queue",
- "core-foundation",
- "core-graphics",
- "cursor-icon",
- "dpi",
- "js-sys",
- "libc",
- "memmap2",
- "ndk",
- "objc2",
- "objc2-app-kit",
- "objc2-foundation",
- "objc2-ui-kit",
- "orbclient",
- "percent-encoding",
- "pin-project",
- "raw-window-handle",
- "redox_syscall 0.4.1",
- "rustix 0.38.44",
- "sctk-adwaita",
- "smithay-client-toolkit",
- "smol_str",
- "tracing",
- "unicode-segmentation",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "wayland-backend",
- "wayland-client",
- "wayland-protocols",
- "wayland-protocols-plasma",
- "web-sys",
- "web-time",
- "windows-sys 0.52.0",
- "x11-dl",
- "x11rb",
- "xkbcommon-dl",
-]
-
[[package]]
name = "winnow"
version = "1.0.3"
@@ -3219,38 +2537,6 @@ dependencies = [
"wasmparser",
]
-[[package]]
-name = "x11-dl"
-version = "2.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
-dependencies = [
- "libc",
- "once_cell",
- "pkg-config",
-]
-
-[[package]]
-name = "x11rb"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
-dependencies = [
- "as-raw-xcb-connection",
- "gethostname",
- "libc",
- "libloading",
- "once_cell",
- "rustix 1.1.4",
- "x11rb-protocol",
-]
-
-[[package]]
-name = "x11rb-protocol"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
-
[[package]]
name = "xcursor"
version = "0.3.10"
@@ -3258,15 +2544,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
[[package]]
-name = "xkbcommon-dl"
-version = "0.4.2"
+name = "xkbcommon"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
+checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
dependencies = [
- "bitflags 2.11.1",
- "dlib",
- "log",
- "once_cell",
+ "libc",
+ "memmap2 0.8.0",
"xkeysym",
]
@@ -3275,6 +2559,9 @@ name = "xkeysym"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+dependencies = [
+ "bytemuck",
+]
[[package]]
name = "xml-rs"
@@ -3355,26 +2642,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
-[[package]]
-name = "zerocopy"
-version = "0.8.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.48"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "zmij"
version = "1.0.21"
diff --git a/Cargo.toml b/Cargo.toml
index f996744..deae035 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,12 @@ edition = "2021"
[dependencies]
clear-ui = { path = "../clear-ui" }
-winit = "0.30"
+smithay-client-toolkit = "0.19.2"
+calloop = "0.13.0"
+calloop-wayland-source = "0.3.0"
+wayland-client = { version = "0.31", features = ["system"] }
+xkeysym = "0.2"
+raw-window-handle = "0.6"
wgpu = "24"
bytemuck = { version = "1", features = ["derive"] }
pollster = "0.4"
diff --git a/src/app.rs b/src/app.rs
index 5e86034..5a52a7d 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -72,7 +72,7 @@ pub enum AppAction {
pub struct PageContent {
pub rects: Vec<([f32; 4], f32, f32, f32, f32)>,
- pub texts: Vec<(String, f32, f32, f32, [f32; 4])>,
+ pub texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>)>,
pub buttons: Vec<ContentButton>,
}
@@ -97,7 +97,11 @@ impl PageContent {
}
pub fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
- self.texts.push((content.to_string(), size, x, y, color));
+ self.texts.push((content.to_string(), size, x, y, color, None));
+ }
+
+ pub fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str) {
+ self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string())));
}
pub fn button(&mut self, label: &str, x: f32, y: f32, w: f32, h: f32,
@@ -117,6 +121,6 @@ impl RenderTarget for PageContent {
}
fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
- self.texts.push((content.to_string(), size, x, y, color));
+ self.texts.push((content.to_string(), size, x, y, color, None));
}
}
diff --git a/src/main.rs b/src/main.rs
index 5960ebf..29b3a10 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,5 @@
use std::sync::Arc;
-use winit::application::ApplicationHandler;
-use winit::event::{ElementState, MouseButton, WindowEvent};
-use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
-use winit::window::{Window, WindowAttributes};
-#[cfg(target_os = "linux")]
-use winit::platform::wayland::WindowAttributesExtWayland;
-
use clear_ui::color;
use clear_ui::widget::{Spinbox, Widget};
use glyphon::{
@@ -17,6 +10,34 @@ use glyphon::{
use clear_system_interface::app::{AppAction, AppState, ContentButton, PageContent};
use clear_system_interface::pages::{self, Page};
+use smithay_client_toolkit::{
+ compositor::{CompositorHandler, CompositorState},
+ delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
+ delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
+ registry::{ProvidesRegistryState, RegistryState},
+ output::{OutputHandler, OutputState},
+ seat::{
+ keyboard::KeyboardHandler,
+ pointer::PointerHandler,
+ Capability, SeatHandler, SeatState,
+ },
+ shell::{
+ xdg::{
+ window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+ XdgShell,
+ },
+ WaylandSurface,
+ },
+ shm::{Shm, ShmHandler},
+};
+use wayland_client::{
+ globals::registry_queue_init,
+ protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+ Connection, QueueHandle, Proxy,
+};
+use calloop::EventLoop;
+use calloop_wayland_source::WaylandSource;
+
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
@@ -62,6 +83,18 @@ fn make_text_buffer(fs: &mut FontSystem, text: &str, size: f32) -> Buffer {
buf
}
+fn make_text_buffer_with_font(fs: &mut FontSystem, text: &str, size: f32, font: Option<&str>) -> Buffer {
+ let metrics = Metrics::new(size, size * 1.4);
+ let mut buf = Buffer::new(fs, metrics);
+ let mut attrs = Attrs::new();
+ if let Some(font_name) = font {
+ attrs = attrs.family(glyphon::Family::Name(font_name));
+ }
+ buf.set_text(fs, text, attrs, glyphon::Shaping::Advanced);
+ buf.shape_until_scroll(fs, true);
+ buf
+}
+
struct AppWidget {
x: f32, y: f32, w: f32, h: f32,
color: [f32; 4],
@@ -89,8 +122,9 @@ enum ColorSelectorAction {
}
struct SystemInterface {
- window: Arc<Window>,
- surface: wgpu::Surface<'static>,
+ window: XdgWindow,
+ surface: wl_surface::WlSurface,
+ wgpu_surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
@@ -144,19 +178,34 @@ struct SystemInterface {
}
impl SystemInterface {
- async fn new(window: Arc<Window>) -> Self {
- let size = window.inner_size();
- let sw = size.width as f32;
- let sh = size.height as f32;
+ async fn new(
+ conn: &Connection,
+ qh: &QueueHandle<App>,
+ compositor_state: &CompositorState,
+ xdg_shell_state: &XdgShell,
+ width: u32,
+ height: u32,
+ ) -> Self {
+ let surface = compositor_state.create_surface(qh);
+ let window = xdg_shell_state.create_window(surface.clone(), WindowDecorations::None, qh);
+ window.set_title("Clear System Interface");
+ window.set_app_id("clear-system-interface");
+ window.set_min_size(Some((820, 680)));
+ window.commit();
+
+ let wayland_handle = Box::leak(Box::new(clear_ui::wayland::WaylandSurfaceHandle {
+ display_ptr: conn.backend().display_id().as_ptr() as *mut std::ffi::c_void,
+ surface_ptr: surface.id().as_ptr() as *mut std::ffi::c_void,
+ }));
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::VULKAN,
..Default::default()
});
- let surface = instance.create_surface(window.clone()).expect("surface");
+ let wgpu_surface = instance.create_surface(wayland_handle).expect("surface");
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
- compatible_surface: Some(&surface),
+ compatible_surface: Some(&wgpu_surface),
force_fallback_adapter: false,
}).await.expect("adapter");
let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
@@ -165,8 +214,8 @@ impl SystemInterface {
required_limits: wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::MemoryUsage,
}, None).await.expect("device");
- let config = surface.get_default_config(&adapter, size.width.max(1), size.height.max(1)).expect("config");
- surface.configure(&device, &config);
+ let config = wgpu_surface.get_default_config(&adapter, width.max(1), height.max(1)).expect("config");
+ wgpu_surface.configure(&device, &config);
let shader_code = clear_ui::SHADER;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
@@ -218,7 +267,7 @@ impl SystemInterface {
let mut text_atlas = TextAtlas::new(&device, &queue, &cache, config.format);
let text_renderer = TextRenderer::new(&mut text_atlas, &device, wgpu::MultisampleState::default(), None);
let mut text_viewport = Viewport::new(&device, &cache);
- text_viewport.update(&queue, Resolution { width: size.width, height: size.height });
+ text_viewport.update(&queue, Resolution { width, height });
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Vertex Buffer"),
@@ -296,9 +345,10 @@ impl SystemInterface {
let (tx_backup, rx_backup) = std::sync::mpsc::channel();
let (tx_color_selector, rx_color_selector) = std::sync::mpsc::channel();
- let scale_factor = (window.scale_factor() as f32).max(2.0) as f64;
+ let scale_factor = 2.0;
+
let mut this = Self {
- window, surface, device, queue, config, render_pipeline,
+ window, surface, wgpu_surface, device, queue, config, render_pipeline,
vertex_buffer, vertex_count: 0,
app,
font_system, swash_cache, text_atlas, text_renderer, text_viewport,
@@ -310,12 +360,12 @@ impl SystemInterface {
rx_processors, rx_system, rx_status, rx_storage, rx_notifications,
rx_backup_state, rx_typeface, tx_backup, rx_backup,
tx_color_selector, rx_color_selector,
- width: size.width, height: size.height,
+ width, height,
needs_rebuild: true,
scroll_y: 0.0,
max_scroll_y: 0.0,
};
- this.rebuild_layout(sw, sh);
+ this.rebuild_layout(width as f32, height as f32);
this
}
@@ -329,8 +379,6 @@ impl SystemInterface {
let hdr_h = self.header_height * s;
let st_h = self.status_height * s;
-
-
// Sidebar bg
widgets.push(AppWidget {
x: 0.0, y: hdr_h, w: sb_w, h: sh - hdr_h - st_h,
@@ -365,7 +413,7 @@ impl SystemInterface {
});
}
- // Content area (logical coords, scaled to physical later)
+ // Content area
let lcx = self.sidebar_width;
let lcy = self.header_height;
let lcw = sw / s - self.sidebar_width;
@@ -387,7 +435,7 @@ impl SystemInterface {
for (_, _, y, _, h) in &pc.rects {
max_y = max_y.max(y + h);
}
- for (_, size, _, y, _) in &pc.texts {
+ for (_, size, _, y, _, _) in &pc.texts {
max_y = max_y.max(y + size);
}
for btn in &pc.buttons {
@@ -405,9 +453,9 @@ impl SystemInterface {
hovering: false, kind: WidgetKind::Static,
});
}
- for (t, size, x, y, tc) in &pc.texts {
+ for (t, size, x, y, tc, font_opt) in &pc.texts {
text_items.push(TextItem {
- buffer: make_text_buffer(&mut self.font_system, t, *size * s),
+ buffer: make_text_buffer_with_font(&mut self.font_system, t, *size * s, font_opt.as_deref()),
x: *x * s, y: (*y - scroll_offset_y) * s,
color: glyphon::Color::rgb(
(tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
@@ -438,8 +486,6 @@ impl SystemInterface {
page_buttons.push(cb);
}
-
-
self.widgets = widgets;
self.text_items = text_items;
self.page_buttons = page_buttons;
@@ -454,7 +500,7 @@ impl SystemInterface {
Page::Display => display::view(&mut self.app.display, cx, cy, cw, ch),
Page::Radios => network::view(&self.app.network, cx, cy, cw, ch),
Page::Layout => layout::view(&mut self.app.layout, cx, cy, cw, ch),
- Page::Processors => processors::view(&self.app.processors, cx, cy, cw, ch),
+ Page::Processors => processors::view(&mut self.app.processors, cx, cy, cw, ch),
Page::Input => input::view(&mut self.app.input, cx, cy, cw, ch),
Page::System => system_info::view(&self.app.system_info, cx, cy, cw, ch),
Page::Status => status::view(&mut self.app.status, cx, cy, cw, ch),
@@ -513,11 +559,11 @@ impl SystemInterface {
text_renderer.prepare(device, queue, font_system, text_atlas, text_viewport, areas, swash_cache).unwrap();
}
- fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
- if size.width > 0 && size.height > 0 {
- self.width = size.width; self.height = size.height;
- self.config.width = size.width; self.config.height = size.height;
- self.surface.configure(&self.device, &self.config);
+ fn resize(&mut self, width: u32, height: u32) {
+ if width > 0 && height > 0 {
+ self.width = width; self.height = height;
+ self.config.width = width; self.config.height = height;
+ self.wgpu_surface.configure(&self.device, &self.config);
self.needs_rebuild = true;
}
}
@@ -596,45 +642,11 @@ impl SystemInterface {
fn handle_action(&mut self, action: &AppAction) {
use pages::*;
match action {
- AppAction::Layout(m) => match m {
- layout::LayoutMessage::PickBackgroundColor => {
- let color = self.app.layout.background_color;
- let tx = self.tx_color_selector.clone();
- tokio::spawn(async move {
- let hex = format!("#{:02X}{:02X}{:02X}", color[0], color[1], color[2]);
- if let Ok(output) = tokio::process::Command::new("clear-colors").arg(&hex).output().await {
- let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
- if s.starts_with('#') && s.len() >= 7 {
- let r = u8::from_str_radix(&s[1..3], 16).unwrap_or(color[0]);
- let g = u8::from_str_radix(&s[3..5], 16).unwrap_or(color[1]);
- let b = u8::from_str_radix(&s[5..7], 16).unwrap_or(color[2]);
- let _ = tx.send(ColorSelectorAction::Background([r, g, b]));
- }
- }
- });
- }
- layout::LayoutMessage::PickBorderColor => {
- let color = self.app.layout.border_color;
- let tx = self.tx_color_selector.clone();
- tokio::spawn(async move {
- let hex = format!("#{:02X}{:02X}{:02X}", color[0], color[1], color[2]);
- if let Ok(output) = tokio::process::Command::new("clear-colors").arg(&hex).output().await {
- let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
- if s.starts_with('#') && s.len() >= 7 {
- let r = u8::from_str_radix(&s[1..3], 16).unwrap_or(color[0]);
- let g = u8::from_str_radix(&s[3..5], 16).unwrap_or(color[1]);
- let b = u8::from_str_radix(&s[5..7], 16).unwrap_or(color[2]);
- let _ = tx.send(ColorSelectorAction::Border([r, g, b]));
- }
- }
- });
- }
- _ => layout::update(&mut self.app.layout, m.clone()),
- },
AppAction::Power(m) => power::update(&mut self.app.power, m.clone()),
AppAction::Audio(m) => audio::update(&mut self.app.audio, m.clone()),
AppAction::Display(m) => display::update(&mut self.app.display, m.clone()),
AppAction::Radios(m) => network::update(&mut self.app.network, m.clone()),
+ AppAction::Layout(m) => layout::update(&mut self.app.layout, m.clone()),
AppAction::Input(m) => input::update(&mut self.app.input, m.clone()),
AppAction::SystemInfo(m) => system_info::update(&mut self.app.system_info, m.clone()),
AppAction::Processors(m) => processors::update(&mut self.app.processors, m.clone()),
@@ -656,595 +668,726 @@ impl SystemInterface {
}
}
- fn handle_event(&mut self, event: &WindowEvent) -> bool {
- match event {
- WindowEvent::MouseWheel { delta, .. } => {
- let s = self.scale_factor as f32;
- if self.cursor_x >= self.sidebar_width * s {
- let scroll_speed = 24.0;
- let dy = match delta {
- winit::event::MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
- winit::event::MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
- };
- let old_scroll = self.scroll_y;
- self.scroll_y = (self.scroll_y + dy).max(0.0).min(self.max_scroll_y);
- if (self.scroll_y - old_scroll).abs() > 0.01 {
- self.needs_rebuild = true;
- return true;
- }
+ fn handle_cursor_moved(&mut self, x: f32, y: f32) -> bool {
+ self.cursor_x = x;
+ self.cursor_y = y;
+ let s = self.scale_factor as f32;
+ let lx = self.cursor_x / s;
+ let ly = self.cursor_y / s + self.scroll_y;
+ let mut changed = false;
+ for w in &mut self.widgets {
+ let was = w.hovering;
+ w.hovering = self.cursor_x >= w.x && self.cursor_x <= w.x + w.w
+ && self.cursor_y >= w.y && self.cursor_y <= w.y + w.h;
+ if w.hovering != was {
+ w.color = if w.hovering { w.hover_color } else { w.color };
+ changed = true;
+ }
+ }
+ if self.app.current_page == Page::Layout {
+ for sb in &mut self.app.layout.spinboxes {
+ if sb.cursor_moved(lx, ly) {
+ changed = true;
}
- false
- }
- WindowEvent::CursorMoved { position, .. } => {
- self.cursor_x = position.x as f32;
- self.cursor_y = position.y as f32;
- let s = self.scale_factor as f32;
- let lx = self.cursor_x / s;
- let ly = self.cursor_y / s + self.scroll_y;
- let mut changed = false;
- for w in &mut self.widgets {
- let was = w.hovering;
- w.hovering = self.cursor_x >= w.x && self.cursor_x <= w.x + w.w
- && self.cursor_y >= w.y && self.cursor_y <= w.y + w.h;
- if w.hovering != was {
- w.color = if w.hovering { w.hover_color } else { w.color };
- changed = true;
- }
+ }
+ if self.app.layout.cascade_offset_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.layout.edge_gap_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ for cp in &mut self.app.layout.color_selectors {
+ if cp.cursor_moved(lx, ly) {
+ changed = true;
}
- if self.app.current_page == Page::Layout {
- for sb in &mut self.app.layout.spinboxes {
- if sb.cursor_moved(lx, ly) {
- changed = true;
- }
- }
- if self.app.layout.cascade_offset_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.layout.edge_gap_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
- for cp in &mut self.app.layout.color_selectors {
- if cp.cursor_moved(lx, ly) {
- changed = true;
- }
- }
+ }
+ }
+ if self.app.current_page == Page::Input {
+ if self.app.input.rate_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.delay_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.tap_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.scroll_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.scroll_friction_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.pointer_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.pointer_friction_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.trackpad_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.trackpad_friction_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ }
+ if self.app.current_page == Page::Audio {
+ for sb in &mut self.app.audio.sink_spinboxes {
+ if sb.cursor_moved(lx, ly) {
+ changed = true;
}
- if self.app.current_page == Page::Input {
- if self.app.input.rate_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.input.delay_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.input.tap_toggle.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.input.scroll_toggle.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.input.scroll_friction_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.input.pointer_toggle.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.input.pointer_friction_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.input.trackpad_toggle.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.input.trackpad_friction_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
+ }
+ for sb in &mut self.app.audio.source_spinboxes {
+ if sb.cursor_moved(lx, ly) {
+ changed = true;
}
- if self.app.current_page == Page::Audio {
- for sb in &mut self.app.audio.sink_spinboxes {
- if sb.cursor_moved(lx, ly) {
- changed = true;
- }
- }
- for sb in &mut self.app.audio.source_spinboxes {
- if sb.cursor_moved(lx, ly) {
- changed = true;
- }
- }
+ }
+ }
+ if self.app.current_page == Page::Display {
+ if self.app.display.brightness_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.display.night_light_label.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ for out in &mut self.app.display.outputs {
+ if out.name_label.cursor_moved(lx, ly) {
+ changed = true;
}
- if self.app.current_page == Page::Display {
- if self.app.display.brightness_spinbox.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.display.night_light_label.cursor_moved(lx, ly) {
- changed = true;
- }
- for out in &mut self.app.display.outputs {
- if out.name_label.cursor_moved(lx, ly) {
- changed = true;
- }
- if out.resolution_label.cursor_moved(lx, ly) {
- changed = true;
- }
- if let Some(ref mut scale_lbl) = out.scale_label {
- if scale_lbl.cursor_moved(lx, ly) {
- changed = true;
- }
- }
- }
+ if out.resolution_label.cursor_moved(lx, ly) {
+ changed = true;
}
- if self.app.current_page == Page::Notifications {
- if self.app.notifications.enable_toggle.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.notifications.bell_toggle.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.notifications.duration_spinbox.cursor_moved(lx, ly) {
+ if let Some(ref mut scale_lbl) = out.scale_label {
+ if scale_lbl.cursor_moved(lx, ly) {
changed = true;
}
}
- if self.app.current_page == Page::Status {
- if self.app.status.status_label.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.status.size_label.cursor_moved(lx, ly) {
- changed = true;
- }
+ }
+ }
+ if self.app.current_page == Page::Notifications {
+ if self.app.notifications.enable_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.notifications.bell_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.notifications.duration_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ }
+ if self.app.current_page == Page::Processors {
+ if self.app.processors.cpu_label.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ for gpu_lbl in &mut self.app.processors.gpu_labels {
+ if gpu_lbl.cursor_moved(lx, ly) {
+ changed = true;
}
- if self.app.current_page == Page::Typeface {
- if self.app.typeface.sans_box.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.typeface.serif_box.cursor_moved(lx, ly) {
- changed = true;
- }
- if self.app.typeface.mono_box.cursor_moved(lx, ly) {
- changed = true;
- }
+ }
+ if self.app.processors.cpu_list_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ }
+ if self.app.current_page == Page::Status {
+ if self.app.status.status_label.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.status.size_label.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ }
+ if self.app.current_page == Page::Typeface {
+ if self.app.typeface.sans_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.typeface.serif_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.typeface.mono_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.typeface.borders_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.typeface.status_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.typeface.fuzzel_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.typeface.terminal_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.typeface.search_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.typeface.list_box.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ }
+ if changed { self.needs_rebuild = true; }
+ changed
+ }
+
+ fn handle_mouse_input(&mut self, button: clear_ui::widget::MouseButton, state: clear_ui::widget::ElementState) -> bool {
+ if button != clear_ui::widget::MouseButton::Left { return false; }
+ if state == clear_ui::widget::ElementState::Released {
+ let (px, py) = (self.cursor_x, self.cursor_y);
+ for btn in &self.page_buttons.clone() {
+ if px >= btn.x && px <= btn.x + btn.w && py >= btn.y && py <= btn.y + btn.h {
+ self.handle_action(&btn.action);
+ self.needs_rebuild = true;
+ return true;
}
- if changed { self.needs_rebuild = true; }
- changed
- }
- WindowEvent::KeyboardInput { event, .. } => {
- if self.app.current_page == Page::Layout {
- let mut changed = false;
- let mut actions = Vec::new();
- for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
- let old = sb.value;
- if sb.keyboard_input(event) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetWidth(
- pages::layout::WidthParam::ALL[i],
- sb.value as u16,
- )
- ));
- }
- changed = true;
- }
- }
- let sb = &mut self.app.layout.cascade_offset_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.edge_gap_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.top_gap_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
- ));
- }
- changed = true;
- }
- for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
- let old = cp.color;
- if cp.keyboard_input(event) {
- if cp.color != old {
- actions.push(AppAction::Layout(match i {
- 0 => pages::layout::LayoutMessage::SetBackground(cp.color),
- _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
- }));
- }
- changed = true;
+ }
+ for w in &self.widgets {
+ if px >= w.x && px <= w.x + w.w && py >= w.y && py <= w.y + w.h {
+ if let WidgetKind::PageButton(p) = &w.kind {
+ if self.app.current_page != *p {
+ self.app.current_page = *p;
+ self.scroll_y = 0.0;
+ self.needs_rebuild = true;
+ return true;
}
}
- for a in &actions {
- self.handle_action(a);
- }
- if changed {
- self.needs_rebuild = true;
- return true;
- }
}
- if self.app.current_page == Page::Notifications {
- let sb = &mut self.app.notifications.duration_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event) {
- let new_val = sb.value;
- drop(sb);
- if new_val != old {
- self.handle_action(&AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(new_val)));
- }
- self.needs_rebuild = true;
- return true;
- }
+ }
+ }
+ let s = self.scale_factor as f32;
+ let lx = self.cursor_x / s;
+ let ly = self.cursor_y / s + self.scroll_y;
+ let mut actions = Vec::new();
+ if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Layout {
+ for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetWidth(
+ pages::layout::WidthParam::ALL[i],
+ sb.value as u16,
+ )
+ ));
}
- if self.app.current_page == Page::Input {
- if self.app.input.rate_spinbox.keyboard_input(event) {
- self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
- self.needs_rebuild = true;
- return true;
- }
- if self.app.input.delay_spinbox.keyboard_input(event) {
- self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
- self.needs_rebuild = true;
- return true;
- }
- if self.app.input.scroll_friction_spinbox.keyboard_input(event) {
- self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
- self.needs_rebuild = true;
- return true;
- }
- if self.app.input.pointer_friction_spinbox.keyboard_input(event) {
- self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
- self.needs_rebuild = true;
- return true;
- }
- if self.app.input.trackpad_friction_spinbox.keyboard_input(event) {
- self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
- self.needs_rebuild = true;
- return true;
- }
+ }
+ let sb = &mut self.app.layout.cascade_offset_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
+ ));
+ }
+ let sb = &mut self.app.layout.edge_gap_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
+ ));
+ }
+ let sb = &mut self.app.layout.top_gap_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
+ ));
+ }
+ for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
+ let old = cp.color;
+ if !cp.hit_test(lx, ly) { cp.unfocus(); }
+ cp.mouse_input(button, state, lx, ly);
+ if cp.take_click() {
+ actions.push(AppAction::Layout(match i {
+ 0 => pages::layout::LayoutMessage::PickBackgroundColor,
+ _ => pages::layout::LayoutMessage::PickBorderColor,
+ }));
}
- if self.app.current_page == Page::Audio {
- let mut actions = Vec::new();
- for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
- let old = sb.value;
- if sb.keyboard_input(event) {
- if sb.value != old {
- let id = self.app.audio.sinks[i].id;
- actions.push(AppAction::Audio(pages::audio::AudioMessage::SinkVolume(id, sb.value as f32 / 100.0)));
- }
- }
- }
- for (i, sb) in self.app.audio.source_spinboxes.iter_mut().enumerate() {
- let old = sb.value;
- if sb.keyboard_input(event) {
- if sb.value != old {
- let id = self.app.audio.sources[i].id;
- actions.push(AppAction::Audio(pages::audio::AudioMessage::SourceVolume(id, sb.value as f32 / 100.0)));
- }
- }
- }
- for a in &actions {
- self.handle_action(a);
- }
- if !actions.is_empty() {
- self.needs_rebuild = true;
- return true;
- }
+ if cp.color != old {
+ actions.push(AppAction::Layout(match i {
+ 0 => pages::layout::LayoutMessage::SetBackground(cp.color),
+ _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
+ }));
}
- if self.app.current_page == Page::Display {
- let sb = &mut self.app.display.brightness_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event) {
- let new_val = sb.value;
- drop(sb);
- if new_val != old {
- self.handle_action(&AppAction::Display(pages::display::DisplayMessage::BrightnessSet(new_val as u32)));
- }
- self.needs_rebuild = true;
- return true;
- }
+ }
+ }
+ if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Input {
+ let sb = &mut self.app.input.rate_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
+ }
+ let sb = &mut self.app.input.delay_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
+ }
+ let sb = &mut self.app.input.scroll_friction_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
+ }
+ let sb = &mut self.app.input.pointer_friction_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
+ }
+ let sb = &mut self.app.input.trackpad_friction_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
+ }
+ }
+ if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Notifications {
+ let sb = &mut self.app.notifications.duration_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(sb.value)));
+ }
+ }
+ if self.app.current_page == Page::Input {
+ let toggle = &mut self.app.input.tap_toggle;
+ toggle.mouse_input(button, state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ToggleTapToClick));
+ }
+ let toggle = &mut self.app.input.scroll_toggle;
+ toggle.mouse_input(button, state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialScroll));
+ }
+ let toggle = &mut self.app.input.pointer_toggle;
+ toggle.mouse_input(button, state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialPointer));
+ }
+ let toggle = &mut self.app.input.trackpad_toggle;
+ toggle.mouse_input(button, state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialTrackpad));
+ }
+ }
+ if self.app.current_page == Page::Notifications {
+ let toggle = &mut self.app.notifications.enable_toggle;
+ toggle.mouse_input(button, state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleEnable));
+ }
+ let toggle = &mut self.app.notifications.bell_toggle;
+ toggle.mouse_input(button, state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleBell));
+ }
+ }
+ if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Audio {
+ for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ let id = self.app.audio.sinks[i].id;
+ actions.push(AppAction::Audio(pages::audio::AudioMessage::SinkVolume(id, sb.value as f32 / 100.0)));
}
- if self.app.current_page == Page::Typeface {
- let mut actions = Vec::new();
- let mut consumed = false;
-
- let tb = &mut self.app.typeface.sans_box;
- if tb.keyboard_input(event) {
- if tb.take_change() {
- actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
- }
- consumed = true;
- }
+ }
+ for (i, sb) in self.app.audio.source_spinboxes.iter_mut().enumerate() {
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ let id = self.app.audio.sources[i].id;
+ actions.push(AppAction::Audio(pages::audio::AudioMessage::SourceVolume(id, sb.value as f32 / 100.0)));
+ }
+ }
+ }
+ if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Display {
+ let sb = &mut self.app.display.brightness_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Display(pages::display::DisplayMessage::BrightnessSet(sb.value as u32)));
+ }
+ let lbl = &mut self.app.display.night_light_label;
+ if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
+ lbl.mouse_input(button, state, lx, ly);
+
+ for out in &mut self.app.display.outputs {
+ let lbl = &mut out.name_label;
+ if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
+ lbl.mouse_input(button, state, lx, ly);
+
+ let lbl2 = &mut out.resolution_label;
+ if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
+ lbl2.mouse_input(button, state, lx, ly);
+
+ if let Some(ref mut scale_lbl) = out.scale_label {
+ if !scale_lbl.hit_test(lx, ly) { scale_lbl.unfocus(); }
+ scale_lbl.mouse_input(button, state, lx, ly);
+ }
+ }
+ }
+ if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Status {
+ let lbl1 = &mut self.app.status.status_label;
+ if !lbl1.hit_test(lx, ly) { lbl1.unfocus(); }
+ lbl1.mouse_input(button, state, lx, ly);
+
+ let lbl2 = &mut self.app.status.size_label;
+ if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
+ lbl2.mouse_input(button, state, lx, ly);
+ }
+ if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Typeface {
+ let tb = &mut self.app.typeface.sans_box;
+ if !tb.hit_test(lx, ly) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
+ }
- let tb = &mut self.app.typeface.serif_box;
- if tb.keyboard_input(event) {
- if tb.take_change() {
- actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSerif(tb.text.clone())));
- }
- consumed = true;
- }
+ let tb = &mut self.app.typeface.serif_box;
+ if !tb.hit_test(lx, ly) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSerif(tb.text.clone())));
+ }
- let tb = &mut self.app.typeface.mono_box;
- if tb.keyboard_input(event) {
- if tb.take_change() {
- actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
- }
- consumed = true;
- }
-
- for a in &actions {
- self.handle_action(a);
- }
- if consumed {
- self.needs_rebuild = true;
- return true;
- }
+ let tb = &mut self.app.typeface.mono_box;
+ if !tb.hit_test(lx, ly) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
+ }
+
+ let tb = &mut self.app.typeface.borders_box;
+ if !tb.hit_test(lx, ly) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetBorders(tb.text.clone())));
+ }
+
+ let tb = &mut self.app.typeface.status_box;
+ if !tb.hit_test(lx, ly) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetStatus(tb.text.clone())));
+ }
+
+ let tb = &mut self.app.typeface.fuzzel_box;
+ if !tb.hit_test(lx, ly) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetFuzzel(tb.text.clone())));
+ }
+
+ let tb = &mut self.app.typeface.terminal_box;
+ if !tb.hit_test(lx, ly) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetTerminal(tb.text.clone())));
+ }
+
+ let tb = &mut self.app.typeface.search_box;
+ if !tb.hit_test(lx, ly) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSearch(tb.text.clone())));
+ }
+ }
+ for a in &actions {
+ self.handle_action(a);
+ }
+ if !actions.is_empty() {
+ self.needs_rebuild = true;
+ return true;
+ }
+ self.needs_rebuild = true;
+ true
+ }
+
+ fn handle_mouse_wheel(&mut self, delta: &clear_ui::widget::MouseScrollDelta) -> bool {
+ let s = self.scale_factor as f32;
+ if self.cursor_x >= self.sidebar_width * s {
+ let lx = self.cursor_x / s;
+ let ly = self.cursor_y / s + self.scroll_y;
+
+ if self.app.current_page == Page::Typeface {
+ let tf = &mut self.app.typeface;
+ if tf.list_box.mouse_wheel(delta, lx, ly) {
+ self.needs_rebuild = true;
+ return true;
}
- false
- }
- WindowEvent::MouseInput { state, button, .. } => {
- if *button != MouseButton::Left { return false; }
- if *state == ElementState::Released {
- let (px, py) = (self.cursor_x, self.cursor_y);
- for btn in &self.page_buttons.clone() {
- if px >= btn.x && px <= btn.x + btn.w && py >= btn.y && py <= btn.y + btn.h {
- self.handle_action(&btn.action);
- self.needs_rebuild = true;
- return true;
- }
- }
- for w in &self.widgets {
- if px >= w.x && px <= w.x + w.w && py >= w.y && py <= w.y + w.h {
- if let WidgetKind::PageButton(p) = &w.kind {
- if self.app.current_page != *p {
- self.app.current_page = *p;
- self.scroll_y = 0.0;
- self.needs_rebuild = true;
- return true;
- }
- }
- }
- }
+ }
+ if self.app.current_page == Page::Processors {
+ let proc = &mut self.app.processors;
+ if proc.cpu_list_box.mouse_wheel(delta, lx, ly) {
+ self.needs_rebuild = true;
+ return true;
}
- let s = self.scale_factor as f32;
- let lx = self.cursor_x / s;
- let ly = self.cursor_y / s + self.scroll_y;
- let mut actions = Vec::new();
- if *state == ElementState::Pressed && self.app.current_page == Page::Layout {
- for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetWidth(
- pages::layout::WidthParam::ALL[i],
- sb.value as u16,
- )
- ));
- }
- }
- let sb = &mut self.app.layout.cascade_offset_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
- ));
- }
- let sb = &mut self.app.layout.edge_gap_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
- ));
- }
- let sb = &mut self.app.layout.top_gap_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
+ }
+
+ let scroll_speed = 24.0;
+ let dy = match delta {
+ clear_ui::widget::MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
+ clear_ui::widget::MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
+ };
+ let old_scroll = self.scroll_y;
+ self.scroll_y = (self.scroll_y + dy).max(0.0).min(self.max_scroll_y);
+ if (self.scroll_y - old_scroll).abs() > 0.01 {
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ false
+ }
+
+ fn handle_key_input(&mut self, event: &clear_ui::widget::KeyEvent) -> bool {
+ if self.app.current_page == Page::Layout {
+ let mut changed = false;
+ let mut actions = Vec::new();
+ for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ if sb.value != old {
actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
+ pages::layout::LayoutMessage::SetWidth(
+ pages::layout::WidthParam::ALL[i],
+ sb.value as u16,
+ )
));
}
- for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
- let old = cp.color;
- if !cp.hit_test(lx, ly) { cp.unfocus(); }
- cp.mouse_input(*button, *state, lx, ly);
- if cp.take_click() {
- actions.push(AppAction::Layout(match i {
- 0 => pages::layout::LayoutMessage::PickBackgroundColor,
- _ => pages::layout::LayoutMessage::PickBorderColor,
- }));
- }
- if cp.color != old {
- actions.push(AppAction::Layout(match i {
- 0 => pages::layout::LayoutMessage::SetBackground(cp.color),
- _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
- }));
- }
- }
+ changed = true;
}
- if *state == ElementState::Pressed && self.app.current_page == Page::Input {
- let sb = &mut self.app.input.rate_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
- }
- let sb = &mut self.app.input.delay_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
- }
- let sb = &mut self.app.input.scroll_friction_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
- }
- let sb = &mut self.app.input.pointer_friction_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
- }
- let sb = &mut self.app.input.trackpad_friction_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
- }
+ }
+ let sb = &mut self.app.layout.cascade_offset_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ if sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
+ ));
}
- if *state == ElementState::Pressed && self.app.current_page == Page::Notifications {
- let sb = &mut self.app.notifications.duration_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(sb.value)));
- }
+ changed = true;
+ }
+ let sb = &mut self.app.layout.edge_gap_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ if sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
+ ));
}
- if self.app.current_page == Page::Input {
- let toggle = &mut self.app.input.tap_toggle;
- toggle.mouse_input(*button, *state, lx, ly);
- if toggle.take_click() {
- actions.push(AppAction::Input(pages::input::InputMessage::ToggleTapToClick));
- }
- let toggle = &mut self.app.input.scroll_toggle;
- toggle.mouse_input(*button, *state, lx, ly);
- if toggle.take_click() {
- actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialScroll));
- }
- let toggle = &mut self.app.input.pointer_toggle;
- toggle.mouse_input(*button, *state, lx, ly);
- if toggle.take_click() {
- actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialPointer));
- }
- let toggle = &mut self.app.input.trackpad_toggle;
- toggle.mouse_input(*button, *state, lx, ly);
- if toggle.take_click() {
- actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialTrackpad));
- }
+ changed = true;
+ }
+ let sb = &mut self.app.layout.top_gap_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ if sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
+ ));
}
- if self.app.current_page == Page::Notifications {
- let toggle = &mut self.app.notifications.enable_toggle;
- toggle.mouse_input(*button, *state, lx, ly);
- if toggle.take_click() {
- actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleEnable));
- }
- let toggle = &mut self.app.notifications.bell_toggle;
- toggle.mouse_input(*button, *state, lx, ly);
- if toggle.take_click() {
- actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleBell));
- }
+ changed = true;
+ }
+ for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
+ let old = cp.color;
+ if cp.keyboard_input(event) {
+ if cp.color != old {
+ actions.push(AppAction::Layout(match i {
+ 0 => pages::layout::LayoutMessage::SetBackground(cp.color),
+ _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
+ }));
+ }
+ changed = true;
}
- if *state == ElementState::Pressed && self.app.current_page == Page::Audio {
- for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- let id = self.app.audio.sinks[i].id;
- actions.push(AppAction::Audio(pages::audio::AudioMessage::SinkVolume(id, sb.value as f32 / 100.0)));
- }
- }
- for (i, sb) in self.app.audio.source_spinboxes.iter_mut().enumerate() {
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- let id = self.app.audio.sources[i].id;
- actions.push(AppAction::Audio(pages::audio::AudioMessage::SourceVolume(id, sb.value as f32 / 100.0)));
- }
- }
+ }
+ for a in &actions {
+ self.handle_action(a);
+ }
+ if changed {
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ if self.app.current_page == Page::Notifications {
+ let sb = &mut self.app.notifications.duration_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ let new_val = sb.value;
+ drop(sb);
+ if new_val != old {
+ self.handle_action(&AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(new_val)));
}
- if *state == ElementState::Pressed && self.app.current_page == Page::Display {
- let sb = &mut self.app.display.brightness_spinbox;
- if !sb.hit_test(lx, ly) { sb.unfocus(); }
- let old = sb.value;
- if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
- actions.push(AppAction::Display(pages::display::DisplayMessage::BrightnessSet(sb.value as u32)));
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ if self.app.current_page == Page::Input {
+ if self.app.input.rate_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
+ self.needs_rebuild = true;
+ return true;
+ }
+ if self.app.input.delay_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
+ self.needs_rebuild = true;
+ return true;
+ }
+ if self.app.input.scroll_friction_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
+ self.needs_rebuild = true;
+ return true;
+ }
+ if self.app.input.pointer_friction_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
+ self.needs_rebuild = true;
+ return true;
+ }
+ if self.app.input.trackpad_friction_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ if self.app.current_page == Page::Audio {
+ let mut actions = Vec::new();
+ for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ if sb.value != old {
+ let id = self.app.audio.sinks[i].id;
+ actions.push(AppAction::Audio(pages::audio::AudioMessage::SinkVolume(id, sb.value as f32 / 100.0)));
}
- let lbl = &mut self.app.display.night_light_label;
- if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
- lbl.mouse_input(*button, *state, lx, ly);
-
- for out in &mut self.app.display.outputs {
- let lbl = &mut out.name_label;
- if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
- lbl.mouse_input(*button, *state, lx, ly);
-
- let lbl2 = &mut out.resolution_label;
- if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
- lbl2.mouse_input(*button, *state, lx, ly);
-
- if let Some(ref mut scale_lbl) = out.scale_label {
- if !scale_lbl.hit_test(lx, ly) { scale_lbl.unfocus(); }
- scale_lbl.mouse_input(*button, *state, lx, ly);
- }
+ }
+ }
+ for (i, sb) in self.app.audio.source_spinboxes.iter_mut().enumerate() {
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ if sb.value != old {
+ let id = self.app.audio.sources[i].id;
+ actions.push(AppAction::Audio(pages::audio::AudioMessage::SourceVolume(id, sb.value as f32 / 100.0)));
}
}
- if *state == ElementState::Pressed && self.app.current_page == Page::Status {
- let lbl1 = &mut self.app.status.status_label;
- if !lbl1.hit_test(lx, ly) { lbl1.unfocus(); }
- lbl1.mouse_input(*button, *state, lx, ly);
-
- let lbl2 = &mut self.app.status.size_label;
- if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
- lbl2.mouse_input(*button, *state, lx, ly);
+ }
+ for a in &actions {
+ self.handle_action(a);
+ }
+ if !actions.is_empty() {
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ if self.app.current_page == Page::Display {
+ let sb = &mut self.app.display.brightness_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ let new_val = sb.value;
+ drop(sb);
+ if new_val != old {
+ self.handle_action(&AppAction::Display(pages::display::DisplayMessage::BrightnessSet(new_val as u32)));
}
- if *state == ElementState::Pressed && self.app.current_page == Page::Typeface {
- let tb = &mut self.app.typeface.sans_box;
- if !tb.hit_test(lx, ly) { tb.unfocus(); }
- if tb.mouse_input(*button, *state, lx, ly) {
- self.needs_rebuild = true;
- }
- if tb.take_change() {
- actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
- }
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ if self.app.current_page == Page::Typeface {
+ let mut actions = Vec::new();
+ let mut consumed = false;
+
+ let tb = &mut self.app.typeface.sans_box;
+ if tb.keyboard_input(event) {
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
+ }
+ consumed = true;
+ }
- let tb = &mut self.app.typeface.serif_box;
- if !tb.hit_test(lx, ly) { tb.unfocus(); }
- if tb.mouse_input(*button, *state, lx, ly) {
- self.needs_rebuild = true;
- }
- if tb.take_change() {
- actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSerif(tb.text.clone())));
- }
+ let tb = &mut self.app.typeface.serif_box;
+ if tb.keyboard_input(event) {
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSerif(tb.text.clone())));
+ }
+ consumed = true;
+ }
- let tb = &mut self.app.typeface.mono_box;
- if !tb.hit_test(lx, ly) { tb.unfocus(); }
- if tb.mouse_input(*button, *state, lx, ly) {
- self.needs_rebuild = true;
- }
- if tb.take_change() {
- actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
- }
+ let tb = &mut self.app.typeface.mono_box;
+ if tb.keyboard_input(event) {
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
}
- for a in &actions {
- self.handle_action(a);
+ consumed = true;
+ }
+
+ let tb = &mut self.app.typeface.borders_box;
+ if tb.keyboard_input(event) {
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetBorders(tb.text.clone())));
}
- if !actions.is_empty() {
- self.needs_rebuild = true;
- return true;
+ consumed = true;
+ }
+
+ let tb = &mut self.app.typeface.status_box;
+ if tb.keyboard_input(event) {
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetStatus(tb.text.clone())));
}
+ consumed = true;
+ }
+
+ let tb = &mut self.app.typeface.fuzzel_box;
+ if tb.keyboard_input(event) {
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetFuzzel(tb.text.clone())));
+ }
+ consumed = true;
+ }
+
+ let tb = &mut self.app.typeface.terminal_box;
+ if tb.keyboard_input(event) {
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetTerminal(tb.text.clone())));
+ }
+ consumed = true;
+ }
+
+ let tb = &mut self.app.typeface.search_box;
+ if tb.keyboard_input(event) {
+ if tb.take_change() {
+ actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSearch(tb.text.clone())));
+ }
+ consumed = true;
+ }
+
+ for a in &actions {
+ self.handle_action(a);
+ }
+ if consumed {
self.needs_rebuild = true;
- self.needs_rebuild = true;
- true
+ return true;
}
- _ => false,
}
+ false
}
fn render(&mut self) {
@@ -1260,10 +1403,10 @@ impl SystemInterface {
self.prepare_text();
- let output = match self.surface.get_current_texture() {
+ let output = match self.wgpu_surface.get_current_texture() {
Ok(t) => t,
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
- self.surface.configure(&self.device, &self.config);
+ self.wgpu_surface.configure(&self.device, &self.config);
return;
}
Err(wgpu::SurfaceError::Timeout) => return,
@@ -1299,57 +1442,352 @@ impl SystemInterface {
}
self.queue.submit(std::iter::once(encoder.finish()));
- self.window.pre_present_notify();
output.present();
}
}
struct App {
+ registry_state: RegistryState,
+ compositor_state: CompositorState,
+ xdg_shell_state: XdgShell,
+ shm_state: Shm,
+ seat_state: SeatState,
+ output_state: OutputState,
+
+ seats: Vec<wl_seat::WlSeat>,
+ pointer: Option<wl_pointer::WlPointer>,
+ keyboard: Option<wl_keyboard::WlKeyboard>,
+
state: Option<SystemInterface>,
initial_page: Page,
+ exit: bool,
+ redraw: bool,
}
-impl App {
- fn new(initial_page: Page) -> Self { Self { state: None, initial_page } }
+
+
+
+impl CompositorHandler for App {
+ fn scale_factor_changed(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _surface: &wl_surface::WlSurface,
+ scale_factor: i32,
+ ) {
+ if let Some(state) = &mut self.state {
+ state.scale_factor = (scale_factor as f32).max(2.0) as f64;
+ state.resize(state.width, state.height);
+ }
+ self.redraw = true;
+ }
+
+ fn transform_changed(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _surface: &wl_surface::WlSurface,
+ _new_transform: wl_output::Transform,
+ ) {}
+
+ fn frame(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _surface: &wl_surface::WlSurface,
+ _time: u32,
+ ) {}
+
+ fn surface_enter(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _surface: &wl_surface::WlSurface,
+ _output: &wl_output::WlOutput,
+ ) {}
+
+ fn surface_leave(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _surface: &wl_surface::WlSurface,
+ _output: &wl_output::WlOutput,
+ ) {}
}
-impl ApplicationHandler for App {
- fn resumed(&mut self, event_loop: &ActiveEventLoop) {
- if self.state.is_some() { return; }
- let mut attributes = WindowAttributes::default()
- .with_title("Clear System Interface")
- .with_decorations(false)
- .with_inner_size(winit::dpi::LogicalSize::new(820, 680));
- #[cfg(target_os = "linux")]
- {
- attributes = attributes.with_name("clear-system-interface", "clear-system-interface");
- }
- let window = Arc::new(event_loop.create_window(attributes).unwrap());
- let mut state = pollster::block_on(SystemInterface::new(window));
- state.app.current_page = self.initial_page;
- state.needs_rebuild = true;
- self.state = Some(state);
- self.state.as_ref().unwrap().window.request_redraw();
+impl OutputHandler for App {
+ fn output_state(&mut self) -> &mut OutputState {
+ &mut self.output_state
+ }
+
+ fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: wl_output::WlOutput) {}
+ fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: wl_output::WlOutput) {}
+ fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _output: wl_output::WlOutput) {}
+}
+
+impl SeatHandler for App {
+ fn seat_state(&mut self) -> &mut SeatState {
+ &mut self.seat_state
+ }
+
+ fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
+ self.seats.push(seat);
+ }
+
+ fn new_capability(
+ &mut self,
+ _conn: &Connection,
+ qh: &QueueHandle<Self>,
+ seat: wl_seat::WlSeat,
+ capability: Capability,
+ ) {
+ if capability == Capability::Pointer && self.pointer.is_none() {
+ let pointer = self.seat_state.get_pointer(qh, &seat).unwrap();
+ self.pointer = Some(pointer);
+ }
+ if capability == Capability::Keyboard && self.keyboard.is_none() {
+ let keyboard = self.seat_state.get_keyboard(qh, &seat, None).unwrap();
+ self.keyboard = Some(keyboard);
+ }
+ }
+
+ fn remove_capability(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _seat: wl_seat::WlSeat,
+ capability: Capability,
+ ) {
+ if capability == Capability::Pointer {
+ self.pointer = None;
+ }
+ if capability == Capability::Keyboard {
+ self.keyboard = None;
+ }
}
- fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: winit::window::WindowId, event: WindowEvent) {
- let redraw = match &event {
- WindowEvent::CloseRequested => { event_loop.exit(); true }
- WindowEvent::Resized(s) => { if let Some(st) = &mut self.state { st.resize(*s); } true }
- WindowEvent::RedrawRequested => {
- if let Some(st) = &mut self.state { st.render(); st.window.request_redraw(); }
- true
+ fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
+ self.seats.retain(|s| s != &seat);
+ }
+}
+
+impl ShmHandler for App {
+ fn shm_state(&mut self) -> &mut Shm {
+ &mut self.shm_state
+ }
+}
+
+impl PointerHandler for App {
+ fn pointer_frame(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _pointer: &wl_pointer::WlPointer,
+ events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
+ ) {
+ use smithay_client_toolkit::seat::pointer::PointerEventKind;
+ for event in events {
+ let (x, y) = event.position;
+ match &event.kind {
+ PointerEventKind::Motion { .. } => {
+ if let Some(st) = &mut self.state {
+ if st.handle_cursor_moved(x as f32, y as f32) {
+ self.redraw = true;
+ }
+ }
+ }
+ PointerEventKind::Press { button, .. } => {
+ let custom_btn = match button {
+ 272 => clear_ui::widget::MouseButton::Left,
+ 273 => clear_ui::widget::MouseButton::Right,
+ _ => continue,
+ };
+ if let Some(st) = &mut self.state {
+ if st.handle_mouse_input(custom_btn, clear_ui::widget::ElementState::Pressed) {
+ self.redraw = true;
+ }
+ }
+ }
+ PointerEventKind::Release { button, .. } => {
+ let custom_btn = match button {
+ 272 => clear_ui::widget::MouseButton::Left,
+ 273 => clear_ui::widget::MouseButton::Right,
+ _ => continue,
+ };
+ if let Some(st) = &mut self.state {
+ if st.handle_mouse_input(custom_btn, clear_ui::widget::ElementState::Released) {
+ self.redraw = true;
+ }
+ }
+ }
+ PointerEventKind::Axis { horizontal, vertical, .. } => {
+ let h_scroll = horizontal.absolute as f32;
+ let v_scroll = vertical.absolute as f32;
+ let delta = clear_ui::widget::MouseScrollDelta::LineDelta(-h_scroll / 10.0, -v_scroll / 10.0);
+ if let Some(st) = &mut self.state {
+ if st.handle_mouse_wheel(&delta) {
+ self.redraw = true;
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+}
+
+impl KeyboardHandler for App {
+ fn enter(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _keyboard: &wl_keyboard::WlKeyboard,
+ _surface: &wl_surface::WlSurface,
+ _serial: u32,
+ _raw_modifiers: &[u32],
+ _keysyms: &[xkeysym::Keysym],
+ ) {}
+
+ fn leave(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _keyboard: &wl_keyboard::WlKeyboard,
+ _surface: &wl_surface::WlSurface,
+ _serial: u32,
+ ) {}
+
+ fn press_key(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _keyboard: &wl_keyboard::WlKeyboard,
+ _serial: u32,
+ event: smithay_client_toolkit::seat::keyboard::KeyEvent,
+ ) {
+ self.handle_key(event, clear_ui::widget::ElementState::Pressed);
+ }
+
+ fn release_key(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _keyboard: &wl_keyboard::WlKeyboard,
+ _serial: u32,
+ event: smithay_client_toolkit::seat::keyboard::KeyEvent,
+ ) {
+ self.handle_key(event, clear_ui::widget::ElementState::Released);
+ }
+
+ fn update_modifiers(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _keyboard: &wl_keyboard::WlKeyboard,
+ _serial: u32,
+ _modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
+ _layout: u32,
+ ) {}
+}
+
+impl App {
+ fn handle_key(&mut self, event: smithay_client_toolkit::seat::keyboard::KeyEvent, state: clear_ui::widget::ElementState) {
+ use clear_ui::widget::{Key, KeyEvent, NamedKey};
+ let logical_key = match event.keysym {
+ xkeysym::Keysym::Escape => Key::Named(NamedKey::Escape),
+ xkeysym::Keysym::Return => Key::Named(NamedKey::Enter),
+ xkeysym::Keysym::BackSpace => Key::Named(NamedKey::Backspace),
+ xkeysym::Keysym::Down => Key::Named(NamedKey::ArrowDown),
+ xkeysym::Keysym::Up => Key::Named(NamedKey::ArrowUp),
+ xkeysym::Keysym::Left => Key::Named(NamedKey::ArrowLeft),
+ xkeysym::Keysym::Right => Key::Named(NamedKey::ArrowRight),
+ xkeysym::Keysym::Tab => Key::Named(NamedKey::Tab),
+ xkeysym::Keysym::Delete => Key::Named(NamedKey::Delete),
+ xkeysym::Keysym::space => Key::Named(NamedKey::Space),
+ _ => {
+ if let Some(ref text) = event.utf8 {
+ Key::Character(text.clone())
+ } else {
+ return;
+ }
}
- _ => self.state.as_mut().map(|st| st.handle_event(&event)).unwrap_or(false)
};
- if redraw { if let Some(st) = &mut self.state { st.window.request_redraw(); } }
+
+ let custom_event = KeyEvent {
+ state,
+ logical_key,
+ text: event.utf8.clone(),
+ repeat: false,
+ };
+
+ if let Some(st) = &mut self.state {
+ if st.handle_key_input(&custom_event) {
+ self.redraw = true;
+ }
+ }
+ }
+}
+
+impl WindowHandler for App {
+ fn configure(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _window: &XdgWindow,
+ configure: WindowConfigure,
+ _serial: u32,
+ ) {
+ let (w, h) = configure.new_size;
+ if let (Some(w), Some(h)) = (w, h) {
+ let width = w.get();
+ let height = h.get();
+ if let Some(state) = &mut self.state {
+ state.resize(width, height);
+ }
+ }
+ self.redraw = true;
+ }
+
+ fn request_close(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _window: &XdgWindow) {
+ self.exit = true;
}
}
+impl ProvidesRegistryState for App {
+ fn registry(&mut self) -> &mut RegistryState {
+ &mut self.registry_state
+ }
+
+ fn runtime_add_global(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _name: u32,
+ _interface: &str,
+ _version: u32,
+ ) {}
+
+ fn runtime_remove_global(
+ &mut self,
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ _name: u32,
+ _interface: &str,
+ ) {}
+}
+
+delegate_compositor!(App);
+delegate_xdg_shell!(App);
+delegate_xdg_window!(App);
+delegate_shm!(App);
+delegate_seat!(App);
+delegate_pointer!(App);
+delegate_keyboard!(App);
+delegate_registry!(App);
+delegate_output!(App);
+
fn main() {
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
let _guard = rt.enter();
- let event_loop = EventLoop::new().unwrap();
- event_loop.set_control_flow(ControlFlow::Poll);
let mut initial_page = Page::ALL[0];
let args: Vec<String> = std::env::args().collect();
@@ -1363,5 +1801,58 @@ fn main() {
}
}
- event_loop.run_app(&mut App::new(initial_page)).unwrap();
+ let conn = Connection::connect_to_env().unwrap();
+ let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
+ let qh = event_queue.handle();
+
+ let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
+ let xdg_shell_state = XdgShell::bind(&globals, &qh).unwrap();
+ let shm_state = Shm::bind(&globals, &qh).unwrap();
+ let seat_state = SeatState::new(&globals, &qh);
+ let output_state = OutputState::new(&globals, &qh);
+
+ let mut app = App {
+ registry_state: RegistryState::new(&globals),
+ compositor_state,
+ xdg_shell_state,
+ shm_state,
+ seat_state,
+ output_state,
+ seats: Vec::new(),
+ pointer: None,
+ keyboard: None,
+ state: None,
+ initial_page,
+ exit: false,
+ redraw: true,
+ };
+
+ let state = pollster::block_on(SystemInterface::new(
+ &conn,
+ &qh,
+ &app.compositor_state,
+ &app.xdg_shell_state,
+ 820,
+ 680,
+ ));
+ app.state = Some(state);
+
+ let mut event_loop = EventLoop::try_new().unwrap();
+ let loop_handle = event_loop.handle();
+ WaylandSource::new(conn, event_queue).insert(loop_handle.clone()).unwrap();
+
+ loop {
+ event_loop
+ .dispatch(std::time::Duration::from_millis(16), &mut app)
+ .unwrap();
+ if app.exit {
+ break;
+ }
+ if app.redraw {
+ app.redraw = false;
+ if let Some(state) = &mut app.state {
+ state.render();
+ }
+ }
+ }
}
diff --git a/src/pages/display.rs b/src/pages/display.rs
index 20d96d9..1ad3c68 100644
--- a/src/pages/display.rs
+++ b/src/pages/display.rs
@@ -61,7 +61,7 @@ impl Default for DisplayState {
max_brightness: 0.0,
outputs: Vec::new(),
night_light: false,
- brightness_spinbox: Spinbox::new(50, 0, 100, 5),
+ brightness_spinbox: Spinbox::new(50, 0, 100, 5).with_unit("%"),
night_light_label: Label::new("Night Light: OFF").with_font_size(13.0).with_color([0xd4, 0xd4, 0xd4]),
}
}
@@ -83,7 +83,7 @@ pub async fn fetch_display_state() -> DisplayState {
DisplayState {
loaded: true,
brightness, max_brightness, outputs, night_light,
- brightness_spinbox: Spinbox::new(pct, 0, 100, 5),
+ brightness_spinbox: Spinbox::new(pct, 0, 100, 5).with_unit("%"),
night_light_label: Label::new(if night_light { "Night Light: ON" } else { "Night Light: OFF" })
.with_font_size(13.0)
.with_color([0xd4, 0xd4, 0xd4]),
diff --git a/src/pages/processors.rs b/src/pages/processors.rs
index 4d45c9b..2c7f855 100644
--- a/src/pages/processors.rs
+++ b/src/pages/processors.rs
@@ -1,18 +1,98 @@
use crate::app::PageContent;
use clear_ui::layout::Section;
+use clear_ui::widget::{Label, ScrollBox};
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
pub struct ProcessorsState {
pub cpu_model: String,
pub cpu_usage: f32,
pub cpu_cores: u32,
- pub gpu: String,
+ pub gpus: Vec<String>,
pub loaded: bool,
+ pub cpu_label: Label,
+ pub gpu_labels: Vec<Label>,
+ pub processes: Vec<(String, String, String)>, // (pid, cpu, comm)
+ pub cpu_list_box: ScrollBox,
+}
+
+impl Default for ProcessorsState {
+ fn default() -> Self {
+ Self {
+ cpu_model: String::new(),
+ cpu_usage: 0.0,
+ cpu_cores: 0,
+ gpus: Vec::new(),
+ loaded: false,
+ cpu_label: Label::new("CPU Info"),
+ gpu_labels: Vec::new(),
+ processes: Vec::new(),
+ cpu_list_box: ScrollBox::new(),
+ }
+ }
}
#[derive(Debug, Clone)]
pub enum ProcessorsMessage {
Refreshed(ProcessorsState),
+ None,
+}
+
+fn read_cpu_temp() -> Option<f32> {
+ if let Ok(entries) = std::fs::read_dir("/sys/class/hwmon") {
+ for entry in entries.filter_map(|e| e.ok()) {
+ let path = entry.path();
+ if let Ok(name) = std::fs::read_to_string(path.join("name")) {
+ let name = name.trim();
+ if name == "thinkpad" {
+ if let Ok(val) = std::fs::read_to_string(path.join("temp1_input")) {
+ if let Ok(temp_milli) = val.trim().parse::<f32>() {
+ return Some(temp_milli / 1000.0);
+ }
+ }
+ } else if name == "coretemp" {
+ if let Ok(val) = std::fs::read_to_string(path.join("temp1_input")) {
+ if let Ok(temp_milli) = val.trim().parse::<f32>() {
+ return Some(temp_milli / 1000.0);
+ }
+ }
+ }
+ }
+ }
+ }
+ None
+}
+
+fn read_thinkpad_gpu_temp() -> Option<f32> {
+ if let Ok(entries) = std::fs::read_dir("/sys/class/hwmon") {
+ for entry in entries.filter_map(|e| e.ok()) {
+ let path = entry.path();
+ if let Ok(name) = std::fs::read_to_string(path.join("name")) {
+ if name.trim() == "thinkpad" {
+ for i in 1..=8 {
+ let label_path = path.join(format!("temp{}_label", i));
+ if let Ok(lbl) = std::fs::read_to_string(&label_path) {
+ if lbl.trim() == "GPU" {
+ if let Ok(val) = std::fs::read_to_string(path.join(format!("temp{}_input", i))) {
+ if let Ok(temp_milli) = val.trim().parse::<f32>() {
+ return Some(temp_milli / 1000.0);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ None
+}
+
+async fn read_nvidia_gpu_temp() -> Option<f32> {
+ let out = tokio::process::Command::new("nvidia-smi")
+ .args(["--query-gpu=temperature.gpu", "--format=csv,noheader,nounits"])
+ .output().await.ok()?;
+ let val_str = String::from_utf8_lossy(&out.stdout);
+ val_str.trim().parse::<f32>().ok()
}
pub async fn fetch_processors_state() -> ProcessorsState {
@@ -56,44 +136,178 @@ pub async fn fetch_processors_state() -> ProcessorsState {
} else { 0.0 }
} as f32;
- let gpu = tokio::process::Command::new("lspci")
- .output().await.ok()
- .and_then(|o| {
- String::from_utf8_lossy(&o.stdout).lines()
- .find(|l| l.contains("VGA") || l.contains("3D"))
- .and_then(|l| l.split(':').nth(2))
- .map(|s| s.trim().to_string())
- })
- .unwrap_or_default();
-
- ProcessorsState { cpu_model, cpu_usage, cpu_cores, gpu, loaded: true }
+ let mut gpus = Vec::new();
+ if let Some(o) = tokio::process::Command::new("lspci").output().await.ok() {
+ for line in String::from_utf8_lossy(&o.stdout).lines() {
+ if line.contains("VGA") || line.contains("3D") {
+ if let Some(name) = line.split(':').nth(2) {
+ let trimmed = name.trim().to_string();
+ if !trimmed.is_empty() {
+ gpus.push(trimmed);
+ }
+ }
+ }
+ }
+ }
+
+ let processes = {
+ let mut list = Vec::new();
+ if let Some(o) = tokio::process::Command::new("ps")
+ .args(["-eo", "pid,%cpu,comm", "--sort=-%cpu"])
+ .output().await.ok()
+ {
+ let text = String::from_utf8_lossy(&o.stdout);
+ for line in text.lines().skip(1) {
+ let parts: Vec<&str> = line.split_whitespace().collect();
+ if parts.len() >= 3 {
+ let pid = parts[0].to_string();
+ let cpu = parts[1].to_string();
+ let comm = parts[2..].join(" ");
+ list.push((pid, cpu, comm));
+ }
+ }
+ }
+ list
+ };
+
+ let cpu_temp = read_cpu_temp();
+ let tp_gpu_temp = read_thinkpad_gpu_temp();
+ let nv_gpu_temp = read_nvidia_gpu_temp().await;
+
+ let cpu_temp_str = cpu_temp.map(|t| format!(" — {:.0}°C", t)).unwrap_or_default();
+ let cpu_label_text = format!("CPU {} ({} cores) — {:.0}%{}", cpu_model, cpu_cores, cpu_usage, cpu_temp_str);
+
+ let gpu_labels = gpus.iter().map(|gpu_name| {
+ let temp = if gpu_name.to_lowercase().contains("nvidia") {
+ nv_gpu_temp.or(tp_gpu_temp)
+ } else {
+ tp_gpu_temp
+ };
+ let temp_str = temp.map(|t| format!(" — {:.0}°C", t)).unwrap_or_default();
+ let text = format!("GPU {}{}", gpu_name, temp_str);
+ Label::new(&text).with_font_size(12.0).with_color([212, 212, 212])
+ }).collect();
+
+ ProcessorsState {
+ cpu_model,
+ cpu_usage,
+ cpu_cores,
+ gpus,
+ loaded: true,
+ cpu_label: Label::new(&cpu_label_text).with_font_size(12.0).with_color([212, 212, 212]),
+ gpu_labels,
+ processes,
+ cpu_list_box: ScrollBox::new(),
+ }
}
const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
+const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
-pub fn view(state: &ProcessorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut ProcessorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
let mut pc = PageContent::new();
- let y = cy + 12.0;
+ let mut y = cy + 12.0;
- let mut sec = Section::new(&mut pc, cx, y, cw, "Processors");
+ // ── CPU Section ──
+ let mut sec = Section::new(&mut pc, cx, y, cw, "CPU");
if !state.loaded {
- sec.text(&mut pc, "Loading processor models and utilization...", 12.0, 0.0, 12.0, TEXT_FG);
+ sec.text(&mut pc, "Loading CPU model and utilization...", 12.0, 0.0, 12.0, TEXT_FG);
sec.spacing(10.0);
} else {
- sec.text(&mut pc,
- &format!("CPU {} ({} cores) — {:.0}%", state.cpu_model, state.cpu_cores, state.cpu_usage),
- 12.0, 0.0, 12.0, TEXT_FG,
- );
- sec.spacing(10.0);
- sec.text(&mut pc, &format!("GPU {}", state.gpu), 12.0, 0.0, 12.0, TEXT_FG);
+ // CPU Info Label
+ sec.widget(&mut pc, &mut state.cpu_label, 12.0, cw - 24.0, 26.0);
+ sec.spacing(12.0);
+
+ // Scrolling box configuration for process list
+ let list_box_x = cx + 12.0;
+ let list_box_y = sec.ay();
+ let list_box_w = cw - 24.0;
+ let list_box_h = 220.0;
+
+ // Render the standardized ScrollBox widget
+ clear_ui::layout::render_widget(&mut pc, &mut state.cpu_list_box, list_box_x, list_box_y, list_box_w, list_box_h);
+
+ // Header for process list columns (drawn static on top of the ScrollBox background)
+ let header_h = 22.0;
+ pc.rect([0.12, 0.12, 0.16, 0.5], list_box_x + 1.0, list_box_y + 1.0, list_box_w - 2.0, header_h);
+ pc.rect([0.18, 0.18, 0.24, 1.0], list_box_x + 1.0, list_box_y + header_h, list_box_w - 2.0, 1.0); // Divider
+
+ pc.text("PID", list_box_x + 12.0, list_box_y + 5.0, 11.0, [0.53, 0.53, 0.60, 1.0]);
+ pc.text("COMMAND", list_box_x + 80.0, list_box_y + 5.0, 11.0, [0.53, 0.53, 0.60, 1.0]);
+ pc.text("CPU %", list_box_x + list_box_w - 60.0, list_box_y + 5.0, 11.0, [0.53, 0.53, 0.60, 1.0]);
+
+ let row_h = 24.0;
+ let row_gap = 2.0;
+ let item_height_full = row_h + row_gap;
+ let content_h = state.processes.len() as f32 * item_height_full;
+
+ // Update ScrollBox bounds for the scrollable viewport (which starts below the header)
+ state.cpu_list_box.update_bounds(content_h, list_box_y + header_h, list_box_h - header_h - 6.0);
+
+ // Visible process rows rendering (virtualized/clipped)
+
+ for (idx, (pid, cpu, comm)) in state.processes.iter().enumerate() {
+ let virtual_y = idx as f32 * item_height_full + 4.0;
+
+ if let Some(draw_y) = state.cpu_list_box.get_item_draw_y(virtual_y, row_h) {
+ // Standard row action button (transparent background, highlights on hover)
+ pc.button(
+ "",
+ list_box_x + 2.0,
+ draw_y,
+ list_box_w - 16.0,
+ row_h,
+ [0.0, 0.0, 0.0, 0.0],
+ [1.0, 1.0, 1.0, 0.06],
+ [0.0, 0.0, 0.0, 0.0],
+ crate::app::AppAction::Processors(ProcessorsMessage::None),
+ );
+
+ pc.text(pid, list_box_x + 12.0, draw_y + 6.0, 12.0, [0.80, 0.80, 0.85, 1.0]);
+ pc.text(comm, list_box_x + 80.0, draw_y + 6.0, 12.0, [0.80, 0.80, 0.85, 1.0]);
+ pc.text(&format!("{}%", cpu), list_box_x + list_box_w - 60.0, draw_y + 6.0, 12.0, [0.56, 0.83, 0.56, 1.0]);
+ }
+ }
+
+ if state.processes.is_empty() {
+ pc.text("No active processes", list_box_x + 12.0, list_box_y + header_h + 16.0, 12.0, TEXT_DIM);
+ }
+
+ sec.content_y += list_box_h;
+ }
+ y = sec.finish(&mut pc);
+
+ // ── GPU Section ──
+ let mut sec_gpu = Section::new(&mut pc, cx, y, cw, "GPU");
+ if !state.loaded {
+ sec_gpu.text(&mut pc, "Loading GPU models...", 12.0, 0.0, 12.0, TEXT_FG);
+ sec_gpu.spacing(10.0);
+ } else {
+ for (i, gpu_lbl) in state.gpu_labels.iter_mut().enumerate() {
+ if i > 0 { sec_gpu.spacing(12.0); }
+ sec_gpu.widget(&mut pc, gpu_lbl, 12.0, cw - 24.0, 26.0);
+ }
}
- sec.finish(&mut pc);
+ sec_gpu.finish(&mut pc);
pc
}
pub fn update(state: &mut ProcessorsState, msg: ProcessorsMessage) {
match msg {
- ProcessorsMessage::Refreshed(new) => { *state = new; }
+ ProcessorsMessage::Refreshed(new) => {
+ state.loaded = new.loaded;
+ state.cpu_model = new.cpu_model;
+ state.cpu_usage = new.cpu_usage;
+ state.cpu_cores = new.cpu_cores;
+ state.gpus = new.gpus;
+ state.cpu_label = new.cpu_label;
+ state.gpu_labels = new.gpu_labels;
+ state.processes = new.processes;
+ let old_scroll = state.cpu_list_box.scroll_y;
+ state.cpu_list_box = new.cpu_list_box;
+ state.cpu_list_box.scroll_y = old_scroll;
+ }
+ ProcessorsMessage::None => {}
}
}
diff --git a/src/pages/typeface.rs b/src/pages/typeface.rs
index ad6b5c6..0696163 100644
--- a/src/pages/typeface.rs
+++ b/src/pages/typeface.rs
@@ -1,9 +1,8 @@
use std::fs;
use crate::app::PageContent;
use clear_ui::layout::Section;
-use clear_ui::widget::{Widget, TextLabel};
-use winit::event::{ElementState, KeyEvent, MouseButton};
-use winit::keyboard::{Key, NamedKey};
+use clear_ui::widget::{Widget, TextLabel, ScrollBox};
+use clear_ui::widget::{ElementState, KeyEvent, MouseButton, Key, NamedKey};
const FONTS_CONF_PATH: &str = "/home/lsgalante/.config/fontconfig/fonts.conf";
@@ -17,6 +16,9 @@ pub struct TextBox {
pub edit_buffer: String,
hovered: bool,
just_changed: bool,
+ label: Option<String>,
+ row_x: f32,
+ row_w: f32,
}
impl TextBox {
@@ -28,9 +30,21 @@ impl TextBox {
edit_buffer: String::new(),
hovered: false,
just_changed: false,
+ label: None,
+ row_x: 0.0,
+ row_w: 0.0,
}
}
+ pub fn with_label(mut self, label: &str) -> Self {
+ self.label = Some(label.to_string());
+ self
+ }
+
+ pub fn set_label(&mut self, label: &str) {
+ self.label = Some(label.to_string());
+ }
+
pub fn take_change(&mut self) -> bool {
let changed = self.just_changed;
self.just_changed = false;
@@ -47,6 +61,7 @@ impl Default for TextBox {
impl Widget for TextBox {
fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+ fn set_row_rect(&mut self, x: f32, w: f32) { self.row_x = x; self.row_w = w; }
fn set_hovered(&mut self, v: bool) { self.hovered = v; }
fn hovered(&self) -> bool { self.hovered }
@@ -54,6 +69,20 @@ impl Widget for TextBox {
[0.10, 0.10, 0.16, 1.0]
}
+ fn hit_test(&self, px: f32, py: f32) -> bool {
+ let (x, y, w, h) = self.rect();
+ let hx = if self.row_w > 0.0 { self.row_x } else { x };
+ let hw = if self.row_w > 0.0 { self.row_w } else { w };
+ let (hy, hh) = if self.label.is_some() {
+ (y - 18.0, h + 18.0)
+ } else {
+ (y, h)
+ };
+ px >= hx && px <= hx + hw && py >= hy && py <= hy + hh
+ }
+
+ fn top_room(&self) -> f32 { if self.label.is_some() { 18.0 } else { 0.0 } }
+
fn cursor_moved(&mut self, px: f32, py: f32) -> bool {
let was = self.hovered;
self.hovered = self.hit_test(px, py);
@@ -119,16 +148,21 @@ impl Widget for TextBox {
}
fn hover_highlight(&self) -> Option<[f32; 4]> {
- if self.editing {
- Some([1.0, 1.0, 1.0, 0.12])
- } else if self.hovered {
- Some([1.0, 1.0, 1.0, 0.06])
- } else {
- None
- }
+ None
}
fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+ let mut quads = Vec::new();
+ if self.hovered {
+ let (hy, hh) = if self.label.is_some() {
+ (self.y - 18.0, self.h + 18.0)
+ } else {
+ (self.y, self.h)
+ };
+ let hx = if self.row_w > 0.0 { self.row_x } else { self.x };
+ let hw = if self.row_w > 0.0 { self.row_w } else { self.w };
+ quads.push((hx, hy, hw, hh, [1.0, 1.0, 1.0, 0.06]));
+ }
let bg_color = if self.editing {
[0.12, 0.12, 0.18, 1.0]
} else {
@@ -141,25 +175,35 @@ impl Widget for TextBox {
} else {
[0.18, 0.18, 0.24, 1.0]
};
- vec![
- (self.x, self.y, self.w, self.h, border_color),
- (self.x + 1.0, self.y + 1.0, self.w - 2.0, self.h - 2.0, bg_color),
- ]
+ quads.push((self.x, self.y, self.w, self.h, border_color));
+ quads.push((self.x + 1.0, self.y + 1.0, self.w - 2.0, self.h - 2.0, bg_color));
+ quads
}
fn text_labels(&self) -> Vec<TextLabel> {
+ let mut labels = Vec::new();
+ if let Some(ref label) = self.label {
+ labels.push(TextLabel {
+ text: label.clone(),
+ x: self.x + 4.0,
+ y: self.y - 14.0,
+ font_size: 12.0,
+ color: [0x83, 0x83, 0x8a],
+ });
+ }
let val_text = if self.editing {
format!("{}|", self.edit_buffer)
} else {
self.text.clone()
};
- vec![TextLabel {
+ labels.push(TextLabel {
text: val_text,
x: self.x + 8.0,
y: self.y + (self.h - 12.0) / 2.0,
font_size: 13.0,
color: if self.editing { [0xee, 0xee, 0xf5] } else { [0xcc, 0xcc, 0xd4] },
- }]
+ });
+ labels
}
}
@@ -171,11 +215,22 @@ pub struct TypefaceState {
pub sans_serif: String,
pub serif: String,
pub monospace: String,
+ pub window_borders: String,
+ pub status_interface: String,
+ pub fuzzel: String,
+ pub terminal: String,
pub all_fonts: Vec<String>,
pub mono_fonts: Vec<String>,
pub sans_box: TextBox,
pub serif_box: TextBox,
pub mono_box: TextBox,
+ pub borders_box: TextBox,
+ pub status_box: TextBox,
+ pub fuzzel_box: TextBox,
+ pub terminal_box: TextBox,
+ pub search_box: TextBox,
+ pub selected_font: Option<String>,
+ pub list_box: ScrollBox,
}
impl Default for TypefaceState {
@@ -185,11 +240,22 @@ impl Default for TypefaceState {
sans_serif: String::new(),
serif: String::new(),
monospace: String::new(),
+ window_borders: String::new(),
+ status_interface: String::new(),
+ fuzzel: String::new(),
+ terminal: String::new(),
all_fonts: Vec::new(),
mono_fonts: Vec::new(),
sans_box: TextBox::default(),
serif_box: TextBox::default(),
mono_box: TextBox::default(),
+ borders_box: TextBox::default(),
+ status_box: TextBox::default(),
+ fuzzel_box: TextBox::default(),
+ terminal_box: TextBox::default(),
+ search_box: TextBox::default(),
+ selected_font: None,
+ list_box: ScrollBox::new(),
}
}
}
@@ -200,6 +266,12 @@ pub enum TypefaceMessage {
SetSans(String),
SetSerif(String),
SetMono(String),
+ SetBorders(String),
+ SetStatus(String),
+ SetFuzzel(String),
+ SetTerminal(String),
+ SetSearch(String),
+ SelectFont(String),
}
fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
@@ -228,17 +300,29 @@ fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
None
}
-pub fn read_preferred_fonts() -> (String, String, String) {
+pub fn read_preferred_fonts() -> (String, String, String, String, String, String, String) {
let content = fs::read_to_string(FONTS_CONF_PATH).unwrap_or_default();
let sans = parse_font_for_alias(&content, "sans-serif").unwrap_or_else(|| "Noto Sans".to_string());
let serif = parse_font_for_alias(&content, "serif").unwrap_or_else(|| "Noto Serif".to_string());
let mono = parse_font_for_alias(&content, "monospace").unwrap_or_else(|| "Noto Sans Mono".to_string());
+ let borders = parse_font_for_alias(&content, "window-borders").unwrap_or_else(|| "Noto Sans".to_string());
+ let status = parse_font_for_alias(&content, "status-interface").unwrap_or_else(|| "Noto Sans".to_string());
+ let fuzzel_font = parse_font_for_alias(&content, "fuzzel").unwrap_or_else(|| "Noto Sans".to_string());
+ let term = parse_font_for_alias(&content, "terminal").unwrap_or_else(|| "Noto Sans Mono".to_string());
- (sans, serif, mono)
+ (sans, serif, mono, borders, status, fuzzel_font, term)
}
-pub fn save_preferred_fonts(sans: &str, serif: &str, mono: &str) {
+pub fn save_preferred_fonts(
+ sans: &str,
+ serif: &str,
+ mono: &str,
+ borders: &str,
+ status: &str,
+ fuzzel: &str,
+ terminal: &str,
+) {
let content = fs::read_to_string(FONTS_CONF_PATH).unwrap_or_default();
let mut dirs = Vec::new();
@@ -284,6 +368,38 @@ pub fn save_preferred_fonts(sans: &str, serif: &str, mono: &str) {
new_content.push_str(&format!(" <string>{}</string>\n", mono));
new_content.push_str(" </edit>\n");
new_content.push_str(" </match>\n");
+
+ // Window Borders
+ new_content.push_str(" <match target=\"pattern\">\n");
+ new_content.push_str(" <test qual=\"any\" name=\"family\"><string>window-borders</string></test>\n");
+ new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
+ new_content.push_str(&format!(" <string>{}</string>\n", borders));
+ new_content.push_str(" </edit>\n");
+ new_content.push_str(" </match>\n");
+
+ // Status Interface
+ new_content.push_str(" <match target=\"pattern\">\n");
+ new_content.push_str(" <test qual=\"any\" name=\"family\"><string>status-interface</string></test>\n");
+ new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
+ new_content.push_str(&format!(" <string>{}</string>\n", status));
+ new_content.push_str(" </edit>\n");
+ new_content.push_str(" </match>\n");
+
+ // Fuzzel
+ new_content.push_str(" <match target=\"pattern\">\n");
+ new_content.push_str(" <test qual=\"any\" name=\"family\"><string>fuzzel</string></test>\n");
+ new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
+ new_content.push_str(&format!(" <string>{}</string>\n", fuzzel));
+ new_content.push_str(" </edit>\n");
+ new_content.push_str(" </match>\n");
+
+ // Terminal
+ new_content.push_str(" <match target=\"pattern\">\n");
+ new_content.push_str(" <test qual=\"any\" name=\"family\"><string>terminal</string></test>\n");
+ new_content.push_str(" <edit name=\"family\" mode=\"assign\" binding=\"same\">\n");
+ new_content.push_str(&format!(" <string>{}</string>\n", terminal));
+ new_content.push_str(" </edit>\n");
+ new_content.push_str(" </match>\n");
new_content.push_str("</fontconfig>\n");
@@ -316,7 +432,7 @@ fn parse_families(output: Option<std::process::Output>) -> Vec<String> {
}
pub async fn fetch_typeface_state() -> TypefaceState {
- let (sans, serif, mono) = read_preferred_fonts();
+ let (sans, serif, mono, borders, status, fuzzel_font, term) = read_preferred_fonts();
let all_output = tokio::process::Command::new("fc-list")
.args([":", "family"])
@@ -328,16 +444,28 @@ pub async fn fetch_typeface_state() -> TypefaceState {
.output().await.ok();
let mono_fonts = parse_families(mono_output);
+ let selected_font = all_fonts.first().cloned();
TypefaceState {
loaded: true,
sans_serif: sans.clone(),
serif: serif.clone(),
monospace: mono.clone(),
+ window_borders: borders.clone(),
+ status_interface: status.clone(),
+ fuzzel: fuzzel_font.clone(),
+ terminal: term.clone(),
all_fonts,
mono_fonts,
- sans_box: TextBox::new(sans),
- serif_box: TextBox::new(serif),
- mono_box: TextBox::new(mono),
+ sans_box: TextBox::new(sans).with_label("Sans-Serif"),
+ serif_box: TextBox::new(serif).with_label("Serif"),
+ mono_box: TextBox::new(mono).with_label("Monospace"),
+ borders_box: TextBox::new(borders).with_label("Window Borders"),
+ status_box: TextBox::new(status).with_label("Status Interface"),
+ fuzzel_box: TextBox::new(fuzzel_font).with_label("Fuzzel"),
+ terminal_box: TextBox::new(term).with_label("Terminal"),
+ search_box: TextBox::new(String::new()).with_label("Filter Fonts"),
+ selected_font,
+ list_box: ScrollBox::new(),
}
}
@@ -350,42 +478,210 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
let widget_w = cw - 24.0;
let widget_h = 26.0;
- // ── Sans-Serif ──
- let mut sec = Section::new(&mut pc, cx, y, cw, "Sans-Serif");
+ let mut sec = Section::new(&mut pc, cx, y, cw, "Typeface Settings");
sec.spacing(8.0);
+
if !state.loaded {
sec.text(&mut pc, "Loading typefaces...", 12.0, 0.0, 12.0, TEXT_DIM);
sec.spacing(18.0);
} else {
+ // Sans-Serif
sec.widget(&mut pc, &mut state.sans_box, 12.0, widget_w, widget_h);
- sec.spacing(8.0);
- }
- y = sec.finish(&mut pc);
+ sec.spacing(12.0);
- // ── Serif ──
- let mut sec = Section::new(&mut pc, cx, y, cw, "Serif");
- sec.spacing(8.0);
- if !state.loaded {
- sec.text(&mut pc, "Loading typefaces...", 12.0, 0.0, 12.0, TEXT_DIM);
- sec.spacing(18.0);
- } else {
+ // Serif
sec.widget(&mut pc, &mut state.serif_box, 12.0, widget_w, widget_h);
+ sec.spacing(12.0);
+
+ // Monospace
+ sec.widget(&mut pc, &mut state.mono_box, 12.0, widget_w, widget_h);
+ sec.spacing(12.0);
+
+ // Window Borders
+ sec.widget(&mut pc, &mut state.borders_box, 12.0, widget_w, widget_h);
+ sec.spacing(12.0);
+
+ // Status Interface
+ sec.widget(&mut pc, &mut state.status_box, 12.0, widget_w, widget_h);
+ sec.spacing(12.0);
+
+ // Fuzzel
+ sec.widget(&mut pc, &mut state.fuzzel_box, 12.0, widget_w, widget_h);
+ sec.spacing(12.0);
+
+ // Terminal
+ sec.widget(&mut pc, &mut state.terminal_box, 12.0, widget_w, widget_h);
sec.spacing(8.0);
}
y = sec.finish(&mut pc);
- // ── Monospace ──
- let mut sec = Section::new(&mut pc, cx, y, cw, "Monospace");
- sec.spacing(8.0);
+ // ── Typefaces Section (List & Preview) ──
+ let mut sec = Section::new(&mut pc, cx, y, cw, "Typefaces");
+ sec.spacing(12.0);
+
if !state.loaded {
- sec.text(&mut pc, "Loading typefaces...", 12.0, 0.0, 12.0, TEXT_DIM);
+ sec.text(&mut pc, "Loading installed fonts...", 12.0, 0.0, 12.0, TEXT_DIM);
sec.spacing(18.0);
} else {
- sec.widget(&mut pc, &mut state.mono_box, 12.0, widget_w, widget_h);
- sec.spacing(8.0);
+ let start_y = sec.ay();
+ let usable_w = cw - 24.0;
+ let gap = 24.0;
+ let left_w = (usable_w - gap) * 0.40;
+ let right_w = (usable_w - gap) * 0.60;
+ let left_x = cx + 12.0;
+ let right_x = left_x + left_w + gap;
+
+ // 1. Render Left Column (Search + List Box)
+ let mut left_y = start_y;
+
+ let top_room = state.search_box.top_room();
+ let search_box_h = widget_h + top_room;
+ state.search_box.set_row_rect(left_x, left_w);
+ clear_ui::layout::render_widget(
+ &mut pc,
+ &mut state.search_box,
+ left_x,
+ left_y + top_room,
+ left_w,
+ widget_h,
+ );
+ left_y += search_box_h + 12.0;
+
+ // Scrolling box configuration
+ let list_box_y = left_y;
+ let list_box_h = 320.0;
+
+ // Render the standardized ScrollBox widget
+ clear_ui::layout::render_widget(&mut pc, &mut state.list_box, left_x, list_box_y, left_w, list_box_h);
+
+ let query = state.search_box.text.to_lowercase();
+ let matching_fonts: Vec<&String> = state.all_fonts.iter()
+ .filter(|font| font.to_lowercase().contains(&query))
+ .collect();
+
+ let btn_h = 24.0;
+ let btn_gap = 4.0;
+ let inner_x = left_x + 4.0;
+ let inner_w = left_w - 16.0; // leave room for scrollbar
+ let item_height_full = btn_h + btn_gap;
+ let content_h = matching_fonts.len() as f32 * item_height_full;
+
+ // Update ScrollBox bounds to clamp and render correctly
+ state.list_box.update_bounds(content_h, list_box_y, list_box_h);
+
+ // Render visible buttons inside scroll region
+
+ for (idx, font_name) in matching_fonts.iter().enumerate() {
+ let virtual_y = idx as f32 * item_height_full;
+
+ // Only render buttons that are completely within the visible area
+ if let Some(draw_y) = state.list_box.get_item_draw_y(virtual_y, btn_h) {
+ let is_selected = state.selected_font.as_ref() == Some(*font_name);
+
+ let (bg, hover_bg, text_color) = if is_selected {
+ ([0.20, 0.40, 0.65, 0.4], [0.30, 0.52, 0.78, 0.6], [0.90, 0.90, 0.95, 1.0])
+ } else {
+ ([0.0, 0.0, 0.0, 0.0], [0.20, 0.20, 0.25, 0.15], [0.70, 0.70, 0.75, 1.0])
+ };
+
+ pc.button(
+ font_name,
+ inner_x,
+ draw_y,
+ inner_w,
+ btn_h,
+ bg,
+ hover_bg,
+ text_color,
+ crate::app::AppAction::Typeface(TypefaceMessage::SelectFont((*font_name).clone())),
+ );
+ }
+ }
+
+ if matching_fonts.is_empty() {
+ pc.text("No fonts match query", inner_x + 8.0, list_box_y + 16.0, 12.0, TEXT_DIM);
+ }
+
+ left_y += list_box_h;
+
+ // 2. Render Right Column (Live Preview)
+ let mut right_y = start_y;
+
+ if let Some(ref font_name) = state.selected_font {
+ let card_h = 240.0;
+ pc.rect([0.10, 0.10, 0.14, 0.3], right_x, right_y, right_w, card_h);
+ pc.rect([0.25, 0.25, 0.35, 0.5], right_x, right_y, right_w, 1.0);
+ pc.rect([0.25, 0.25, 0.35, 0.5], right_x, right_y + card_h - 1.0, right_w, 1.0);
+ pc.rect([0.25, 0.25, 0.35, 0.5], right_x, right_y, 1.0, card_h);
+ pc.rect([0.25, 0.25, 0.35, 0.5], right_x + right_w - 1.0, right_y, 1.0, card_h);
+
+ let text_padding_x = 16.0;
+ let mut text_y = right_y + 16.0;
+
+ pc.text(&format!("Family: {}", font_name), right_x + text_padding_x, text_y, 15.0, [0.90, 0.90, 0.95, 1.0]);
+ text_y += 28.0;
+
+ pc.rect([0.22, 0.22, 0.30, 0.8], right_x + text_padding_x, text_y, right_w - (text_padding_x * 2.0), 1.0);
+ text_y += 16.0;
+
+ pc.text_with_font(
+ "abcdefghijklmnopqrstuvwxyz",
+ right_x + text_padding_x,
+ text_y,
+ 13.0,
+ [0.75, 0.75, 0.80, 1.0],
+ font_name,
+ );
+ text_y += 22.0;
+
+ pc.text_with_font(
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ right_x + text_padding_x,
+ text_y,
+ 13.0,
+ [0.75, 0.75, 0.80, 1.0],
+ font_name,
+ );
+ text_y += 22.0;
+
+ pc.text_with_font(
+ "0123456789 (!@#$%&*?)",
+ right_x + text_padding_x,
+ text_y,
+ 13.0,
+ [0.75, 0.75, 0.80, 1.0],
+ font_name,
+ );
+ text_y += 26.0;
+
+ pc.text_with_font(
+ "The quick brown fox jumps over the lazy dog.",
+ right_x + text_padding_x,
+ text_y,
+ 18.0,
+ [0.90, 0.90, 0.95, 1.0],
+ font_name,
+ );
+ text_y += 32.0;
+
+ pc.text_with_font(
+ "Pack my box with five dozen liquor jugs.",
+ right_x + text_padding_x,
+ text_y,
+ 24.0,
+ [0.95, 0.95, 1.0, 1.0],
+ font_name,
+ );
+
+ right_y += card_h;
+ } else {
+ pc.text("Select a font to preview", right_x + 12.0, right_y + 20.0, 13.0, TEXT_DIM);
+ right_y += 40.0;
+ }
+
+ sec.content_y = left_y.max(right_y);
}
sec.finish(&mut pc);
-
pc
}
@@ -395,33 +691,140 @@ pub fn update(state: &mut TypefaceState, msg: TypefaceMessage) {
state.loaded = new.loaded;
state.all_fonts = new.all_fonts;
state.mono_fonts = new.mono_fonts;
+ if state.selected_font.is_none() {
+ state.selected_font = new.selected_font.clone();
+ }
if !state.sans_box.editing {
state.sans_serif = new.sans_serif.clone();
- state.sans_box.text = new.sans_serif;
+ state.sans_box = new.sans_box;
}
if !state.serif_box.editing {
state.serif = new.serif.clone();
- state.serif_box.text = new.serif;
+ state.serif_box = new.serif_box;
}
if !state.mono_box.editing {
state.monospace = new.monospace.clone();
- state.mono_box.text = new.monospace;
+ state.mono_box = new.mono_box;
+ }
+ if !state.borders_box.editing {
+ state.window_borders = new.window_borders.clone();
+ state.borders_box = new.borders_box;
+ }
+ if !state.status_box.editing {
+ state.status_interface = new.status_interface.clone();
+ state.status_box = new.status_box;
}
+ if !state.fuzzel_box.editing {
+ state.fuzzel = new.fuzzel.clone();
+ state.fuzzel_box = new.fuzzel_box;
+ }
+ if !state.terminal_box.editing {
+ state.terminal = new.terminal.clone();
+ state.terminal_box = new.terminal_box;
+ }
+ if !state.search_box.editing {
+ state.search_box = new.search_box;
+ }
+ let old_scroll = state.list_box.scroll_y;
+ state.list_box = new.list_box;
+ state.list_box.scroll_y = old_scroll;
}
TypefaceMessage::SetSans(sans) => {
state.sans_serif = sans.clone();
state.sans_box.text = sans;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace);
+ save_preferred_fonts(
+ &state.sans_serif,
+ &state.serif,
+ &state.monospace,
+ &state.window_borders,
+ &state.status_interface,
+ &state.fuzzel,
+ &state.terminal,
+ );
}
TypefaceMessage::SetSerif(serif) => {
state.serif = serif.clone();
state.serif_box.text = serif;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace);
+ save_preferred_fonts(
+ &state.sans_serif,
+ &state.serif,
+ &state.monospace,
+ &state.window_borders,
+ &state.status_interface,
+ &state.fuzzel,
+ &state.terminal,
+ );
}
TypefaceMessage::SetMono(mono) => {
state.monospace = mono.clone();
state.mono_box.text = mono;
- save_preferred_fonts(&state.sans_serif, &state.serif, &state.monospace);
+ save_preferred_fonts(
+ &state.sans_serif,
+ &state.serif,
+ &state.monospace,
+ &state.window_borders,
+ &state.status_interface,
+ &state.fuzzel,
+ &state.terminal,
+ );
+ }
+ TypefaceMessage::SetBorders(borders) => {
+ state.window_borders = borders.clone();
+ state.borders_box.text = borders;
+ save_preferred_fonts(
+ &state.sans_serif,
+ &state.serif,
+ &state.monospace,
+ &state.window_borders,
+ &state.status_interface,
+ &state.fuzzel,
+ &state.terminal,
+ );
+ }
+ TypefaceMessage::SetStatus(status) => {
+ state.status_interface = status.clone();
+ state.status_box.text = status;
+ save_preferred_fonts(
+ &state.sans_serif,
+ &state.serif,
+ &state.monospace,
+ &state.window_borders,
+ &state.status_interface,
+ &state.fuzzel,
+ &state.terminal,
+ );
+ }
+ TypefaceMessage::SetFuzzel(fuzzel) => {
+ state.fuzzel = fuzzel.clone();
+ state.fuzzel_box.text = fuzzel;
+ save_preferred_fonts(
+ &state.sans_serif,
+ &state.serif,
+ &state.monospace,
+ &state.window_borders,
+ &state.status_interface,
+ &state.fuzzel,
+ &state.terminal,
+ );
+ }
+ TypefaceMessage::SetTerminal(term) => {
+ state.terminal = term.clone();
+ state.terminal_box.text = term;
+ save_preferred_fonts(
+ &state.sans_serif,
+ &state.serif,
+ &state.monospace,
+ &state.window_borders,
+ &state.status_interface,
+ &state.fuzzel,
+ &state.terminal,
+ );
+ }
+ TypefaceMessage::SetSearch(search) => {
+ state.search_box.text = search;
+ }
+ TypefaceMessage::SelectFont(font) => {
+ state.selected_font = Some(font);
}
}
}