From 6b1ba22d1f260163973d2cad13ec9fbe9a62b26d Mon Sep 17 00:00:00 2001
From: Jesus Bracho <jessebracho@gmail.com>
Date: Tue, 2 Mar 2021 00:08:46 +0000
Subject: [PATCH] Implement stacking and splitting

---
 CHANGELOG.md                         |   1 +
 client/src/lib.rs                    |  24 +++++
 common/src/comp/controller.rs        |   8 ++
 common/src/comp/inventory/mod.rs     |  54 +++++++++++
 server/src/events/inventory_manip.rs | 112 +++++++++++++++++++++--
 voxygen/src/hud/mod.rs               |  41 ++++++++-
 voxygen/src/session.rs               |  58 ++++++++++++
 voxygen/src/ui/widgets/slot.rs       | 131 ++++++++++++++++++++++++---
 8 files changed, 406 insertions(+), 23 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e929be2ddb..ba39d8af27 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Talk animation
 - New bosses in 5 lower dungeons
 = New enemies in 5 lower dungeons
+- Item stacking and splitting
 
 ### Changed
 
diff --git a/client/src/lib.rs b/client/src/lib.rs
index c30c5db091..d181381543 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -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 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) {
         // Get the health component from the entity
 
diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs
index 733d47ef2e..20891432fe 100644
--- a/common/src/comp/controller.rs
+++ b/common/src/comp/controller.rs
@@ -23,7 +23,9 @@ pub enum InventoryManip {
     Collect(Vec3<i32>),
     Use(InvSlotId),
     Swap(InvSlotId, InvSlotId),
+    SplitSwap(InvSlotId, InvSlotId),
     Drop(InvSlotId),
+    SplitDrop(InvSlotId),
     CraftRecipe(String),
 }
 
@@ -40,7 +42,9 @@ pub enum SlotManip {
     Collect(Vec3<i32>),
     Use(Slot),
     Swap(Slot, Slot),
+    SplitSwap(Slot, Slot),
     Drop(Slot),
+    SplitDrop(Slot),
     CraftRecipe(String),
 }
 
@@ -63,7 +67,11 @@ impl From<InventoryManip> for SlotManip {
             InventoryManip::Swap(inv1, 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::SplitDrop(inv) => Self::SplitDrop(Slot::Inventory(inv)),
             InventoryManip::CraftRecipe(recipe) => Self::CraftRecipe(recipe),
         }
     }
diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs
index df78f961a5..666de975d7 100644
--- a/common/src/comp/inventory/mod.rs
+++ b/common/src/comp/inventory/mod.rs
@@ -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
     /// exists.
     pub fn insert_or_stack_at(
@@ -215,6 +241,11 @@ impl Inventory {
         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
     pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
         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
     pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
         self.slots_mut()
diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs
index 48cc8c0ede..f73e871503 100644
--- a/server/src/events/inventory_manip.rs
+++ b/server/src/events/inventory_manip.rs
@@ -5,7 +5,8 @@ use vek::{Rgb, Vec3};
 
 use common::{
     comp::{
-        self, item,
+        self,
+        item::{self, MaterialStatManifest},
         slot::{self, Slot},
     },
     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(mut inventory) = ecs.write_storage::<comp::Inventory>().get_mut(entity)
                 {
-                    dropped_items.extend(inventory.swap(a, b).into_iter().map(|x| {
-                        (
-                            *pos,
-                            state
-                                .read_component_copied::<comp::Ori>(entity)
-                                .unwrap_or_default(),
-                            x,
-                        )
-                    }));
+                    let mut merged_stacks = false;
+
+                    // If both slots have items and we're attemping to drag from one stack
+                    // into another, stack the items.
+                    if let (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) = (a, b) {
+                        merged_stacks |= inventory.merge_stack_into(slot_a, slot_b);
+                    }
+
+                    // 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) => {
             let item = match slot {
                 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) => {
             if let Some(mut inv) = state
                 .ecs()
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index 706bed30b3..7550b7bbf6 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -391,7 +391,13 @@ pub enum Event {
         slot_b: comp::slot::Slot,
         bypass_dialog: bool,
     },
+    SplitSwapSlots {
+        slot_a: comp::slot::Slot,
+        slot_b: comp::slot::Slot,
+        bypass_dialog: bool,
+    },
     DropSlot(comp::slot::Slot),
+    SplitDropSlot(comp::slot::Slot),
     ChangeHotbarState(Box<HotbarState>),
     TradeAction(TradeAction),
     Ability3(bool),
@@ -769,7 +775,15 @@ impl Hud {
         let hotbar_state =
             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 {
             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) => {
                     // Item used (selected and then clicked again)
                     if let Some(from) = to_slot(from) {
diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs
index b37dec9d5e..a7e863c183 100644
--- a/voxygen/src/session.rs
+++ b/voxygen/src/session.rs
@@ -1136,6 +1136,57 @@ impl PlayState for SessionState {
                             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) => {
                         let mut client = self.client.borrow_mut();
                         client.drop_slot(x);
@@ -1143,6 +1194,13 @@ impl PlayState for SessionState {
                             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) => {
                         let client = self.client.borrow();
 
diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs
index b190acb5c4..2a936c6712 100644
--- a/voxygen/src/ui/widgets/slot.rs
+++ b/voxygen/src/ui/widgets/slot.rs
@@ -94,7 +94,13 @@ where
 
 #[derive(Clone, Copy)]
 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),
     Idle,
 }
@@ -110,6 +116,10 @@ pub enum Event<K> {
     Dragged(K, K),
     // Dragged to open space
     Dropped(K),
+    // Dropped half of the stack
+    SplitDropped(K),
+    // Dragged half of the stack
+    SplitDragged(K, K),
     // Clicked while selected
     Used(K),
 }
@@ -127,21 +137,56 @@ pub struct SlotManager<S: SumSlot> {
     // Note: could potentially be specialized for each slot if needed
     drag_img_size: Vec2<f32>,
     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>
 where
     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 {
             state: ManagerState::Idle,
             slot_ids: Vec::new(),
             slots: Vec::new(),
             events: Vec::new(),
             drag_id: gen.next(),
-            drag_img_size,
             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
         // 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 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() {
                 // Get widget under the mouse
                 if let Some(id) = input.widget_under_mouse {
@@ -186,6 +255,7 @@ where
                 // Mouse released stop dragging
                 self.state = ManagerState::Idle;
             }
+
             // Draw image of contents being dragged
             let [mouse_x, mouse_y] = input.mouse.xy;
             let size = self.drag_img_size.map(|e| e as f64).into_array();
@@ -193,6 +263,34 @@ where
                 .wh(size)
                 .xy([mouse_x, mouse_y])
                 .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())
@@ -204,6 +302,7 @@ where
         slot: S,
         ui: &conrod_core::Ui,
         content_img: Option<Vec<image::Id>>,
+        drag_amount: Option<u32>,
     ) -> Interaction {
         // Add to list of slots
         self.slot_ids.push(widget);
@@ -212,7 +311,7 @@ where
         let filled = content_img.is_some();
         // If the slot is no longer filled deselect it or cancel dragging
         match &self.state {
-            ManagerState::Selected(id, _) | ManagerState::Dragging(id, _, _)
+            ManagerState::Selected(id, _) | ManagerState::Dragging(id, _, _, _)
                 if *id == widget && !filled =>
             {
                 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
         match &mut self.state {
             ManagerState::Selected(id, stored_slot)
-            | ManagerState::Dragging(id, stored_slot, _)
+            | ManagerState::Dragging(id, stored_slot, _, _)
                 if *id == widget =>
             {
                 *stored_slot = slot
@@ -278,24 +377,26 @@ where
                     // If something is selected, deselect
                     self.state = ManagerState::Idle;
                 },
-                ManagerState::Dragging(_, _, _) => {},
+                ManagerState::Dragging(_, _, _, _) => {},
             }
         }
 
         // If not dragging and there is a drag event on this slot start dragging
         if input.drags().left().next().is_some()
-            && !matches!(self.state, ManagerState::Dragging(_, _, _))
+            && !matches!(self.state, ManagerState::Dragging(_, _, _, _))
         {
             // Start dragging if widget is filled
-            if let Some(img) = content_img {
-                self.state = ManagerState::Dragging(widget, slot, img[0]);
+            if let Some(images) = content_img {
+                if !images.is_empty() {
+                    self.state = ManagerState::Dragging(widget, slot, images[0], drag_amount);
+                }
             }
         }
 
         // Determine whether this slot is being interacted with
         match self.state {
             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,
         }
     }
@@ -488,7 +589,13 @@ where
         let content_images = state.cached_images.as_ref().map(|c| c.1.clone());
         // Get whether this slot is selected
         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
         let content_images = if let Interaction::Dragging = interaction {