Skip to main content

teksilo_scene/items/
image.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! [`ImageItem`] — a raster image at a local-coord rectangle.
5//!
6//! `ImageItem` renders a raster image registered in the Canvas image registry
7//! at a caller-specified rectangle in local item coordinates. The image
8//! reference is a string key into that registry, not a path — apps pre-load
9//! images and then name them here.
10//!
11//! ## When to use
12//!
13//! Use `ImageItem` when you need a static or swappable raster graphic in
14//! the lightweight tier (no arena overhead). For interactive images that need
15//! focus, drag-and-drop, or rich accessibility, embed a full `ImageWidget`
16//! as a heavyweight scene widget instead.
17//!
18//! ## Example
19//!
20//! ```ignore
21//! use teksilo_scene::{SceneModel, ImageItem};
22//! use teksilo_canvas::Rect;
23//! use teksilo_i18n::lit;
24//!
25//! let model = SceneModel::new();
26//! let item = ImageItem::new(Rect::new(0.0, 0.0, 64.0, 64.0), "avatar")
27//!     .label(lit!("User avatar"))
28//!     .draggable(true);
29//! model.add_item(item, teksilo_canvas::Point::new(100.0, 50.0));
30//! ```
31
32use accesskit::Role;
33use teksilo_canvas::{Canvas, Rect};
34use teksilo_core::accessibility::AccessNodeBuilder;
35
36use crate::flags::ItemFlags;
37use crate::item::{SceneItem, SceneItemA11yContext, SceneItemPaintContext};
38use crate::items::{AccessSubtreeMode, ItemA11yOverrides};
39use teksilo_i18n::LocalizedString;
40
41/// A raster image in a local-coord rectangle.
42///
43/// The image is referenced by a string key into the Canvas image registry.
44/// Place the item in the scene via `Scene::add_item`; the key must resolve
45/// to a registered image at paint time.
46#[derive(Debug)]
47pub struct ImageItem {
48    local_bounds: Rect,
49    name: String,
50    label: Option<String>,
51    flags: ItemFlags,
52    a11y: ItemA11yOverrides,
53}
54
55impl ImageItem {
56    /// An image item of the given size in local coordinates,
57    /// referencing the image registered under `name`. The `name` is
58    /// the Canvas-image-registry identifier — not a user-visible
59    /// string, so it is not localized.
60    pub fn new(local_bounds: Rect, name: impl Into<String>) -> Self {
61        Self {
62            local_bounds,
63            name: name.into(),
64            label: None,
65            flags: ItemFlags::default(),
66            a11y: ItemA11yOverrides::default(),
67        }
68    }
69
70    /// Human-readable label.
71    pub fn label(mut self, label: impl Into<LocalizedString>) -> Self {
72        let ls: LocalizedString = label.into();
73        self.label = Some(ls.resolve_now());
74        self
75    }
76
77    /// Opt the image into drag-to-move.
78    pub fn draggable(mut self, draggable: bool) -> Self {
79        self.flags.set(ItemFlags::IS_DRAGGABLE, draggable);
80        self
81    }
82
83    crate::items::item_a11y_builders!();
84}
85
86impl SceneItem for ImageItem {
87    fn local_bounds(&self) -> Rect {
88        self.local_bounds
89    }
90
91    fn set_local_bounds(&mut self, bounds: Rect) {
92        self.local_bounds = bounds;
93    }
94
95    fn paint(&self, canvas: &mut Canvas, _ctx: &SceneItemPaintContext<'_>) {
96        canvas.draw_image(self.local_bounds, self.name.clone());
97    }
98
99    fn label(&self) -> Option<String> {
100        self.label.clone()
101    }
102
103    fn initial_flags(&self) -> ItemFlags {
104        self.flags
105    }
106
107    fn access_subtree_mode(&self) -> AccessSubtreeMode {
108        self.a11y.subtree_mode()
109    }
110
111    fn accessibility(&self, builder: &mut AccessNodeBuilder, _ctx: &SceneItemA11yContext) {
112        builder.set_role(Role::Image);
113        if let Some(label) = self.label() {
114            builder.set_name(label);
115        }
116        self.a11y.apply(builder);
117    }
118}