From 461ad03d27690d0ac761a474a25b6198cd33933e Mon Sep 17 00:00:00 2001
From: "Zed A. Shaw" <zed.shaw@gmail.com>
Date: Sun, 8 Jun 2025 00:37:30 -0400
Subject: [PATCH] Taking things from the LootUI to the StatusUI works way
 better now and there's a DropTarget to match the GrabSource.

---
 gui/event_router.cpp |  2 +-
 gui/fsm.cpp          | 62 ++++++++++++++++++++++++++------------------
 gui/guecstra.cpp     | 14 ++++++++++
 gui/guecstra.hpp     | 15 +++--------
 gui/loot_ui.cpp      | 31 ++++++++++++----------
 gui/loot_ui.hpp      |  7 ++---
 gui/status_ui.cpp    | 23 ++++++++--------
 gui/status_ui.hpp    |  7 +++--
 8 files changed, 92 insertions(+), 69 deletions(-)

diff --git a/gui/event_router.cpp b/gui/event_router.cpp
index 62e4b99..cb6321d 100644
--- a/gui/event_router.cpp
+++ b/gui/event_router.cpp
@@ -65,7 +65,7 @@ namespace gui {
           set_event(gui::Event::KEY_PRESS);
           break;
         default:
-          dbc::sentinel("invalid event");
+          dbc::sentinel(fmt::format("invalid event: {}", int(ev)));
       }
     }
 
diff --git a/gui/fsm.cpp b/gui/fsm.cpp
index c1a5f1b..6f41720 100644
--- a/gui/fsm.cpp
+++ b/gui/fsm.cpp
@@ -27,6 +27,7 @@ namespace gui {
     $loot_ui($level),
     $font{FONT_FILE_NAME}
     {
+      $window.setPosition({0,0});
     }
 
   void FSM::event(Event ev) {
@@ -132,31 +133,42 @@ namespace gui {
         state(State::IDLE);
         break;
       case LOOT_SELECT: {
-          int slot_id = std::any_cast<int>(data);
+          $grab_source = std::any_cast<DinkyECS::Entity>(data);
 
-          if(auto world_entity = $loot_ui.select_slot(slot_id)) {
-            $grab_source = $loot_ui.$gui.entity("item_", slot_id);
+          if(auto world_entity = $loot_ui.select_slot(*$grab_source)) {
             auto& source = $loot_ui.get_grabber(*$grab_source);
-
             source.grab($window);
             source.move($router.position);
 
-            $status_ui.select_slot(slot_id, *world_entity);
+            $status_ui.select_slot(*world_entity);
+          } else {
+            // BUG: need a cancel operation here
+            $window.setMouseCursorVisible(true);
+            $grab_source = std::nullopt;
           }
         } break;
       case INV_SELECT: {
-          std::string slot_name = std::any_cast<std::string>(data);
-          int slot_id = $status_ui.place_slot(slot_name);
-          dbc::check(slot_id != -1, "status_ui is trying to place -1 slot_id");
-          if(slot_id != -1) {
-            $loot_ui.remove_slot(slot_id);
+          auto gui_id = std::any_cast<DinkyECS::Entity>(data);
+          dbc::log(fmt::format("INV_SELECT $grab_source null? {} gui_id {}",
+                $grab_source == std::nullopt, gui_id));
+
+          if($grab_source) {
+            auto& drop = $status_ui.$gui.get<guecs::DropTarget>(gui_id);
+
+            if(drop.action()) {
+              $loot_ui.remove_slot(*$grab_source);
+              $grab_source = std::nullopt;
+            }
+
+            $window.setMouseCursorVisible(true);
+            dbc::log("INV_SELECT back to looting");
+            state(State::LOOTING);
           }
-          $window.setMouseCursorVisible(true);
-          state(State::LOOTING);
         } break;
       case MOUSE_CLICK:
         mouse_action(false);
         break;
+      case MOUSE_DRAG:
       case MOUSE_MOVE: {
         if($grab_source) {
           auto& source = $loot_ui.get_grabber(*$grab_source);
@@ -164,16 +176,9 @@ namespace gui {
         }
         mouse_action(true);
        } break;
-      case MOUSE_DRAG_START: {
-          mouse_action(false);
-       } break;
-      case MOUSE_DRAG: {
-        if($grab_source) {
-          auto& source = $loot_ui.get_grabber(*$grab_source);
-          source.move($router.position);
-        }
-        mouse_action(true);
-       } break;
+      case MOUSE_DRAG_START:
+        dbc::check(false, "DRAG START IN LOOT GRAB");
+        break;
       case MOUSE_DROP:
         mouse_action(false);
         break;
@@ -186,6 +191,10 @@ namespace gui {
   }
 
   void FSM::INV_GRAB(Event ev, std::any data) {
+    dbc::log("INV_SELECT NOT IMPlEMENTED");
+    state(State::LOOTING);
+    return;
+
     using enum Event;
     (void)data;
 
@@ -297,9 +306,12 @@ namespace gui {
         auto& data = items["TORCH_BAD"];
 
         for(int i = 0; $loot_ui.contents.size() < 10; i++) {
-          auto torch = $level.world->entity();
-          components::configure_entity(*$level.world, torch, data["components"]);
-          $loot_ui.contents.push_back(torch);
+          auto gui_id = $loot_ui.$gui.entity("item_", i);
+          if(!$loot_ui.contents.contains(gui_id)) {
+            auto torch = $level.world->entity();
+            components::configure_entity(*$level.world, torch, data["components"]);
+            $loot_ui.contents.try_emplace(gui_id, torch);
+          }
         }
 
         $loot_ui.update();
diff --git a/gui/guecstra.cpp b/gui/guecstra.cpp
index cbb1bf3..553c83d 100644
--- a/gui/guecstra.cpp
+++ b/gui/guecstra.cpp
@@ -19,4 +19,18 @@ namespace guecs {
       target.send<Events::GUI>(event, ent, data);
     }};
   }
+
+
+  void GrabSource::grab(sf::RenderWindow& window) {
+    window.setMouseCursorVisible(false);
+    sprite->setOrigin({128, 128});
+  }
+
+  void GrabSource::move(sf::Vector2i position) {
+    sprite->setPosition({
+        float(position.x),
+        float(position.y)
+    });
+  }
+
 }
diff --git a/gui/guecstra.hpp b/gui/guecstra.hpp
index 4ad2d0c..88be0c4 100644
--- a/gui/guecstra.hpp
+++ b/gui/guecstra.hpp
@@ -9,20 +9,11 @@ namespace guecs {
   Clickable make_action(DinkyECS::World& target, Events::GUI event, std::any data);
 
   struct GrabSource : public Sprite {
-    void grab(sf::RenderWindow& window) {
-      window.setMouseCursorVisible(false);
-      sprite->setOrigin({128, 128});
-    }
-
-    void move(sf::Vector2i position) {
-      sprite->setPosition({
-          float(position.x),
-          float(position.y)
-      });
-    }
+    void grab(sf::RenderWindow& window);
+    void move(sf::Vector2i position);
   };
 
   struct DropTarget {
-
+    std::function<bool()> action;
   };
 }
diff --git a/gui/loot_ui.cpp b/gui/loot_ui.cpp
index 1a5d51d..fcb0a25 100644
--- a/gui/loot_ui.cpp
+++ b/gui/loot_ui.cpp
@@ -38,7 +38,7 @@ namespace gui {
           THEME.TRANSPARENT, THEME.LIGHT_MID });
       $gui.set<guecs::Effect>(id, {0.4f, "ui_shader"});
       $gui.set<guecs::Clickable>(id, {
-          guecs::make_action(*$level.world, Events::GUI::LOOT_SELECT, {i})
+          guecs::make_action(*$level.world, Events::GUI::LOOT_SELECT, {id})
       });
     }
 
@@ -46,20 +46,16 @@ namespace gui {
     update();
   }
 
-  std::optional<DinkyECS::Entity> LootUI::select_slot(int slot_id) {
-    if(slot_id >= 0 && size_t(slot_id) < contents.size()) {
+  std::optional<DinkyECS::Entity> LootUI::select_slot(DinkyECS::Entity slot_id) {
+    if(contents.contains(slot_id)) {
       return contents.at(slot_id);
     } else {
       return std::nullopt;
     }
   }
 
-  void LootUI::remove_slot(int slot_id) {
-    dbc::check(size_t(slot_id) < contents.size(),
-        fmt::format("invalid slot id {} give, contents.size={}",
-          slot_id, contents.size()));
-
-    contents.erase(contents.begin() + slot_id);
+  void LootUI::remove_slot(DinkyECS::Entity slot_id) {
+    contents.erase(slot_id);
     update();
   }
 
@@ -73,8 +69,10 @@ namespace gui {
         $gui.remove<guecs::Sprite>(id);
       }
 
-      if(i < contents.size()) {
-        auto item = contents.at(i);
+      if(contents.contains(id)) {
+        auto item = contents.at(id);
+        dbc::check($level.world->has<components::Sprite>(item),
+            "item in inventory UI doesn't exist in world. New level?");
         auto& sprite = $level.world->get<components::Sprite>(item);
         guecs::GrabSource grabber{sprite.name};
         $gui.set_init<guecs::Sprite>(id, grabber);
@@ -82,8 +80,14 @@ namespace gui {
     }
   }
 
-  guecs::GrabSource& LootUI::get_grabber(DinkyECS::Entity entity) {
-    return static_cast<guecs::GrabSource&>($gui.get<guecs::Sprite>(entity));
+  bool LootUI::has_grabber(DinkyECS::Entity gui_id) {
+    return $gui.has<guecs::Sprite>(gui_id);
+  }
+
+  guecs::GrabSource& LootUI::get_grabber(DinkyECS::Entity gui_id) {
+    dbc::check(has_grabber(gui_id), "invalid GrabSource requested, entity isn't in the GUI.");
+
+    return static_cast<guecs::GrabSource&>($gui.get<guecs::Sprite>(gui_id));
   }
 
   void LootUI::render(sf::RenderWindow& window) {
@@ -92,6 +96,7 @@ namespace gui {
 
   void LootUI::update_level(GameLevel &level) {
     $level = level;
+    contents.clear();
     init();
   }
 
diff --git a/gui/loot_ui.hpp b/gui/loot_ui.hpp
index 9367f34..9b3cc95 100644
--- a/gui/loot_ui.hpp
+++ b/gui/loot_ui.hpp
@@ -12,7 +12,7 @@ namespace gui {
       bool active = false;
       guecs::UI $gui;
       GameLevel $level;
-      std::vector<DinkyECS::Entity> contents;
+      std::unordered_map<DinkyECS::Entity, DinkyECS::Entity> contents;
 
       LootUI(GameLevel level);
 
@@ -21,8 +21,9 @@ namespace gui {
       void render(sf::RenderWindow& window);
       void update_level(GameLevel &level);
       bool mouse(float x, float y, bool hover);
-      std::optional<DinkyECS::Entity> select_slot(int slot);
-      void remove_slot(int slot_id);
+      std::optional<DinkyECS::Entity> select_slot(DinkyECS::Entity slot);
+      void remove_slot(DinkyECS::Entity slot_id);
       guecs::GrabSource& get_grabber(DinkyECS::Entity entity);
+      bool has_grabber(DinkyECS::Entity gui_id);
   };
 }
diff --git a/gui/status_ui.cpp b/gui/status_ui.cpp
index cd36a8d..f681a0a 100644
--- a/gui/status_ui.cpp
+++ b/gui/status_ui.cpp
@@ -43,7 +43,10 @@ namespace gui {
         } else {
           $gui.set<Textual>(button, {guecs::to_wstring(name)});
           $gui.set<Clickable>(button, {
-            guecs::make_action(*$level.world, Events::GUI::INV_SELECT, {name})
+            guecs::make_action(*$level.world, Events::GUI::INV_SELECT, {button})
+          });
+          $gui.set<DropTarget>(button, {
+              [&, button]() -> bool { return place_slot(button); }
           });
         }
       }
@@ -80,22 +83,20 @@ namespace gui {
     init();
   }
 
-  void StatusUI::select_slot(int slot_id, DinkyECS::Entity entity) {
-    $selected_slot = slot_id;
+  void StatusUI::select_slot(DinkyECS::Entity entity) {
     $selected_entity = entity;
   }
 
-  int StatusUI::place_slot(const std::string &name) {
-    fmt::println("LOOT slot={}, entity={} PLACE into slot={}",
-        $selected_slot, $selected_entity, name);
-
+  bool StatusUI::place_slot(DinkyECS::Entity gui_id) {
     if($level.world->has<components::Sprite>($selected_entity)) {
       auto& sprite = $level.world->get<components::Sprite>($selected_entity);
-      auto gui_id = $gui.entity(name);
+
       $gui.set_init<guecs::Sprite>(gui_id, {sprite.name});
-      $slots.insert_or_assign(name, $selected_entity);
-    }
 
-    return $selected_slot;
+      $slots.insert_or_assign(gui_id, $selected_entity);
+      return true;
+    } else {
+      return false;
+    }
   }
 }
diff --git a/gui/status_ui.hpp b/gui/status_ui.hpp
index 3be241f..cacc294 100644
--- a/gui/status_ui.hpp
+++ b/gui/status_ui.hpp
@@ -10,10 +10,9 @@ namespace gui {
   class StatusUI {
     public:
       guecs::UI $gui;
-      std::unordered_map<std::string, DinkyECS::Entity> $slots;
+      std::unordered_map<DinkyECS::Entity, DinkyECS::Entity> $slots;
       GameLevel $level;
       ritual::UI $ritual_ui;
-      int $selected_slot = -1;
       DinkyECS::Entity $selected_entity;
 
       StatusUI(GameLevel level);
@@ -23,7 +22,7 @@ namespace gui {
       void init();
       void render(sf::RenderWindow &window);
       void update();
-      void select_slot(int slot_id, DinkyECS::Entity entity);
-      int place_slot(const std::string &name);
+      void select_slot(DinkyECS::Entity entity);
+      bool place_slot(DinkyECS::Entity gui_id);
   };
 }