From 27cb5fb38ec1cf5cf0c90dd0434559cb415e31cd Mon Sep 17 00:00:00 2001 From: Mathieu GRENET Date: Wed, 16 Sep 2026 11:22:34 +0200 Subject: [PATCH] =?UTF-8?q?Des=20captures=20de=20r=C3=A9f=C3=A9rence,=20et?= =?UTF-8?q?=20le=20vrai=20blocage=20n'=C3=A9tait=20pas=20lavapipe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Décision 2 de docs/v7-frontend.md. La note disait « il faut lavapipe dans l'image du workflow, donc ça n'a pas été tenté ». lavapipe est la partie facile. Le vrai blocage est le graphe de dépendances, et il est côté Windows : egui_kittest rend par wgpu ; lbm-hook épingle windows 0.62 ; cargo unifie windows-core à 0.62 pour wgpu-hal ; gpu-allocator 0.28, que le back-end DX12 de wgpu-hal appelle, a été bâti contre 0.58. Leurs ID3D12Device sont alors deux types différents et wgpu-hal lui-même ne compile pas. Rien ici ne peut corriger ça — c'est à l'amont. Et une dev-dependency atteint `cargo test`, qui est exactement ce que la CI lance, donc wgpu est tenu hors de Windows par une dépendance ciblée cfg(not(windows)). Vérifié dans les deux sens : le lint Windows du workspace reste propre, le rendu marche sous Linux. Quatre captures : la barre en Running, la barre sans agent (le seul état dont l'arbre d'accessibilité ne peut pas répondre — « désactivé » y est un drapeau, à l'écran c'est une couleur), la carte à deux écrans, un cadre avec son nom. Deux choses que le premier rendu a montrées et qu'aucun test d'arbre n'aurait pu montrer : - **Harness::render peint sur rien.** Le `Ui` d'`App::ui` n'a pas de fond, la fenêtre l'enveloppe dans un CentralPanel, et une capture sans panneau rend du blanc sur transparent — composité ensuite sur ce que le visionneur met derrière. La première série ressemblait à des rectangles noirs sur papier blanc, avec un bloc sombre parasite qui n'était que de l'alpha prémultiplié. Image et application doivent être la même chose, sinon la référence épingle une fiction. - **À 900×600, la carte à deux écrans n'a aucun nom.** Le nom vaut la moitié de la bordure du haut, ce qui passe ici sous le plancher de 7 pt. Les tests d'arbre existants n'ont jamais vu ça : ils utilisent une fenêtre de 1200×900. C'est la question ouverte du document rendue visible — le plancher décide bien du cas courant. Les références ont été engendrées sur un GPU réel ; si le rastériseur logiciel de la CI ne s'y accorde pas, l'artefact du workflow donne ce qu'elle a rendu. Co-Authored-By: Claude Opus 5 --- .github/workflows/dotnet-desktop.yml | 20 ++ rust/Cargo.lock | 325 +++++++++++++++++- rust/crates/lbm-ui/Cargo.toml | 11 + rust/crates/lbm-ui/tests/snapshots.rs | 137 ++++++++ .../tests/snapshots/bottom-bar-no-agent.png | Bin 0 -> 4792 bytes .../tests/snapshots/bottom-bar-running.png | Bin 0 -> 4631 bytes .../tests/snapshots/map-two-screens.png | Bin 0 -> 12025 bytes .../lbm-ui/tests/snapshots/monitor-frame.png | Bin 0 -> 2935 bytes 8 files changed, 489 insertions(+), 4 deletions(-) create mode 100644 rust/crates/lbm-ui/tests/snapshots.rs create mode 100644 rust/crates/lbm-ui/tests/snapshots/bottom-bar-no-agent.png create mode 100644 rust/crates/lbm-ui/tests/snapshots/bottom-bar-running.png create mode 100644 rust/crates/lbm-ui/tests/snapshots/map-two-screens.png create mode 100644 rust/crates/lbm-ui/tests/snapshots/monitor-frame.png diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index 530a114b..d1ed2788 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -46,9 +46,29 @@ jobs: - name: Format run: cargo fmt --check + # The reference snapshots of `lbm-ui` render through wgpu, and a runner has no GPU. + # lavapipe is Mesa's software Vulkan device; `mesa-vulkan-drivers` is what ships it. + # Nothing else in the workspace needs it — every other test asks the accessibility + # tree — so a runner without it would still pass everything but the snapshots. + - name: Software Vulkan for the reference snapshots + run: | + sudo apt-get update + sudo apt-get install -y mesa-vulkan-drivers + - name: Test run: cargo test + # What the render actually produced, when it did not match. Without this a snapshot + # failure is a number of differing pixels and nothing to look at — and looking is the + # entire point of having rendered. + - name: Upload the snapshots that did not match + if: failure() + uses: actions/upload-artifact@v4 + with: + name: snapshot-diffs + path: rust/crates/lbm-ui/tests/snapshots/** + if-no-files-found: ignore + # Benches are plain binaries nothing else builds; keep them compiling. - name: Build benches run: cargo bench --no-run diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 3751fc77..de3c19cb 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -152,6 +152,12 @@ dependencies = [ "cc", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.6.1" @@ -177,6 +183,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -189,6 +204,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + [[package]] name = "arboard" version = "3.6.1" @@ -227,6 +248,15 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + [[package]] name = "ashpd" version = "0.13.12" @@ -703,6 +733,8 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ + "serde", + "termcolor", "unicode-width", ] @@ -721,6 +753,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.52.0", +] + [[package]] name = "combine" version = "4.6.8" @@ -837,6 +879,25 @@ dependencies = [ "itertools 0.13.0", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.22" @@ -870,6 +931,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "dify" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ce0fb972943b4e88cd03b8f92953df0c71bb05e0bde8e5b684895d808013cc" +dependencies = [ + "anyhow", + "colored", + "getopts", + "image", + "rayon", +] + [[package]] name = "digest" version = "0.11.3" @@ -972,12 +1046,14 @@ dependencies = [ "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", + "pollster", "profiling", "raw-window-handle", "static_assertions", "wasm-bindgen", "web-sys", "web-time", + "wgpu", "windows-sys 0.61.2", "winit", ] @@ -1064,11 +1140,19 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb3f3effb2726fee23449293e67acbf6cc6ce35525b1722bd5743bb348114d5" dependencies = [ + "dify", + "eframe", "egui", + "egui-wgpu", + "image", "kittest", "log", + "open", + "pollster", "serde", + "tempfile", "toml", + "wgpu", ] [[package]] @@ -1447,6 +1531,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -1585,6 +1678,20 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.20", + "windows 0.58.0", +] + [[package]] name = "guillotiere" version = "0.7.0" @@ -1624,6 +1731,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -1807,6 +1916,25 @@ dependencies = [ "serde_core", ] +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1910,6 +2038,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + [[package]] name = "khronos_api" version = "3.1.0" @@ -2293,6 +2432,7 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 1.1.0", + "spirv", "thiserror 2.0.20", "unicode-ident", ] @@ -2427,7 +2567,7 @@ dependencies = [ "objc2-core-data", "objc2-core-image", "objc2-foundation 0.2.2", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -2512,7 +2652,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] @@ -2593,6 +2733,18 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.0", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -2603,7 +2755,21 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", ] [[package]] @@ -2631,7 +2797,7 @@ dependencies = [ "objc2-core-location", "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", @@ -2685,6 +2851,16 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "open" +version = "5.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +dependencies = [ + "is-wsl", + "libc", +] + [[package]] name = "orbclient" version = "0.3.55" @@ -2695,6 +2871,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2907,6 +3092,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6355899e1c9462875b6757c79f3caa011a1fdae12bbb1a2e72dd1f234f8336" + [[package]] name = "polycool" version = "0.4.0" @@ -2940,6 +3131,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -3012,12 +3209,50 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "read-fonts" version = "0.41.0" @@ -3482,6 +3717,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.13.0", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3565,6 +3809,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4245,6 +4498,8 @@ dependencies = [ "hashbrown 0.17.1", "js-sys", "log", + "naga", + "parking_lot", "portable-atomic", "profiling", "raw-window-handle", @@ -4284,12 +4539,42 @@ dependencies = [ "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.20", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-naga-bridge", "wgpu-types", ] +[[package]] +name = "wgpu-core-deps-apple" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061f3d319a40d39d00b1ecc2c33b89fe21d4e6fe01859df3500a3a8ecccd6b68" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98b86cf4abf524a902dd35f18ca6a3f08fc2ae9847c8f10b48e30491b1f0b86" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-wasm" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d260ca2ca8adc654f513a869bf3cac0b47f5b8d3f87803d6942015f8de597aae" +dependencies = [ + "wgpu-hal", +] + [[package]] name = "wgpu-core-deps-windows-linux-android" version = "30.0.1" @@ -4305,22 +4590,54 @@ version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b7fb58561a792bc237628ba0792e332de418fefe145f13b5ed8201e6d52f58" dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", "bitflags 2.13.0", + "block2 0.6.2", + "bytemuck", "cfg-if", "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", "hashbrown 0.17.1", + "js-sys", + "khronos-egl", + "libc", "libloading", "log", "naga", "naga-types", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", "portable-atomic", "portable-atomic-util", + "profiling", + "range-alloc", "raw-window-handle", + "raw-window-metal", "renderdoc-sys", + "smallvec", "static_assertions", "thiserror 2.0.20", + "wasm-bindgen", + "wayland-sys", + "web-sys", "wgpu-naga-bridge", "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-result 0.4.1", ] [[package]] diff --git a/rust/crates/lbm-ui/Cargo.toml b/rust/crates/lbm-ui/Cargo.toml index c0cfc261..312d62ae 100644 --- a/rust/crates/lbm-ui/Cargo.toml +++ b/rust/crates/lbm-ui/Cargo.toml @@ -28,3 +28,14 @@ serde_json.workspace = true # dev-dependency because the loading belongs to whoever owns the egui context, not to # the view — but the two have to be shown to meet. lbm-icons = { path = "../lbm-icons" } + +# Rendering, for the reference snapshots. **Linux only, and not by taste.** +# `egui_kittest`'s snapshot feature renders through wgpu, and wgpu cannot enter this +# workspace's graph on Windows: `lbm-hook` pins `windows` 0.62, cargo unifies +# `windows-core` to 0.62 for `wgpu-hal`, and `gpu-allocator` 0.28 — which wgpu-hal's DX12 +# backend calls — was built against 0.58. Their `ID3D12Device` are then two different +# types and wgpu-hal itself does not compile. Nothing here can fix that; it is upstream's +# to resolve. A dev-dependency reaches `cargo test`, which is exactly what CI runs, so it +# is kept off Windows entirely rather than left to break the job. +[target.'cfg(not(windows))'.dev-dependencies] +egui_kittest = { version = "0.36", features = ["wgpu", "snapshot"] } diff --git a/rust/crates/lbm-ui/tests/snapshots.rs b/rust/crates/lbm-ui/tests/snapshots.rs new file mode 100644 index 00000000..ac67bfc9 --- /dev/null +++ b/rust/crates/lbm-ui/tests/snapshots.rs @@ -0,0 +1,137 @@ +//! What the views actually look like. +//! +//! Every other test in this crate asks the **accessibility tree** — which is how `lbm-ui` +//! can be tested at all on a Wayland machine, and which is genuinely the right question +//! for "is the name there", "did the click land", "is Save disabled". It is the wrong +//! question for "does this look like anything". A view can have a perfect tree and paint +//! white on white, put a label under a panel, or lose its background: the tree says all +//! is well because, to the tree, all is. +//! +//! So these render. `egui_kittest` draws through wgpu and compares against a committed +//! PNG; a view that changes shape fails until someone looks at the diff and says yes. +//! +//! **Linux only, and not by taste.** The renderer is wgpu, and wgpu cannot enter this +//! workspace's dependency graph on Windows — see the note on the target-gated +//! dev-dependency in `Cargo.toml`. On Windows this file compiles to nothing. +//! +//! The references are generated **on CI's renderer**, not on a developer's. Software +//! rasterization (lavapipe) and a real GPU do not antialias identically, so a PNG made +//! here would fail there and vice versa; `UPDATE_SNAPSHOTS=1` regenerates, and the +//! workflow uploads what it rendered when a comparison fails. +#![cfg(not(windows))] + +use egui_kittest::Harness; +use lbm_layout::geo::Rect; +use lbm_ui::frame::{self, Ratio}; +use lbm_ui::map::{self, MapMonitor}; + +/// A window of a stated size, with a background — both on purpose. +/// +/// The size, because the fit follows the window: a harness left at its default would make +/// every reference depend on that default. +/// +/// The `CentralPanel`, because **`Harness::render` paints on nothing.** The `Ui` handed to +/// `eframe::App::ui` has no background of its own and the window wraps it in a panel +/// (`main.rs`); a snapshot taken without one renders white-on-transparent, which composites +/// into whatever the viewer happens to put behind it. The first run of these tests produced +/// exactly that — a map that looked like black rectangles on white paper, and a stray dark +/// block that was nothing but premultiplied alpha. Picture and app must be the same thing +/// or the reference is pinning a fiction. +fn harness<'a>(size: (f32, f32), mut app: impl FnMut(&mut egui::Ui) + 'a) -> Harness<'a> { + Harness::builder() + .with_size(egui::vec2(size.0, size.1)) + .build_ui(move |ui| { + egui::CentralPanel::default().show(ui, &mut app); + }) +} + +/// The two screens the other map tests use, with the 20 mm bezel the oracle's desktops +/// really have. +fn two_screens() -> Vec> { + vec![ + MapMonitor { + id: "left", + name: "Left screen", + mm_outside: Rect::new(0.0, 0.0, 640.0, 380.0), + mm_content: Rect::new(20.0, 20.0, 600.0, 340.0), + logo: None, + wallpaper: None, + }, + MapMonitor { + id: "right", + name: "Right screen", + mm_outside: Rect::new(700.0, 0.0, 640.0, 380.0), + mm_content: Rect::new(720.0, 20.0, 600.0, 340.0), + logo: None, + wallpaper: None, + }, + ] +} + +/// The bar, running, with a screen selected — the state a user spends their time in. +#[test] +fn the_bottom_bar_running() { + let state = lbm_ui::State { + engine: lbm_ui::Engine::Running, + hook_connected: true, + ..Default::default() + }; + let mut harness = harness((520.0, 60.0), |ui| { + lbm_ui::bottom_bar(ui, &state); + }); + harness.run(); + harness.snapshot("bottom-bar-running"); +} + +/// And with no agent: **every button disabled**. The one state the tree cannot vouch for +/// on its own — "disabled" in the tree is a flag, on screen it is a colour, and a theme +/// that greyed nothing would read as an app that simply does not respond. +#[test] +fn the_bottom_bar_without_an_agent() { + let state = lbm_ui::State::default(); + let mut harness = harness((520.0, 60.0), |ui| { + lbm_ui::bottom_bar(ui, &state); + }); + harness.run(); + harness.snapshot("bottom-bar-no-agent"); +} + +/// The map: two screens, one selected. The bezels, the fit — and **no names**, at a size +/// a window is very likely to be. That is not a bug in the picture, it is the picture of +/// the open question: the name is half the top bezel, here that lands under the 7 pt +/// legibility floor, and the floor drops it. `docs/v7-frontend.md` says the floor decides +/// the common case; this is the common case, and it is why the reference is kept. +#[test] +fn the_map_with_two_screens() { + let screens = two_screens(); + let mut harness = harness((900.0, 600.0), |ui| { + let fit = map::fit(map::extent(&screens), ui.max_rect()); + map::draw(ui, &screens, &fit, Some("left")); + }); + harness.run(); + harness.snapshot("map-two-screens"); +} + +/// One frame, large. This is the picture behind the open question about the name: it is +/// **half the top bezel**, printed on the plastic, and on a thin-bezel screen that lands +/// between 1 and 5 points. Here the bezel is 20 mm and the frame is big, so the name is +/// comfortably drawn — which is exactly the case the table in `docs/v7-frontend.md` says +/// is *not* the common one. +#[test] +fn a_monitor_frame_with_its_name() { + let outside = Rect::new(0.0, 0.0, 640.0, 380.0); + let content = Rect::new(20.0, 20.0, 600.0, 340.0); + let drawn = frame::draw(outside, content, (0.0, 0.0), Ratio { x: 0.6, y: 0.6 }); + let mut harness = harness((420.0, 260.0), |ui| { + frame::monitor( + ui, + &drawn, + &frame::Look { + name: "Odyssey G80SD", + ..Default::default() + }, + ); + }); + harness.run(); + harness.snapshot("monitor-frame"); +} diff --git a/rust/crates/lbm-ui/tests/snapshots/bottom-bar-no-agent.png b/rust/crates/lbm-ui/tests/snapshots/bottom-bar-no-agent.png new file mode 100644 index 0000000000000000000000000000000000000000..3870b6e2e738b02eda737fa87d3f58a2e9373b96 GIT binary patch literal 4792 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 literal 0 HcmV?d00001 diff --git a/rust/crates/lbm-ui/tests/snapshots/bottom-bar-running.png b/rust/crates/lbm-ui/tests/snapshots/bottom-bar-running.png new file mode 100644 index 0000000000000000000000000000000000000000..e04e8dc54ad510420685fab4f855e5053d3a695b GIT binary patch literal 4631 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 literal 0 HcmV?d00001 diff --git a/rust/crates/lbm-ui/tests/snapshots/map-two-screens.png b/rust/crates/lbm-ui/tests/snapshots/map-two-screens.png new file mode 100644 index 0000000000000000000000000000000000000000..fa1c815378d40da527007c5d7b043fbc8e94f453 GIT binary patch literal 12025 zcmeAS@N?(olHy`uVBq!ia0y~yU~XYxV2a>i1BzH3)ws>Tpm)I2#WAFU@$CWT1uBu# zj(z-J{;v1LndL>^$?I}&Z-b)Do_W)}*3UCCR(kidYKACF0&4GoViy^2Kqe|@L&`P@gW$^+r30SSpwOOX=jfThRL zSQw23c+og`ipkMrIhrg-lO?DLGeGT>(F{160Y@|7Xa*dvO@c+A_Fvgh%sXA~-zlEs z>z+dTi~sF^d4Df@qgn5>zg+(R%<$fRci%k8$6r>0$?tge=_Rslg2!;MY zh5wgwp!8jVcK`a{@c^wyyXt@cpiy?}zp%9Z&!sWo4((U_>lbg=DgB@S=V|;uRY))Q z@Aj{Vdt#=t!hBP!V8X%)71bM7zG|M1oj?pq?w6sL2 zR7R^0)RE=U>SMI}7_B~_RmN!HFj_bu)fS^osnMnsB14Sy!Xf0}e2c2v{!gGo4gZ!` z+?-w;2^);qFL@9xv4sAY7ld^B5%tU0|AK=?*&qF@@w=u&Mlt`@ztX>Z`oGERud`qR zQR}b&(Rcl@|H{VYe+>UFt=(GIX|EH_=KKt|Y z^B?`zAL4f^9``=O7?UY|x979p$EHCYDuX%3M%{)~ISuygH5yr?kp=Bq4W?3mG+B-& z%h6;BY>^G}*4b!29nGhs`E)d&QqW&ig!N5f9kD?)QU1ey>IrY&$DcnN0++Nic)I$z JtaD0e0stFq1;YRU literal 0 HcmV?d00001 diff --git a/rust/crates/lbm-ui/tests/snapshots/monitor-frame.png b/rust/crates/lbm-ui/tests/snapshots/monitor-frame.png new file mode 100644 index 0000000000000000000000000000000000000000..3a42ad2d76b67b9439e1061515395b07df408a20 GIT binary patch literal 2935 zcmeAS@N?(olHy`uVBq!ia0y~yU|a&kEF5e=k@NtE+YAiccAhSdAr*{o4=^uKiJW%q zUFzbt@>Zb z5*}N6H8ec->;3)*5V6(&ehbEc#Y(T%+c$yLtzP%*y?X&j9Z=%`Uk;F7ki>rZgAlRs zzuy;s(y#a$USC=aQTE@`zy8