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}