max / alloy
- Co-Authored-By
- Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 files changed,
+1229 insertions,
-1 deletion
| @@ -2,6 +2,9 @@ | |||
| 2 | 2 | **/*.rs.bk | |
| 3 | 3 | Cargo.lock.bak | |
| 4 | 4 | ||
| 5 | + | # Generated by alloy_ui/build.rs from tokens.toml. | |
| 6 | + | crates/alloy_ui/src/generated/tokens.rs | |
| 7 | + | ||
| 5 | 8 | # Build/dist outputs | |
| 6 | 9 | /dist/build/ | |
| 7 | 10 | /dist/*.iso |
| @@ -19,7 +19,7 @@ | |||
| 19 | 19 | - [ ] Audit Departure's unicode coverage; document the fallback chain (Iosevka) for out-of-range code points. | |
| 20 | 20 | - [x] Set up the repo: git init, license, contribution model. → **GPLv3-or-later** (deliberate pick; egui is MIT/Apache so the choice was open). [LICENSE](../LICENSE), [CONTRIBUTING.md](../CONTRIBUTING.md). | |
| 21 | 21 | - [ ] Branding assets: wordmark/logotype, repo social card, favicon. Deferred until first user-facing surface ships. | |
| 22 | - | - [ ] Scaffold the Alloy egui design-system crate (themed widgets, extension traits on `egui::Ui`, palette + typography constants). | |
| 22 | + | - [x] Scaffold the Alloy egui design-system crate. → `crates/alloy_ui/` with `tokens.toml` → `build.rs` → `src/generated/tokens.rs` pipeline, `apply_alloy_visuals`, starter primitives (`primary_button`, `section`, `field_row`, `SeverityTag`, `focus_ring`), and an `examples/showcase` binary. Widget set is intentionally minimal — grow it as marquee apps need it, not preemptively. | |
| 23 | 23 | - [ ] Pick the first concrete deliverable to build (lockscreen v1 or package GUI — both are listed as egui apps; sequencing matters). | |
| 24 | 24 | ||
| 25 | 25 | ## v0 stack to package and document |
| @@ -1,0 +1,2928 @@ | |||
| 1 | + | # This file is automatically @generated by Cargo. | |
| 2 | + | # It is not intended for manual editing. | |
| 3 | + | version = 4 | |
| 4 | + | ||
| 5 | + | [[package]] | |
| 6 | + | name = "accesskit" | |
| 7 | + | version = "0.24.1" | |
| 8 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 9 | + | checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" | |
| 10 | + | dependencies = [ | |
| 11 | + | "uuid", | |
| 12 | + | ] | |
| 13 | + | ||
| 14 | + | [[package]] | |
| 15 | + | name = "adler2" | |
| 16 | + | version = "2.0.1" | |
| 17 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 18 | + | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" | |
| 19 | + | ||
| 20 | + | [[package]] | |
| 21 | + | name = "ahash" | |
| 22 | + | version = "0.8.12" | |
| 23 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 24 | + | checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" | |
| 25 | + | dependencies = [ | |
| 26 | + | "cfg-if", | |
| 27 | + | "once_cell", | |
| 28 | + | "version_check", | |
| 29 | + | "zerocopy", | |
| 30 | + | ] | |
| 31 | + | ||
| 32 | + | [[package]] | |
| 33 | + | name = "alloy_ui" | |
| 34 | + | version = "0.0.0" | |
| 35 | + | dependencies = [ | |
| 36 | + | "eframe", | |
| 37 | + | "egui", | |
| 38 | + | "palette", | |
| 39 | + | "serde", | |
| 40 | + | "toml", | |
| 41 | + | ] | |
| 42 | + | ||
| 43 | + | [[package]] | |
| 44 | + | name = "android-activity" | |
| 45 | + | version = "0.6.1" | |
| 46 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 47 | + | checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" | |
| 48 | + | dependencies = [ | |
| 49 | + | "android-properties", | |
| 50 | + | "bitflags 2.13.0", | |
| 51 | + | "cc", | |
| 52 | + | "jni", | |
| 53 | + | "libc", | |
| 54 | + | "log", | |
| 55 | + | "ndk", | |
| 56 | + | "ndk-context", | |
| 57 | + | "ndk-sys", | |
| 58 | + | "num_enum", | |
| 59 | + | "thiserror 2.0.18", | |
| 60 | + | ] | |
| 61 | + | ||
| 62 | + | [[package]] | |
| 63 | + | name = "android-properties" | |
| 64 | + | version = "0.2.2" | |
| 65 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 66 | + | checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" | |
| 67 | + | ||
| 68 | + | [[package]] | |
| 69 | + | name = "approx" | |
| 70 | + | version = "0.5.1" | |
| 71 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 72 | + | checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" | |
| 73 | + | dependencies = [ | |
| 74 | + | "num-traits", | |
| 75 | + | ] | |
| 76 | + | ||
| 77 | + | [[package]] | |
| 78 | + | name = "arboard" | |
| 79 | + | version = "3.6.1" | |
| 80 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 81 | + | checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" | |
| 82 | + | dependencies = [ | |
| 83 | + | "clipboard-win", | |
| 84 | + | "image", | |
| 85 | + | "log", | |
| 86 | + | "objc2 0.6.4", | |
| 87 | + | "objc2-app-kit 0.3.2", | |
| 88 | + | "objc2-core-foundation", | |
| 89 | + | "objc2-core-graphics", | |
| 90 | + | "objc2-foundation 0.3.2", | |
| 91 | + | "parking_lot", | |
| 92 | + | "percent-encoding", | |
| 93 | + | "windows-sys 0.60.2", | |
| 94 | + | "x11rb", | |
| 95 | + | ] | |
| 96 | + | ||
| 97 | + | [[package]] | |
| 98 | + | name = "arrayvec" | |
| 99 | + | version = "0.7.7" | |
| 100 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 101 | + | checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" | |
| 102 | + | ||
| 103 | + | [[package]] | |
| 104 | + | name = "atomic-waker" | |
| 105 | + | version = "1.1.2" | |
| 106 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 107 | + | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" | |
| 108 | + | ||
| 109 | + | [[package]] | |
| 110 | + | name = "autocfg" | |
| 111 | + | version = "1.5.1" | |
| 112 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 113 | + | checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" | |
| 114 | + | ||
| 115 | + | [[package]] | |
| 116 | + | name = "bitflags" | |
| 117 | + | version = "1.3.2" | |
| 118 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 119 | + | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | |
| 120 | + | ||
| 121 | + | [[package]] | |
| 122 | + | name = "bitflags" | |
| 123 | + | version = "2.13.0" | |
| 124 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 125 | + | checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" | |
| 126 | + | ||
| 127 | + | [[package]] | |
| 128 | + | name = "block2" | |
| 129 | + | version = "0.5.1" | |
| 130 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 131 | + | checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" | |
| 132 | + | dependencies = [ | |
| 133 | + | "objc2 0.5.2", | |
| 134 | + | ] | |
| 135 | + | ||
| 136 | + | [[package]] | |
| 137 | + | name = "block2" | |
| 138 | + | version = "0.6.2" | |
| 139 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 140 | + | checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" | |
| 141 | + | dependencies = [ | |
| 142 | + | "objc2 0.6.4", | |
| 143 | + | ] | |
| 144 | + | ||
| 145 | + | [[package]] | |
| 146 | + | name = "bumpalo" | |
| 147 | + | version = "3.20.3" | |
| 148 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 149 | + | checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" | |
| 150 | + | ||
| 151 | + | [[package]] | |
| 152 | + | name = "by_address" | |
| 153 | + | version = "1.2.1" | |
| 154 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 155 | + | checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" | |
| 156 | + | ||
| 157 | + | [[package]] | |
| 158 | + | name = "bytemuck" | |
| 159 | + | version = "1.25.0" | |
| 160 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 161 | + | checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" | |
| 162 | + | dependencies = [ | |
| 163 | + | "bytemuck_derive", | |
| 164 | + | ] | |
| 165 | + | ||
| 166 | + | [[package]] | |
| 167 | + | name = "bytemuck_derive" | |
| 168 | + | version = "1.10.2" | |
| 169 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 170 | + | checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" | |
| 171 | + | dependencies = [ | |
| 172 | + | "proc-macro2", | |
| 173 | + | "quote", | |
| 174 | + | "syn", | |
| 175 | + | ] | |
| 176 | + | ||
| 177 | + | [[package]] | |
| 178 | + | name = "byteorder-lite" | |
| 179 | + | version = "0.1.0" | |
| 180 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 181 | + | checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" | |
| 182 | + | ||
| 183 | + | [[package]] | |
| 184 | + | name = "bytes" | |
| 185 | + | version = "1.12.0" | |
| 186 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 187 | + | checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" | |
| 188 | + | ||
| 189 | + | [[package]] | |
| 190 | + | name = "calloop" | |
| 191 | + | version = "0.13.0" | |
| 192 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 193 | + | checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" | |
| 194 | + | dependencies = [ | |
| 195 | + | "bitflags 2.13.0", | |
| 196 | + | "log", | |
| 197 | + | "polling", | |
| 198 | + | "rustix 0.38.44", | |
| 199 | + | "slab", | |
| 200 | + | "thiserror 1.0.69", | |
| 201 | + | ] | |
| 202 | + | ||
| 203 | + | [[package]] | |
| 204 | + | name = "calloop" | |
| 205 | + | version = "0.14.4" | |
| 206 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 207 | + | checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" | |
| 208 | + | dependencies = [ | |
| 209 | + | "bitflags 2.13.0", | |
| 210 | + | "polling", | |
| 211 | + | "rustix 1.1.4", | |
| 212 | + | "slab", | |
| 213 | + | "tracing", | |
| 214 | + | ] | |
| 215 | + | ||
| 216 | + | [[package]] | |
| 217 | + | name = "calloop-wayland-source" | |
| 218 | + | version = "0.4.1" | |
| 219 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 220 | + | checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" | |
| 221 | + | dependencies = [ | |
| 222 | + | "calloop 0.14.4", | |
| 223 | + | "rustix 1.1.4", | |
| 224 | + | "wayland-backend", | |
| 225 | + | "wayland-client", | |
| 226 | + | ] | |
| 227 | + | ||
| 228 | + | [[package]] | |
| 229 | + | name = "cc" | |
| 230 | + | version = "1.2.65" | |
| 231 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 232 | + | checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" | |
| 233 | + | dependencies = [ | |
| 234 | + | "find-msvc-tools", | |
| 235 | + | "jobserver", | |
| 236 | + | "libc", | |
| 237 | + | "shlex", | |
| 238 | + | ] | |
| 239 | + | ||
| 240 | + | [[package]] | |
| 241 | + | name = "cfg-if" | |
| 242 | + | version = "1.0.4" | |
| 243 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 244 | + | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | |
| 245 | + | ||
| 246 | + | [[package]] | |
| 247 | + | name = "cfg_aliases" | |
| 248 | + | version = "0.2.1" | |
| 249 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 250 | + | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" | |
| 251 | + | ||
| 252 | + | [[package]] | |
| 253 | + | name = "cgl" | |
| 254 | + | version = "0.3.2" | |
| 255 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 256 | + | checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" | |
| 257 | + | dependencies = [ | |
| 258 | + | "libc", | |
| 259 | + | ] | |
| 260 | + | ||
| 261 | + | [[package]] | |
| 262 | + | name = "clipboard-win" | |
| 263 | + | version = "5.4.1" | |
| 264 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 265 | + | checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" | |
| 266 | + | dependencies = [ | |
| 267 | + | "error-code", | |
| 268 | + | ] | |
| 269 | + | ||
| 270 | + | [[package]] | |
| 271 | + | name = "color" | |
| 272 | + | version = "0.3.3" | |
| 273 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 274 | + | checksum = "2ec7c5eb7a16992b1904d76c517d170ab353b0e0b3d5a0c81a8a0cd1037893cf" | |
| 275 | + | dependencies = [ | |
| 276 | + | "bytemuck", | |
| 277 | + | ] | |
| 278 | + | ||
| 279 | + | [[package]] | |
| 280 | + | name = "combine" | |
| 281 | + | version = "4.6.7" | |
| 282 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 283 | + | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" | |
| 284 | + | dependencies = [ | |
| 285 | + | "bytes", | |
| 286 | + | "memchr", | |
| 287 | + | ] | |
| 288 | + | ||
| 289 | + | [[package]] | |
| 290 | + | name = "concurrent-queue" | |
| 291 | + | version = "2.5.0" | |
| 292 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 293 | + | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" | |
| 294 | + | dependencies = [ | |
| 295 | + | "crossbeam-utils", | |
| 296 | + | ] | |
| 297 | + | ||
| 298 | + | [[package]] | |
| 299 | + | name = "core-foundation" | |
| 300 | + | version = "0.9.4" | |
| 301 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 302 | + | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" | |
| 303 | + | dependencies = [ | |
| 304 | + | "core-foundation-sys", | |
| 305 | + | "libc", | |
| 306 | + | ] | |
| 307 | + | ||
| 308 | + | [[package]] | |
| 309 | + | name = "core-foundation" | |
| 310 | + | version = "0.10.1" | |
| 311 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 312 | + | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" | |
| 313 | + | dependencies = [ | |
| 314 | + | "core-foundation-sys", | |
| 315 | + | "libc", | |
| 316 | + | ] | |
| 317 | + | ||
| 318 | + | [[package]] | |
| 319 | + | name = "core-foundation-sys" | |
| 320 | + | version = "0.8.7" | |
| 321 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 322 | + | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" | |
| 323 | + | ||
| 324 | + | [[package]] | |
| 325 | + | name = "core-graphics" | |
| 326 | + | version = "0.23.2" | |
| 327 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 328 | + | checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" | |
| 329 | + | dependencies = [ | |
| 330 | + | "bitflags 1.3.2", | |
| 331 | + | "core-foundation 0.9.4", | |
| 332 | + | "core-graphics-types", | |
| 333 | + | "foreign-types", | |
| 334 | + | "libc", | |
| 335 | + | ] | |
| 336 | + | ||
| 337 | + | [[package]] | |
| 338 | + | name = "core-graphics-types" | |
| 339 | + | version = "0.1.3" | |
| 340 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 341 | + | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" | |
| 342 | + | dependencies = [ | |
| 343 | + | "bitflags 1.3.2", | |
| 344 | + | "core-foundation 0.9.4", | |
| 345 | + | "libc", | |
| 346 | + | ] | |
| 347 | + | ||
| 348 | + | [[package]] | |
| 349 | + | name = "crc32fast" | |
| 350 | + | version = "1.5.0" | |
| 351 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 352 | + | checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" | |
| 353 | + | dependencies = [ | |
| 354 | + | "cfg-if", | |
| 355 | + | ] | |
| 356 | + | ||
| 357 | + | [[package]] | |
| 358 | + | name = "crossbeam-utils" | |
| 359 | + | version = "0.8.21" | |
| 360 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 361 | + | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" | |
| 362 | + | ||
| 363 | + | [[package]] | |
| 364 | + | name = "crunchy" | |
| 365 | + | version = "0.2.4" | |
| 366 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 367 | + | checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" | |
| 368 | + | ||
| 369 | + | [[package]] | |
| 370 | + | name = "cursor-icon" | |
| 371 | + | version = "1.2.0" | |
| 372 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 373 | + | checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" | |
| 374 | + | ||
| 375 | + | [[package]] | |
| 376 | + | name = "dispatch" | |
| 377 | + | version = "0.2.0" | |
| 378 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 379 | + | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" | |
| 380 | + | ||
| 381 | + | [[package]] | |
| 382 | + | name = "dispatch2" | |
| 383 | + | version = "0.3.1" | |
| 384 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 385 | + | checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" | |
| 386 | + | dependencies = [ | |
| 387 | + | "bitflags 2.13.0", | |
| 388 | + | "objc2 0.6.4", | |
| 389 | + | ] | |
| 390 | + | ||
| 391 | + | [[package]] | |
| 392 | + | name = "displaydoc" | |
| 393 | + | version = "0.2.6" | |
| 394 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 395 | + | checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" | |
| 396 | + | dependencies = [ | |
| 397 | + | "proc-macro2", | |
| 398 | + | "quote", | |
| 399 | + | "syn", | |
| 400 | + | ] | |
| 401 | + | ||
| 402 | + | [[package]] | |
| 403 | + | name = "dlib" | |
| 404 | + | version = "0.5.3" | |
| 405 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 406 | + | checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" | |
| 407 | + | dependencies = [ | |
| 408 | + | "libloading", | |
| 409 | + | ] | |
| 410 | + | ||
| 411 | + | [[package]] | |
| 412 | + | name = "document-features" | |
| 413 | + | version = "0.2.12" | |
| 414 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 415 | + | checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" | |
| 416 | + | dependencies = [ | |
| 417 | + | "litrs", | |
| 418 | + | ] | |
| 419 | + | ||
| 420 | + | [[package]] | |
| 421 | + | name = "downcast-rs" | |
| 422 | + | version = "1.2.1" | |
| 423 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 424 | + | checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" | |
| 425 | + | ||
| 426 | + | [[package]] | |
| 427 | + | name = "dpi" | |
| 428 | + | version = "0.1.2" | |
| 429 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 430 | + | checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" | |
| 431 | + | ||
| 432 | + | [[package]] | |
| 433 | + | name = "ecolor" | |
| 434 | + | version = "0.34.3" | |
| 435 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 436 | + | checksum = "a05fbfa222ffb51989d5ccf33e5f7aebfcf96c5023413856b0c3618a7f79896e" | |
| 437 | + | dependencies = [ | |
| 438 | + | "bytemuck", | |
| 439 | + | "emath", | |
| 440 | + | ] | |
| 441 | + | ||
| 442 | + | [[package]] | |
| 443 | + | name = "eframe" | |
| 444 | + | version = "0.34.3" | |
| 445 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 446 | + | checksum = "f98fe83b2589105b69dd25ca1e0fa2135a6e864d502fd8e08978f937e128cfef" | |
| 447 | + | dependencies = [ | |
| 448 | + | "ahash", | |
| 449 | + | "bytemuck", | |
| 450 | + | "document-features", | |
| 451 | + | "egui", | |
| 452 | + | "egui-winit", | |
| 453 | + | "egui_glow", | |
| 454 | + | "glow", | |
| 455 | + | "glutin", | |
| 456 | + | "glutin-winit", | |
| 457 | + | "image", | |
| 458 | + | "js-sys", | |
| 459 | + | "log", | |
| 460 | + | "objc2 0.6.4", | |
| 461 | + | "objc2-app-kit 0.3.2", | |
| 462 | + | "objc2-foundation 0.3.2", | |
| 463 | + | "parking_lot", | |
| 464 | + | "percent-encoding", | |
| 465 | + | "profiling", | |
| 466 | + | "raw-window-handle", | |
| 467 | + | "static_assertions", | |
| 468 | + | "wasm-bindgen", | |
| 469 | + | "wasm-bindgen-futures", | |
| 470 | + | "web-sys", | |
| 471 | + | "web-time", | |
| 472 | + | "windows-sys 0.61.2", | |
| 473 | + | "winit", | |
| 474 | + | ] | |
| 475 | + | ||
| 476 | + | [[package]] | |
| 477 | + | name = "egui" | |
| 478 | + | version = "0.34.3" | |
| 479 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 480 | + | checksum = "42112be0ae157289312b92b3dfaf20e911b5a3c4c65d4aab0e7c47fbc0ce16e3" | |
| 481 | + | dependencies = [ | |
| 482 | + | "accesskit", | |
| 483 | + | "ahash", | |
| 484 | + | "bitflags 2.13.0", | |
| 485 | + | "emath", | |
| 486 | + | "epaint", | |
| 487 | + | "log", | |
| 488 | + | "nohash-hasher", | |
| 489 | + | "profiling", | |
| 490 | + | "smallvec", | |
| 491 | + | "unicode-segmentation", | |
| 492 | + | ] | |
| 493 | + | ||
| 494 | + | [[package]] | |
| 495 | + | name = "egui-winit" | |
| 496 | + | version = "0.34.3" | |
| 497 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 498 | + | checksum = "967c5b323625d46d46a59b5daba3fef742248d27693cc18972458619858c4239" | |
| 499 | + | dependencies = [ | |
| 500 | + | "arboard", |
Lines truncated
| @@ -1,0 +1,21 @@ | |||
| 1 | + | [workspace] | |
| 2 | + | resolver = "3" | |
| 3 | + | members = ["crates/*"] | |
| 4 | + | ||
| 5 | + | [workspace.package] | |
| 6 | + | edition = "2024" | |
| 7 | + | rust-version = "1.86" | |
| 8 | + | license = "GPL-3.0-or-later" | |
| 9 | + | repository = "https://github.com/maxjmath/alloy" | |
| 10 | + | authors = ["Max Johnson <me@maxj.phd>"] | |
| 11 | + | ||
| 12 | + | [workspace.dependencies] | |
| 13 | + | egui = { version = "0.34", default-features = false, features = ["default_fonts"] } | |
| 14 | + | eframe = { version = "0.34", default-features = false, features = ["default_fonts", "glow"] } | |
| 15 | + | toml = "0.8" | |
| 16 | + | palette = { version = "0.7", default-features = false, features = ["std"] } | |
| 17 | + | ||
| 18 | + | [profile.release] | |
| 19 | + | lto = "thin" | |
| 20 | + | codegen-units = 1 | |
| 21 | + | strip = "symbols" |
| @@ -1,0 +1,24 @@ | |||
| 1 | + | [package] | |
| 2 | + | name = "alloy_ui" | |
| 3 | + | version = "0.0.0" | |
| 4 | + | description = "Alloy design system: tokens, themed primitives, and layout combinators for egui." | |
| 5 | + | edition.workspace = true | |
| 6 | + | rust-version.workspace = true | |
| 7 | + | license.workspace = true | |
| 8 | + | repository.workspace = true | |
| 9 | + | authors.workspace = true | |
| 10 | + | ||
| 11 | + | [dependencies] | |
| 12 | + | egui.workspace = true | |
| 13 | + | ||
| 14 | + | [build-dependencies] | |
| 15 | + | toml = { workspace = true } | |
| 16 | + | palette = { workspace = true } | |
| 17 | + | serde = { version = "1", features = ["derive"] } | |
| 18 | + | ||
| 19 | + | [dev-dependencies] | |
| 20 | + | eframe.workspace = true | |
| 21 | + | ||
| 22 | + | [[example]] | |
| 23 | + | name = "showcase" | |
| 24 | + | path = "examples/showcase.rs" |
| @@ -1,0 +1,191 @@ | |||
| 1 | + | //! Compile `tokens.toml` into `src/generated/tokens.rs`. | |
| 2 | + | //! | |
| 3 | + | //! OKLCH colors are converted to gamma-encoded sRGB via the `palette` crate. | |
| 4 | + | //! The output file is committed-but-regenerated: it lives under `src/generated/` | |
| 5 | + | //! so `rust-analyzer` can see it without a full build, but `cargo build` always | |
| 6 | + | //! rewrites it from the TOML so the TOML is the only authority. | |
| 7 | + | ||
| 8 | + | use std::env; | |
| 9 | + | use std::fmt::Write as _; | |
| 10 | + | use std::fs; | |
| 11 | + | use std::path::PathBuf; | |
| 12 | + | ||
| 13 | + | use palette::{FromColor, Oklch, Srgb}; | |
| 14 | + | use serde::Deserialize; | |
| 15 | + | ||
| 16 | + | #[derive(Deserialize)] | |
| 17 | + | struct Tokens { | |
| 18 | + | tint: Tint, | |
| 19 | + | ramp: Ramp, | |
| 20 | + | accents: std::collections::BTreeMap<String, Oklch3>, | |
| 21 | + | typography: Typography, | |
| 22 | + | geometry: Geometry, | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | #[derive(Deserialize)] | |
| 26 | + | struct Tint { | |
| 27 | + | hue: f32, | |
| 28 | + | chroma: f32, | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | #[derive(Deserialize)] | |
| 32 | + | struct Ramp { | |
| 33 | + | light: std::collections::BTreeMap<String, f32>, | |
| 34 | + | dark: std::collections::BTreeMap<String, f32>, | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | #[derive(Deserialize)] | |
| 38 | + | struct Oklch3 { | |
| 39 | + | l: f32, | |
| 40 | + | c: f32, | |
| 41 | + | h: f32, | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | #[derive(Deserialize)] | |
| 45 | + | struct Typography { | |
| 46 | + | font_mono_working: String, | |
| 47 | + | font_mono_working_noligatures: String, | |
| 48 | + | font_mono_display: String, | |
| 49 | + | size_caption: f32, | |
| 50 | + | size_body: f32, | |
| 51 | + | size_section_header: f32, | |
| 52 | + | size_title: f32, | |
| 53 | + | size_readout_sm: f32, | |
| 54 | + | size_readout_md: f32, | |
| 55 | + | size_readout_lg: f32, | |
| 56 | + | size_readout_xl: f32, | |
| 57 | + | weight_body: u32, | |
| 58 | + | weight_emphasis: u32, | |
| 59 | + | tracking_body: f32, | |
| 60 | + | tracking_display: f32, | |
| 61 | + | tracking_display_caps: f32, | |
| 62 | + | line_height_body: f32, | |
| 63 | + | line_height_tabular: f32, | |
| 64 | + | line_height_display: f32, | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | #[derive(Deserialize)] | |
| 68 | + | struct Geometry { | |
| 69 | + | space_xs: f32, | |
| 70 | + | space_sm: f32, | |
| 71 | + | space_md: f32, | |
| 72 | + | space_lg: f32, | |
| 73 | + | space_xl: f32, | |
| 74 | + | radius_control: f32, | |
| 75 | + | radius_overlay: f32, | |
| 76 | + | radius_data: f32, | |
| 77 | + | border_width_default: f32, | |
| 78 | + | border_width_focus: f32, | |
| 79 | + | focus_ring_offset: f32, | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | fn oklch_to_rgb8(l: f32, c: f32, h: f32) -> (u8, u8, u8) { | |
| 83 | + | let oklch = Oklch::new(l, c, h); | |
| 84 | + | let srgb: Srgb = Srgb::from_color(oklch); | |
| 85 | + | let (r, g, b) = srgb.into_format::<u8>().into_components(); | |
| 86 | + | (r, g, b) | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | fn emit_ramp(out: &mut String, name: &str, hue: f32, chroma: f32, stops: &std::collections::BTreeMap<String, f32>) { | |
| 90 | + | writeln!(out, " pub mod {name} {{").unwrap(); | |
| 91 | + | writeln!(out, " use egui::Color32;").unwrap(); | |
| 92 | + | for (stop, l) in stops { | |
| 93 | + | let (r, g, b) = oklch_to_rgb8(*l, chroma, hue); | |
| 94 | + | let upper = stop.to_uppercase(); | |
| 95 | + | writeln!( | |
| 96 | + | out, | |
| 97 | + | " pub const {upper}: Color32 = Color32::from_rgb({r}, {g}, {b}); // L={l}", | |
| 98 | + | ) | |
| 99 | + | .unwrap(); | |
| 100 | + | } | |
| 101 | + | writeln!(out, " }}").unwrap(); | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | fn main() { | |
| 105 | + | let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); | |
| 106 | + | let toml_path = manifest_dir.join("tokens.toml"); | |
| 107 | + | let out_dir = manifest_dir.join("src/generated"); | |
| 108 | + | let out_path = out_dir.join("tokens.rs"); | |
| 109 | + | ||
| 110 | + | println!("cargo:rerun-if-changed={}", toml_path.display()); | |
| 111 | + | println!("cargo:rerun-if-changed=build.rs"); | |
| 112 | + | ||
| 113 | + | let text = fs::read_to_string(&toml_path).expect("read tokens.toml"); | |
| 114 | + | let tokens: Tokens = toml::from_str(&text).expect("parse tokens.toml"); | |
| 115 | + | ||
| 116 | + | let mut out = String::new(); | |
| 117 | + | out.push_str("// Generated by build.rs from tokens.toml. Do not edit by hand.\n"); | |
| 118 | + | out.push_str("// Edit tokens.toml (or docs/TOKENS.md upstream of it) instead.\n\n"); | |
| 119 | + | out.push_str("#![allow(dead_code)]\n\n"); | |
| 120 | + | ||
| 121 | + | // Color ramps. | |
| 122 | + | out.push_str("pub mod colors {\n"); | |
| 123 | + | emit_ramp(&mut out, "light", tokens.tint.hue, tokens.tint.chroma, &tokens.ramp.light); | |
| 124 | + | emit_ramp(&mut out, "dark", tokens.tint.hue, tokens.tint.chroma, &tokens.ramp.dark); | |
| 125 | + | ||
| 126 | + | writeln!(out, " pub mod accents {{").unwrap(); | |
| 127 | + | writeln!(out, " use egui::Color32;").unwrap(); | |
| 128 | + | for (name, v) in &tokens.accents { | |
| 129 | + | let (r, g, b) = oklch_to_rgb8(v.l, v.c, v.h); | |
| 130 | + | let upper = name.to_uppercase(); | |
| 131 | + | writeln!( | |
| 132 | + | out, | |
| 133 | + | " pub const {upper}: Color32 = Color32::from_rgb({r}, {g}, {b}); // oklch({l}% {c} {h})", | |
| 134 | + | l = v.l * 100.0, c = v.c, h = v.h, | |
| 135 | + | ) | |
| 136 | + | .unwrap(); | |
| 137 | + | } | |
| 138 | + | writeln!(out, " }}").unwrap(); | |
| 139 | + | out.push_str("}\n\n"); | |
| 140 | + | ||
| 141 | + | // Typography. | |
| 142 | + | let t = &tokens.typography; | |
| 143 | + | out.push_str("pub mod typography {\n"); | |
| 144 | + | writeln!(out, " pub const FONT_MONO_WORKING: &str = {:?};", t.font_mono_working).unwrap(); | |
| 145 | + | writeln!(out, " pub const FONT_MONO_WORKING_NOLIGATURES: &str = {:?};", t.font_mono_working_noligatures).unwrap(); | |
| 146 | + | writeln!(out, " pub const FONT_MONO_DISPLAY: &str = {:?};", t.font_mono_display).unwrap(); | |
| 147 | + | writeln!(out, " pub const SIZE_CAPTION: f32 = {};", as_float(t.size_caption)).unwrap(); | |
| 148 | + | writeln!(out, " pub const SIZE_BODY: f32 = {};", as_float(t.size_body)).unwrap(); | |
| 149 | + | writeln!(out, " pub const SIZE_SECTION_HEADER: f32 = {};", as_float(t.size_section_header)).unwrap(); | |
| 150 | + | writeln!(out, " pub const SIZE_TITLE: f32 = {};", as_float(t.size_title)).unwrap(); | |
| 151 | + | writeln!(out, " pub const SIZE_READOUT_SM: f32 = {};", as_float(t.size_readout_sm)).unwrap(); | |
| 152 | + | writeln!(out, " pub const SIZE_READOUT_MD: f32 = {};", as_float(t.size_readout_md)).unwrap(); | |
| 153 | + | writeln!(out, " pub const SIZE_READOUT_LG: f32 = {};", as_float(t.size_readout_lg)).unwrap(); | |
| 154 | + | writeln!(out, " pub const SIZE_READOUT_XL: f32 = {};", as_float(t.size_readout_xl)).unwrap(); | |
| 155 | + | writeln!(out, " pub const WEIGHT_BODY: u32 = {};", t.weight_body).unwrap(); | |
| 156 | + | writeln!(out, " pub const WEIGHT_EMPHASIS: u32 = {};", t.weight_emphasis).unwrap(); | |
| 157 | + | writeln!(out, " pub const TRACKING_BODY: f32 = {};", as_float(t.tracking_body)).unwrap(); | |
| 158 | + | writeln!(out, " pub const TRACKING_DISPLAY: f32 = {};", as_float(t.tracking_display)).unwrap(); | |
| 159 | + | writeln!(out, " pub const TRACKING_DISPLAY_CAPS: f32 = {};", as_float(t.tracking_display_caps)).unwrap(); | |
| 160 | + | writeln!(out, " pub const LINE_HEIGHT_BODY: f32 = {};", as_float(t.line_height_body)).unwrap(); | |
| 161 | + | writeln!(out, " pub const LINE_HEIGHT_TABULAR: f32 = {};", as_float(t.line_height_tabular)).unwrap(); | |
| 162 | + | writeln!(out, " pub const LINE_HEIGHT_DISPLAY: f32 = {};", as_float(t.line_height_display)).unwrap(); | |
| 163 | + | out.push_str("}\n\n"); | |
| 164 | + | ||
| 165 | + | // Geometry. | |
| 166 | + | let g = &tokens.geometry; | |
| 167 | + | out.push_str("pub mod geometry {\n"); | |
| 168 | + | writeln!(out, " pub const SPACE_XS: f32 = {};", as_float(g.space_xs)).unwrap(); | |
| 169 | + | writeln!(out, " pub const SPACE_SM: f32 = {};", as_float(g.space_sm)).unwrap(); | |
| 170 | + | writeln!(out, " pub const SPACE_MD: f32 = {};", as_float(g.space_md)).unwrap(); | |
| 171 | + | writeln!(out, " pub const SPACE_LG: f32 = {};", as_float(g.space_lg)).unwrap(); | |
| 172 | + | writeln!(out, " pub const SPACE_XL: f32 = {};", as_float(g.space_xl)).unwrap(); | |
| 173 | + | writeln!(out, " pub const RADIUS_CONTROL: f32 = {};", as_float(g.radius_control)).unwrap(); | |
| 174 | + | writeln!(out, " pub const RADIUS_OVERLAY: f32 = {};", as_float(g.radius_overlay)).unwrap(); | |
| 175 | + | writeln!(out, " pub const RADIUS_DATA: f32 = {};", as_float(g.radius_data)).unwrap(); | |
| 176 | + | writeln!(out, " pub const BORDER_WIDTH_DEFAULT: f32 = {};", as_float(g.border_width_default)).unwrap(); | |
| 177 | + | writeln!(out, " pub const BORDER_WIDTH_FOCUS: f32 = {};", as_float(g.border_width_focus)).unwrap(); | |
| 178 | + | writeln!(out, " pub const FOCUS_RING_OFFSET: f32 = {};", as_float(g.focus_ring_offset)).unwrap(); | |
| 179 | + | out.push_str("}\n"); | |
| 180 | + | ||
| 181 | + | fs::create_dir_all(&out_dir).expect("create generated dir"); | |
| 182 | + | fs::write(&out_path, out).expect("write tokens.rs"); | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | fn as_float(v: f32) -> String { | |
| 186 | + | if v.fract() == 0.0 { | |
| 187 | + | format!("{v:.1}") | |
| 188 | + | } else { | |
| 189 | + | format!("{v}") | |
| 190 | + | } | |
| 191 | + | } |
| @@ -1,0 +1,113 @@ | |||
| 1 | + | //! Renders the Alloy ramp, accent palette, and a handful of widgets so the | |
| 2 | + | //! tokens can be verified visually. Run with `cargo run -p alloy_ui --example showcase`. | |
| 3 | + | ||
| 4 | + | use alloy_ui::{ | |
| 5 | + | Mode, Severity, SeverityTag, apply_alloy_visuals, colors, field_row, geometry, primary_button, | |
| 6 | + | section, typography, | |
| 7 | + | }; | |
| 8 | + | use eframe::egui; | |
| 9 | + | ||
| 10 | + | struct Showcase { | |
| 11 | + | mode: Mode, | |
| 12 | + | ssid: String, | |
| 13 | + | password: String, | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | impl Default for Showcase { | |
| 17 | + | fn default() -> Self { | |
| 18 | + | Self { | |
| 19 | + | mode: Mode::Light, | |
| 20 | + | ssid: String::new(), | |
| 21 | + | password: String::new(), | |
| 22 | + | } | |
| 23 | + | } | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | impl eframe::App for Showcase { | |
| 27 | + | fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { | |
| 28 | + | apply_alloy_visuals(ctx, self.mode); | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { | |
| 32 | + | ui.horizontal(|ui| { | |
| 33 | + | ui.label(egui::RichText::new("ALLOY SHOWCASE").size(typography::SIZE_TITLE)); | |
| 34 | + | if ui.button(format!("Mode: {:?}", self.mode)).clicked() { | |
| 35 | + | self.mode = match self.mode { | |
| 36 | + | Mode::Light => Mode::Dark, | |
| 37 | + | Mode::Dark => Mode::Light, | |
| 38 | + | }; | |
| 39 | + | } | |
| 40 | + | }); | |
| 41 | + | ||
| 42 | + | section(ui, "Chrome ramp", |ui| { | |
| 43 | + | ui.horizontal_wrapped(|ui| { | |
| 44 | + | let stops: [(&str, egui::Color32); 10] = match self.mode { | |
| 45 | + | Mode::Light => [ | |
| 46 | + | ("surface-sunken", colors::light::SURFACE_SUNKEN), | |
| 47 | + | ("surface", colors::light::SURFACE), | |
| 48 | + | ("surface-raised", colors::light::SURFACE_RAISED), | |
| 49 | + | ("surface-overlay", colors::light::SURFACE_OVERLAY), | |
| 50 | + | ("border-subtle", colors::light::BORDER_SUBTLE), | |
| 51 | + | ("border", colors::light::BORDER), | |
| 52 | + | ("border-strong", colors::light::BORDER_STRONG), | |
| 53 | + | ("text-muted", colors::light::TEXT_MUTED), | |
| 54 | + | ("text-secondary", colors::light::TEXT_SECONDARY), | |
| 55 | + | ("text-primary", colors::light::TEXT_PRIMARY), | |
| 56 | + | ], | |
| 57 | + | Mode::Dark => [ | |
| 58 | + | ("surface-sunken", colors::dark::SURFACE_SUNKEN), | |
| 59 | + | ("surface", colors::dark::SURFACE), | |
| 60 | + | ("surface-raised", colors::dark::SURFACE_RAISED), | |
| 61 | + | ("surface-overlay", colors::dark::SURFACE_OVERLAY), | |
| 62 | + | ("border-subtle", colors::dark::BORDER_SUBTLE), | |
| 63 | + | ("border", colors::dark::BORDER), | |
| 64 | + | ("border-strong", colors::dark::BORDER_STRONG), | |
| 65 | + | ("text-muted", colors::dark::TEXT_MUTED), | |
| 66 | + | ("text-secondary", colors::dark::TEXT_SECONDARY), | |
| 67 | + | ("text-primary", colors::dark::TEXT_PRIMARY), | |
| 68 | + | ], | |
| 69 | + | }; | |
| 70 | + | for (name, color) in stops { | |
| 71 | + | ui.vertical(|ui| { | |
| 72 | + | let (rect, _) = | |
| 73 | + | ui.allocate_exact_size(egui::vec2(72.0, 48.0), egui::Sense::hover()); | |
| 74 | + | ui.painter().rect_filled(rect, geometry::RADIUS_DATA, color); | |
| 75 | + | ui.label(egui::RichText::new(name).size(typography::SIZE_CAPTION)); | |
| 76 | + | }); | |
| 77 | + | } | |
| 78 | + | }); | |
| 79 | + | }); | |
| 80 | + | ||
| 81 | + | section(ui, "Information palette", |ui| { | |
| 82 | + | ui.horizontal(|ui| { | |
| 83 | + | ui.add(SeverityTag::new(Severity::Info)); | |
| 84 | + | ui.add(SeverityTag::new(Severity::Healthy)); | |
| 85 | + | ui.add(SeverityTag::new(Severity::Warn)); | |
| 86 | + | ui.add(SeverityTag::new(Severity::Error)); | |
| 87 | + | }); | |
| 88 | + | }); | |
| 89 | + | ||
| 90 | + | section(ui, "Network", |ui| { | |
| 91 | + | field_row(ui, "SSID", |ui| { | |
| 92 | + | ui.text_edit_singleline(&mut self.ssid); | |
| 93 | + | }); | |
| 94 | + | field_row(ui, "Password", |ui| { | |
| 95 | + | ui.add(egui::TextEdit::singleline(&mut self.password).password(true)); | |
| 96 | + | }); | |
| 97 | + | ui.add_space(geometry::SPACE_SM); | |
| 98 | + | let _ = primary_button(ui, "Connect"); | |
| 99 | + | }); | |
| 100 | + | } | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | fn main() -> eframe::Result<()> { | |
| 104 | + | let options = eframe::NativeOptions { | |
| 105 | + | viewport: egui::ViewportBuilder::default().with_inner_size([900.0, 700.0]), | |
| 106 | + | ..Default::default() | |
| 107 | + | }; | |
| 108 | + | eframe::run_native( | |
| 109 | + | "Alloy showcase", | |
| 110 | + | options, | |
| 111 | + | Box::new(|_cc| Ok(Box::<Showcase>::default())), | |
| 112 | + | ) | |
| 113 | + | } |
| @@ -1,0 +1,19 @@ | |||
| 1 | + | //! Alloy design system for egui. | |
| 2 | + | //! | |
| 3 | + | //! Token constants (color ramps, typography scale, geometry) are generated at | |
| 4 | + | //! build time from `tokens.toml`. See `docs/TOKENS.md` for the canonical | |
| 5 | + | //! decisions and `docs/COMPONENT-LIBRARY.md` for the four authoring idioms | |
| 6 | + | //! this crate exposes (functions, builders, HOFs, `impl Widget`). | |
| 7 | + | //! | |
| 8 | + | //! Per [DESIGN-LANGUAGE.md]: chrome is tinted-greyscale, color is reserved for | |
| 9 | + | //! information. Accents never appear on chrome. | |
| 10 | + | ||
| 11 | + | mod generated { | |
| 12 | + | pub mod tokens; | |
| 13 | + | } | |
| 14 | + | pub mod theme; | |
| 15 | + | pub mod widgets; | |
| 16 | + | ||
| 17 | + | pub use generated::tokens::{colors, geometry, typography}; | |
| 18 | + | pub use theme::{Mode, apply_alloy_visuals}; | |
| 19 | + | pub use widgets::*; |
| @@ -1,0 +1,130 @@ | |||
| 1 | + | //! Per-frame theme application. | |
| 2 | + | //! | |
| 3 | + | //! Apps call `apply_alloy_visuals` once per frame at the top of `update`. There | |
| 4 | + | //! is no other path by which Alloy visuals enter an app; this is enforced by | |
| 5 | + | //! convention, not by the type system, so the discipline matters. | |
| 6 | + | ||
| 7 | + | use egui::{Color32, CornerRadius, Stroke, Visuals, style::WidgetVisuals}; | |
| 8 | + | ||
| 9 | + | use crate::generated::tokens::{colors, geometry}; | |
| 10 | + | ||
| 11 | + | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
| 12 | + | pub enum Mode { | |
| 13 | + | Light, | |
| 14 | + | Dark, | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | pub fn apply_alloy_visuals(ctx: &egui::Context, mode: Mode) { | |
| 18 | + | let mut style = (*ctx.global_style()).clone(); | |
| 19 | + | style.visuals = match mode { | |
| 20 | + | Mode::Light => visuals_light(), | |
| 21 | + | Mode::Dark => visuals_dark(), | |
| 22 | + | }; | |
| 23 | + | style.spacing.item_spacing = egui::vec2(geometry::SPACE_MD, geometry::SPACE_SM); | |
| 24 | + | style.spacing.button_padding = egui::vec2(geometry::SPACE_MD, geometry::SPACE_SM); | |
| 25 | + | style.spacing.window_margin = egui::Margin::same(geometry::SPACE_LG as i8); | |
| 26 | + | ctx.set_global_style(style); | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | fn visuals_light() -> Visuals { | |
| 30 | + | build_visuals(Palette { | |
| 31 | + | surface_sunken: colors::light::SURFACE_SUNKEN, | |
| 32 | + | surface: colors::light::SURFACE, | |
| 33 | + | surface_raised: colors::light::SURFACE_RAISED, | |
| 34 | + | surface_overlay: colors::light::SURFACE_OVERLAY, | |
| 35 | + | border_subtle: colors::light::BORDER_SUBTLE, | |
| 36 | + | border: colors::light::BORDER, | |
| 37 | + | border_strong: colors::light::BORDER_STRONG, | |
| 38 | + | text_muted: colors::light::TEXT_MUTED, | |
| 39 | + | text_secondary: colors::light::TEXT_SECONDARY, | |
| 40 | + | text_primary: colors::light::TEXT_PRIMARY, | |
| 41 | + | dark: false, | |
| 42 | + | }) | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | fn visuals_dark() -> Visuals { | |
| 46 | + | build_visuals(Palette { | |
| 47 | + | surface_sunken: colors::dark::SURFACE_SUNKEN, | |
| 48 | + | surface: colors::dark::SURFACE, | |
| 49 | + | surface_raised: colors::dark::SURFACE_RAISED, | |
| 50 | + | surface_overlay: colors::dark::SURFACE_OVERLAY, | |
| 51 | + | border_subtle: colors::dark::BORDER_SUBTLE, | |
| 52 | + | border: colors::dark::BORDER, | |
| 53 | + | border_strong: colors::dark::BORDER_STRONG, | |
| 54 | + | text_muted: colors::dark::TEXT_MUTED, | |
| 55 | + | text_secondary: colors::dark::TEXT_SECONDARY, | |
| 56 | + | text_primary: colors::dark::TEXT_PRIMARY, | |
| 57 | + | dark: true, | |
| 58 | + | }) | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | struct Palette { | |
| 62 | + | surface_sunken: Color32, | |
| 63 | + | surface: Color32, | |
| 64 | + | surface_raised: Color32, | |
| 65 | + | surface_overlay: Color32, | |
| 66 | + | border_subtle: Color32, | |
| 67 | + | border: Color32, | |
| 68 | + | border_strong: Color32, | |
| 69 | + | text_muted: Color32, | |
| 70 | + | text_secondary: Color32, | |
| 71 | + | text_primary: Color32, | |
| 72 | + | dark: bool, | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | fn build_visuals(p: Palette) -> Visuals { | |
| 76 | + | let base = if p.dark { Visuals::dark() } else { Visuals::light() }; | |
| 77 | + | ||
| 78 | + | let radius = CornerRadius::same(geometry::RADIUS_CONTROL as u8); | |
| 79 | + | let border = Stroke::new(geometry::BORDER_WIDTH_DEFAULT, p.border); | |
| 80 | + | let border_subtle = Stroke::new(geometry::BORDER_WIDTH_DEFAULT, p.border_subtle); | |
| 81 | + | let focus = Stroke::new(geometry::BORDER_WIDTH_FOCUS, p.border_strong); | |
| 82 | + | ||
| 83 | + | let widget = |bg: Color32, fg: Color32, stroke: Stroke| WidgetVisuals { | |
| 84 | + | bg_fill: bg, | |
| 85 | + | weak_bg_fill: bg, | |
| 86 | + | bg_stroke: stroke, | |
| 87 | + | fg_stroke: Stroke::new(geometry::BORDER_WIDTH_DEFAULT, fg), | |
| 88 | + | corner_radius: radius, | |
| 89 | + | expansion: 0.0, | |
| 90 | + | }; | |
| 91 | + | ||
| 92 | + | let mut v = base; | |
| 93 | + | v.window_fill = p.surface; | |
| 94 | + | v.panel_fill = p.surface; | |
| 95 | + | v.faint_bg_color = p.surface_sunken; | |
| 96 | + | v.extreme_bg_color = p.surface_sunken; | |
| 97 | + | v.code_bg_color = p.surface_sunken; | |
| 98 | + | ||
| 99 | + | v.window_stroke = border; | |
| 100 | + | v.window_corner_radius = CornerRadius::same(geometry::RADIUS_OVERLAY as u8); | |
| 101 | + | ||
| 102 | + | v.widgets.noninteractive = widget(p.surface_raised, p.text_primary, border_subtle); | |
| 103 | + | v.widgets.inactive = widget(p.surface_raised, p.text_primary, border); | |
| 104 | + | v.widgets.hovered = widget(p.surface_overlay, p.text_primary, border); | |
| 105 | + | v.widgets.active = widget(p.surface, p.text_primary, border); | |
| 106 | + | v.widgets.open = widget(p.surface_overlay, p.text_primary, border); | |
| 107 | + | ||
| 108 | + | v.selection.bg_fill = p.border_strong; | |
| 109 | + | v.selection.stroke = Stroke::new(geometry::BORDER_WIDTH_DEFAULT, p.text_primary); | |
| 110 | + | ||
| 111 | + | v.hyperlink_color = p.text_primary; | |
| 112 | + | v.warn_fg_color = p.text_secondary; | |
| 113 | + | v.error_fg_color = p.text_primary; | |
| 114 | + | ||
| 115 | + | // Focus is chrome, not data — borrow border_strong, never an accent. | |
| 116 | + | v.widgets.hovered.fg_stroke = Stroke::new(geometry::BORDER_WIDTH_DEFAULT, p.text_primary); | |
| 117 | + | v.window_highlight_topmost = false; | |
| 118 | + | v.override_text_color = Some(p.text_primary); | |
| 119 | + | ||
| 120 | + | // The egui struct doesn't expose a typed "focus ring" — focus is currently | |
| 121 | + | // implied by hover state. The themed widgets in `widgets.rs` paint an | |
| 122 | + | // explicit focus ring using `focus`/`FOCUS_RING_OFFSET` per the design | |
| 123 | + | // language. The `_ = focus` line keeps the binding load-bearing if visuals | |
| 124 | + | // grow a slot for it later. | |
| 125 | + | let _ = focus; | |
| 126 | + | let _ = p.text_secondary; | |
| 127 | + | let _ = p.text_muted; | |
| 128 | + | ||
| 129 | + | v | |
| 130 | + | } |
| @@ -1,0 +1,139 @@ | |||
| 1 | + | //! Themed primitives and layout combinators. | |
| 2 | + | //! | |
| 3 | + | //! See `docs/COMPONENT-LIBRARY.md` for the four idioms (functions, builders, | |
| 4 | + | //! HOFs, `impl Widget`). This module starts with the smallest useful set so | |
| 5 | + | //! marquee apps have something to build against; primitives land here, not in | |
| 6 | + | //! app crates. | |
| 7 | + | ||
| 8 | + | use egui::{Response, RichText, Stroke, Ui, Widget}; | |
| 9 | + | ||
| 10 | + | use crate::generated::tokens::{colors, geometry, typography}; | |
| 11 | + | ||
| 12 | + | /// One-shot primary button. Themed against `surface-raised` with a default | |
| 13 | + | /// border; egui handles hover/active state through the visuals applied in | |
| 14 | + | /// `theme::apply_alloy_visuals`. | |
| 15 | + | pub fn primary_button(ui: &mut Ui, label: &str) -> Response { | |
| 16 | + | ui.add( | |
| 17 | + | egui::Button::new( | |
| 18 | + | RichText::new(label) | |
| 19 | + | .size(typography::SIZE_BODY) | |
| 20 | + | .color(theme_text(ui)), | |
| 21 | + | ) | |
| 22 | + | .corner_radius(geometry::RADIUS_CONTROL), | |
| 23 | + | ) | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | /// Section header + content block. The `title` renders at `size-section-header` | |
| 27 | + | /// in the working font; spacing follows the geometry tokens. | |
| 28 | + | pub fn section<R>(ui: &mut Ui, title: &str, content: impl FnOnce(&mut Ui) -> R) -> R { | |
| 29 | + | ui.add_space(geometry::SPACE_LG); | |
| 30 | + | ui.label( | |
| 31 | + | RichText::new(title) | |
| 32 | + | .size(typography::SIZE_SECTION_HEADER) | |
| 33 | + | .strong() | |
| 34 | + | .color(theme_text(ui)), | |
| 35 | + | ); | |
| 36 | + | ui.add_space(geometry::SPACE_SM); | |
| 37 | + | let result = ui.scope(content).inner; | |
| 38 | + | ui.add_space(geometry::SPACE_MD); | |
| 39 | + | result | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | /// Form-row layout: label on the left, control area on the right. | |
| 43 | + | pub fn field_row<R>(ui: &mut Ui, label: &str, control: impl FnOnce(&mut Ui) -> R) -> R { | |
| 44 | + | ui.horizontal(|ui| { | |
| 45 | + | ui.add_sized( | |
| 46 | + | [120.0, 0.0], | |
| 47 | + | egui::Label::new( | |
| 48 | + | RichText::new(label) | |
| 49 | + | .size(typography::SIZE_BODY) | |
| 50 | + | .color(theme_text_secondary(ui)), | |
| 51 | + | ), | |
| 52 | + | ); | |
| 53 | + | ui.scope(control).inner | |
| 54 | + | }) | |
| 55 | + | .inner | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | /// Severity tag — categorical accent on text, never on chrome. | |
| 59 | + | #[derive(Debug, Clone, Copy)] | |
| 60 | + | pub enum Severity { | |
| 61 | + | Info, | |
| 62 | + | Healthy, | |
| 63 | + | Warn, | |
| 64 | + | Error, | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | impl Severity { | |
| 68 | + | fn color(self) -> egui::Color32 { | |
| 69 | + | match self { | |
| 70 | + | Severity::Info => colors::accents::INFO, | |
| 71 | + | Severity::Healthy => colors::accents::HEALTHY, | |
| 72 | + | Severity::Warn => colors::accents::WARN, | |
| 73 | + | Severity::Error => colors::accents::ERROR, | |
| 74 | + | } | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | fn label(self) -> &'static str { | |
| 78 | + | match self { | |
| 79 | + | Severity::Info => "INFO", | |
| 80 | + | Severity::Healthy => "OK", | |
| 81 | + | Severity::Warn => "WARN", | |
| 82 | + | Severity::Error => "ERROR", | |
| 83 | + | } | |
| 84 | + | } | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | pub struct SeverityTag { | |
| 88 | + | severity: Severity, | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | impl SeverityTag { | |
| 92 | + | pub fn new(severity: Severity) -> Self { | |
| 93 | + | Self { severity } | |
| 94 | + | } | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | impl Widget for SeverityTag { | |
| 98 | + | fn ui(self, ui: &mut Ui) -> Response { | |
| 99 | + | let color = self.severity.color(); | |
| 100 | + | let text = RichText::new(self.severity.label()) | |
| 101 | + | .size(typography::SIZE_CAPTION) | |
| 102 | + | .color(color); | |
| 103 | + | ui.add(egui::Label::new(text)) | |
| 104 | + | } | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | fn theme_text(ui: &Ui) -> egui::Color32 { | |
| 108 | + | ui.visuals().widgets.inactive.fg_stroke.color | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | fn theme_text_secondary(ui: &Ui) -> egui::Color32 { | |
| 112 | + | // Secondary text isn't a first-class slot in egui visuals; pull from the | |
| 113 | + | // generated tokens directly. Mode picked from `dark_mode` so the secondary | |
| 114 | + | // value tracks the active palette. | |
| 115 | + | if ui.visuals().dark_mode { | |
| 116 | + | colors::dark::TEXT_SECONDARY | |
| 117 | + | } else { | |
| 118 | + | colors::light::TEXT_SECONDARY | |
| 119 | + | } | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | /// Paint a focus ring around the last response, per design language | |
| 123 | + | /// (2px `border-strong` ring offset 1px outside the control geometry). | |
| 124 | + | pub fn focus_ring(ui: &Ui, response: &Response) { | |
| 125 | + | if response.has_focus() { | |
| 126 | + | let rect = response.rect.expand(geometry::FOCUS_RING_OFFSET); | |
| 127 | + | let color = if ui.visuals().dark_mode { | |
| 128 | + | colors::dark::BORDER_STRONG | |
| 129 | + | } else { | |
| 130 | + | colors::light::BORDER_STRONG | |
| 131 | + | }; | |
| 132 | + | ui.painter().rect_stroke( | |
| 133 | + | rect, | |
| 134 | + | geometry::RADIUS_CONTROL, | |
| 135 | + | Stroke::new(geometry::BORDER_WIDTH_FOCUS, color), | |
| 136 | + | egui::StrokeKind::Outside, | |
| 137 | + | ); | |
| 138 | + | } | |
| 139 | + | } |
| @@ -1,0 +1,88 @@ | |||
| 1 | + | # Alloy design tokens. | |
| 2 | + | # | |
| 3 | + | # This file is the build-time input. `build.rs` parses it, converts OKLCH | |
| 4 | + | # colors to sRGB, and emits `src/generated/tokens.rs` with typed constants. | |
| 5 | + | # | |
| 6 | + | # Canonical rationale and the decisions behind these values live in | |
| 7 | + | # `docs/TOKENS.md`. Edit that first; transcribe the resolved numbers here. | |
| 8 | + | # Round-trip is one-way: TOKENS.md -> tokens.toml -> tokens.rs. | |
| 9 | + | ||
| 10 | + | [tint] | |
| 11 | + | # Global tint hue and chroma for all chrome (surfaces, borders, text). | |
| 12 | + | hue = 80.0 | |
| 13 | + | chroma = 0.012 | |
| 14 | + | ||
| 15 | + | # Chrome ramp lightness stops. Same hue/chroma; only L varies. | |
| 16 | + | # Light mode is canonical; dark is the elevation-preserving derivative. | |
| 17 | + | [ramp.light] | |
| 18 | + | surface_sunken = 0.92 | |
| 19 | + | surface = 0.96 | |
| 20 | + | surface_raised = 0.98 | |
| 21 | + | surface_overlay = 0.995 | |
| 22 | + | border_subtle = 0.88 | |
| 23 | + | border = 0.80 | |
| 24 | + | border_strong = 0.65 | |
| 25 | + | text_muted = 0.55 | |
| 26 | + | text_secondary = 0.38 | |
| 27 | + | text_primary = 0.18 | |
| 28 | + | ||
| 29 | + | [ramp.dark] | |
| 30 | + | surface_sunken = 0.08 | |
| 31 | + | surface = 0.12 | |
| 32 | + | surface_raised = 0.15 | |
| 33 | + | surface_overlay = 0.19 | |
| 34 | + | border_subtle = 0.22 | |
| 35 | + | border = 0.30 | |
| 36 | + | border_strong = 0.48 | |
| 37 | + | text_muted = 0.60 | |
| 38 | + | text_secondary = 0.78 | |
| 39 | + | text_primary = 0.94 | |
| 40 | + | ||
| 41 | + | # Information palette. Identical OKLCH in both modes by design — accents | |
| 42 | + | # are recognized by hue, and inverting them per mode would defeat that. | |
| 43 | + | [accents] | |
| 44 | + | error = { l = 0.55, c = 0.18, h = 25.0 } | |
| 45 | + | warn = { l = 0.70, c = 0.15, h = 85.0 } | |
| 46 | + | healthy = { l = 0.62, c = 0.16, h = 145.0 } | |
| 47 | + | info = { l = 0.60, c = 0.16, h = 240.0 } | |
| 48 | + | syntax = { l = 0.55, c = 0.18, h = 310.0 } | |
| 49 | + | ||
| 50 | + | [typography] | |
| 51 | + | font_mono_working = "IosevkaNerdFont" | |
| 52 | + | font_mono_working_noligatures = "IosevkaTermNerdFont" | |
| 53 | + | font_mono_display = "DepartureMonoNerdFontMono" | |
| 54 | + | ||
| 55 | + | size_caption = 12.0 | |
| 56 | + | size_body = 14.0 | |
| 57 | + | size_section_header = 16.0 | |
| 58 | + | size_title = 20.0 | |
| 59 | + | size_readout_sm = 16.0 | |
| 60 | + | size_readout_md = 24.0 | |
| 61 | + | size_readout_lg = 48.0 | |
| 62 | + | size_readout_xl = 72.0 | |
| 63 | + | ||
| 64 | + | weight_body = 400 | |
| 65 | + | weight_emphasis = 600 | |
| 66 | + | ||
| 67 | + | tracking_body = 0.0 | |
| 68 | + | tracking_display = 0.04 | |
| 69 | + | tracking_display_caps = 0.08 | |
| 70 | + | ||
| 71 | + | line_height_body = 1.4 | |
| 72 | + | line_height_tabular = 1.3 | |
| 73 | + | line_height_display = 1.0 | |
| 74 | + | ||
| 75 | + | [geometry] | |
| 76 | + | space_xs = 2.0 | |
| 77 | + | space_sm = 4.0 | |
| 78 | + | space_md = 8.0 | |
| 79 | + | space_lg = 16.0 | |
| 80 | + | space_xl = 24.0 | |
| 81 | + | ||
| 82 | + | radius_control = 4.0 | |
| 83 | + | radius_overlay = 6.0 | |
| 84 | + | radius_data = 0.0 | |
| 85 | + | ||
| 86 | + | border_width_default = 1.0 | |
| 87 | + | border_width_focus = 2.0 | |
| 88 | + | focus_ring_offset = 1.0 |