diff --git a/assets/bevy_logo_dark.png b/assets/bevy_logo_dark.png
new file mode 100644
index 0000000..f144a3a
Binary files /dev/null and b/assets/bevy_logo_dark.png differ
diff --git a/assets/bevy_logo_light.png b/assets/bevy_logo_light.png
new file mode 100644
index 0000000..3e2cbdf
Binary files /dev/null and b/assets/bevy_logo_light.png differ
diff --git a/assets/icon.png b/assets/icon.png
index 3e2cbdf..8c9640b 100644
Binary files a/assets/icon.png and b/assets/icon.png differ
diff --git a/bevy_elements_core/src/lib.rs b/bevy_elements_core/src/lib.rs
index e3f8734..719fa02 100644
--- a/bevy_elements_core/src/lib.rs
+++ b/bevy_elements_core/src/lib.rs
@@ -167,8 +167,25 @@ impl Default for TextElementBundle {
}
}
-#[derive(Component, Default)]
-pub struct ManualTextProperties;
+#[derive(Bundle)]
+pub struct ImageElementBundle {
+ pub element: Element,
+ #[bundle]
+ pub image: ImageBundle,
+}
+
+impl Default for ImageElementBundle {
+ fn default() -> Self {
+ ImageElementBundle {
+ element: Element::inline(),
+ image: ImageBundle {
+ background_color: BackgroundColor(Color::WHITE),
+ ..Default::default()
+ },
+ }
+ }
+}
+
#[derive(Debug)]
pub enum ElementsError {
/// An unsupported selector was found on a style sheet rule.
diff --git a/bevy_elements_core/src/params.rs b/bevy_elements_core/src/params.rs
index aafdada..e710453 100644
--- a/bevy_elements_core/src/params.rs
+++ b/bevy_elements_core/src/params.rs
@@ -1,5 +1,5 @@
use std::{
- any::{Any, TypeId},
+ any::{type_name, Any, TypeId},
fmt::Debug,
mem,
};
@@ -31,6 +31,7 @@ pub enum Variant {
Params(Params),
BindFrom(BindFromUntyped),
BindTo(BindToUntyped),
+ Any(Box),
}
impl Debug for Variant {
@@ -45,6 +46,7 @@ impl Debug for Variant {
Variant::Elements(_) => write!(f, "Variant::Elements"),
Variant::BindFrom(_) => write!(f, "Variant::BindFrom"),
Variant::BindTo(_) => write!(f, "Variant::BindTo"),
+ Variant::Any(_) => write!(f, "Variant::Any"),
}
}
}
@@ -91,6 +93,7 @@ impl Variant {
Variant::Params(_) => TypeId::of::() == TypeId::of::(),
Variant::BindFrom(_) => TypeId::of::() == TypeId::of::(),
Variant::BindTo(_) => TypeId::of::() == TypeId::of::(),
+ Variant::Any(v) => v.is::(),
}
}
@@ -105,6 +108,7 @@ impl Variant {
Variant::Params(v) => try_cast::(v),
Variant::BindFrom(v) => try_cast::(v),
Variant::BindTo(v) => try_cast::(v),
+ Variant::Any(v) => v.downcast_ref::(),
}
}
pub fn get_mut(&mut self) -> Option<&mut T> {
@@ -118,6 +122,7 @@ impl Variant {
Variant::Params(v) => try_cast_mut::(v),
Variant::BindFrom(v) => try_cast_mut::(v),
Variant::BindTo(v) => try_cast_mut::(v),
+ Variant::Any(v) => v.downcast_mut::(),
}
}
@@ -132,6 +137,13 @@ impl Variant {
Variant::Params(v) => try_take::(v),
Variant::BindFrom(v) => try_take::(v),
Variant::BindTo(v) => try_take::(v),
+ Variant::Any(v) => match v.downcast::() {
+ Ok(v) => Some(*v),
+ Err(v) => {
+ error!("Can't cast {:?} to {}", v, type_name::());
+ None
+ }
+ },
}
}
@@ -409,6 +421,15 @@ impl From for Variant {
}
}
+impl TryFrom for String {
+ type Error = String;
+ fn try_from(variant: Variant) -> Result {
+ variant
+ .take::()
+ .ok_or("Can't cast variant to String".to_string())
+ }
+}
+
impl From<&str> for Variant {
fn from(v: &str) -> Self {
Variant::String(v.to_string())
@@ -456,8 +477,10 @@ macro_rules! bindattr {
match __attr {
Some($crate::Variant::BindFrom(__b)) => $ctx.commands().add(__b.to($crate::bind!(=> __elem, $($target)*))),
Some($crate::Variant::BindTo(__b)) => $ctx.commands().add(__b.from($crate::bind!(<= __elem, $($target)*))),
- Some($crate::Variant::$typ(__v)) => __value = Some(__v),
- Some(__attr) => error!("Unsupported value for '{}' param: {:?}", __key, __attr),
+ Some(__attr) => match $typ::try_from(__attr) {
+ Ok(__v) => __value = Some(__v),
+ Err(__err) => error!("Invalid value for '{}' param: {}", __key, __err)
+ },
_ => ()
};
__value
diff --git a/bevy_elements_core/src/property/impls.rs b/bevy_elements_core/src/property/impls.rs
index 640624a..c33d08c 100644
--- a/bevy_elements_core/src/property/impls.rs
+++ b/bevy_elements_core/src/property/impls.rs
@@ -253,7 +253,7 @@ mod style {
/// Impls for `bevy_text` [`Text`] component
mod text {
use super::*;
- use crate::{Defaults, ManualTextProperties};
+ use crate::Defaults;
#[derive(Default, Clone)]
pub enum FontPath {
@@ -272,7 +272,7 @@ mod text {
impl Property for FontColorProperty {
type Cache = Color;
type Components = &'static mut Text;
- type Filters = (With, Without);
+ type Filters = With;
fn name() -> Tag {
tag!("color")
@@ -313,7 +313,7 @@ mod text {
impl Property for FontProperty {
type Cache = FontPath;
type Components = &'static mut Text;
- type Filters = (With, Without);
+ type Filters = With;
fn name() -> Tag {
tag!("font")
@@ -385,7 +385,7 @@ mod text {
impl Property for FontSizeProperty {
type Cache = f32;
type Components = &'static mut Text;
- type Filters = (With, Without);
+ type Filters = With;
fn name() -> Tag {
tag!("font-size")
diff --git a/bevy_elements_widgets/src/img.rs b/bevy_elements_widgets/src/img.rs
new file mode 100644
index 0000000..9512014
--- /dev/null
+++ b/bevy_elements_widgets/src/img.rs
@@ -0,0 +1,244 @@
+use bevy::{prelude::*, utils::HashMap};
+use bevy_elements_core::*;
+use bevy_elements_macro::*;
+
+pub(crate) struct ImgPlugin;
+impl Plugin for ImgPlugin {
+ fn build(&self, app: &mut App) {
+ app.register_widget::
();
+
+ app.init_resource::();
+ app.add_system(load_img);
+ app.add_system(update_img_size);
+ app.add_system(update_img_layout);
+ }
+}
+
+#[derive(Resource, Deref, DerefMut, Default)]
+struct ImageRegistry(HashMap, Entity>);
+
+#[derive(Default, Clone, Copy, PartialEq, Debug)]
+pub enum ImgMode {
+ #[default]
+ Fit,
+ Cover,
+ Stretch,
+ Source,
+}
+
+impl TryFrom for ImgMode {
+ type Error = String;
+ fn try_from(value: Variant) -> Result {
+ match value {
+ Variant::String(s) if &s == "fit" => Ok(ImgMode::Fit),
+ Variant::String(s) if &s == "cover" => Ok(ImgMode::Cover),
+ Variant::String(s) if &s == "stretch" => Ok(ImgMode::Stretch),
+ Variant::String(s) if &s == "source" => Ok(ImgMode::Source),
+ Variant::String(s) => Err(format!("Can't parse `{}` as ImgMode", s)),
+ variant => {
+ if let Some(value) = variant.take::() {
+ Ok(value)
+ } else {
+ Err("Invalid value for ImgMode".to_string())
+ }
+ }
+ }
+ }
+}
+
+impl From for Variant {
+ fn from(mode: ImgMode) -> Self {
+ Variant::Any(Box::new(mode))
+ }
+}
+
+#[derive(Component, Widget)]
+#[alias(img)]
+/// The `
` tag is used to load image and show it content on the UI screen.
+/// The `
` tag has two properties:
+/// - `src`: Specifies the path to the image
+/// - `mode`: Specifies how an image should fits the space:
+/// - `fit`: resize the image to fit the box keeping it aspect ratio
+/// - `cover`: resize the image to cover the box keeping it aspect ratio
+/// - `stretch`: resize image to take all the space ignoring the aspect ratio
+/// - `source`: do not resize the image
+pub struct Img {
+ #[param]
+ pub src: String,
+ #[param]
+ pub mode: ImgMode,
+ entity: Entity,
+ size: Vec2,
+}
+
+impl WidgetBuilder for Img {
+ fn setup(&mut self, ctx: &mut ElementContext) {
+ ctx.commands().entity(self.entity).insert(ImageBundle {
+ style: Style {
+ display: Display::None,
+ ..default()
+ },
+ ..default()
+ });
+ ctx.insert(ElementBundle::default())
+ .push_children(&[self.entity]);
+ }
+}
+
+fn load_img(
+ asset_server: Res,
+ mut elements: Query<(Entity, &mut Img), Changed
>,
+ mut images: Query<(&mut UiImage, &mut Style)>,
+ mut registry: ResMut,
+ assets: Res>,
+ mut events: EventWriter>,
+) {
+ for (entity, mut img) in elements.iter_mut() {
+ let handle = asset_server.load(&img.src);
+ registry.insert(handle.clone_weak(), entity);
+ let (mut image, mut style) = images.get_mut(img.entity).unwrap();
+ image.0 = handle.clone();
+
+ // force inner image size recalculation if Image asset already loaded
+ if assets.contains(&handle) {
+ style.display = Display::Flex;
+ events.send(AssetEvent::Modified {
+ handle: handle.clone_weak(),
+ });
+ } else {
+ if img.size != Vec2::ZERO {
+ img.size = Vec2::ZERO;
+ }
+ style.display = Display::None;
+ }
+ }
+}
+
+fn update_img_size(
+ mut elements: Query<&mut Img>,
+ assets: Res>,
+ mut asset_events: EventReader>,
+ mut registry: ResMut,
+) {
+ for event in asset_events.iter() {
+ match event {
+ AssetEvent::Removed { handle } => {
+ let Some(entity) = registry.remove(&handle) else { continue };
+ let Ok(mut element) = elements.get_mut(entity) else { continue };
+ element.size = Vec2::ZERO;
+ }
+ AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
+ let Some(entity) = registry.get(&handle) else { continue };
+ let Ok(mut element) = elements.get_mut(*entity) else { continue };
+ let Some(asset) = assets.get(handle) else { continue };
+ if element.size != asset.size() {
+ element.size = asset.size();
+ }
+ }
+ }
+ }
+}
+
+fn update_img_layout(
+ elements: Query<(&Img, &Node), Or<(Changed
, Changed)>>,
+ mut styles: Query<&mut Style>,
+) {
+ for (element, node) in elements.iter() {
+ let Ok(mut style) = styles.get_mut(element.entity) else { continue };
+ if element.size.x.abs() < f32::EPSILON
+ || element.size.y.abs() < f32::EPSILON
+ || node.size().x.abs() < f32::EPSILON
+ || node.size().y.abs() < f32::EPSILON
+ {
+ style.display = Display::None;
+ continue;
+ } else {
+ style.display = Display::Flex;
+ }
+ let aspect = element.size.y / element.size.x;
+ match element.mode {
+ ImgMode::Fit => {
+ let (width, height) = if aspect > 1.0 {
+ let width = node.size().x;
+ let height = width * aspect;
+ if height > node.size().y {
+ let width = width * (node.size().y / height);
+ let height = node.size().y;
+ (width, height)
+ } else {
+ (width, height)
+ }
+ } else {
+ let height = node.size().y;
+ let width = height / aspect;
+ if width > node.size().x {
+ let height = height * (node.size().x / width);
+ let width = node.size().x;
+ (width, height)
+ } else {
+ (width, height)
+ }
+ };
+ style.min_size.height = Val::Px(height);
+ style.min_size.width = Val::Px(width);
+ style.size = style.min_size;
+ let hmargin = 0.5 * (node.size().x - width);
+ let vmargin = 0.5 * (node.size().y - height);
+
+ style.margin.top = Val::Px(vmargin.max(0.));
+ style.margin.bottom = Val::Px(vmargin.max(0.));
+ style.margin.left = Val::Px(hmargin.max(0.));
+ style.margin.right = Val::Px(hmargin.max(0.));
+ }
+ ImgMode::Cover => {
+ let (width, height) = if aspect > 1.0 {
+ let width = node.size().x;
+ let height = width * aspect;
+ if height < node.size().y {
+ let width = width * (node.size().y / height);
+ let height = node.size().y;
+ (width, height)
+ } else {
+ (width, height)
+ }
+ } else {
+ let height = node.size().y;
+ let width = height / aspect;
+ if width < node.size().x {
+ let height = height * (node.size().x / width);
+ let width = node.size().x;
+ (width, height)
+ } else {
+ (width, height)
+ }
+ };
+
+ style.min_size.height = Val::Px(height);
+ style.min_size.width = Val::Px(width);
+ style.size = style.min_size;
+ let hmargin = 0.5 * (node.size().x - width);
+ let vmargin = 0.5 * (node.size().y - height);
+
+ style.margin.top = Val::Px(vmargin.min(0.));
+ style.margin.bottom = Val::Px(vmargin.min(0.));
+ style.margin.left = Val::Px(hmargin.min(0.));
+ style.margin.right = Val::Px(hmargin.min(0.));
+ }
+ ImgMode::Stretch => {
+ style.min_size = Size::new(Val::Undefined, Val::Undefined);
+ style.size = Size::new(Val::Percent(100.), Val::Percent(100.));
+ style.margin = UiRect::all(Val::Px(0.));
+ }
+ ImgMode::Source => {
+ style.size = Size::new(Val::Px(element.size.x), Val::Px(element.size.y));
+ style.min_size = style.size;
+ let hmargin = 0.5 * (node.size().x - element.size.x);
+ let vmargin = 0.5 * (node.size().y - element.size.y);
+ style.margin.left = Val::Px(hmargin);
+ style.margin.right = Val::Px(hmargin);
+ style.margin.top = Val::Px(vmargin);
+ style.margin.bottom = Val::Px(vmargin);
+ }
+ }
+ }
+}
diff --git a/bevy_elements_widgets/src/lib.rs b/bevy_elements_widgets/src/lib.rs
index 361a9c9..3efe076 100644
--- a/bevy_elements_widgets/src/lib.rs
+++ b/bevy_elements_widgets/src/lib.rs
@@ -1,4 +1,5 @@
pub mod common;
+pub mod img;
pub mod input;
use bevy::prelude::Plugin;
@@ -7,6 +8,7 @@ pub struct WidgetsPlugin;
impl Plugin for WidgetsPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
+ app.add_plugin(img::ImgPlugin);
app.add_plugin(input::InputPlugins);
app.add_plugin(common::CommonsPlugin);
}
@@ -16,5 +18,7 @@ pub mod prelude {
#[doc(inline)]
pub use crate::common::*;
#[doc(inline)]
+ pub use crate::img::*;
+ #[doc(inline)]
pub use crate::input::*;
}
diff --git a/examples/hello_world.rs b/examples/hello_world.rs
index 6f48a3c..d185f81 100644
--- a/examples/hello_world.rs
+++ b/examples/hello_world.rs
@@ -16,4 +16,4 @@ fn setup(mut commands: Commands) {
"Hello, ""world""!"
+
+
+
+ "Mode:"
+
+
+
+
+
+
+
+ "Source:"
+
+
+
+
+