mandara

Mandara patches to gpui

Vendored from zed-industries/zed at ec86047d67acd6fddd81392f44ed6ebcc6e7f531 (crates/gpui, Apache-2.0, see LICENSE-APACHE) and wired in through the workspace’s [patch."https://github.com/zed-industries/zed"], next to the vendored renderer (vendor/gpui_wgpu, whose patch 9 is the GPU half of patch 1 here). Every source change is marked Mandara patch in a comment. Only crates/gpui is vendored; every other Zed crate (gpui_platform, gpui_linux, gpui_macros, refineable, collections, …) still comes from the same git revision and is built against this copy through the patch.

1. Rounded content masks (window.rs, style.rs)

Upstream ContentMask is only a rectangle, so a .rounded_*() .overflow_hidden() card clipped its children to its bounding box: a cover image, a colour band or a hover fill that reached the card’s edge was drawn as a square over the card’s rounded corners.

The scene’s primitive structs embed ContentMask<ScaledPixels>, so every one grows by 16 bytes (Quad 160 → 176, Shadow / MonochromeSprite / SubpixelSprite 112 → 128, Underline 64 → 80, PolychromeSprite 96 → 112). The vendored gpui_wgpu mirrors that layout; upstream’s Metal renderer (gpui_apple, macOS only, never built for Mandara) does not.

2. Hover styles without an id (elements/div.rs)

At this revision Interactivity::paint only notifies the view when an element’s .hover() state flips if the element has element state, that is an .id(): the listener compares the hit test against hover_state, which lives in the element state, and without it does nothing. The hover style itself is still resolved at paint from the hitbox, so an id-less hoverable element (every setting row in Mandara Studio, many shell list rows) showed its hover only when something else re-rendered its view: a scroll, a timer, the pointer entering the window. Moving the pointer from one row to the next left the highlight on the first one (theHamdiz/mandara#2). group_hover without an id was already covered (paint_hover_group_handler), and .on_hover listeners have their own path.

3. Image decoders for GPUI’s own formats only (Cargo.toml)

Upstream enables nearly every decoder of the image crate (Zed’s image viewer opens all of them). Image and ImageFormat here name PNG, JPEG, WebP, GIF, SVG, BMP, TIFF, ICO and PNM, and Mandara itself only loads PNG, JPEG, WebP, GIF, BMP and SVG (wallpapers, avatars, album art, icons, thumbnails), so the DDS, EXR, farbfeld, HDR, QOI and TGA decoders are dropped. img() still decodes every format ImageFormat names, so a clipboard or notification image in one of them keeps its preview; a file in a dropped format fails to load like any other unsupported file. Together with vendor/pure-onnx-ocr’s patch 1 (which stops the OCR engine turning on all of the image crate’s default formats) this takes exr, qoi, pulp, zune-inflate, lebe, bit_field and their helpers out of the build.

4. Fractional bounds for SDF curves (window.rs, window/subpixel.rs)

paint_quad aligns layout rectangles to device pixels. Applying that rule separately to every dot in the Power charge arc moves neighbouring centers by different amounts and leaves an uneven contour, especially at 1× scale.

Window::paint_subpixel_quad keeps those bounds at their fractional device coordinates. It shares insertion with paint_quad: opacity, content masks, stroke widths and border handling use the same code. Ordinary layout quads still use their existing pixel alignment. No primitive format or shader changes are needed; SDF quads already accept floating-point bounds.

The Power painter uses this method for its round marks. It keeps the same geometry, number of quads and colour interpolation.

5. Current atomic update API (profiler.rs, profiler/journal.rs)

The profiler’s three atomic updates use try_update, the current stable name for the deprecated fetch_update alias. Ordering and retry behavior are unchanged. This keeps standalone renderer and GPUI tests free of Rust 1.99 deprecation warnings without suppressing the lint.

6. Stop a frame when its window closes (window.rs)

A view can remove its window while drawing its final fade frame. The frame callback now returns after that draw, before updating or waking the removed window again. This avoids a spurious window not found error when leaving the screenshot overlay. The regression test closes a view during a real platform frame callback and checks that no further frame wake is sent.

Tests

elements::div::tests::hover_without_an_id_updates_its_cached_view (patch 2, TestAppContext): a cached child view with one id-less .hover(bg) element re-renders once on enter and once on leave, not on motion within it, and the painted frame carries the hover background exactly while hovered. It fails without the patch.

window::mandara_rounded_mask_tests (unit and TestAppContext):

window::subpixel::tests::fractional_sdf_bounds_keep_the_legacy_clip_and_opacity checks actual painted scene quads at 1×, 1.25×, 1.5× and 2×: precise bounds, unchanged legacy snapping, rounded clipping, opacity, radii and border widths.

The library suite passes 413 tests with two existing ignores. Like gpui_wgpu, some tests include_bytes! Zed’s assets/, so run them from a copy laid out like Zed’s tree (the vendored renderer’s [patch] points at ../gpui, so copy both):

mkdir -p /tmp/zt/crates && ln -s ~/.cargo/git/checkouts/zed-*/ec86047/assets /tmp/zt/assets
cp -r vendor/gpui vendor/gpui_wgpu /tmp/zt/crates/
CARGO_TARGET_DIR=/tmp/zt/target cargo test --manifest-path /tmp/zt/crates/gpui/Cargo.toml --features test-support --lib
CARGO_TARGET_DIR=/tmp/zt/target cargo test --manifest-path /tmp/zt/crates/gpui_wgpu/Cargo.toml --features test-support

7. Shared empty font features (text_system/font_features.rs)

font() and TextStyle::default() previously allocated a new Arc<Vec<_>> for every empty feature list. Their defaults now share one immutable list; deserializing an empty list uses it too. The stored type, hashing, equality and serialized values stay the same. In-tree readers never depend on unique Arc ownership. Code editing a returned list must use Arc::make_mut, which preserves the defaults and other fonts. Mandara also shares its immutable tabular-number feature list instead of rebuilding its vector and tag string.

Mandara’s framework_allocations tests exercise default construction with a live allocating control, empty and configured wire values, hash equality, and independent edits. Release frame and memory measurements remain a separate acceptance step; operation allocation counts are not whole-frame counts.

8. Releasing decoded images (app.rs, elements/img.rs, platform.rs)

Upstream can drop an image’s texture only through a &mut Window that is being updated, and remove_asset alone leaves the texture in every atlas. Views that release a page’s pictures when it closes (Studio) run inside a window update, so their own atlas kept every texture.

elements::img tests cover eviction of loaded and uncached images and the single moved frame.

9. Painting a clipping container’s own outline (window.rs)

A card that clips its children to rounded corners (patch 1) and draws its own cut or squircle outline as a child canvas had that outline clipped by its own rounded mask too, so the cut corners and squircles came out rounded.

Mandara’s components::surfaces tests cover the mask (a card flush with a rounded panel’s corner keeps that corner and loses only its own rounding).

10. FreeBSD uses the Linux platform’s layer shell and queue (gpui.rs, platform.rs, window.rs)

Upstream’s Linux platform (gpui_linux, chosen by gpui_platform for any(target_os = "linux", target_os = "freebsd")) also serves FreeBSD, but a few items it needs from this crate were gated on target_os = "linux" alone, so a FreeBSD build of gpui_linux failed: the priority queue its dispatcher uses, and the Wayland layer-shell types its Wayland client creates layer surfaces with. Each cfg now reads any(target_os = "linux", target_os = "freebsd"):

Linux is unchanged: every item keeps its Linux cfg. Nothing in gpui_linux or gpui_wgpu needed changing; both already treat FreeBSD like Linux (Wayland client, wl_touch, fonts, input, the wgpu renderer). The FreeBSD check in CI (cargo clippy --workspace --all-targets --target x86_64-unknown-freebsd) covers this patch.

11. Forgetting dropped views (app.rs, app/entity_map.rs)

Mandara builds its sidebar cards when the sidebar opens and drops them when it closes, so views come and go all session. Two things kept a trace of each dropped view:

Behaviour is otherwise unchanged: an empty entry and a missing one mean the same to every reader of the map. app::test::notifying_a_dropped_entity_leaves_no_trace covers both.

12. Giving back a large frame’s room (window.rs, arena.rs, taffy.rs, scene.rs, key_dispatch.rs, bounds_tree.rs)

A window keeps the capacity its largest frame needed: the element arena’s 1 MiB chunks, the layout tree’s node storage, the dispatch tree, the scene’s primitive lists and the frame’s listener and hitbox lists are cleared every frame but never shrink. That is right while a surface shows (no allocation per frame), but after Mandara’s sidebar closes the room its cards needed (a Sudoku board is thousands of elements) stayed for good, about 1.2 MiB for one card.

Window::shrink_frame_buffers(cx) frees that spare room once, at a lifecycle boundary the host chooses: both frames shrink to their contents (Frame::shrink_to_fit, with DispatchTree, Scene and BoundsTree helpers), the layout engine starts from a fresh tree (it is rebuilt at the start of every frame anyway) and the arena keeps only its first chunk (Arena::shrink, which does nothing while a draw holds it or anything is allocated in it). It does nothing during a draw. Frames are unchanged otherwise, and a surface that shows again grows the room back on its first frame.

Packaging changes

Cargo.toml spells out the workspace-inherited fields and dependencies with the exact specs from Zed’s root manifest at that revision (sibling Zed crates from the same git revision), declares its own empty [workspace] so it stays outside Mandara’s, sets publish = false, turns doc tests off and keeps only unexpected_cfgs = "allow" of Zed’s workspace lints. Vendored: src/, build.rs, README.md (the crate docs include it), LICENSE-APACHE and the one 378-byte JPEG a unit test embeds (examples/image/). Not vendored: examples, benches, docs, integration tests and the Windows resources (only read on Windows). The dev-dependencies only examples, doc tests and benches use (gpui_platform, reqwest_client, bench_metrics) are dropped; the rest are kept for the library tests.

Upstreaming notes

The change is behaviour-preserving for rectangular masks (identical clip bounds and output; the shaders skip the SDF when every radius is zero) and fixes a long-standing gap (the ContentMask doc comment always planned for “more complex shapes”). Upstreaming needs the Metal shaders and gpui_apple’s ContentMask literal updated the same way as gpui_wgpu’s.