mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Implement stacking and splitting
This commit is contained in:
parent
e114786fdb
commit
c0573cca44
@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Talk animation
|
- Talk animation
|
||||||
- New bosses in 5 lower dungeons
|
- New bosses in 5 lower dungeons
|
||||||
= New enemies in 5 lower dungeons
|
= New enemies in 5 lower dungeons
|
||||||
|
- Item stacking and splitting
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -668,6 +668,30 @@ impl Client {
|
|||||||
|
|
||||||
pub fn is_dead(&self) -> bool { self.current::<comp::Health>().map_or(false, |h| h.is_dead) }
|
pub fn is_dead(&self) -> bool { self.current::<comp::Health>().map_or(false, |h| h.is_dead) }
|
||||||
|
|
||||||
|
pub fn split_swap_slots(&mut self, a: comp::slot::Slot, b: comp::slot::Slot) {
|
||||||
|
match (a, b) {
|
||||||
|
(Slot::Equip(equip), slot) | (slot, Slot::Equip(equip)) => {
|
||||||
|
self.control_action(ControlAction::LoadoutManip(LoadoutManip::Swap(equip, slot)))
|
||||||
|
},
|
||||||
|
(Slot::Inventory(inv1), Slot::Inventory(inv2)) => {
|
||||||
|
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryManip(
|
||||||
|
InventoryManip::SplitSwap(inv1, inv2),
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_drop_slot(&mut self, slot: comp::slot::Slot) {
|
||||||
|
match slot {
|
||||||
|
Slot::Equip(equip) => {
|
||||||
|
self.control_action(ControlAction::LoadoutManip(LoadoutManip::Drop(equip)))
|
||||||
|
},
|
||||||
|
Slot::Inventory(inv) => self.send_msg(ClientGeneral::ControlEvent(
|
||||||
|
ControlEvent::InventoryManip(InventoryManip::SplitDrop(inv)),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pick_up(&mut self, entity: EcsEntity) {
|
pub fn pick_up(&mut self, entity: EcsEntity) {
|
||||||
// Get the health component from the entity
|
// Get the health component from the entity
|
||||||
|
|
||||||
|
@ -23,7 +23,9 @@ pub enum InventoryManip {
|
|||||||
Collect(Vec3<i32>),
|
Collect(Vec3<i32>),
|
||||||
Use(InvSlotId),
|
Use(InvSlotId),
|
||||||
Swap(InvSlotId, InvSlotId),
|
Swap(InvSlotId, InvSlotId),
|
||||||
|
SplitSwap(InvSlotId, InvSlotId),
|
||||||
Drop(InvSlotId),
|
Drop(InvSlotId),
|
||||||
|
SplitDrop(InvSlotId),
|
||||||
CraftRecipe(String),
|
CraftRecipe(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +42,9 @@ pub enum SlotManip {
|
|||||||
Collect(Vec3<i32>),
|
Collect(Vec3<i32>),
|
||||||
Use(Slot),
|
Use(Slot),
|
||||||
Swap(Slot, Slot),
|
Swap(Slot, Slot),
|
||||||
|
SplitSwap(Slot, Slot),
|
||||||
Drop(Slot),
|
Drop(Slot),
|
||||||
|
SplitDrop(Slot),
|
||||||
CraftRecipe(String),
|
CraftRecipe(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +67,11 @@ impl From<InventoryManip> for SlotManip {
|
|||||||
InventoryManip::Swap(inv1, inv2) => {
|
InventoryManip::Swap(inv1, inv2) => {
|
||||||
Self::Swap(Slot::Inventory(inv1), Slot::Inventory(inv2))
|
Self::Swap(Slot::Inventory(inv1), Slot::Inventory(inv2))
|
||||||
},
|
},
|
||||||
|
InventoryManip::SplitSwap(inv1, inv2) => {
|
||||||
|
Self::SplitSwap(Slot::Inventory(inv1), Slot::Inventory(inv2))
|
||||||
|
},
|
||||||
InventoryManip::Drop(inv) => Self::Drop(Slot::Inventory(inv)),
|
InventoryManip::Drop(inv) => Self::Drop(Slot::Inventory(inv)),
|
||||||
|
InventoryManip::SplitDrop(inv) => Self::SplitDrop(Slot::Inventory(inv)),
|
||||||
InventoryManip::CraftRecipe(recipe) => Self::CraftRecipe(recipe),
|
InventoryManip::CraftRecipe(recipe) => Self::CraftRecipe(recipe),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,32 @@ impl Inventory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merge the stack of items at src into the stack at dst if the items are
|
||||||
|
/// compatible and stackable, and return whether anything was changed
|
||||||
|
pub fn merge_stack_into(&mut self, src: InvSlotId, dst: InvSlotId) -> bool {
|
||||||
|
let mut amount = None;
|
||||||
|
if let (Some(srcitem), Some(dstitem)) = (self.get(src), self.get(dst)) {
|
||||||
|
// The equality check ensures the items have the same definition, to avoid e.g.
|
||||||
|
// transmuting coins to diamonds, and the stackable check avoids creating a
|
||||||
|
// stack of swords
|
||||||
|
if srcitem == dstitem && srcitem.is_stackable() {
|
||||||
|
amount = Some(srcitem.amount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(amount) = amount {
|
||||||
|
self.remove(src);
|
||||||
|
let dstitem = self
|
||||||
|
.get_mut(dst)
|
||||||
|
.expect("self.get(dst) was Some right above this");
|
||||||
|
dstitem
|
||||||
|
.increase_amount(amount)
|
||||||
|
.expect("already checked is_stackable");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if inserting item exists in given cell. Inserts an item if it
|
/// Checks if inserting item exists in given cell. Inserts an item if it
|
||||||
/// exists.
|
/// exists.
|
||||||
pub fn insert_or_stack_at(
|
pub fn insert_or_stack_at(
|
||||||
@ -215,6 +241,11 @@ impl Inventory {
|
|||||||
self.slot(inv_slot_id).and_then(Option::as_ref)
|
self.slot(inv_slot_id).and_then(Option::as_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutably get content of a slot
|
||||||
|
fn get_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut Item> {
|
||||||
|
self.slot_mut(inv_slot_id).and_then(Option::as_mut)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a reference to the item (if any) equipped in the given EquipSlot
|
/// Returns a reference to the item (if any) equipped in the given EquipSlot
|
||||||
pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
|
pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
|
||||||
self.loadout.equipped(equip_slot)
|
self.loadout.equipped(equip_slot)
|
||||||
@ -275,6 +306,29 @@ impl Inventory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes half of the items from a slot in the inventory
|
||||||
|
pub fn take_half(
|
||||||
|
&mut self,
|
||||||
|
inv_slot_id: InvSlotId,
|
||||||
|
msm: &MaterialStatManifest,
|
||||||
|
) -> Option<Item> {
|
||||||
|
if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
|
||||||
|
if item.is_stackable() && item.amount() > 1 {
|
||||||
|
let mut return_item = item.duplicate(msm);
|
||||||
|
let returning_amount = item.amount() / 2;
|
||||||
|
item.decrease_amount(returning_amount).ok()?;
|
||||||
|
return_item
|
||||||
|
.set_amount(returning_amount)
|
||||||
|
.expect("Items duplicated from a stackable item must be stackable.");
|
||||||
|
Some(return_item)
|
||||||
|
} else {
|
||||||
|
self.remove(inv_slot_id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Takes all items from the inventory
|
/// Takes all items from the inventory
|
||||||
pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
|
pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
|
||||||
self.slots_mut()
|
self.slots_mut()
|
||||||
|
@ -5,7 +5,8 @@ use vek::{Rgb, Vec3};
|
|||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self, item,
|
self,
|
||||||
|
item::{self, MaterialStatManifest},
|
||||||
slot::{self, Slot},
|
slot::{self, Slot},
|
||||||
},
|
},
|
||||||
consts::MAX_PICKUP_RANGE,
|
consts::MAX_PICKUP_RANGE,
|
||||||
@ -429,15 +430,26 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Slo
|
|||||||
if let Some(pos) = ecs.read_storage::<comp::Pos>().get(entity) {
|
if let Some(pos) = ecs.read_storage::<comp::Pos>().get(entity) {
|
||||||
if let Some(mut inventory) = ecs.write_storage::<comp::Inventory>().get_mut(entity)
|
if let Some(mut inventory) = ecs.write_storage::<comp::Inventory>().get_mut(entity)
|
||||||
{
|
{
|
||||||
dropped_items.extend(inventory.swap(a, b).into_iter().map(|x| {
|
let mut merged_stacks = false;
|
||||||
(
|
|
||||||
*pos,
|
// If both slots have items and we're attemping to drag from one stack
|
||||||
state
|
// into another, stack the items.
|
||||||
.read_component_copied::<comp::Ori>(entity)
|
if let (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) = (a, b) {
|
||||||
.unwrap_or_default(),
|
merged_stacks |= inventory.merge_stack_into(slot_a, slot_b);
|
||||||
x,
|
}
|
||||||
)
|
|
||||||
}));
|
// If the stacks weren't mergable carry out a swap.
|
||||||
|
if !merged_stacks {
|
||||||
|
dropped_items.extend(inventory.swap(a, b).into_iter().map(|x| {
|
||||||
|
(
|
||||||
|
*pos,
|
||||||
|
state
|
||||||
|
.read_component_copied::<comp::Ori>(entity)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
x,
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,6 +459,55 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Slo
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
comp::SlotManip::SplitSwap(slot, target) => {
|
||||||
|
let msm = state.ecs().read_resource::<MaterialStatManifest>();
|
||||||
|
let mut inventories = state.ecs().write_storage::<comp::Inventory>();
|
||||||
|
let mut inventory = if let Some(inventory) = inventories.get_mut(entity) {
|
||||||
|
inventory
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
?entity,
|
||||||
|
"Can't manipulate inventory, entity doesn't have one"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If both slots have items and we're attemping to split from one stack
|
||||||
|
// into another, ensure that they are the same type of item. If they are
|
||||||
|
// the same type do nothing, as you don't want to overwrite the existing item.
|
||||||
|
|
||||||
|
if let (Slot::Inventory(source_inv_slot_id), Slot::Inventory(target_inv_slot_id)) =
|
||||||
|
(slot, target)
|
||||||
|
{
|
||||||
|
if let Some(source_item) = inventory.get(source_inv_slot_id) {
|
||||||
|
if let Some(target_item) = inventory.get(target_inv_slot_id) {
|
||||||
|
if source_item != target_item {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = match slot {
|
||||||
|
Slot::Inventory(slot) => inventory.take_half(slot, &msm),
|
||||||
|
Slot::Equip(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(item) = item {
|
||||||
|
if let Slot::Inventory(target) = target {
|
||||||
|
inventory.insert_or_stack_at(target, item).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(inventory);
|
||||||
|
drop(inventories);
|
||||||
|
drop(msm);
|
||||||
|
|
||||||
|
state.write_component(
|
||||||
|
entity,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Swapped),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
comp::SlotManip::Drop(slot) => {
|
comp::SlotManip::Drop(slot) => {
|
||||||
let item = match slot {
|
let item = match slot {
|
||||||
Slot::Inventory(slot) => state
|
Slot::Inventory(slot) => state
|
||||||
@ -480,6 +541,37 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Slo
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
comp::SlotManip::SplitDrop(slot) => {
|
||||||
|
let msm = state.ecs().read_resource::<MaterialStatManifest>();
|
||||||
|
let item = match slot {
|
||||||
|
Slot::Inventory(slot) => state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Inventory>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|mut inv| inv.take_half(slot, &msm)),
|
||||||
|
Slot::Equip(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: We should really require the drop and write to be atomic!
|
||||||
|
if let (Some(mut item), Some(pos)) =
|
||||||
|
(item, state.ecs().read_storage::<comp::Pos>().get(entity))
|
||||||
|
{
|
||||||
|
item.put_in_world();
|
||||||
|
dropped_items.push((
|
||||||
|
*pos,
|
||||||
|
state
|
||||||
|
.read_component_copied::<comp::Ori>(entity)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
item,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
drop(msm);
|
||||||
|
state.write_component(
|
||||||
|
entity,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Dropped),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
comp::SlotManip::CraftRecipe(recipe) => {
|
comp::SlotManip::CraftRecipe(recipe) => {
|
||||||
if let Some(mut inv) = state
|
if let Some(mut inv) = state
|
||||||
.ecs()
|
.ecs()
|
||||||
|
@ -391,7 +391,13 @@ pub enum Event {
|
|||||||
slot_b: comp::slot::Slot,
|
slot_b: comp::slot::Slot,
|
||||||
bypass_dialog: bool,
|
bypass_dialog: bool,
|
||||||
},
|
},
|
||||||
|
SplitSwapSlots {
|
||||||
|
slot_a: comp::slot::Slot,
|
||||||
|
slot_b: comp::slot::Slot,
|
||||||
|
bypass_dialog: bool,
|
||||||
|
},
|
||||||
DropSlot(comp::slot::Slot),
|
DropSlot(comp::slot::Slot),
|
||||||
|
SplitDropSlot(comp::slot::Slot),
|
||||||
ChangeHotbarState(Box<HotbarState>),
|
ChangeHotbarState(Box<HotbarState>),
|
||||||
TradeAction(TradeAction),
|
TradeAction(TradeAction),
|
||||||
Ability3(bool),
|
Ability3(bool),
|
||||||
@ -769,7 +775,15 @@ impl Hud {
|
|||||||
let hotbar_state =
|
let hotbar_state =
|
||||||
HotbarState::new(global_state.profile.get_hotbar_slots(server, character_id));
|
HotbarState::new(global_state.profile.get_hotbar_slots(server, character_id));
|
||||||
|
|
||||||
let slot_manager = slots::SlotManager::new(ui.id_generator(), Vec2::broadcast(40.0));
|
let slot_manager = slots::SlotManager::new(
|
||||||
|
ui.id_generator(),
|
||||||
|
Vec2::broadcast(40.0)
|
||||||
|
// TODO(heyzoos) Will be useful for whoever works on rendering the number of items "in hand".
|
||||||
|
// fonts.cyri.conrod_id,
|
||||||
|
// Vec2::new(1.0, 1.0),
|
||||||
|
// fonts.cyri.scale(12),
|
||||||
|
// TEXT_COLOR,
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ui,
|
ui,
|
||||||
@ -2788,6 +2802,31 @@ impl Hud {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
slot::Event::SplitDropped(from) => {
|
||||||
|
// Drop item
|
||||||
|
if let Some(from) = to_slot(from) {
|
||||||
|
events.push(Event::SplitDropSlot(from));
|
||||||
|
} else if let Hotbar(h) = from {
|
||||||
|
self.hotbar.clear_slot(h);
|
||||||
|
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
slot::Event::SplitDragged(a, b) => {
|
||||||
|
// Swap between slots
|
||||||
|
if let (Some(a), Some(b)) = (to_slot(a), to_slot(b)) {
|
||||||
|
events.push(Event::SplitSwapSlots {
|
||||||
|
slot_a: a,
|
||||||
|
slot_b: b,
|
||||||
|
bypass_dialog: false,
|
||||||
|
});
|
||||||
|
} else if let (Inventory(i), Hotbar(h)) = (a, b) {
|
||||||
|
self.hotbar.add_inventory_link(h, i.slot);
|
||||||
|
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
||||||
|
} else if let (Hotbar(a), Hotbar(b)) = (a, b) {
|
||||||
|
self.hotbar.swap(a, b);
|
||||||
|
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
|
||||||
|
}
|
||||||
|
},
|
||||||
slot::Event::Used(from) => {
|
slot::Event::Used(from) => {
|
||||||
// Item used (selected and then clicked again)
|
// Item used (selected and then clicked again)
|
||||||
if let Some(from) = to_slot(from) {
|
if let Some(from) = to_slot(from) {
|
||||||
|
@ -1136,6 +1136,57 @@ impl PlayState for SessionState {
|
|||||||
self.client.borrow_mut().swap_slots(slot_a, slot_b);
|
self.client.borrow_mut().swap_slots(slot_a, slot_b);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
HudEvent::SplitSwapSlots {
|
||||||
|
slot_a,
|
||||||
|
slot_b,
|
||||||
|
bypass_dialog,
|
||||||
|
} => {
|
||||||
|
let mut move_allowed = true;
|
||||||
|
if !bypass_dialog {
|
||||||
|
if let Some(inventory) = self
|
||||||
|
.client
|
||||||
|
.borrow()
|
||||||
|
.state()
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<comp::Inventory>()
|
||||||
|
.get(self.client.borrow().entity())
|
||||||
|
{
|
||||||
|
match (slot_a, slot_b) {
|
||||||
|
(Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
|
||||||
|
| (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
|
||||||
|
if !inventory.can_swap(inv_slot, equip_slot) {
|
||||||
|
move_allowed = false;
|
||||||
|
} else {
|
||||||
|
let slot_deficit =
|
||||||
|
inventory.free_after_swap(equip_slot, inv_slot);
|
||||||
|
if slot_deficit < 0 {
|
||||||
|
self.hud.set_prompt_dialog(
|
||||||
|
PromptDialogSettings::new(
|
||||||
|
format!(
|
||||||
|
"This will result in dropping {} \
|
||||||
|
item(s) on the ground. Are you sure?",
|
||||||
|
slot_deficit.abs()
|
||||||
|
),
|
||||||
|
HudEvent::SwapSlots {
|
||||||
|
slot_a,
|
||||||
|
slot_b,
|
||||||
|
bypass_dialog: true,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
move_allowed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if move_allowed {
|
||||||
|
self.client.borrow_mut().split_swap_slots(slot_a, slot_b);
|
||||||
|
}
|
||||||
|
},
|
||||||
HudEvent::DropSlot(x) => {
|
HudEvent::DropSlot(x) => {
|
||||||
let mut client = self.client.borrow_mut();
|
let mut client = self.client.borrow_mut();
|
||||||
client.drop_slot(x);
|
client.drop_slot(x);
|
||||||
@ -1143,6 +1194,13 @@ impl PlayState for SessionState {
|
|||||||
client.disable_lantern();
|
client.disable_lantern();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
HudEvent::SplitDropSlot(x) => {
|
||||||
|
let mut client = self.client.borrow_mut();
|
||||||
|
client.split_drop_slot(x);
|
||||||
|
if let comp::slot::Slot::Equip(comp::slot::EquipSlot::Lantern) = x {
|
||||||
|
client.disable_lantern();
|
||||||
|
}
|
||||||
|
},
|
||||||
HudEvent::ChangeHotbarState(state) => {
|
HudEvent::ChangeHotbarState(state) => {
|
||||||
let client = self.client.borrow();
|
let client = self.client.borrow();
|
||||||
|
|
||||||
|
@ -94,7 +94,13 @@ where
|
|||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum ManagerState<K> {
|
enum ManagerState<K> {
|
||||||
Dragging(widget::Id, K, image::Id),
|
Dragging(
|
||||||
|
widget::Id,
|
||||||
|
K,
|
||||||
|
image::Id,
|
||||||
|
/// Amount of items being dragged in the stack.
|
||||||
|
Option<u32>,
|
||||||
|
),
|
||||||
Selected(widget::Id, K),
|
Selected(widget::Id, K),
|
||||||
Idle,
|
Idle,
|
||||||
}
|
}
|
||||||
@ -110,6 +116,10 @@ pub enum Event<K> {
|
|||||||
Dragged(K, K),
|
Dragged(K, K),
|
||||||
// Dragged to open space
|
// Dragged to open space
|
||||||
Dropped(K),
|
Dropped(K),
|
||||||
|
// Dropped half of the stack
|
||||||
|
SplitDropped(K),
|
||||||
|
// Dragged half of the stack
|
||||||
|
SplitDragged(K, K),
|
||||||
// Clicked while selected
|
// Clicked while selected
|
||||||
Used(K),
|
Used(K),
|
||||||
}
|
}
|
||||||
@ -127,21 +137,56 @@ pub struct SlotManager<S: SumSlot> {
|
|||||||
// Note: could potentially be specialized for each slot if needed
|
// Note: could potentially be specialized for each slot if needed
|
||||||
drag_img_size: Vec2<f32>,
|
drag_img_size: Vec2<f32>,
|
||||||
pub mouse_over_slot: Option<S>,
|
pub mouse_over_slot: Option<S>,
|
||||||
|
/* TODO(heyzoos) Will be useful for whoever works on rendering the number of items "in
|
||||||
|
* hand".
|
||||||
|
*
|
||||||
|
* drag_amount_id: widget::Id,
|
||||||
|
* drag_amount_shadow_id: widget::Id, */
|
||||||
|
|
||||||
|
/* Asset ID pointing to a font set.
|
||||||
|
* amount_font: font::Id, */
|
||||||
|
|
||||||
|
/* Specifies the size of the font used to display number of items held in
|
||||||
|
* a stack when dragging.
|
||||||
|
* amount_font_size: u32, */
|
||||||
|
|
||||||
|
/* Specifies how much space should be used in the margins of the item
|
||||||
|
* amount relative to the slot.
|
||||||
|
* amount_margins: Vec2<f32>, */
|
||||||
|
|
||||||
|
/* Specifies the color of the text used to display the number of items held
|
||||||
|
* in a stack when dragging.
|
||||||
|
* amount_text_color: Color, */
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> SlotManager<S>
|
impl<S> SlotManager<S>
|
||||||
where
|
where
|
||||||
S: SumSlot,
|
S: SumSlot,
|
||||||
{
|
{
|
||||||
pub fn new(mut gen: widget::id::Generator, drag_img_size: Vec2<f32>) -> Self {
|
pub fn new(
|
||||||
|
mut gen: widget::id::Generator,
|
||||||
|
drag_img_size: Vec2<f32>,
|
||||||
|
/* TODO(heyzoos) Will be useful for whoever works on rendering the number of items "in
|
||||||
|
* hand". amount_font: font::Id,
|
||||||
|
* amount_margins: Vec2<f32>,
|
||||||
|
* amount_font_size: u32,
|
||||||
|
* amount_text_color: Color, */
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: ManagerState::Idle,
|
state: ManagerState::Idle,
|
||||||
slot_ids: Vec::new(),
|
slot_ids: Vec::new(),
|
||||||
slots: Vec::new(),
|
slots: Vec::new(),
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
drag_id: gen.next(),
|
drag_id: gen.next(),
|
||||||
drag_img_size,
|
|
||||||
mouse_over_slot: None,
|
mouse_over_slot: None,
|
||||||
|
// TODO(heyzoos) Will be useful for whoever works on rendering the number of items "in
|
||||||
|
// hand". drag_amount_id: gen.next(),
|
||||||
|
// drag_amount_shadow_id: gen.next(),
|
||||||
|
// amount_font,
|
||||||
|
// amount_font_size,
|
||||||
|
// amount_margins,
|
||||||
|
// amount_text_color,
|
||||||
|
drag_img_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,8 +211,32 @@ where
|
|||||||
|
|
||||||
// If dragging and mouse is released check if there is a slot widget under the
|
// If dragging and mouse is released check if there is a slot widget under the
|
||||||
// mouse
|
// mouse
|
||||||
if let ManagerState::Dragging(_, slot, content_img) = &self.state {
|
if let ManagerState::Dragging(_, slot, content_img, drag_amount) = &self.state {
|
||||||
let content_img = *content_img;
|
let content_img = *content_img;
|
||||||
|
let drag_amount = *drag_amount;
|
||||||
|
|
||||||
|
// If we are dragging and we right click, drop half the stack
|
||||||
|
// on the ground or into the slot under the cursor. This only
|
||||||
|
// works with open slots or slots containing the same kind of
|
||||||
|
// item.
|
||||||
|
|
||||||
|
if drag_amount.is_some() {
|
||||||
|
if let Some(id) = input.widget_under_mouse {
|
||||||
|
if ui.widget_input(id).clicks().right().next().is_some() {
|
||||||
|
if id == ui.window {
|
||||||
|
let temp_slot = *slot;
|
||||||
|
self.events.push(Event::SplitDropped(temp_slot));
|
||||||
|
} else if let Some(idx) = slot_ids.iter().position(|slot_id| *slot_id == id)
|
||||||
|
{
|
||||||
|
let (from, to) = (*slot, slots[idx]);
|
||||||
|
if from != to {
|
||||||
|
self.events.push(Event::SplitDragged(from, to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let mouse::ButtonPosition::Up = input.mouse.buttons.left() {
|
if let mouse::ButtonPosition::Up = input.mouse.buttons.left() {
|
||||||
// Get widget under the mouse
|
// Get widget under the mouse
|
||||||
if let Some(id) = input.widget_under_mouse {
|
if let Some(id) = input.widget_under_mouse {
|
||||||
@ -186,6 +255,7 @@ where
|
|||||||
// Mouse released stop dragging
|
// Mouse released stop dragging
|
||||||
self.state = ManagerState::Idle;
|
self.state = ManagerState::Idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw image of contents being dragged
|
// Draw image of contents being dragged
|
||||||
let [mouse_x, mouse_y] = input.mouse.xy;
|
let [mouse_x, mouse_y] = input.mouse.xy;
|
||||||
let size = self.drag_img_size.map(|e| e as f64).into_array();
|
let size = self.drag_img_size.map(|e| e as f64).into_array();
|
||||||
@ -193,6 +263,34 @@ where
|
|||||||
.wh(size)
|
.wh(size)
|
||||||
.xy([mouse_x, mouse_y])
|
.xy([mouse_x, mouse_y])
|
||||||
.set(self.drag_id, ui);
|
.set(self.drag_id, ui);
|
||||||
|
|
||||||
|
// TODO(heyzoos) Will be useful for whoever works on rendering the
|
||||||
|
// number of items "in hand".
|
||||||
|
//
|
||||||
|
// if let Some(drag_amount) = drag_amount {
|
||||||
|
// Text::new(format!("{}", drag_amount).as_str())
|
||||||
|
// .parent(self.drag_id)
|
||||||
|
// .font_id(self.amount_font)
|
||||||
|
// .font_size(self.amount_font_size)
|
||||||
|
// .bottom_right_with_margins_on(
|
||||||
|
// self.drag_id,
|
||||||
|
// self.amount_margins.x as f64,
|
||||||
|
// self.amount_margins.y as f64,
|
||||||
|
// )
|
||||||
|
// .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||||
|
// .set(self.drag_amount_shadow_id, ui);
|
||||||
|
// Text::new(format!("{}", drag_amount).as_str())
|
||||||
|
// .parent(self.drag_id)
|
||||||
|
// .font_id(self.amount_font)
|
||||||
|
// .font_size(self.amount_font_size)
|
||||||
|
// .bottom_right_with_margins_on(
|
||||||
|
// self.drag_id,
|
||||||
|
// self.amount_margins.x as f64,
|
||||||
|
// self.amount_margins.y as f64,
|
||||||
|
// )
|
||||||
|
// .color(self.amount_text_color)
|
||||||
|
// .set(self.drag_amount_id, ui);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mem::replace(&mut self.events, Vec::new())
|
std::mem::replace(&mut self.events, Vec::new())
|
||||||
@ -204,6 +302,7 @@ where
|
|||||||
slot: S,
|
slot: S,
|
||||||
ui: &conrod_core::Ui,
|
ui: &conrod_core::Ui,
|
||||||
content_img: Option<Vec<image::Id>>,
|
content_img: Option<Vec<image::Id>>,
|
||||||
|
drag_amount: Option<u32>,
|
||||||
) -> Interaction {
|
) -> Interaction {
|
||||||
// Add to list of slots
|
// Add to list of slots
|
||||||
self.slot_ids.push(widget);
|
self.slot_ids.push(widget);
|
||||||
@ -212,7 +311,7 @@ where
|
|||||||
let filled = content_img.is_some();
|
let filled = content_img.is_some();
|
||||||
// If the slot is no longer filled deselect it or cancel dragging
|
// If the slot is no longer filled deselect it or cancel dragging
|
||||||
match &self.state {
|
match &self.state {
|
||||||
ManagerState::Selected(id, _) | ManagerState::Dragging(id, _, _)
|
ManagerState::Selected(id, _) | ManagerState::Dragging(id, _, _, _)
|
||||||
if *id == widget && !filled =>
|
if *id == widget && !filled =>
|
||||||
{
|
{
|
||||||
self.state = ManagerState::Idle;
|
self.state = ManagerState::Idle;
|
||||||
@ -223,7 +322,7 @@ where
|
|||||||
// If this is the selected/dragged widget make sure the slot value is up to date
|
// If this is the selected/dragged widget make sure the slot value is up to date
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
ManagerState::Selected(id, stored_slot)
|
ManagerState::Selected(id, stored_slot)
|
||||||
| ManagerState::Dragging(id, stored_slot, _)
|
| ManagerState::Dragging(id, stored_slot, _, _)
|
||||||
if *id == widget =>
|
if *id == widget =>
|
||||||
{
|
{
|
||||||
*stored_slot = slot
|
*stored_slot = slot
|
||||||
@ -278,24 +377,26 @@ where
|
|||||||
// If something is selected, deselect
|
// If something is selected, deselect
|
||||||
self.state = ManagerState::Idle;
|
self.state = ManagerState::Idle;
|
||||||
},
|
},
|
||||||
ManagerState::Dragging(_, _, _) => {},
|
ManagerState::Dragging(_, _, _, _) => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not dragging and there is a drag event on this slot start dragging
|
// If not dragging and there is a drag event on this slot start dragging
|
||||||
if input.drags().left().next().is_some()
|
if input.drags().left().next().is_some()
|
||||||
&& !matches!(self.state, ManagerState::Dragging(_, _, _))
|
&& !matches!(self.state, ManagerState::Dragging(_, _, _, _))
|
||||||
{
|
{
|
||||||
// Start dragging if widget is filled
|
// Start dragging if widget is filled
|
||||||
if let Some(img) = content_img {
|
if let Some(images) = content_img {
|
||||||
self.state = ManagerState::Dragging(widget, slot, img[0]);
|
if !images.is_empty() {
|
||||||
|
self.state = ManagerState::Dragging(widget, slot, images[0], drag_amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine whether this slot is being interacted with
|
// Determine whether this slot is being interacted with
|
||||||
match self.state {
|
match self.state {
|
||||||
ManagerState::Selected(id, _) if id == widget => Interaction::Selected,
|
ManagerState::Selected(id, _) if id == widget => Interaction::Selected,
|
||||||
ManagerState::Dragging(id, _, _) if id == widget => Interaction::Dragging,
|
ManagerState::Dragging(id, _, _, _) if id == widget => Interaction::Dragging,
|
||||||
_ => Interaction::None,
|
_ => Interaction::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,7 +589,13 @@ where
|
|||||||
let content_images = state.cached_images.as_ref().map(|c| c.1.clone());
|
let content_images = state.cached_images.as_ref().map(|c| c.1.clone());
|
||||||
// Get whether this slot is selected
|
// Get whether this slot is selected
|
||||||
let interaction = self.slot_manager.map_or(Interaction::None, |m| {
|
let interaction = self.slot_manager.map_or(Interaction::None, |m| {
|
||||||
m.update(id, slot_key.into(), ui, content_images.clone())
|
m.update(
|
||||||
|
id,
|
||||||
|
slot_key.into(),
|
||||||
|
ui,
|
||||||
|
content_images.clone(),
|
||||||
|
slot_key.amount(content_source),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
// No content if it is being dragged
|
// No content if it is being dragged
|
||||||
let content_images = if let Interaction::Dragging = interaction {
|
let content_images = if let Interaction::Dragging = interaction {
|
||||||
|
Loading…
Reference in New Issue
Block a user