Skip to main content

rdi_core/
overlay_plan.rs

1//! Animation-renderer-visible types.
2//!
3//! Part of the transparent-overlay animation architecture. These types
4//! describe the per-icon rendering plan the engine hands to the backend
5//! at the start of every animation, plus the outcome of the final Shell
6//! commit.
7//!
8//! Everything in this module is plain data (no callbacks, no COM types)
9//! so it is safe to build on any thread. Backends turn it into
10//! platform-specific overlay resources on the worker thread.
11//!
12//! # Field responsibilities
13//!
14//! The `IconRenderPlan` is a **shared build target** — the engine fills
15//! in the fields it knows about, and the backend enriches the ones it
16//! knows how to source from the platform. Specifically:
17//!
18//! | Field | Written by |
19//! | --- | --- |
20//! | `id` | Engine (from `IconAnimationSpec`) |
21//! | `source_position` | Engine (from `list_icons` snapshot) |
22//! | `final_position` | Engine (from `IconAnimationSpec::target`) |
23//! | `size_px` | Engine sets a fallback; backend refines |
24//! | `image` | **Backend** — `None` if not resolvable |
25//! | `label` | **Backend** — `None` if not resolvable |
26//! | `selected` | **Backend** |
27//! | `focused` | **Backend** |
28
29use crate::{IconId, Point};
30
31/// Per-icon rendering plan the engine hands to
32/// [`DesktopBackend::begin_overlay_session`](crate::DesktopBackend::begin_overlay_session).
33///
34/// See the module-level documentation for who fills which field. The
35/// engine builds the plan from an `IconAnimationSpec` + the current
36/// icon snapshot; the backend enriches it with platform data (icon
37/// bitmap, label text, selection state, refined size).
38#[derive(Clone, Debug, PartialEq)]
39pub struct IconRenderPlan {
40    /// Optional shader applied to all enabled artwork for this icon.
41    pub effect: Option<crate::Effect>,
42    /// Keep this non-participating desktop icon visible at its original position.
43    pub stationary: bool,
44    /// Stable icon identifier — matches
45    /// [`IconSnapshot::id`](crate::IconSnapshot::id).
46    pub id: IconId,
47    /// Where the icon is *right now*, in the same virtual-screen
48    /// coordinate space used by the rest of the API. The overlay
49    /// pre-renders its first frame at this position so the transition
50    /// from real icons → overlay icons is invisible.
51    pub source_position: Point,
52    /// Where the icon will be at the end of the animation. The backend
53    /// teleports the *real* Shell icon here on the first overlay
54    /// commit, before hiding the real icons.
55    pub final_position: Point,
56    /// Rendered icon size on the source monitor, in pixels. The engine
57    /// sets this to a placeholder (`(48, 48)` from
58    /// [`placeholder`](Self::placeholder)) and the backend may refine
59    /// it in-place using `IFolderView2::GetViewModeAndIconSize` +
60    /// per-monitor DPI.
61    pub size_px: (u32, u32),
62    /// Raw premultiplied BGRA icon bitmap. `None` when the backend
63    /// could not resolve one (very rare). The renderer falls back to
64    /// a solid placeholder in that case.
65    pub image: Option<IconBitmap>,
66    /// Label metadata for text rendering below the icon.
67    pub label: Option<IconLabel>,
68    /// Per-icon render offset in physical pixels applied at draw
69    /// time by the overlay renderer.
70    /// `IFolderView2::GetItemPosition` returns Explorer's bitmap
71    /// top-left in X but sits ~2 DIPs *above* the actual bitmap
72    /// top in Y, so overlay icons render 1–2 DIPs high without
73    /// this correction. Backend populates this from a
74    /// DPI-scaled empirical constant
75    /// (`BITMAP_TOP_INSET_DIP × icon_scale`); engine leaves it at
76    /// `(0, 0)` and renderers that do not need the shift may
77    /// ignore it.
78    pub render_offset_px: (i32, i32),
79    /// Optional shortcut-arrow overlay bitmap, kept separate from
80    /// [`Self::image`] so the renderer can anchor it to the actual
81    /// thumbnail's bottom-left corner — mirrored by the same
82    /// shadow-reserve offset the base bitmap uses so the arrow
83    /// lives in the drop-shadow gutter below-left of the thumbnail
84    /// (matches Explorer). For slot-filling icons (default
85    /// shortcuts to folders / exes / anything whose shell icon
86    /// fills the slot) the anchor coincides with the slot
87    /// bottom-left, so nothing moves for that case.
88    ///
89    /// Kept out of `image` because for image / video / PDF
90    /// thumbnails at ≥ 150 % DPI the shell returns a bitmap smaller
91    /// than the slot; an arrow blended in at extraction time
92    /// co-shifts with the (aspect-fit-centered) thumbnail and never
93    /// overhangs the thumbnail bottom the way Explorer's does.
94    ///
95    /// `None` for icons the shell reports no shortcut arrow for,
96    /// for callers who disabled `draw_shortcut_overlay`, or for
97    /// backends (stub / fake) that don't source shell overlays.
98    /// The admin-elevation shield is *not* split off — it is still
99    /// blended into [`Self::image`] at extraction time (bottom-
100    /// right, half-slot size) because its slot-relative anchor
101    /// happens to coincide with the base-bitmap anchor for every
102    /// icon that carries a shield today.
103    pub shortcut_arrow_image: Option<IconBitmap>,
104}
105
106impl IconRenderPlan {
107    /// Convenience constructor used by the engine before the backend
108    /// enriches the plan: no image, no label, hard-coded 48×48
109    /// placeholder size.
110    ///
111    /// `source_position` is where the icon lives *now*;
112    /// `final_position` is where the animation will land it. The
113    /// backend uses `final_position` to teleport the real Shell icon
114    /// to its destination on the first overlay commit (so unhiding at
115    /// the end of the animation is a no-op instead of a visible jump).
116    pub fn placeholder(id: IconId, source_position: Point, final_position: Point) -> Self {
117        Self {
118            effect: None,
119            stationary: false,
120            id,
121            source_position,
122            final_position,
123            size_px: (48, 48),
124            image: None,
125            label: None,
126            render_offset_px: (0, 0),
127            shortcut_arrow_image: None,
128        }
129    }
130}
131
132/// Premultiplied BGRA icon bitmap the renderer can upload directly to a
133/// GPU texture / Direct2D bitmap.
134///
135/// Bytes are `pixels[y * stride + x * 4] = [B, G, R, A]` with premultiplied
136/// alpha. `stride` is in bytes and must equal `width * 4` (no padding).
137#[derive(Clone, Debug, PartialEq)]
138pub struct IconBitmap {
139    pub width: u32,
140    pub height: u32,
141    /// Row stride in bytes (`= width * 4`).
142    pub stride: u32,
143    pub pixels: Vec<u8>,
144}
145
146/// Label metadata for an icon.
147#[derive(Clone, Debug, PartialEq)]
148pub struct IconLabel {
149    pub text: String,
150    pub bounds_px: (u32, u32),
151}
152
153/// Global feature toggles for the overlay renderer, so callers can
154/// request a minimal / stylised look without patching the backend.
155/// All fields default to `true` — draw everything Explorer does.
156///
157/// The engine passes this struct to
158/// [`DesktopBackend::begin_overlay_session`](crate::DesktopBackend::begin_overlay_session)
159/// exactly once per animation; the backend uses it while enriching
160/// [`IconRenderPlan`]s and building overlay resources. When a
161/// decoration is disabled, the backend also skips the associated
162/// Shell COM query (cheaper session start, no wasted work).
163///
164/// Consumers on the Rust side normally build this via
165/// `OverlayRenderOptions::default()` and flip individual fields;
166/// Python callers pass the same three booleans as keyword arguments
167/// on `AnimationOptions`.
168#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169pub struct OverlayRenderOptions {
170    /// Draw the icon title text (label) below each icon.
171    pub draw_labels: bool,
172    /// Composite the Shell overlay-slot badge (shortcut arrow,
173    /// sharing hand, sync-cloud, …) into each icon bitmap. Backed by
174    /// `SHGetFileInfoW(SHGFI_OVERLAYINDEX)` + `SHIL_JUMBO` extraction
175    /// — disabling this also skips the Shell query.
176    pub draw_shortcut_overlay: bool,
177    /// Composite the admin-elevation shield badge (`SIID_SHIELD`) into
178    /// each icon bitmap when the item's
179    /// `IExtractIconW::GetIconLocation(GIL_CHECKSHIELD)` reports
180    /// `GIL_SHIELD`. Disabling this skips both the shield probe and
181    /// the blend.
182    pub draw_shield_overlay: bool,
183}
184
185impl OverlayRenderOptions {
186    /// All decorations on — the default.
187    pub const fn all_enabled() -> Self {
188        Self {
189            draw_labels: true,
190            draw_shortcut_overlay: true,
191            draw_shield_overlay: true,
192        }
193    }
194
195    /// All decorations off — draw only the base icon bitmaps.
196    pub const fn minimal() -> Self {
197        Self {
198            draw_labels: false,
199            draw_shortcut_overlay: false,
200            draw_shield_overlay: false,
201        }
202    }
203}
204
205impl Default for OverlayRenderOptions {
206    fn default() -> Self {
207        Self::all_enabled()
208    }
209}
210
211/// Outcome of the final Shell commit issued by
212/// [`DesktopBackend::finalize_overlay_session`](crate::DesktopBackend::finalize_overlay_session).
213///
214/// Surfaced to callers through
215/// [`AnimationHandle::final_commit`](crate::AnimationHandle::final_commit).
216///
217/// * `moved_ids` — icons the Shell confirmed at their new position via
218///   `IFolderView2::GetItemPosition` polling. **This is a confirmation
219///   signal, not a census**: the Windows backend stops polling at the
220///   first icon that lands, so a successful commit of 200 icons yields
221///   a single id here. Treat non-empty as "the commit reached the
222///   Shell" and nothing more.
223/// * `missing_ids` — icons that were not resolvable by the backend
224///   (same semantics as
225///   [`DesktopBackend::set_positions`](crate::DesktopBackend::set_positions)).
226///   Unlike `moved_ids` this **is** complete: every id listed here was
227///   left wherever the Shell last had it.
228#[derive(Clone, Debug, Default, PartialEq)]
229pub struct FinalCommitOutcome {
230    pub moved_ids: Vec<IconId>,
231    pub missing_ids: Vec<IconId>,
232}
233
234/// Off-screen render output from
235/// [`DesktopBackend::render_overlay_snapshot`](crate::DesktopBackend::render_overlay_snapshot).
236///
237/// Pixel buffer is tightly-packed premultiplied BGRA — `stride =
238/// width * 4` (no row padding). Callers can hand it directly to any
239/// image library that accepts BGRA with a known stride (e.g. PIL's
240/// `Image.frombuffer("RGBA", (w, h), buf, "raw", "BGRA", 0, 1)`).
241///
242/// `geometry` is the authoritative per-icon paint answer, populated
243/// from D2D's per-icon `DrawBitmap` rect and DirectWrite's
244/// `IDWriteTextLayout::GetMetrics`. Callers doing pixel-level
245/// diagnostics should prefer these rects to any CV-based
246/// segmentation of `pixels`.
247/// Backends that can't compute geometry (e.g. the platform stub)
248/// return an empty `geometry` vector.
249#[derive(Clone, Debug, PartialEq, Eq)]
250pub struct SnapshotFrame {
251    pub width: u32,
252    pub height: u32,
253    /// Row-major premultiplied BGRA pixel data.
254    pub pixels: Vec<u8>,
255    /// One entry per icon that was actually drawn into the frame,
256    /// in the same order as the caller's `positions` slice. Skips
257    /// icons whose id was not present in the backend's cache.
258    pub geometry: Vec<SnapshotIconGeometry>,
259}
260
261/// Per-icon paint geometry captured during
262/// [`SnapshotFrame`] rendering.
263///
264/// Rects are expressed as `(x, y, width, height)` in overlay-canvas
265/// physical pixels (same coordinate space as the pixel buffer,
266/// origin at the top-left, y grows downward).
267///
268/// `icon_rect_px` is the rect passed to D2D `DrawBitmap` — the
269/// exact bounds the icon (or placeholder tile) was drawn into.
270/// Aspect-fit padding sits *outside* this rect, not inside it.
271///
272/// `label_rect_px` is derived from `IDWriteTextLayout::GetMetrics`
273/// after the layout was populated with the icon's display name,
274/// then translated by the label draw origin. It reflects post-wrap,
275/// post-trim glyph extents — the pixel-truthful answer to "where
276/// does the label actually land". `None` when the icon has no
277/// label (headless plans, empty display name).
278///
279/// `arrow_rect_px` is the D2D `DrawBitmap` rect used for the
280/// shortcut-arrow overlay. The arrow is anchored to the actual
281/// thumbnail's bottom-left, mirrored by `shadow_reserve_dip`. For
282/// slot-filling icons that coincides with the slot bottom-left.
283/// `None` when the icon has no shortcut arrow.
284#[derive(Clone, Debug, PartialEq, Eq)]
285pub struct SnapshotIconGeometry {
286    pub id: IconId,
287    pub icon_rect_px: (i32, i32, u32, u32),
288    pub label_rect_px: Option<(i32, i32, u32, u32)>,
289    pub arrow_rect_px: Option<(i32, i32, u32, u32)>,
290}