You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bevy_ui: Ability to display an image without scaling. #7349
What problem does this solve or what need does it fill?
Having images in UI that you want to be displayed exactly without any scaling. No resizing allowed.
For example, I want to create a toolbar button with an icon in it. The image assets for the button's background and the icons are intended to be fixed size and displayed as is, exactly that size.
What solution would you like?
Currently we have an ImageMode enum component, which seems to have been created for this exact purpose (controlling the behavior of image scaling). It only has one variant: KeepAspect. It seems like the sensible/appropriate place for this new addition. We could add a new variant: Exact, that preserves image dimensions exactly / allows no resizing.
While we are at it, we could also add a ImageMode::Stretch that allows arbitrary resizing without regard for aspect ratio. Then, the ImageMode feature set would feel "complete" and cover the most common use cases.
What alternative(s) have you considered?
I could use the Style properties to force the node to have exactly the size of the image, but this is boilerplatey and error-prone. It would be nice if the layout system could take care of it automatically.
I'd like to add that currently it seems impossible to display an image in UI that should not be scaled.
I have a use case where I want to display a minimap in UI. The minimap image is rendered by the game, so it can be generated at any size. I want to display it without any respect to UiScale or the window's scale factor, so it appears crisp.
At the moment you have to write your own image widget.
Something like this (for 0.11 but only needs trivial changes to work with main) :
use bevy::prelude::*;use bevy::render::Extract;use bevy::render::RenderApp;use bevy::ui::ContentSize;use bevy::ui::ExtractedUiNode;use bevy::ui::ExtractedUiNodes;use bevy::ui::FixedMeasure;use bevy::ui::FocusPolicy;use bevy::ui::RenderUiSystem;use bevy::ui::UiStack;use bevy::ui::UiSystem;use bevy::window::PrimaryWindow;/// A UI node that is an image#[derive(Bundle,Debug,Default)]pubstructExactImageBundle{/// Describes the logical size of the node////// This field is automatically managed by the UI layout system./// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.pubnode:Node,/// Styles which control the layout (size and position) of the node and it's children/// In some cases these styles also affect how the node drawn/painted.pubstyle:Style,/// The calculated size based on the given imagepubcalculated_size:ContentSize,/// The background color, which serves as a "fill" for this node////// Combines with `UiImage` to tint the provided image.pubbackground_color:BackgroundColor,/// The image of the nodepubimage:ExactImage,/// The size of the image in pixels////// This field is set automaticallypubimage_size:ExactImageSize,/// Whether this node should block interaction with lower nodespubfocus_policy:FocusPolicy,/// The transform of the node////// This field is automatically managed by the UI layout system./// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.pubtransform:Transform,/// The global transform of the node////// This field is automatically managed by the UI layout system./// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.pubglobal_transform:GlobalTransform,/// Describes the visibility properties of the nodepubvisibility:Visibility,/// Algorithmically-computed indication of whether an entity is visible and should be extracted for renderingpubcomputed_visibility:ComputedVisibility,/// Indicates the depth at which the node should appear in the UIpubz_index:ZIndex,}/// The 2D texture displayed for this UI node#[derive(Component,Clone,Debug,Reflect)]#[reflect(Component,Default)]pubstructExactImage{/// Handle to the texturepubtexture:Handle<Image>,/// Whether the image should be flipped along its x-axispubflip_x:bool,/// Whether the image should be flipped along its y-axispubflip_y:bool,/// Scaling to apply to image sizepubscale:Vec2,}implDefaultforExactImage{fndefault() -> Self{Self{texture:Default::default(),flip_x:Default::default(),flip_y:Default::default(),scale:Vec2::ONE,}}}implExactImage{pubfnnew(texture:Handle<Image>) -> Self{Self{
texture,
..Default::default()}}/// flip the image along its x-axis#[must_use]pubconstfnwith_flip_x(mutself) -> Self{self.flip_x = true;self}/// flip the image along its y-axis#[must_use]pubconstfnwith_flip_y(mutself) -> Self{self.flip_y = true;self}}implFrom<Handle<Image>>forExactImage{fnfrom(texture:Handle<Image>) -> Self{Self::new(texture)}}/// The size of the image's texture////// This component is updated automatically by [`update_image_content_size_system`]#[derive(Component,Debug,Copy,Clone,Default,Reflect)]#[reflect(Component,Default)]pubstructExactImageSize{/// The size of the image's texture////// This field is updated automatically by [`update_image_content_size_system`]pub(crate)size:Vec2,}implExactImageSize{/// The size of the image's texturepubfnsize(&self) -> Vec2{self.size}}/// Updates content size of the node based on the image providedpubfnupdate_exact_image_content_size_system(mutprevious_combined_scale_factor:Local<f64>,ui_scale:Res<UiScale>,windows:Query<&Window,With<PrimaryWindow>>,textures:Res<Assets<Image>>,mutquery:Query<(&mutContentSize,&ExactImage,&mutExactImageSize)>,){let combined_scale_factor = windows
.get_single().map(|window| window.resolution.scale_factor()).unwrap_or(1.)* ui_scale.scale;for(mut content_size, image,mut image_size)in&mut query {ifletSome(texture) = textures.get(&image.texture){let size = image.scale*Vec2::new(
texture.texture_descriptor.size.widthasf32,
texture.texture_descriptor.size.heightasf32,);if size != image_size.size{
image_size.size = size;
content_size.set(FixedMeasure{size: size * image.scale,});}}}*previous_combined_scale_factor = combined_scale_factor;}pubfnextract_exact_sized_image(mutextracted_uinodes:ResMut<ExtractedUiNodes>,images:Extract<Res<Assets<Image>>>,ui_stack:Extract<Res<UiStack>>,uinode_query:Extract<Query<(&Node,&GlobalTransform,&BackgroundColor,&ExactImage,&ExactImageSize,&ComputedVisibility,Option<&CalculatedClip>,),Without<UiTextureAtlasImage>,>,>,){for(stack_index, entity)in ui_stack.uinodes.iter().enumerate(){ifletOk((node, transform, color, image, image_size, visibility, clip)) =
uinode_query.get(*entity){// Skip invisible and completely transparent nodesif !visibility.is_visible() || color.0.a() == 0.0{continue;}if !images.contains(&image.texture){continue;}let transform = transform.compute_matrix()*Mat4::from_translation(0.5*(image_size.size - node.size()).extend(0.));
extracted_uinodes.uinodes.push(ExtractedUiNode{
stack_index,
transform,color: color.0,rect:Rect{min:Vec2::ZERO,max: image_size.size,},clip: clip.map(|clip| clip.clip),image: image.texture.clone(),atlas_size:None,flip_x: image.flip_x,flip_y: image.flip_y,});};}}pubstructExactImagePlugin;implPluginforExactImagePlugin{fnbuild(&self,app:&mut bevy::app::App){
app.add_systems(PostUpdate,
update_exact_image_content_size_system.before(UiSystem::Layout),);let render_app = match app.get_sub_app_mut(RenderApp){Ok(render_app) => render_app,Err(_) => return,};
render_app.add_systems(ExtractSchedule,(extract_exact_sized_image
.after(RenderUiSystem::ExtractNode).before(RenderUiSystem::ExtractAtlasNode),),);}}
There's some bug with the scale and scale_factor (like always), the images display correctly but the size of the containing UI node is off. If you don't care about fitting the image to the layout you don't need the widget system or ContentSize stuff and can just retrieve the size of the image from assets in the extraction system.
I implemented a more advanced image widget for Bevy a while ago but I haven't upstreamed it yet because the API was a bit too complicated and I couldn't decide about some of the behaviours or whether to support things like letterboxing etc (and I also have way too many open PRs already).
Another thing to be aware of with images in Bevy UI is that because of layout coordinate rounding sometimes the size of a node might be adjusted by a pixel to close gaps in the layout which will ruin a lot of pixel art unless you use a custom widget like the one above.
Activity
mockersf commentedon Jan 24, 2023
Not anymore...
#6674
inodentry commentedon Jan 25, 2023
#6674 makes sense given that it was useless in its old form. Only one enum variant is equivalent to a unit type, i.e a no-op.
I think this issue is worth keeping open, for how we could make it (or something like it) useful, so it can be worth having / bringing back. :)
[-]bevy_ui: ImageMode::Exact[/-][+]bevy_ui: Ability to display an image without scaling.[/+]inodentry commentedon Sep 25, 2023
I'd like to add that currently it seems impossible to display an image in UI that should not be scaled.
I have a use case where I want to display a minimap in UI. The minimap image is rendered by the game, so it can be generated at any size. I want to display it without any respect to UiScale or the window's scale factor, so it appears crisp.
inodentry commentedon Sep 25, 2023
Further, the fields of
UiImageSizeare private (ugh), meaning I can't even hack around it easily.ickshonpe commentedon Sep 26, 2023
At the moment you have to write your own image widget.
Something like this (for 0.11 but only needs trivial changes to work with main) :
There's some bug with the scale and scale_factor (like always), the images display correctly but the size of the containing UI node is off. If you don't care about fitting the image to the layout you don't need the widget system or
ContentSizestuff and can just retrieve the size of the image from assets in the extraction system.I implemented a more advanced image widget for Bevy a while ago but I haven't upstreamed it yet because the API was a bit too complicated and I couldn't decide about some of the behaviours or whether to support things like letterboxing etc (and I also have way too many open PRs already).
ickshonpe commentedon Sep 26, 2023
Another thing to be aware of with images in Bevy UI is that because of layout coordinate rounding sometimes the size of a node might be adjusted by a pixel to close gaps in the layout which will ruin a lot of pixel art unless you use a custom widget like the one above.
mghildiy commentedon Mar 29, 2024
May I give it a try?
musjj commentedon Apr 2, 2024
Wanted to implement a custom cursor using UI (because sprites are always drawn below UI), but got blocked by this.