From 0c8259583a936696a446a0dfc0f265c7b4d0f78a Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Fri, 13 Mar 2026 13:42:53 +0300 Subject: [PATCH] feat: Updated dragged item behavior - Added Inventory.can_replace - Added tests for Inventory.can_replace - Dragged item is now a copy of original item - Dragged item can be rotated while dragged --- src/inventory/mod.rs | 17 +++++++ src/inventory/ui.rs | 116 ++++++++++++++++++++++++++++++++++--------- src/tests.rs | 46 ++++++++++++++++- 3 files changed, 155 insertions(+), 24 deletions(-) diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index c931720..20878e0 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -80,6 +80,23 @@ impl Inventory { self.can_fit(item_query, children.as_slice(), rotated_item.size, *position) } + pub fn can_replace( + &self, + item_query: Query<&item::Item>, + contained_items: &[Entity], + replacable_item: Entity, + queried_item: &item::Item, + ) -> bool { + let Some(position) = &queried_item.position else { + warn!("Trying to query rotated item without position"); + return false; + }; + let children = contained_items.iter() + .filter_map(|e| if e.ne(&&replacable_item) { Some(*e) } else { None }) + .collect::>(); + self.can_fit(item_query, children.as_slice(), queried_item.size, *position) + } + fn find_free_space_inner( &self, item_query: Query<&item::Item>, diff --git a/src/inventory/ui.rs b/src/inventory/ui.rs index 18e8f13..c662a5a 100644 --- a/src/inventory/ui.rs +++ b/src/inventory/ui.rs @@ -9,19 +9,22 @@ const TEMP_ITEM_PATH: &'static str = "sprites/items/choco_bar.png"; #[derive(Component, Reflect)] #[require(Node)] -pub struct UiInventory(Entity); +pub struct UiInventory(pub Entity); #[derive(Component, Reflect)] #[require(Node, ImageNode)] -pub struct UiInventorySlot(UVec2); +pub struct UiInventorySlot(pub UVec2); #[derive(Component, Reflect)] #[require(Node, ImageNode)] -pub struct UiItem(Entity); +pub struct UiItem(pub Entity); -#[derive(Component)] +#[derive(Component, Reflect)] pub struct HoveredItem; +#[derive(Component, Reflect)] +pub struct DraggedItem(pub Item); + fn ui_item_node_data(item: &Item) -> (Val, Val, Val, Val, UiTransform) { match item.rotated { true => ( @@ -41,6 +44,15 @@ fn ui_item_node_data(item: &Item) -> (Val, Val, Val, Val, UiTransform) { } } +fn update_ui_node(item: &Item, mut node: Mut<'_, Node>, mut ui_transform: Mut<'_, UiTransform>) { + let (left, top, min_width, min_height, new_ui_transform) = ui_item_node_data(item); + node.left = left; + node.top = top; + node.min_width = min_width; + node.min_height = min_height; + ui_transform.rotation = new_ui_transform.rotation; +} + fn on_slot_over(e: On>, mut query: Query<&mut ImageNode, With>) { if let Ok(mut image) = query.get_mut(e.event_target()) { image.color = Color::WHITE.darker(0.3); @@ -53,40 +65,72 @@ fn on_slot_out(e: On>, mut query: Query<&mut ImageNode, With>, mut commands: Commands, query: Query>) { +fn on_item_over( + e: On>, + mut commands: Commands, + query: Query<(), With>, + has_hovered_item: Option>>, +) { + if has_hovered_item.is_some() { + return; + } if let Ok(_) = query.get(e.event_target()) { commands.entity(e.event_target()).insert(HoveredItem); } } -fn on_item_out(e: On>, mut commands: Commands, query: Query>) { +fn on_item_out( + e: On>, + mut commands: Commands, + query: Query<(), (With, With)>, +) { if let Ok(_) = query.get(e.event_target()) { commands.entity(e.event_target()).remove::(); } } +fn on_item_drag_start( + e: On>, + mut commands: Commands, + ui_query: Query<&UiItem, With>, + item_query: Query<&Item>, +) { + if let Ok(ui_item) = ui_query.get(e.event_target()) { + let Ok(item) = item_query.get(ui_item.0) else { + error!("UiItem {} is pointing to non-existing Item", e.event_target()); + return; + }; + commands.entity(e.event_target()).insert(DraggedItem(item.clone())); + } +} + fn on_item_drag(e: On>, mut query: Query<&mut UiTransform, With>) { if let Ok(mut transform) = query.get_mut(e.event_target()) { transform.translation = Val2::px(e.distance.x, e.distance.y); } } -fn on_item_drag_end(e: On>, mut query: Query<&mut UiTransform, With>) { +fn on_item_drag_end( + e: On>, + mut commands: Commands, + mut query: Query<&mut UiTransform, With>, +) { if let Ok(mut transform) = query.get_mut(e.event_target()) { transform.translation = Val2::ZERO; + commands.entity(e.event_target()).remove::(); } } fn on_item_drag_drop( event: On>, mut commands: Commands, - ui_item_query: Query<(Entity, &UiItem)>, + mut ui_item_query: Query<(Entity, &UiItem, &mut DraggedItem, &mut Node, &mut UiTransform)>, ui_inventory_query: Query<&UiInventory>, mut item_query: Query<&mut Item>, slot_query: Query<(&ChildOf, &UiInventorySlot, Option<&Children>)>, inventory_query: Query<(&Inventory, Option<&Children>)>, ) { - let Ok((ui_item_entity, UiItem(item_entity))) = ui_item_query.get(event.dropped) else { + let Ok((ui_item_entity, UiItem(item_entity), mut dragged_item, node, ui_transform)) = ui_item_query.get_mut(event.dropped) else { return; }; let Ok((slot_parent, UiInventorySlot(new_position), slot_children)) = slot_query.get(event.event_target()) else { @@ -107,11 +151,29 @@ fn on_item_drag_drop( None => &[], }; + let temp_item = &mut dragged_item.0; + temp_item.position = Some(*new_position); + + if inventory.can_replace(item_query.as_readonly(), items, *item_entity, temp_item) { + info!("Replaced item"); + let mut item = item_query.get_mut(*item_entity).unwrap(); + item.position = temp_item.position; + item.size = temp_item.size; + item.rotated = temp_item.rotated; + commands.entity(ui_item_entity).insert(ChildOf(event.event_target())); + update_ui_node(item.as_ref(), node, ui_transform); + } else { + if let Ok(item) = item_query.get(*item_entity) { + update_ui_node(item, node, ui_transform); + } + } + /* if inventory.can_move(item_query.as_readonly(), items, *item_entity, *new_position) { let mut item = item_query.get_mut(*item_entity).unwrap(); item.position = Some(*new_position); commands.entity(ui_item_entity).insert(ChildOf(event.event_target())); } + */ } pub fn on_ui_rotate( @@ -119,9 +181,9 @@ pub fn on_ui_rotate( mut item_query: Query<&mut Item>, parent_query: Query<&ChildOf>, inventory_query: Query<(&Inventory, Option<&Children>)>, - ui_item_query: Query<(&UiItem, &mut UiTransform, &mut Node), With>, + ui_item_query: Query<(&UiItem, &mut UiTransform, &mut Node, Option<&mut DraggedItem>), With>, ) { - for (ui_item, mut ui_transform, mut node) in ui_item_query { + for (ui_item, ui_transform, node, maybe_dragged) in ui_item_query { let Ok(item_parent) = parent_query.get(ui_item.0) else { continue; }; @@ -133,18 +195,25 @@ pub fn on_ui_rotate( Some(children) => &children[..], None => &[], }; - - if inventory.can_rotate(item_query.as_readonly(), children, ui_item.0) { - let Ok(mut item) = item_query.get_mut(ui_item.0) else { - continue; - }; - item.rotate(); - let (left, top, min_width, min_height, new_ui_transform) = ui_item_node_data(&item); - node.left = left; - node.top = top; - node.min_width = min_width; - node.min_height = min_height; - ui_transform.rotation = new_ui_transform.rotation; + let (was_rotated, item) = match maybe_dragged { + Some(mut temp_item) => { + temp_item.0.rotate(); + (true, Some(temp_item.0.clone())) + }, + None => { + let result = inventory.can_rotate(item_query.as_readonly(), children, ui_item.0); + let mut out_item = None; + if result { + if let Ok(mut item) = item_query.get_mut(ui_item.0) { + item.rotate(); + out_item = Some(item.clone()); + } + } + (result, out_item) + }, + }; + if was_rotated { + update_ui_node(&item.unwrap(), node, ui_transform); } } } @@ -278,6 +347,7 @@ pub fn setup_ui_inventory( commands.spawn(ui_item_bundle(item, *entity, temp_item_image.clone())) .observe(on_item_over) .observe(on_item_out) + .observe(on_item_drag_start) .observe(on_item_drag) .observe(on_item_drag_end); }); diff --git a/src/tests.rs b/src/tests.rs index 5030b2f..ead9dc3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -78,7 +78,7 @@ mod input { mod inventory { use bevy::prelude::*; - use crate::inventory::{Inventory, item::Item}; + use crate::inventory::{Inventory, item::Item, ui::DraggedItem}; fn inventory() -> Inventory { Inventory::new(UVec2::splat(4)) @@ -192,6 +192,21 @@ mod inventory { } } + fn replace_item( + item_query: Query<&Item>, + replacable_item: Query<(Entity, &DraggedItem, &RotatingMarker)>, + inventory_query: Query<(&Inventory, &Children)>, + ) { + let (inventory, children) = inventory_query.single().unwrap(); + let (replacable_id, DraggedItem(replacing_item), RotatingMarker(assertion)) = replacable_item.single() + .unwrap(); + if *assertion { + assert!(inventory.can_replace(item_query, children, replacable_id, replacing_item)); + } else { + assert!(!inventory.can_replace(item_query, children, replacable_id, replacing_item)); + } + } + #[test] fn everything_fits() { let mut world = World::new(); @@ -346,4 +361,33 @@ mod inventory { world.run_system(system).expect("Error on running system"); } + + #[test] + fn can_replace_item() { + let mut world = World::new(); + + let system = world.register_system(replace_item); + + world.spawn(inventory()) + .with_child((item_a(), DraggedItem(item_c()), RotatingMarker(true))) + .with_child(item_b()) + .with_child(item_d()); + + world.run_system(system).expect("Error on running system"); + } + + #[test] + fn cannot_replace_item() { + let mut world = World::new(); + + let system = world.register_system(replace_item); + + world.spawn(inventory()) + .with_child((item_a(), DraggedItem(item_c()), RotatingMarker(false))) + .with_child(item_b()) + .with_child(item_c()) + .with_child(item_d()); + + world.run_system(system).expect("Error on running system"); + } }