use bevy::prelude::*;
use bevy::ui_widgets::observe;
use bevy::asset::LoadState;
use std::collections::HashMap;
use crate::{events::*, utils::*};
use super::*;
#[derive(Resource, Debug, Default)]
pub(crate) struct ImageHandleMap {
pub(crate) maps: HashMap<Entity, Handle<Image>>
}
#[derive(Component)]
pub struct MakaraImage;
#[derive(Component, Default)]
pub struct ImagePath(pub String);
#[derive(Component, Default)]
pub struct ImageLoadStateNeedsCheck(pub bool);
pub struct ImageWidget<'a, 'w, 's> {
pub entity: Entity,
pub class: &'a mut Class,
pub style: WidgetStyle<'a>,
pub image_node: &'a mut ImageNode,
pub image_path: &'a mut ImagePath,
pub need_check: &'a mut ImageLoadStateNeedsCheck,
pub asset_server: &'a AssetServer,
pub(crate) handle_maps: &'a mut ImageHandleMap,
pub commands: &'a mut Commands<'w, 's>
}
impl<'a, 'w, 's> ImageWidget<'a, 'w, 's> {
pub fn set_path(&mut self, new_path: String) {
let new_handle: Handle<Image> = self.asset_server.load(&new_path);
self.handle_maps.maps.insert(self.entity, new_handle.clone());
self.image_node.image = new_handle;
self.image_path.0 = new_path.to_string();
self.need_check.0 = true;
self.commands.trigger(Change {
entity: self.entity,
data: new_path.to_string()
});
self.commands.trigger(Loading {
entity: self.entity
});
}
}
type IsImageOnly = (
(
With<MakaraImage>,
Without<MakaraCheckbox>,
Without<MakaraCheckboxButton>,
Without<MakaraColumn>,
Without<MakaraRow>,
Without<MakaraRoot>,
Without<MakaraButton>,
Without<MakaraDropdown>,
Without<MakaraDropdownOverlay>,
Without<MakaraCircular>,
Without<MakaraLink>,
Without<MakaraModal>,
Without<MakaraModalBackdrop>,
),
(
Without<MakaraProgressBar>,
Without<MakaraRadio>,
Without<MakaraRadioGroup>,
Without<MakaraScroll>,
Without<MakaraScrollbar>,
Without<MakaraTextInput>,
Without<MakaraTextInputCursor>,
Without<MakaraSlider>,
Without<MakaraSliderThumb>,
Without<MakaraSelect>,
Without<MakaraSelectOverlay>,
)
);
#[derive(SystemParam)]
pub struct ImageQuery<'w, 's> {
pub image_related: Query<
'w, 's,
(
Entity,
&'static Id,
&'static mut Class,
&'static mut ImageNode,
&'static mut ImagePath,
&'static mut ImageLoadStateNeedsCheck
),
IsImageOnly
>,
pub style: StyleQuery<'w, 's, IsImageOnly>,
pub asset_server: Res<'w, AssetServer>,
pub(crate) handle_maps: ResMut<'w, ImageHandleMap>,
pub commands: Commands<'w, 's>
}
impl<'w, 's> WidgetQuery<'w, 's> for ImageQuery<'w, 's> {
type WidgetView<'a> = ImageWidget<'a, 'w, 's> where Self: 'a;
fn get_components<'a>(&'a mut self, entity: Entity) -> Option<Self::WidgetView<'a>> {
let ImageQuery { image_related, style, asset_server, handle_maps, commands } = self;
let (_, _, class, image_node, image_path, need_check) = image_related.get_mut(entity).ok()?;
let style_bundle = style.query.get_mut(entity).ok()?;
let (node, bg, border_color, shadow, z_index) = style_bundle;
return Some(ImageWidget {
entity,
class: class.into_inner(),
style: WidgetStyle {
node: node.into_inner(),
background_color: bg.into_inner(),
border_color: border_color.into_inner(),
shadow: shadow.into_inner(),
z_index: z_index.into_inner(),
},
need_check: need_check.into_inner(),
image_node: image_node.into_inner(),
image_path: image_path.into_inner(),
asset_server: asset_server,
handle_maps: handle_maps,
commands: commands
});
}
fn find_by_id<'a>(&'a mut self, target_id: &str) -> Option<Self::WidgetView<'a>> {
let entity = self.image_related.iter()
.find(|(_, id, _, _, _, _)| id.0 == target_id)
.map(|(e, _, _, _, _, _)| e)?;
self.get_components(entity)
}
fn find_by_entity<'a>(&'a mut self, target_entity: Entity) -> Option<Self::WidgetView<'a>> {
self.get_components(target_entity)
}
fn find_by_class(&self, target_class: &str) -> Vec<Entity> {
self.image_related.iter()
.filter(|(_, _, class, _, _, _)| class.0.split(" ").any(|word| word == target_class))
.map(|(e, _, _, _, _, _)| e)
.collect()
}
}
#[derive(Bundle)]
pub struct ImageBundle {
pub id_class: IdAndClass,
pub style: ContainerStyle,
pub image_node: ImageNode,
pub image_path: ImagePath,
pub tooltip_bundle: TooltipBundle
}
impl Default for ImageBundle {
fn default() -> Self {
let style = ContainerStyle {
node: Node {
width: auto(),
height: auto(),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
background_color: BackgroundColor(Color::NONE),
shadow: BoxShadow::default(),
..default()
};
let id_class = IdAndClass::default();
let image_path = ImagePath::default();
let tooltip_bundle = TooltipBundle::default();
Self { id_class, style, image_path, tooltip_bundle, image_node: ImageNode::default() }
}
}
impl Widget for ImageBundle {
fn build(mut self) -> impl Bundle {
process_built_in_spacing_class(&self.id_class.class, &mut self.style.node);
(
self.id_class,
self.style,
self.image_path,
self.image_node,
MakaraImage,
MakaraWidget,
ImageLoadStateNeedsCheck::default(),
observe(on_image_mouse_over),
observe(on_mouse_out),
observe(on_image_mouse_click)
)
}
}
impl SetContainerStyle for ImageBundle {
fn container_style(&mut self) -> &mut ContainerStyle {
&mut self.style
}
}
impl SetToolTip for ImageBundle {
fn set_tooltip(&mut self) -> &mut TooltipBundle {
&mut self.tooltip_bundle
}
}
impl SetIdAndClass for ImageBundle {
fn id_and_class(&mut self) -> &mut IdAndClass {
&mut self.id_class
}
}
pub fn image(path: &str) -> ImageBundle {
let mut bundle = ImageBundle::default();
bundle.image_path.0 = path.to_string();
bundle
}
fn on_image_mouse_over(
mut over: On<Pointer<Over>>,
mut commands: Commands,
mut tooltips: Query<
(&mut Node, &ComputedNode, &TooltipPosition, &UseTooltip),
With<MakaraTooltip>
>,
images: Query<
(&Children, &UiTransform, &ComputedNode),
With<MakaraImage>
>,
) {
if let Ok((children, transform, computed)) = images.get(over.entity) {
show_or_hide_tooltip(true, &mut tooltips, Some(computed), Some(transform), children);
}
commands.trigger(MouseOver {
entity: over.entity,
});
over.propagate(false);
}
fn on_image_mouse_click(
mut click: On<Pointer<Click>>,
mut commands: Commands
) {
commands.trigger(Clicked {
entity: click.entity
});
click.propagate(false);
}
pub(crate) fn detect_new_image_added(
mut images: Query<
(Entity, &mut ImageNode, &mut ImageLoadStateNeedsCheck, &ImagePath),
Added<MakaraImage>
>,
mut commands: Commands,
mut handle_maps: ResMut<ImageHandleMap>,
asset_server: Res<AssetServer>
) {
for (entity, mut image_node, mut need_check, image_path) in images.iter_mut() {
if image_path.0.is_empty() {
continue;
}
let handle: Handle<Image> = asset_server.load(&image_path.0);
handle_maps.maps.insert(entity, handle.clone());
image_node.image = handle;
commands.trigger(Loading { entity });
need_check.0 = true;
}
}
pub(crate) fn track_image_loading_state(
handle_maps: Res<ImageHandleMap>,
asset_server: Res<AssetServer>,
mut commands: Commands,
mut images: Query<(Entity, &mut ImageLoadStateNeedsCheck)>,
) {
for (entity, mut need_check) in images.iter_mut() {
if !need_check.0 {
continue;
}
if let Some(handle) = handle_maps.maps.get(&entity) {
match asset_server.load_state(handle) {
LoadState::Loaded => {
commands.trigger(Loaded { entity });
need_check.0 = false;
}
_ => {}
}
}
}
}
pub(crate) fn detect_image_built(
mut commands: Commands,
images: Query<Entity, Added<MakaraImage>>
) {
for entity in images.iter() {
commands.trigger(WidgetBuilt {
entity
});
}
}
pub(crate) fn can_run_image_systems(q: Query<&MakaraImage>) -> bool {
q.count() > 0
}