Skip to content

Commit

Permalink
[widgets] Implement <img>, closes #7
Browse files Browse the repository at this point in the history
  • Loading branch information
jkb0o committed Dec 8, 2022
1 parent bbd6d59 commit 9040fac
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 10 deletions.
Binary file added assets/bevy_logo_dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bevy_logo_light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 19 additions & 2 deletions bevy_elements_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
29 changes: 26 additions & 3 deletions bevy_elements_core/src/params.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::{
any::{Any, TypeId},
any::{type_name, Any, TypeId},
fmt::Debug,
mem,
};
Expand Down Expand Up @@ -31,6 +31,7 @@ pub enum Variant {
Params(Params),
BindFrom(BindFromUntyped),
BindTo(BindToUntyped),
Any(Box<dyn Any>),
}

impl Debug for Variant {
Expand All @@ -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"),
}
}
}
Expand Down Expand Up @@ -91,6 +93,7 @@ impl Variant {
Variant::Params(_) => TypeId::of::<T>() == TypeId::of::<Params>(),
Variant::BindFrom(_) => TypeId::of::<T>() == TypeId::of::<BindFromUntyped>(),
Variant::BindTo(_) => TypeId::of::<T>() == TypeId::of::<BindToUntyped>(),
Variant::Any(v) => v.is::<T>(),
}
}

Expand All @@ -105,6 +108,7 @@ impl Variant {
Variant::Params(v) => try_cast::<T, Params>(v),
Variant::BindFrom(v) => try_cast::<T, BindFromUntyped>(v),
Variant::BindTo(v) => try_cast::<T, BindToUntyped>(v),
Variant::Any(v) => v.downcast_ref::<T>(),
}
}
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
Expand All @@ -118,6 +122,7 @@ impl Variant {
Variant::Params(v) => try_cast_mut::<T, Params>(v),
Variant::BindFrom(v) => try_cast_mut::<T, BindFromUntyped>(v),
Variant::BindTo(v) => try_cast_mut::<T, BindToUntyped>(v),
Variant::Any(v) => v.downcast_mut::<T>(),
}
}

Expand All @@ -132,6 +137,13 @@ impl Variant {
Variant::Params(v) => try_take::<T, Params>(v),
Variant::BindFrom(v) => try_take::<T, BindFromUntyped>(v),
Variant::BindTo(v) => try_take::<T, BindToUntyped>(v),
Variant::Any(v) => match v.downcast::<T>() {
Ok(v) => Some(*v),
Err(v) => {
error!("Can't cast {:?} to {}", v, type_name::<T>());
None
}
},
}
}

Expand Down Expand Up @@ -409,6 +421,15 @@ impl From<String> for Variant {
}
}

impl TryFrom<Variant> for String {
type Error = String;
fn try_from(variant: Variant) -> Result<Self, Self::Error> {
variant
.take::<String>()
.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())
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions bevy_elements_core/src/property/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -272,7 +272,7 @@ mod text {
impl Property for FontColorProperty {
type Cache = Color;
type Components = &'static mut Text;
type Filters = (With<Node>, Without<ManualTextProperties>);
type Filters = With<Node>;

fn name() -> Tag {
tag!("color")
Expand Down Expand Up @@ -313,7 +313,7 @@ mod text {
impl Property for FontProperty {
type Cache = FontPath;
type Components = &'static mut Text;
type Filters = (With<Node>, Without<ManualTextProperties>);
type Filters = With<Node>;

fn name() -> Tag {
tag!("font")
Expand Down Expand Up @@ -385,7 +385,7 @@ mod text {
impl Property for FontSizeProperty {
type Cache = f32;
type Components = &'static mut Text;
type Filters = (With<Node>, Without<ManualTextProperties>);
type Filters = With<Node>;

fn name() -> Tag {
tag!("font-size")
Expand Down
244 changes: 244 additions & 0 deletions bevy_elements_widgets/src/img.rs
Original file line number Diff line number Diff line change
@@ -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::<Img>();

app.init_resource::<ImageRegistry>();
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<Handle<Image>, Entity>);

#[derive(Default, Clone, Copy, PartialEq, Debug)]
pub enum ImgMode {
#[default]
Fit,
Cover,
Stretch,
Source,
}

impl TryFrom<Variant> for ImgMode {
type Error = String;
fn try_from(value: Variant) -> Result<Self, Self::Error> {
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::<ImgMode>() {
Ok(value)
} else {
Err("Invalid value for ImgMode".to_string())
}
}
}
}
}

impl From<ImgMode> for Variant {
fn from(mode: ImgMode) -> Self {
Variant::Any(Box::new(mode))
}
}

#[derive(Component, Widget)]
#[alias(img)]
/// The `<img>` tag is used to load image and show it content on the UI screen.
/// The `<img>` 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<AssetServer>,
mut elements: Query<(Entity, &mut Img), Changed<Img>>,
mut images: Query<(&mut UiImage, &mut Style)>,
mut registry: ResMut<ImageRegistry>,
assets: Res<Assets<Image>>,
mut events: EventWriter<AssetEvent<Image>>,
) {
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<Assets<Image>>,
mut asset_events: EventReader<AssetEvent<Image>>,
mut registry: ResMut<ImageRegistry>,
) {
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<Img>, Changed<Node>)>>,
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);
}
}
}
}
Loading

0 comments on commit 9040fac

Please sign in to comment.