Skip to main content

fret_ui_kit/
image_metadata.rs

1use fret_core::ImageId;
2use fret_runtime::GlobalsHost;
3use std::collections::HashMap;
4
5/// Optional, policy-owned image metadata used by ecosystem components.
6///
7/// This is intentionally **not** a `UiServices` capability:
8/// - the mechanism layer (`fret-ui`) must remain backend-agnostic, and
9/// - ADR 0124 explicitly discourages implicit layout dependence on intrinsic image size.
10///
11/// Instead, apps/components that already know image dimensions (e.g. decoders, caches, streaming
12/// sources) can record them here to enable ergonomic recipes like "aspect-ratio wrappers".
13#[derive(Debug, Default, Clone)]
14pub struct ImageMetadataStore {
15    by_id: HashMap<ImageId, ImageMetadata>,
16}
17
18#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
19pub struct ImageMetadata {
20    pub intrinsic_size_px: Option<(u32, u32)>,
21}
22
23impl ImageMetadataStore {
24    pub fn set_intrinsic_size_px(&mut self, image: ImageId, size_px: (u32, u32)) {
25        self.by_id.entry(image).or_default().intrinsic_size_px = Some(size_px);
26    }
27
28    pub fn clear_intrinsic_size_px(&mut self, image: ImageId) {
29        if let Some(entry) = self.by_id.get_mut(&image) {
30            entry.intrinsic_size_px = None;
31            if *entry == ImageMetadata::default() {
32                self.by_id.remove(&image);
33            }
34        }
35    }
36
37    pub fn intrinsic_size_px(&self, image: ImageId) -> Option<(u32, u32)> {
38        self.by_id.get(&image)?.intrinsic_size_px
39    }
40
41    pub fn aspect_ratio(&self, image: ImageId) -> Option<f32> {
42        let (w, h) = self.intrinsic_size_px(image)?;
43        if w == 0 || h == 0 {
44            return None;
45        }
46        Some((w as f32) / (h as f32))
47    }
48}
49
50/// Mutably accesses the global [`ImageMetadataStore`], creating it if needed.
51///
52/// This is a convenience wrapper around [`GlobalsHost::with_global_mut`], intended for apps and
53/// policy layers that already know intrinsic image dimensions (e.g. decoders, caches, streaming
54/// sources).
55pub fn with_image_metadata_store_mut<H: GlobalsHost, R>(
56    host: &mut H,
57    f: impl FnOnce(&mut ImageMetadataStore) -> R,
58) -> R {
59    host.with_global_mut(ImageMetadataStore::default, |store, _host| f(store))
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn image_metadata_store_reports_aspect_ratio() {
68        let mut store = ImageMetadataStore::default();
69        let img = ImageId::default();
70        store.set_intrinsic_size_px(img, (1920, 1080));
71        assert_eq!(store.aspect_ratio(img), Some(1920.0 / 1080.0));
72    }
73
74    #[test]
75    fn image_metadata_store_ignores_zero_dimensions() {
76        let mut store = ImageMetadataStore::default();
77        let img = ImageId::default();
78        store.set_intrinsic_size_px(img, (0, 1080));
79        assert_eq!(store.aspect_ratio(img), None);
80        store.set_intrinsic_size_px(img, (1920, 0));
81        assert_eq!(store.aspect_ratio(img), None);
82    }
83}