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::*;
|
use super::*;
|
||||||
|
|
||||||
// TODO: replace with proper sprite
|
|
||||||
const LOCKPICK_SPRITE: &'static str = "sprites/items/lockpick.png";
|
const LOCKPICK_SPRITE: &'static str = "sprites/items/lockpick.png";
|
||||||
|
|
||||||
#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)]
|
#[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);
|
let image = asset_server.load(LOCKPICK_SPRITE);
|
||||||
(
|
(
|
||||||
Item::new(uvec2(1, 1), position),
|
Item::new(uvec2(1, 1), position),
|
||||||
ItemImage(image),
|
ItemData {
|
||||||
|
image,
|
||||||
|
name: "Lockpick".into(),
|
||||||
|
description: "Consumable item used to picking locked doors".into(),
|
||||||
|
},
|
||||||
Lockpick,
|
Lockpick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,17 @@ use bevy::prelude::*;
|
||||||
|
|
||||||
pub mod lockpick;
|
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)]
|
#[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)]
|
#[derive(Component, Clone, Debug, Reflect, PartialEq, Eq)]
|
||||||
#[reflect(Component, Clone, Debug, PartialEq)]
|
#[reflect(Component, Clone, Debug, PartialEq)]
|
||||||
#[require(ItemImage)]
|
#[require(ItemData)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub size: UVec2,
|
pub size: UVec2,
|
||||||
pub position: UVec2,
|
pub position: UVec2,
|
||||||
|
|
|
||||||
|
|
@ -153,5 +153,5 @@ pub fn setup_world(
|
||||||
uvec2(1, 1),
|
uvec2(1, 1),
|
||||||
)).with_child(lockpick_bundle(&asset_server, UVec2::ZERO));
|
)).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::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn ui_manager_bundle(children: Vec<Entity>, aligned_left: bool) -> impl Bundle {
|
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(),
|
GlobalZIndex::default(),
|
||||||
Children::spawn(SpawnWith(move |parent: &mut RelatedSpawner<ChildOf>| {
|
Children::spawn(SpawnWith(move |parent: &mut RelatedSpawner<ChildOf>| {
|
||||||
let scroll_area_id = parent.spawn((
|
let scroll_area_id = parent.spawn((
|
||||||
|
InventoryScrollArea,
|
||||||
Node {
|
Node {
|
||||||
width: percent(100.),
|
width: percent(100.),
|
||||||
height: percent(100.),
|
height: percent(100.),
|
||||||
|
|
@ -40,7 +43,14 @@ pub fn ui_manager_bundle(children: Vec<Entity>, aligned_left: bool) -> impl Bund
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
ScrollPosition(Vec2::ZERO),
|
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((
|
parent.spawn((
|
||||||
Node {
|
Node {
|
||||||
min_width: px(8),
|
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)]
|
#[require(Node)]
|
||||||
pub struct UiInventoryManager;
|
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)]
|
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy, Reflect)]
|
||||||
#[reflect(Component, Debug, PartialEq, Clone)]
|
#[reflect(Component, Debug, PartialEq, Clone)]
|
||||||
#[require(Node)]
|
#[require(Node)]
|
||||||
|
|
@ -38,6 +47,11 @@ pub struct UiItem(pub Entity);
|
||||||
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||||
pub struct HoveredItem;
|
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)]
|
#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)]
|
||||||
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||||
pub struct HoveredSlot;
|
pub struct HoveredSlot;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,28 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use bevy_input::mouse::MouseScrollUnit;
|
||||||
|
|
||||||
|
use crate::item::ItemData;
|
||||||
|
|
||||||
use super::*;
|
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(
|
pub fn on_slot_over(
|
||||||
e: On<Pointer<Over>>,
|
e: On<Pointer<Over>>,
|
||||||
mut commands: Commands,
|
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(
|
pub fn on_item_over(
|
||||||
e: On<Pointer<Over>>,
|
e: On<Pointer<Over>>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
query: Query<(), With<UiItem>>,
|
ui_item_query: Query<&UiItem>,
|
||||||
|
item_data_query: Query<&ItemData>,
|
||||||
has_hovered_item: Option<Single<(), With<HoveredItem>>>,
|
has_hovered_item: Option<Single<(), With<HoveredItem>>>,
|
||||||
) {
|
) {
|
||||||
if has_hovered_item.is_some() {
|
if has_hovered_item.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Ok(_) = query.get(e.event_target()) {
|
if let Ok(UiItem(item_id)) = ui_item_query.get(e.event_target()) {
|
||||||
commands.entity(e.event_target()).insert(HoveredItem);
|
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>)>,
|
query: Query<(), (With<UiItem>, With<HoveredItem>)>,
|
||||||
) {
|
) {
|
||||||
if let Ok(_) = query.get(e.event_target()) {
|
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,
|
inventory::ActiveInventory,
|
||||||
item::{
|
item::{
|
||||||
Item,
|
Item,
|
||||||
ItemImage,
|
ItemData,
|
||||||
},
|
},
|
||||||
player::Player,
|
player::Player,
|
||||||
ui::UiRoot,
|
ui::UiRoot,
|
||||||
|
|
@ -25,7 +25,7 @@ pub fn setup_ui_inventory(
|
||||||
player_query: Query<(), With<Player>>,
|
player_query: Query<(), With<Player>>,
|
||||||
active_inventory_query: Query<Entity, With<ActiveInventory>>,
|
active_inventory_query: Query<Entity, With<ActiveInventory>>,
|
||||||
item_query: Query<&Item>,
|
item_query: Query<&Item>,
|
||||||
item_image_query: Query<&ItemImage>,
|
item_image_query: Query<&ItemData>,
|
||||||
root_query: Query<Entity, With<UiRoot>>,
|
root_query: Query<Entity, With<UiRoot>>,
|
||||||
) {
|
) {
|
||||||
let Ok(root) = root_query.single() else {
|
let Ok(root) = root_query.single() else {
|
||||||
|
|
@ -57,12 +57,12 @@ pub fn setup_ui_inventory(
|
||||||
match item_query.get(item_entity) {
|
match item_query.get(item_entity) {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
let item_image = item_image_query.get(item_entity)
|
let item_image = item_image_query.get(item_entity)
|
||||||
.expect("ItemImage is required on Item");
|
.expect("ItemData is required on Item");
|
||||||
Some((item, item_entity, item_image))
|
Some((item, item_entity, &item_image.image))
|
||||||
},
|
},
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}).collect::<Vec<(&Item, Entity, &ItemImage)>>()
|
}).collect::<Vec<(&Item, Entity, &Handle<Image>)>>()
|
||||||
}
|
}
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
@ -76,7 +76,7 @@ pub fn setup_ui_inventory(
|
||||||
if let Some((item, entity, item_image)) = items.iter()
|
if let Some((item, entity, item_image)) = items.iter()
|
||||||
.find(|(i, _, _)| i.position == UVec2::new(x, y)) {
|
.find(|(i, _, _)| i.position == UVec2::new(x, y)) {
|
||||||
slot_commands.with_children(|commands| {
|
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_over)
|
||||||
.observe(on_item_out)
|
.observe(on_item_out)
|
||||||
.observe(on_item_drag_start)
|
.observe(on_item_drag_start)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue