1use crate::{ComputedUiRenderTargetInfo, ContentSize, Measure, MeasureArgs, Node, NodeMeasure};
2use bevy_asset::{AsAssetId, AssetId, Assets, Handle};
3use bevy_color::Color;
4use bevy_ecs::prelude::*;
5use bevy_image::{prelude::*, TRANSPARENT_IMAGE_HANDLE};
6use bevy_math::{Rect, UVec2, Vec2};
7use bevy_reflect::{std_traits::ReflectDefault, Reflect};
8use bevy_sprite::TextureSlicer;
9use taffy::{MaybeMath, MaybeResolve};
10
11#[derive(Component, Clone, Debug, Reflect)]
13#[reflect(Component, Default, Debug, Clone)]
14#[require(Node, ImageNodeSize, ContentSize)]
15pub struct ImageNode {
16 pub color: Color,
21 pub image: Handle<Image>,
25 pub texture_atlas: Option<TextureAtlas>,
27 pub flip_x: bool,
29 pub flip_y: bool,
31 pub rect: Option<Rect>,
37 pub image_mode: NodeImageMode,
39}
40
41impl Default for ImageNode {
42 fn default() -> Self {
50 ImageNode {
51 color: Color::WHITE,
54 texture_atlas: None,
55 image: TRANSPARENT_IMAGE_HANDLE,
57 flip_x: false,
58 flip_y: false,
59 rect: None,
60 image_mode: NodeImageMode::Auto,
61 }
62 }
63}
64
65impl ImageNode {
66 pub fn new(texture: Handle<Image>) -> Self {
68 Self {
69 image: texture,
70 color: Color::WHITE,
71 ..Default::default()
72 }
73 }
74
75 pub fn solid_color(color: Color) -> Self {
79 Self {
80 image: Handle::default(),
81 color,
82 flip_x: false,
83 flip_y: false,
84 texture_atlas: None,
85 rect: None,
86 image_mode: NodeImageMode::Auto,
87 }
88 }
89
90 pub fn from_atlas_image(image: Handle<Image>, atlas: TextureAtlas) -> Self {
92 Self {
93 image,
94 texture_atlas: Some(atlas),
95 ..Default::default()
96 }
97 }
98
99 #[must_use]
101 pub const fn with_color(mut self, color: Color) -> Self {
102 self.color = color;
103 self
104 }
105
106 #[must_use]
108 pub const fn with_flip_x(mut self) -> Self {
109 self.flip_x = true;
110 self
111 }
112
113 #[must_use]
115 pub const fn with_flip_y(mut self) -> Self {
116 self.flip_y = true;
117 self
118 }
119
120 #[must_use]
121 pub const fn with_rect(mut self, rect: Rect) -> Self {
122 self.rect = Some(rect);
123 self
124 }
125
126 #[must_use]
127 pub const fn with_mode(mut self, mode: NodeImageMode) -> Self {
128 self.image_mode = mode;
129 self
130 }
131}
132
133impl From<Handle<Image>> for ImageNode {
134 fn from(texture: Handle<Image>) -> Self {
135 Self::new(texture)
136 }
137}
138
139impl AsAssetId for ImageNode {
140 type Asset = Image;
141
142 fn as_asset_id(&self) -> AssetId<Self::Asset> {
143 self.image.id()
144 }
145}
146
147#[derive(Default, Debug, Clone, PartialEq, Reflect)]
149#[reflect(Clone, Default, PartialEq)]
150pub enum NodeImageMode {
151 #[default]
153 Auto,
154 Stretch,
156 Sliced(TextureSlicer),
158 Tiled {
160 tile_x: bool,
162 tile_y: bool,
164 stretch_value: f32,
167 },
168}
169
170impl NodeImageMode {
171 #[inline]
173 pub const fn uses_slices(&self) -> bool {
174 matches!(
175 self,
176 NodeImageMode::Sliced(..) | NodeImageMode::Tiled { .. }
177 )
178 }
179}
180
181#[derive(Component, Debug, Copy, Clone, Default, Reflect)]
185#[reflect(Component, Default, Debug, Clone)]
186pub struct ImageNodeSize {
187 size: UVec2,
191}
192
193impl ImageNodeSize {
194 #[inline]
196 pub const fn size(&self) -> UVec2 {
197 self.size
198 }
199}
200
201#[derive(Clone)]
202pub struct ImageMeasure {
204 pub size: Vec2,
206}
207
208fn resolve_calc(_calc_ptr: *const (), _parent_size: f32) -> f32 {
210 0.0
211}
212
213impl Measure for ImageMeasure {
214 fn measure(&mut self, measure_args: MeasureArgs, style: &taffy::Style) -> Vec2 {
215 let MeasureArgs {
216 width,
217 height,
218 available_width,
219 available_height,
220 ..
221 } = measure_args;
222
223 let parent_width = available_width.into_option();
225 let parent_height = available_height.into_option();
226
227 let s_aspect_ratio = style.aspect_ratio;
229 let s_width = style.size.width.maybe_resolve(parent_width, resolve_calc);
230 let s_min_width = style
231 .min_size
232 .width
233 .maybe_resolve(parent_width, resolve_calc);
234 let s_max_width = style
235 .max_size
236 .width
237 .maybe_resolve(parent_width, resolve_calc);
238 let s_height = style.size.height.maybe_resolve(parent_height, resolve_calc);
239 let s_min_height = style
240 .min_size
241 .height
242 .maybe_resolve(parent_height, resolve_calc);
243 let s_max_height = style
244 .max_size
245 .height
246 .maybe_resolve(parent_height, resolve_calc);
247
248 let width = width.or(s_width
251 .or(s_min_width)
252 .maybe_clamp(s_min_width, s_max_width));
253 let height = height.or(s_height
254 .or(s_min_height)
255 .maybe_clamp(s_min_height, s_max_height));
256
257 let aspect_ratio = s_aspect_ratio.unwrap_or_else(|| self.size.x / self.size.y);
259
260 let taffy_size = taffy::Size { width, height }.maybe_apply_aspect_ratio(Some(aspect_ratio));
263
264 Vec2 {
266 x: taffy_size
267 .width
268 .unwrap_or(self.size.x)
269 .maybe_clamp(s_min_width, s_max_width),
270 y: taffy_size
271 .height
272 .unwrap_or(self.size.y)
273 .maybe_clamp(s_min_height, s_max_height),
274 }
275 }
276}
277
278type UpdateImageFilter = (With<Node>, Without<crate::prelude::Text>);
279
280pub fn update_image_content_size_system(
282 textures: Res<Assets<Image>>,
283 atlases: Res<Assets<TextureAtlasLayout>>,
284 mut query: Query<
285 (
286 &mut ContentSize,
287 Ref<ImageNode>,
288 &mut ImageNodeSize,
289 Ref<ComputedUiRenderTargetInfo>,
290 ),
291 UpdateImageFilter,
292 >,
293) {
294 for (mut content_size, image, mut image_size, computed_target) in &mut query {
295 if !matches!(image.image_mode, NodeImageMode::Auto)
296 || image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
297 {
298 if image.is_changed() {
299 content_size.measure = None;
301 }
302 continue;
303 }
304
305 if let Some(size) =
306 image
307 .rect
308 .map(|rect| rect.size().as_uvec2())
309 .or_else(|| match &image.texture_atlas {
310 Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()),
311 None => textures.get(&image.image).map(Image::size),
312 })
313 {
314 if size != image_size.size || computed_target.is_changed() || content_size.is_added() {
316 image_size.size = size;
317 content_size.set(NodeMeasure::Image(ImageMeasure {
318 size: size.as_vec2() * computed_target.scale_factor(),
320 }));
321 }
322 }
323 }
324}