generated from 2ndbeam/bevy-template
feat: Inventory mouse scroll & visible item data
This commit is contained in:
parent
c5c6378303
commit
0e18bb6df5
7 changed files with 119 additions and 18 deletions
|
|
@ -2,7 +2,6 @@ use bevy::prelude::*;
|
|||
|
||||
use super::*;
|
||||
|
||||
// TODO: replace with proper sprite
|
||||
const LOCKPICK_SPRITE: &'static str = "sprites/items/lockpick.png";
|
||||
|
||||
#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)]
|
||||
|
|
@ -14,7 +13,11 @@ pub fn lockpick_bundle(asset_server: &Res<AssetServer>, position: UVec2) -> impl
|
|||
let image = asset_server.load(LOCKPICK_SPRITE);
|
||||
(
|
||||
Item::new(uvec2(1, 1), position),
|
||||
ItemImage(image),
|
||||
ItemData {
|
||||
image,
|
||||
name: "Lockpick".into(),
|
||||
description: "Consumable item used to picking locked doors".into(),
|
||||
},
|
||||
Lockpick,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@ use bevy::prelude::*;
|
|||
|
||||
pub mod lockpick;
|
||||
|
||||
#[derive(Component, Debug, Deref, DerefMut, PartialEq, Eq, Default, Clone, Reflect)]
|
||||
#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Reflect)]
|
||||
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||
pub struct ItemImage(pub Handle<Image>);
|
||||
pub struct ItemData {
|
||||
pub image: Handle<Image>,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
// TODO: get rid of Option in position, it's no longer needed
|
||||
#[derive(Component, Clone, Debug, Reflect, PartialEq, Eq)]
|
||||
#[reflect(Component, Clone, Debug, PartialEq)]
|
||||
#[require(ItemImage)]
|
||||
#[require(ItemData)]
|
||||
pub struct Item {
|
||||
pub size: UVec2,
|
||||
pub position: UVec2,
|
||||
|
|
|
|||
|
|
@ -153,5 +153,5 @@ pub fn setup_world(
|
|||
uvec2(1, 1),
|
||||
)).with_child(lockpick_bundle(&asset_server, UVec2::ZERO));
|
||||
|
||||
commands.spawn(player_bundle(&asset_server, vec2(meters(-6.), 0.)));
|
||||
commands.spawn(player_bundle(&asset_server, vec2(meters(-6.), meters(4.))));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use bevy::{
|
|||
},
|
||||
};
|
||||
|
||||
use crate::item::ItemData;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn ui_manager_bundle(children: Vec<Entity>, aligned_left: bool) -> impl Bundle {
|
||||
|
|
@ -29,6 +31,7 @@ pub fn ui_manager_bundle(children: Vec<Entity>, aligned_left: bool) -> impl Bund
|
|||
GlobalZIndex::default(),
|
||||
Children::spawn(SpawnWith(move |parent: &mut RelatedSpawner<ChildOf>| {
|
||||
let scroll_area_id = parent.spawn((
|
||||
InventoryScrollArea,
|
||||
Node {
|
||||
width: percent(100.),
|
||||
height: percent(100.),
|
||||
|
|
@ -40,7 +43,14 @@ pub fn ui_manager_bundle(children: Vec<Entity>, aligned_left: bool) -> impl Bund
|
|||
..default()
|
||||
},
|
||||
ScrollPosition(Vec2::ZERO),
|
||||
)).add_children(children.as_slice()).id();
|
||||
Pickable {
|
||||
is_hoverable: true,
|
||||
should_block_lower: false,
|
||||
}
|
||||
))
|
||||
.add_children(children.as_slice())
|
||||
.observe(observers::on_scroll)
|
||||
.id();
|
||||
parent.spawn((
|
||||
Node {
|
||||
min_width: px(8),
|
||||
|
|
@ -146,3 +156,46 @@ pub fn ui_item_bundle(item: &Item, item_entity: Entity, image: Handle<Image>) ->
|
|||
)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hovered_item_data_bundle(item_data: &ItemData) -> impl Bundle {
|
||||
(
|
||||
HoveredItemData,
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
min_width: percent(100.),
|
||||
top: percent(100.),
|
||||
align_self: AlignSelf::Start,
|
||||
justify_self: JustifySelf::Center,
|
||||
overflow: Overflow::visible(),
|
||||
border: UiRect::all(px(1.)),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(Color::hsla(0., 0., 0.3, 0.8)),
|
||||
BorderColor::all(Color::BLACK),
|
||||
Pickable::IGNORE,
|
||||
Name::new("HoveredItemData"),
|
||||
children![
|
||||
(
|
||||
Text::new(item_data.name.clone()),
|
||||
TextFont {
|
||||
font_size: 24.,
|
||||
..default()
|
||||
},
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
TextColor::WHITE,
|
||||
Pickable::IGNORE,
|
||||
),
|
||||
(
|
||||
Text::new(item_data.description.clone()),
|
||||
TextFont {
|
||||
font_size: 14.,
|
||||
..default()
|
||||
},
|
||||
TextLayout::new(Justify::Justified, LineBreak::WordBoundary),
|
||||
TextColor(Color::hsl(0., 0., 0.8)),
|
||||
Pickable::IGNORE,
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,15 @@ pub mod systems;
|
|||
#[require(Node)]
|
||||
pub struct UiInventoryManager;
|
||||
|
||||
#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||
#[require(Node)]
|
||||
pub struct InventoryScrollArea;
|
||||
|
||||
#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||
pub struct HoveredScrollArea;
|
||||
|
||||
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Debug, PartialEq, Clone)]
|
||||
#[require(Node)]
|
||||
|
|
@ -38,6 +47,11 @@ pub struct UiItem(pub Entity);
|
|||
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||
pub struct HoveredItem;
|
||||
|
||||
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Debug, PartialEq, Clone)]
|
||||
#[require(Node)]
|
||||
pub struct HoveredItemData;
|
||||
|
||||
#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||
pub struct HoveredSlot;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,28 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_input::mouse::MouseScrollUnit;
|
||||
|
||||
use crate::item::ItemData;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn on_scroll(
|
||||
e: On<Pointer<Scroll>>,
|
||||
mut q: Query<&mut ScrollPosition, With<InventoryScrollArea>>,
|
||||
) {
|
||||
let Ok(mut scroll_position) = q.get_mut(e.event_target()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let multiplier = match e.unit {
|
||||
MouseScrollUnit::Line => {
|
||||
MouseScrollUnit::SCROLL_UNIT_CONVERSION_FACTOR
|
||||
},
|
||||
MouseScrollUnit::Pixel => 1.,
|
||||
};
|
||||
|
||||
scroll_position.0.y -= e.y * multiplier;
|
||||
}
|
||||
|
||||
pub fn on_slot_over(
|
||||
e: On<Pointer<Over>>,
|
||||
mut commands: Commands,
|
||||
|
|
@ -29,14 +50,20 @@ pub fn on_slot_out(e: On<Pointer<Out>>, mut query: Query<&mut ImageNode, With<Ui
|
|||
pub fn on_item_over(
|
||||
e: On<Pointer<Over>>,
|
||||
mut commands: Commands,
|
||||
query: Query<(), With<UiItem>>,
|
||||
ui_item_query: Query<&UiItem>,
|
||||
item_data_query: Query<&ItemData>,
|
||||
has_hovered_item: Option<Single<(), With<HoveredItem>>>,
|
||||
) {
|
||||
if has_hovered_item.is_some() {
|
||||
return;
|
||||
}
|
||||
if let Ok(_) = query.get(e.event_target()) {
|
||||
commands.entity(e.event_target()).insert(HoveredItem);
|
||||
if let Ok(UiItem(item_id)) = ui_item_query.get(e.event_target()) {
|
||||
let Ok(item_data) = item_data_query.get(*item_id) else {
|
||||
error!("UiItem {} is pointing to non-existing Item", e.event_target());
|
||||
return;
|
||||
};
|
||||
commands.entity(e.event_target()).insert(HoveredItem)
|
||||
.with_child(bundles::hovered_item_data_bundle(item_data));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +73,8 @@ pub fn on_item_out(
|
|||
query: Query<(), (With<UiItem>, With<HoveredItem>)>,
|
||||
) {
|
||||
if let Ok(_) = query.get(e.event_target()) {
|
||||
commands.entity(e.event_target()).remove::<HoveredItem>();
|
||||
commands.entity(e.event_target()).remove::<HoveredItem>()
|
||||
.despawn_children();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
inventory::ActiveInventory,
|
||||
item::{
|
||||
Item,
|
||||
ItemImage,
|
||||
ItemData,
|
||||
},
|
||||
player::Player,
|
||||
ui::UiRoot,
|
||||
|
|
@ -25,7 +25,7 @@ pub fn setup_ui_inventory(
|
|||
player_query: Query<(), With<Player>>,
|
||||
active_inventory_query: Query<Entity, With<ActiveInventory>>,
|
||||
item_query: Query<&Item>,
|
||||
item_image_query: Query<&ItemImage>,
|
||||
item_image_query: Query<&ItemData>,
|
||||
root_query: Query<Entity, With<UiRoot>>,
|
||||
) {
|
||||
let Ok(root) = root_query.single() else {
|
||||
|
|
@ -57,12 +57,12 @@ pub fn setup_ui_inventory(
|
|||
match item_query.get(item_entity) {
|
||||
Ok(item) => {
|
||||
let item_image = item_image_query.get(item_entity)
|
||||
.expect("ItemImage is required on Item");
|
||||
Some((item, item_entity, item_image))
|
||||
.expect("ItemData is required on Item");
|
||||
Some((item, item_entity, &item_image.image))
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
}).collect::<Vec<(&Item, Entity, &ItemImage)>>()
|
||||
}).collect::<Vec<(&Item, Entity, &Handle<Image>)>>()
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
|
@ -76,7 +76,7 @@ pub fn setup_ui_inventory(
|
|||
if let Some((item, entity, item_image)) = items.iter()
|
||||
.find(|(i, _, _)| i.position == UVec2::new(x, y)) {
|
||||
slot_commands.with_children(|commands| {
|
||||
commands.spawn(ui_item_bundle(item, *entity, item_image.0.clone()))
|
||||
commands.spawn(ui_item_bundle(item, *entity, (*item_image).clone()))
|
||||
.observe(on_item_over)
|
||||
.observe(on_item_out)
|
||||
.observe(on_item_drag_start)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue