Inventory mouse shortcut improvements (#13146)

Co-authored-by: Muhammad Rifqi Priyo Susanto <muhammadrifqipriyosusanto@gmail.com>
This commit is contained in:
OgelGames 2023-06-05 20:00:32 +10:00 committed by GitHub
parent 23f7aab354
commit 252c79d53a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 520 additions and 168 deletions

@ -682,7 +682,7 @@ void Client::step(float dtime)
the local inventory (so the player notices the lag problem the local inventory (so the player notices the lag problem
and knows something is wrong). and knows something is wrong).
*/ */
if (m_inventory_from_server) { if (m_inventory_from_server && !inhibit_inventory_revert) {
float interval = 10.0f; float interval = 10.0f;
float count_before = std::floor(m_inventory_from_server_age / interval); float count_before = std::floor(m_inventory_from_server_age / interval);

@ -437,11 +437,14 @@ public:
ModChannel *getModChannel(const std::string &channel) override; ModChannel *getModChannel(const std::string &channel) override;
const std::string &getFormspecPrepend() const; const std::string &getFormspecPrepend() const;
inline MeshGrid getMeshGrid() inline MeshGrid getMeshGrid()
{ {
return m_mesh_grid; return m_mesh_grid;
} }
bool inhibit_inventory_revert = false;
private: private:
void loadMods(); void loadMods();

@ -3746,10 +3746,15 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text,
void GUIFormSpecMenu::updateSelectedItem() void GUIFormSpecMenu::updateSelectedItem()
{ {
// Don't update when dragging an item
if (m_selected_item && (m_selected_dragging || m_left_dragging))
return;
verifySelectedItem(); verifySelectedItem();
// If craftresult is nonempty and nothing else is selected, select it now. // If craftresult is not empty and nothing else is selected,
if (!m_selected_item) { // try to move it somewhere or select it now
if (!m_selected_item || m_shift_move_after_craft) {
for (const GUIInventoryList *e : m_inventorylists) { for (const GUIInventoryList *e : m_inventorylists) {
if (e->getListname() != "craftpreview") if (e->getListname() != "craftpreview")
continue; continue;
@ -3767,14 +3772,48 @@ void GUIFormSpecMenu::updateSelectedItem()
if (item.empty()) if (item.empty())
continue; continue;
// Grab selected item from the crafting result list GUIInventoryList::ItemSpec s = GUIInventoryList::ItemSpec();
m_selected_item = new GUIInventoryList::ItemSpec; s.inventoryloc = e->getInventoryloc();
m_selected_item->inventoryloc = e->getInventoryloc(); s.listname = "craftresult";
m_selected_item->listname = "craftresult"; s.i = 0;
m_selected_item->i = 0; s.slotsize = e->getSlotSize();
m_selected_item->slotsize = e->getSlotSize();
m_selected_amount = item.count; if (m_shift_move_after_craft) {
m_selected_dragging = false; // Try to shift-move the crafted item to the next list in the ring after the "craft" list.
// We don't look for the "craftresult" list because it's a hidden list,
// and shouldn't be part of the formspec, thus it won't be in the list ring.
do {
s16 r = getNextInventoryRing(s.inventoryloc, "craft");
if (r < 0) // Not found
break;
const ListRingSpec &to_ring = m_inventory_rings[r];
Inventory *inv_to = m_invmgr->getInventory(to_ring.inventoryloc);
if (!inv_to)
break;
InventoryList *list_to = inv_to->getList(to_ring.listname);
if (!list_to)
break;
IMoveAction *a = new IMoveAction();
a->count = item.count;
a->from_inv = s.inventoryloc;
a->from_list = s.listname;
a->from_i = s.i;
a->to_inv = to_ring.inventoryloc;
a->to_list = to_ring.listname;
a->move_somewhere = true;
m_invmgr->inventoryAction(a);
} while (0);
m_shift_move_after_craft = false;
} else {
// Grab selected item from the crafting result list
m_selected_item = new GUIInventoryList::ItemSpec(s);
m_selected_amount = item.count;
m_selected_dragging = false;
}
break; break;
} }
} }
@ -3821,6 +3860,25 @@ ItemStack GUIFormSpecMenu::verifySelectedItem()
return ItemStack(); return ItemStack();
} }
s16 GUIFormSpecMenu::getNextInventoryRing(
const InventoryLocation &inventoryloc, const std::string &listname)
{
u16 rings = m_inventory_rings.size();
if (rings < 2)
return -1;
// Look for the source ring
s16 index = -1;
for (u16 i = 0; i < rings; i++) {
ListRingSpec &lr = m_inventory_rings[i];
if (lr.inventoryloc == inventoryloc && lr.listname == listname) {
// Set the index to the next ring
index = (i + 1) % rings;
break;
}
}
return index;
}
void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode)
{ {
if(m_text_dst) if(m_text_dst)
@ -4052,19 +4110,6 @@ void GUIFormSpecMenu::tryClose()
} }
} }
enum ButtonEventType : u8
{
BET_LEFT,
BET_RIGHT,
BET_MIDDLE,
BET_WHEEL_UP,
BET_WHEEL_DOWN,
BET_UP,
BET_DOWN,
BET_MOVE,
BET_OTHER
};
bool GUIFormSpecMenu::OnEvent(const SEvent& event) bool GUIFormSpecMenu::OnEvent(const SEvent& event)
{ {
if (event.EventType==EET_KEY_INPUT_EVENT) { if (event.EventType==EET_KEY_INPUT_EVENT) {
@ -4117,13 +4162,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
/* Mouse event other than movement, or crossing the border of inventory /* Mouse event other than movement, or crossing the border of inventory
field while holding right mouse button field while holding left, right, or middle mouse button
*/ */
if (event.EventType == EET_MOUSE_INPUT_EVENT && if (event.EventType == EET_MOUSE_INPUT_EVENT &&
(event.MouseInput.Event != EMIE_MOUSE_MOVED || (event.MouseInput.Event != EMIE_MOUSE_MOVED ||
(event.MouseInput.Event == EMIE_MOUSE_MOVED && ((event.MouseInput.isLeftPressed() ||
event.MouseInput.isRightPressed() && event.MouseInput.isRightPressed() ||
getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) { event.MouseInput.isMiddlePressed()) &&
getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) {
// Get selected item and hovered/clicked item (s) // Get selected item and hovered/clicked item (s)
@ -4132,13 +4178,15 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
GUIInventoryList::ItemSpec s = getItemAtPos(m_pointer); GUIInventoryList::ItemSpec s = getItemAtPos(m_pointer);
Inventory *inv_selected = NULL; Inventory *inv_selected = NULL;
InventoryList *list_selected = NULL;
Inventory *inv_s = NULL; Inventory *inv_s = NULL;
InventoryList *list_s = NULL; InventoryList *list_s = NULL;
if (m_selected_item) { if (m_selected_item) {
inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc); inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
sanity_check(inv_selected); sanity_check(inv_selected);
sanity_check(inv_selected->getList(m_selected_item->listname) != NULL); list_selected = inv_selected->getList(m_selected_item->listname);
sanity_check(list_selected);
} }
u32 s_count = 0; u32 s_count = 0;
@ -4174,12 +4222,21 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
s_count = list_s->getItem(s.i).count; s_count = list_s->getItem(s.i).count;
} while(0); } while(0);
bool identical = m_selected_item && s.isValid() && // True if the hovered slot is the selected slot
(inv_selected == inv_s) && bool identical = m_selected_item && s.isValid() && (*m_selected_item == s);
(m_selected_item->listname == s.listname) &&
(m_selected_item->i == s.i);
ButtonEventType button = BET_LEFT; // True if the hovered slot is empty
bool empty = s.isValid() && list_s->getItem(s.i).empty();
// True if the hovered item would stack with the selected item
bool matching = false;
if (m_selected_item && s.isValid()) {
ItemStack a = list_selected->getItem(m_selected_item->i);
ItemStack b = list_s->getItem(s.i);
matching = a.stacksWith(b);
}
ButtonEventType button = BET_OTHER;
ButtonEventType updown = BET_OTHER; ButtonEventType updown = BET_OTHER;
switch (event.MouseInput.Event) { switch (event.MouseInput.Event) {
case EMIE_LMOUSE_PRESSED_DOWN: case EMIE_LMOUSE_PRESSED_DOWN:
@ -4220,6 +4277,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
// from s to the next inventory ring. // from s to the next inventory ring.
u32 shift_move_amount = 0; u32 shift_move_amount = 0;
// Set this number to a positive value to generate a move action
// from s to m_selected_item.
u32 pickup_amount = 0;
// Set this number to a positive value to generate a drop action // Set this number to a positive value to generate a drop action
// from m_selected_item. // from m_selected_item.
u32 drop_amount = 0; u32 drop_amount = 0;
@ -4228,92 +4289,154 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
u32 craft_amount = 0; u32 craft_amount = 0;
switch (updown) { switch (updown) {
case BET_DOWN: case BET_DOWN: {
// Some mouse button has been pressed // Some mouse button has been pressed
//infostream << "Mouse button " << button << " pressed at p=(" if (m_held_mouse_button != BET_OTHER)
// << event.MouseInput.X << "," << event.MouseInput.Y << ")" break;
// << std::endl; if (button == BET_LEFT || button == BET_RIGHT || button == BET_MIDDLE)
m_held_mouse_button = button;
m_selected_dragging = false; if (!s.isValid()) {
if (m_selected_item && !getAbsoluteClippingRect().isPointInside(m_pointer)) {
if (s.isValid() && s.listname == "craftpreview") {
// Craft preview has been clicked: craft
craft_amount = (button == BET_MIDDLE ? 10 : 1);
} else if (!m_selected_item) {
if (s_count && button != BET_WHEEL_UP) {
// Non-empty stack has been clicked: select or shift-move it
m_selected_item = new GUIInventoryList::ItemSpec(s);
u32 count;
if (button == BET_RIGHT)
count = (s_count + 1) / 2;
else if (button == BET_MIDDLE)
count = MYMIN(s_count, 10);
else if (button == BET_WHEEL_DOWN)
count = 1;
else // left
count = s_count;
if (!event.MouseInput.Shift) {
// no shift: select item
m_selected_amount = count;
m_selected_dragging = button != BET_WHEEL_DOWN;
m_auto_place = false;
} else {
// shift pressed: move item, right click moves 1
shift_move_amount = button == BET_RIGHT ? 1 : count;
}
}
} else { // m_selected_item != NULL
assert(m_selected_amount >= 1);
if (s.isValid()) {
// Clicked a slot: move
if (button == BET_RIGHT || button == BET_WHEEL_UP)
move_amount = 1;
else if (button == BET_MIDDLE)
move_amount = MYMIN(m_selected_amount, 10);
else if (button == BET_LEFT)
move_amount = m_selected_amount;
// else wheeldown
if (identical) {
if (button == BET_WHEEL_DOWN) {
if (m_selected_amount < s_count)
++m_selected_amount;
} else {
if (move_amount >= m_selected_amount)
m_selected_amount = 0;
else
m_selected_amount -= move_amount;
move_amount = 0;
}
}
} else if (!getAbsoluteClippingRect().isPointInside(m_pointer)
&& button != BET_WHEEL_DOWN) {
// Clicked outside of the window: drop // Clicked outside of the window: drop
if (button == BET_RIGHT || button == BET_WHEEL_UP) if (button == BET_RIGHT || button == BET_WHEEL_UP)
drop_amount = 1; drop_amount = 1;
else if (button == BET_MIDDLE) else if (button == BET_MIDDLE)
drop_amount = MYMIN(m_selected_amount, 10); drop_amount = MYMIN(m_selected_amount, 10);
else // left else if (button == BET_LEFT)
drop_amount = m_selected_amount; drop_amount = m_selected_amount;
} }
break;
} }
break; if (s.listname == "craftpreview") {
case BET_UP: // Craft preview has been clicked: craft
if (button == BET_MIDDLE)
craft_amount = 10;
else if (event.MouseInput.Shift && button == BET_LEFT)
// TODO: We should craft everything with shift-left-click,
// but the slow crafting code limits us, so we only craft one
craft_amount = 1;
//craft_amount = list_s->getItem(s.i).getStackMax(m_client->idef());
else
craft_amount = 1;
// Holding shift moves the crafted item to the inventory
m_shift_move_after_craft = event.MouseInput.Shift;
} else if (!m_selected_item && button != BET_WHEEL_UP && !empty) {
// Non-empty stack has been clicked: select or shift-move it
u32 count = 0;
if (button == BET_RIGHT)
count = (s_count + 1) / 2;
else if (button == BET_MIDDLE)
count = MYMIN(s_count, 10);
else if (button == BET_WHEEL_DOWN)
count = 1;
else if (button == BET_LEFT)
count = s_count;
if (event.MouseInput.Shift) {
// Shift pressed: move item, right click moves 1
shift_move_amount = button == BET_RIGHT ? 1 : count;
} else {
// No shift: select item
m_selected_item = new GUIInventoryList::ItemSpec(s);
m_selected_amount = count;
m_selected_dragging = button != BET_WHEEL_DOWN;
}
} else if (m_selected_item) {
// Clicked a slot: move
if (button == BET_RIGHT || button == BET_WHEEL_UP)
move_amount = 1;
else if (button == BET_WHEEL_DOWN)
pickup_amount = MYMIN(s_count, 1);
else if (button == BET_MIDDLE)
move_amount = MYMIN(m_selected_amount, 10);
else if (button == BET_LEFT)
move_amount = m_selected_amount;
if (event.MouseInput.Shift && !identical && matching) {
// Shift-move all items the same as the selected item to the next list
move_amount = 0;
// Try to find somewhere to move the items to
s16 r = getNextInventoryRing(s.inventoryloc, s.listname);
if (r < 0) // Not found
break;
const ListRingSpec &to_ring = m_inventory_rings[r];
Inventory *inv_to = m_invmgr->getInventory(to_ring.inventoryloc);
if (!inv_to)
break;
InventoryList *list_to = inv_to->getList(to_ring.listname);
if (!list_to)
break;
ItemStack slct = list_selected->getItem(m_selected_item->i);
for (s32 i = 0; i < list_s->getSize(); i++) {
// Skip the selected slot
if (i == m_selected_item->i)
continue;
ItemStack item = list_s->getItem(i);
if (slct.stacksWith(item)) {
IMoveAction *a = new IMoveAction();
a->count = item.count;
a->from_inv = s.inventoryloc;
a->from_list = s.listname;
a->from_i = i;
a->to_inv = to_ring.inventoryloc;
a->to_list = to_ring.listname;
a->move_somewhere = true;
m_invmgr->inventoryAction(a);
}
}
} else if (button == BET_LEFT && (empty || matching)) {
// We don't know if the user is left-dragging or just moving
// the item, so assume that they are left-dragging,
// and wait for the next event before moving the item
m_left_dragging = true;
m_client->inhibit_inventory_revert = true;
m_left_drag_stack = list_selected->getItem(m_selected_item->i);
m_left_drag_amount = m_selected_amount;
m_left_drag_stacks.emplace_back(s, list_s->getItem(s.i));
move_amount = 0;
} else if (identical) {
// Change the selected amount instead of moving
if (button == BET_WHEEL_DOWN) {
if (m_selected_amount < s_count)
++m_selected_amount;
} else if (button == BET_WHEEL_UP) {
if (m_selected_amount > 0)
--m_selected_amount;
} else {
if (move_amount >= m_selected_amount)
m_selected_amount = 0;
else
m_selected_amount -= move_amount;
}
move_amount = 0;
pickup_amount = 0;
}
}
break;
}
case BET_UP: {
// Some mouse button has been released // Some mouse button has been released
//infostream<<"Mouse button "<<button<<" released at p=(" if (m_held_mouse_button != BET_OTHER && m_held_mouse_button != button)
// <<p.X<<","<<p.Y<<")"<<std::endl; break;
m_held_mouse_button = BET_OTHER;
if (m_selected_dragging && m_selected_item) { if (m_selected_dragging && m_selected_item) {
if (s.isValid()) { if (s.isValid() && !identical && (empty || matching)) {
if (!identical) { // Dragged to different slot: move all selected
// Dragged to different slot: move all selected move_amount = m_selected_amount;
move_amount = m_selected_amount;
}
} else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) { } else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) {
// Dragged outside of window: drop all selected // Dragged outside of window: drop all selected
drop_amount = m_selected_amount; drop_amount = m_selected_amount;
@ -4321,60 +4444,215 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
m_selected_dragging = false; m_selected_dragging = false;
// Keep track of whether the mouse button be released
// One click is drag without dropping. Click + release if (m_left_dragging && button == BET_LEFT) {
// + click changes to drop item when moved mode m_left_dragging = false;
if (m_selected_item) m_client->inhibit_inventory_revert = false;
m_auto_place = true;
break; if (m_left_drag_stacks.size() > 1) {
case BET_MOVE: // Finalize the left-dragging
// Mouse has been moved and rmb is down and mouse pointer just for (auto &ds : m_left_drag_stacks) {
// entered a new inventory field (checked in the entry-if, this // Check how many items we should move to this slot,
// is the only action here that is generated by mouse movement) // it may be less than the full split
if (m_selected_item && s.isValid() && s.listname != "craftpreview") { Inventory *inv_to = m_invmgr->getInventory(ds.first.inventoryloc);
// Move 1 item InventoryList *list_to = inv_to->getList(ds.first.listname);
// TODO: middle mouse to move 10 items might be handy ItemStack stack_to = list_to->getItem(ds.first.i);
if (m_auto_place) { u16 amount = stack_to.count - ds.second.count;
// Only move an item if the destination slot is empty
// or contains the same item type as what is going to be IMoveAction *a = new IMoveAction();
// moved a->count = amount;
InventoryList *list_from = inv_selected->getList(m_selected_item->listname); a->from_inv = m_selected_item->inventoryloc;
InventoryList *list_to = list_s; a->from_list = m_selected_item->listname;
assert(list_from && list_to); a->from_i = m_selected_item->i;
ItemStack stack_from = list_from->getItem(m_selected_item->i); a->to_inv = ds.first.inventoryloc;
ItemStack stack_to = list_to->getItem(s.i); a->to_list = ds.first.listname;
if (stack_to.empty() || stack_to.name == stack_from.name) a->to_i = ds.first.i;
move_amount = 1; m_invmgr->inventoryAction(a);
}
} else if (identical) {
// Put the selected item back where it came from
m_selected_amount = 0;
} else if (s.isValid()) {
// Move the selected item
move_amount = m_selected_amount;
}
m_left_drag_stacks.clear();
}
break;
}
case BET_MOVE: {
// Mouse button is down and mouse pointer entered a new inventory field
if (!s.isValid() || s.listname == "craftpreview")
break;
if (!m_selected_item && event.MouseInput.Shift) {
// Shift-move items while dragging
if (m_held_mouse_button == BET_RIGHT)
shift_move_amount = 1;
else if (m_held_mouse_button == BET_MIDDLE)
shift_move_amount = MYMIN(s_count, 10);
else if (m_held_mouse_button == BET_LEFT)
shift_move_amount = s_count;
} else if (m_selected_item) {
if (m_held_mouse_button != BET_LEFT) {
// Move items if the destination slot is empty
// or contains the same item type as what is going to be moved
if (!m_selected_dragging && (empty || matching)) {
if (m_held_mouse_button == BET_RIGHT)
move_amount = 1;
else if (m_held_mouse_button == BET_MIDDLE)
move_amount = MYMIN(m_selected_amount, 10);
}
} else if (m_left_dragging && (empty || matching) &&
m_left_drag_amount > m_left_drag_stacks.size()) {
// Add the slot to the left-drag list if it doesn't exist
bool found = false;
for (auto &ds : m_left_drag_stacks) {
if (s == ds.first) {
found = true;
break;
}
}
if (!found) {
m_left_drag_stacks.emplace_back(s, list_s->getItem(s.i));
}
} else if (m_selected_dragging && matching && !identical) {
// Pickup items of the same type while dragging
pickup_amount = s_count;
}
} else if (m_held_mouse_button == BET_LEFT) {
// Start picking up items
m_selected_item = new GUIInventoryList::ItemSpec(s);
m_selected_amount = s_count;
m_selected_dragging = true;
}
break;
}
case BET_OTHER: {
// Some other mouse event has occured
// Currently only left-double-click should trigger this
if (!s.isValid() || event.MouseInput.Event != EMIE_LMOUSE_DOUBLE_CLICK)
break;
// Abort left-dragging
m_left_dragging = false;
m_client->inhibit_inventory_revert = false;
m_left_drag_stacks.clear();
// Both the selected item and the hovered item need to be checked
// because we don't know exactly when the double-click happened
ItemStack slct;
if (!m_selected_item && !empty)
slct = list_s->getItem(s.i);
else if (m_selected_item && (identical || empty))
slct = list_selected->getItem(m_selected_item->i);
// Pickup all of the item from the list
if (slct.count > 0) {
for (s32 i = 0; i < list_s->getSize(); i++) {
// Skip the selected slot
if (i == s.i)
continue;
ItemStack item = list_s->getItem(i);
if (slct.stacksWith(item)) {
// Found a match, check if we can pick it up
bool full = false;
u16 amount = item.count;
ItemStack leftover = slct.addItem(item, m_client->idef());
if (!leftover.empty()) {
amount -= leftover.count;
full = true;
}
if (amount > 0) {
IMoveAction *a = new IMoveAction();
a->count = amount;
a->from_inv = s.inventoryloc;
a->from_list = s.listname;
a->from_i = i;
a->to_inv = s.inventoryloc;
a->to_list = s.listname;
a->to_i = s.i;
m_invmgr->inventoryAction(a);
if (m_selected_item)
m_selected_amount += amount;
}
if (full) // Stack is full, stop
break;
}
} }
} }
break; break;
}
default: default:
break; break;
} }
// Update left-dragged slots
if (m_left_dragging && m_left_drag_stacks.size() > 1) {
// The split amount will always at least one, because the number
// of slots will never be greater than the selected amount
u16 split_amount = m_left_drag_amount / m_left_drag_stacks.size();
ItemStack stack_from = m_left_drag_stack;
m_selected_amount = m_left_drag_amount;
for (auto &ds : m_left_drag_stacks) {
Inventory *inv_to = m_invmgr->getInventory(ds.first.inventoryloc);
InventoryList *list_to = inv_to->getList(ds.first.listname);
if (ds.first == *m_selected_item) {
// Adding to the source stack, just change the selected amount
m_selected_amount -= split_amount;
} else {
// Reset the stack to its original state
list_to->changeItem(ds.first.i, ds.second);
// Add the new split to the stack
ItemStack add_stack = stack_from;
add_stack.count = split_amount;
ItemStack leftover = list_to->addItem(ds.first.i, add_stack);
// Remove the split items from the source stack
u16 moved = split_amount - leftover.count;
m_selected_amount -= moved;
stack_from.count -= moved;
}
}
// Save the adjusted source stack
list_selected->changeItem(m_selected_item->i, stack_from);
}
// Possibly send inventory action to server // Possibly send inventory action to server
if (move_amount > 0) { if (move_amount > 0) {
// Send IAction::Move // Send IAction::Move
assert(m_selected_item && m_selected_item->isValid()); assert(m_selected_item && m_selected_item->isValid());
assert(s.isValid()); assert(s.isValid());
assert(list_selected && list_s);
assert(inv_selected && inv_s); ItemStack stack_from = list_selected->getItem(m_selected_item->i);
InventoryList *list_from = inv_selected->getList(m_selected_item->listname); ItemStack stack_to = list_s->getItem(s.i);
InventoryList *list_to = list_s;
assert(list_from && list_to);
ItemStack stack_from = list_from->getItem(m_selected_item->i);
ItemStack stack_to = list_to->getItem(s.i);
// Check how many items can be moved // Check how many items can be moved
move_amount = stack_from.count = MYMIN(move_amount, stack_from.count); move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
ItemStack leftover = stack_to.addItem(stack_from, m_client->idef()); ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
bool move = true;
// If source stack cannot be added to destination stack at all, // If source stack cannot be added to destination stack at all,
// they are swapped // they are swapped
if (leftover.count == stack_from.count && if (leftover.count == stack_from.count && leftover.name == stack_from.name) {
leftover.name == stack_from.name) {
if (m_selected_swap.empty()) { if (m_selected_swap.empty()) {
m_selected_amount = stack_to.count; m_selected_amount = stack_to.count;
m_selected_dragging = false; m_selected_dragging = false;
@ -4383,7 +4661,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
// Skip next validation checks due async inventory calls // Skip next validation checks due async inventory calls
m_selected_swap = stack_to; m_selected_swap = stack_to;
} else { } else {
move = false; move_amount = 0;
} }
} }
// Source stack goes fully into destination stack // Source stack goes fully into destination stack
@ -4396,7 +4674,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
m_selected_amount -= move_amount; m_selected_amount -= move_amount;
} }
if (move) { if (move_amount > 0) {
infostream << "Handing IAction::Move to manager" << std::endl; infostream << "Handing IAction::Move to manager" << std::endl;
IMoveAction *a = new IMoveAction(); IMoveAction *a = new IMoveAction();
a->count = move_amount; a->count = move_amount;
@ -4408,31 +4686,64 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
a->to_i = s.i; a->to_i = s.i;
m_invmgr->inventoryAction(a); m_invmgr->inventoryAction(a);
} }
} else if (shift_move_amount > 0) { } else if (pickup_amount > 0) {
u32 mis = m_inventory_rings.size(); // Send IAction::Move
u32 i = 0;
for (; i < mis; i++) { assert(m_selected_item && m_selected_item->isValid());
const ListRingSpec &sp = m_inventory_rings[i]; assert(s.isValid());
if (sp.inventoryloc == s.inventoryloc assert(list_selected && list_s);
&& sp.listname == s.listname)
break; ItemStack stack_from = list_s->getItem(s.i);
ItemStack stack_to = list_selected->getItem(m_selected_item->i);
// Only move if the items are exactly the same,
// we shouldn't attempt to pickup different items
if (matching) {
// Check how many items can be moved
pickup_amount = stack_from.count = MYMIN(pickup_amount, stack_from.count);
ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
pickup_amount -= leftover.count;
} else {
pickup_amount = 0;
} }
if (pickup_amount > 0) {
m_selected_amount += pickup_amount;
infostream << "Handing IAction::Move to manager" << std::endl;
IMoveAction *a = new IMoveAction();
a->count = pickup_amount;
a->from_inv = s.inventoryloc;
a->from_list = s.listname;
a->from_i = s.i;
a->to_inv = m_selected_item->inventoryloc;
a->to_list = m_selected_item->listname;
a->to_i = m_selected_item->i;
m_invmgr->inventoryAction(a);
}
} else if (shift_move_amount > 0) {
// Try to shift-move the item
do { do {
if (i >= mis) // if not found s16 r = getNextInventoryRing(s.inventoryloc, s.listname);
if (r < 0) // Not found
break; break;
u32 to_inv_ind = (i + 1) % mis;
const ListRingSpec &to_inv_sp = m_inventory_rings[to_inv_ind]; const ListRingSpec &to_ring = m_inventory_rings[r];
InventoryList *list_from = list_s; InventoryList *list_from = list_s;
if (!s.isValid()) if (!s.isValid())
break; break;
Inventory *inv_to = m_invmgr->getInventory(to_inv_sp.inventoryloc); Inventory *inv_to = m_invmgr->getInventory(to_ring.inventoryloc);
if (!inv_to) if (!inv_to)
break; break;
InventoryList *list_to = inv_to->getList(to_inv_sp.listname); InventoryList *list_to = inv_to->getList(to_ring.listname);
if (!list_to) if (!list_to)
break; break;
// Check how many items can be moved
ItemStack stack_from = list_from->getItem(s.i); ItemStack stack_from = list_from->getItem(s.i);
assert(shift_move_amount <= stack_from.count); shift_move_amount = MYMIN(shift_move_amount, stack_from.count);
if (shift_move_amount == 0)
break;
infostream << "Handing IAction::Move to manager" << std::endl; infostream << "Handing IAction::Move to manager" << std::endl;
IMoveAction *a = new IMoveAction(); IMoveAction *a = new IMoveAction();
@ -4440,19 +4751,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
a->from_inv = s.inventoryloc; a->from_inv = s.inventoryloc;
a->from_list = s.listname; a->from_list = s.listname;
a->from_i = s.i; a->from_i = s.i;
a->to_inv = to_inv_sp.inventoryloc; a->to_inv = to_ring.inventoryloc;
a->to_list = to_inv_sp.listname; a->to_list = to_ring.listname;
a->move_somewhere = true; a->move_somewhere = true;
m_invmgr->inventoryAction(a); m_invmgr->inventoryAction(a);
} while (0); } while (0);
} else if (drop_amount > 0) { } else if (drop_amount > 0) {
// Send IAction::Drop // Send IAction::Drop
assert(m_selected_item && m_selected_item->isValid()); assert(m_selected_item && m_selected_item->isValid());
assert(inv_selected); assert(list_selected);
InventoryList *list_from = inv_selected->getList(m_selected_item->listname); ItemStack stack_from = list_selected->getItem(m_selected_item->i);
assert(list_from);
ItemStack stack_from = list_from->getItem(m_selected_item->i);
// Check how many items can be dropped // Check how many items can be dropped
drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count); drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
@ -4466,10 +4776,11 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
a->from_list = m_selected_item->listname; a->from_list = m_selected_item->listname;
a->from_i = m_selected_item->i; a->from_i = m_selected_item->i;
m_invmgr->inventoryAction(a); m_invmgr->inventoryAction(a);
} else if (craft_amount > 0) { } else if (craft_amount > 0) {
assert(s.isValid()); assert(s.isValid());
// if there are no items selected or the selected item // If there are no items selected or the selected item
// belongs to craftresult list, proceed with crafting // belongs to craftresult list, proceed with crafting
if (!m_selected_item || if (!m_selected_item ||
!m_selected_item->isValid() || m_selected_item->listname == "craftresult") { !m_selected_item->isValid() || m_selected_item->listname == "craftresult") {
@ -4485,8 +4796,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
// If m_selected_amount has been decreased to zero, deselect // If m_selected_amount has been decreased to zero,
if (m_selected_amount == 0) { // and we are not left-dragging, deselect
if (m_selected_amount == 0 && !m_left_dragging) {
m_selected_swap.clear(); m_selected_swap.clear();
delete m_selected_item; delete m_selected_item;
m_selected_item = nullptr; m_selected_item = nullptr;

@ -64,6 +64,19 @@ enum FormspecQuitMode {
quit_mode_cancel quit_mode_cancel
}; };
enum ButtonEventType : u8
{
BET_LEFT,
BET_RIGHT,
BET_MIDDLE,
BET_WHEEL_UP,
BET_WHEEL_DOWN,
BET_UP,
BET_DOWN,
BET_MOVE,
BET_OTHER
};
struct TextDest struct TextDest
{ {
virtual ~TextDest() = default; virtual ~TextDest() = default;
@ -258,6 +271,8 @@ public:
void updateSelectedItem(); void updateSelectedItem();
ItemStack verifySelectedItem(); ItemStack verifySelectedItem();
s16 getNextInventoryRing(const InventoryLocation &inventoryloc, const std::string &listname);
void acceptInput(FormspecQuitMode quitmode=quit_mode_no); void acceptInput(FormspecQuitMode quitmode=quit_mode_no);
bool preprocessEvent(const SEvent& event); bool preprocessEvent(const SEvent& event);
bool OnEvent(const SEvent& event); bool OnEvent(const SEvent& event);
@ -332,6 +347,13 @@ protected:
u16 m_selected_amount = 0; u16 m_selected_amount = 0;
bool m_selected_dragging = false; bool m_selected_dragging = false;
ItemStack m_selected_swap; ItemStack m_selected_swap;
ButtonEventType m_held_mouse_button = BET_OTHER;
bool m_shift_move_after_craft = false;
u16 m_left_drag_amount = 0;
ItemStack m_left_drag_stack;
std::vector<std::pair<GUIInventoryList::ItemSpec, ItemStack>> m_left_drag_stacks;
bool m_left_dragging = false;
gui::IGUIStaticText *m_tooltip_element = nullptr; gui::IGUIStaticText *m_tooltip_element = nullptr;
@ -340,8 +362,6 @@ protected:
u64 m_hovered_time = 0; u64 m_hovered_time = 0;
s32 m_old_tooltip_id = -1; s32 m_old_tooltip_id = -1;
bool m_auto_place = false;
bool m_allowclose = true; bool m_allowclose = true;
bool m_lock = false; bool m_lock = false;
v2u32 m_lockscreensize; v2u32 m_lockscreensize;

@ -43,6 +43,12 @@ public:
{ {
} }
bool operator==(const ItemSpec& other)
{
return inventoryloc == other.inventoryloc &&
listname == other.listname && i == other.i;
}
bool isValid() const { return i != -1; } bool isValid() const { return i != -1; }
InventoryLocation inventoryloc; InventoryLocation inventoryloc;

@ -392,6 +392,13 @@ bool ItemStack::itemFits(ItemStack newitem,
return newitem.empty(); return newitem.empty();
} }
bool ItemStack::stacksWith(ItemStack other) const
{
return (this->name == other.name &&
this->wear == other.wear &&
this->metadata == other.metadata);
}
ItemStack ItemStack::takeItem(u32 takecount) ItemStack ItemStack::takeItem(u32 takecount)
{ {
if(takecount == 0 || count == 0) if(takecount == 0 || count == 0)

@ -162,6 +162,10 @@ struct ItemStack
ItemStack *restitem, // may be NULL ItemStack *restitem, // may be NULL
IItemDefManager *itemdef) const; IItemDefManager *itemdef) const;
// Checks if another itemstack would stack with this one.
// Does not check if the item actually fits in the stack.
bool stacksWith(ItemStack other) const;
// Takes some items. // Takes some items.
// If there are not enough, takes as many as it can. // If there are not enough, takes as many as it can.
// Returns empty item if couldn't take any. // Returns empty item if couldn't take any.