yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
item_interaction_handler.cc
Go to the documentation of this file.
1// Related header
3
4// C++ standard library
5#include <algorithm>
6
7// Third-party library headers
8#include "absl/strings/str_format.h"
9#include "imgui/imgui.h"
10
11// Project headers
14
15namespace yaze::editor {
16
21
22bool ItemInteractionHandler::HandleClick(int canvas_x, int canvas_y) {
23 if (!HasValidContext())
24 return false;
25
27 if (IsWithinBounds(canvas_x, canvas_y)) {
28 PlaceItemAtPosition(canvas_x, canvas_y);
29 }
30 return true;
31 }
32
33 // Try to select item at position
34 auto item_index = GetEntityAtPosition(canvas_x, canvas_y);
35 if (item_index.has_value()) {
36 SelectItem(*item_index);
37 is_dragging_ = true;
39 ImVec2(static_cast<float>(canvas_x), static_cast<float>(canvas_y));
41 return true;
42 }
43
45 return false;
46}
47
48void ItemInteractionHandler::HandleDrag(ImVec2 current_pos, ImVec2 delta) {
49 if (!is_dragging_ || !selected_item_index_.has_value())
50 return;
51 drag_current_pos_ = current_pos;
52}
53
55 if (!is_dragging_ || !selected_item_index_.has_value()) {
56 is_dragging_ = false;
57 return;
58 }
59
60 auto* room = GetCurrentRoom();
61 if (!room) {
62 is_dragging_ = false;
63 return;
64 }
65
66 const int pixel_x = std::clamp(static_cast<int>(drag_current_pos_.x), 0,
68 const int pixel_y = std::clamp(static_cast<int>(drag_current_pos_.y), 0,
70
71 // PotItem position encoding:
72 // high byte * 16 = Y, low byte * 4 = X
73 const int encoded_x = pixel_x / 4;
74 const int encoded_y = pixel_y / 16;
75 const uint16_t next_position =
76 static_cast<uint16_t>((encoded_y << 8) | encoded_x);
77
78 auto& pot_items = room->GetPotItems();
79 if (*selected_item_index_ < pot_items.size()) {
80 if (pot_items[*selected_item_index_].position == next_position) {
81 is_dragging_ = false;
82 return;
83 }
84
86 pot_items[*selected_item_index_].position = next_position;
87 room->MarkPotItemsDirty();
90 }
91
92 is_dragging_ = false;
93}
94
97 return;
98
99 auto* canvas = ctx_->canvas;
100 const auto pointer_screen_pos = GetPointerScreenPosition();
101 if (!pointer_screen_pos.has_value())
102 return;
103
104 const DungeonCanvasTransform transform = GetCanvasTransform();
105 const auto [canvas_x, canvas_y] =
106 transform.ScreenToRoomPixelCoordinates(*pointer_screen_pos);
107
108 // Snap to 8-pixel grid
109 int snapped_x =
111 int snapped_y =
113
114 // Draw ghost rectangle for item preview
115 const ImVec2 rect_min = transform.RoomPixelsToScreen(
116 ImVec2(static_cast<float>(snapped_x), static_cast<float>(snapped_y)));
117 const ImVec2 rect_size = transform.RoomSizeToScreen(ImVec2(16, 16));
118 const ImVec2 rect_max(rect_min.x + rect_size.x, rect_min.y + rect_size.y);
119
120 const auto& theme = AgentUI::GetTheme();
121 ImVec4 fill_color = theme.dungeon_selection_primary;
122 fill_color.w = 0.35f;
123 ImVec4 outline_color = theme.dungeon_selection_primary;
124 outline_color.w = 0.85f;
125
126 canvas->draw_list()->AddRectFilled(rect_min, rect_max,
127 ImGui::GetColorU32(fill_color));
128 canvas->draw_list()->AddRect(
129 rect_min, rect_max, ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
130
131 // Draw item ID label
132 std::string label = absl::StrFormat("%02X", preview_item_id_);
133 canvas->draw_list()->AddText(rect_min, ImGui::GetColorU32(theme.text_primary),
134 label.c_str());
135}
136
138 if (!selected_item_index_.has_value() || !HasValidContext())
139 return;
140
141 auto* room = GetCurrentRoom();
142 if (!room)
143 return;
144
145 const auto& pot_items = room->GetPotItems();
146 if (*selected_item_index_ >= pot_items.size())
147 return;
148
149 const auto& pot_item = pot_items[*selected_item_index_];
150 int pixel_x = pot_item.GetPixelX();
151 int pixel_y = pot_item.GetPixelY();
152
153 // If dragging, use current drag position
154 if (is_dragging_) {
155 pixel_x = static_cast<int>(drag_current_pos_.x);
156 pixel_y = static_cast<int>(drag_current_pos_.y);
157 // Snap to 8-pixel grid
160 }
161
162 ImDrawList* draw_list = ImGui::GetWindowDrawList();
163 const DungeonCanvasTransform transform = GetCanvasTransform();
164 const float scale = transform.scale();
165 const ImVec2 pos = transform.RoomPixelsToScreen(
166 ImVec2(static_cast<float>(pixel_x), static_cast<float>(pixel_y)));
167 const ImVec2 size = transform.RoomSizeToScreen(ImVec2(16, 16));
168
169 // Animated selection
170 static float pulse = 0.0f;
171 pulse += ImGui::GetIO().DeltaTime * 3.0f;
172 float alpha = 0.5f + 0.3f * sinf(pulse);
173
174 ImU32 color = IM_COL32(255, 255, 0, 180); // Yellow
175 ImU32 fill_color =
176 (color & 0x00FFFFFF) | (static_cast<ImU32>(alpha * 100) << 24);
177
178 draw_list->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y),
179 fill_color);
180 draw_list->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), color, 0.0f,
181 0, 2.0f);
182
183 // Draw label
184 ImVec2 text_pos(pos.x, pos.y - 14 * scale);
185 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 220), "Item");
186}
187
189 int canvas_x, int canvas_y) const {
190 if (!HasValidContext() || !IsWithinBounds(canvas_x, canvas_y))
191 return std::nullopt;
192
193 auto* room = ctx_->GetCurrentRoomConst();
194 if (!room)
195 return std::nullopt;
196
197 // Check pot items
198 const auto& pot_items = room->GetPotItems();
199 for (size_t i = 0; i < pot_items.size(); ++i) {
200 const auto& pot_item = pot_items[i];
201
202 int item_x = pot_item.GetPixelX();
203 int item_y = pot_item.GetPixelY();
204
205 // 16x16 hitbox
206 if (canvas_x >= item_x && canvas_x < item_x + 16 && canvas_y >= item_y &&
207 canvas_y < item_y + 16) {
208 return i;
209 }
210 }
211
212 return std::nullopt;
213}
214
219
221 selected_item_index_ = std::nullopt;
222 is_dragging_ = false;
223}
224
226 if (!selected_item_index_.has_value() || !HasValidContext())
227 return;
228
229 auto* room = GetCurrentRoom();
230 if (!room)
231 return;
232
233 auto& pot_items = room->GetPotItems();
234 if (*selected_item_index_ >= pot_items.size())
235 return;
236
238 pot_items.erase(pot_items.begin() +
239 static_cast<ptrdiff_t>(*selected_item_index_));
240 room->MarkPotItemsDirty();
244}
245
247 if (!HasValidContext()) {
248 return;
249 }
250
251 auto* room = GetCurrentRoom();
252 if (!room || room->GetPotItems().empty()) {
253 return;
254 }
255
257 room->GetPotItems().clear();
258 room->MarkPotItemsDirty();
262}
263
265 int delta_pixel_y) {
266 if (!selected_item_index_.has_value() || !HasValidContext()) {
267 return false;
268 }
269
270 auto* room = GetCurrentRoom();
271 if (!room) {
272 return false;
273 }
274
275 auto& pot_items = room->GetPotItems();
276 if (*selected_item_index_ >= pot_items.size()) {
277 return false;
278 }
279
280 auto& pot_item = pot_items[*selected_item_index_];
281 constexpr int kRoomPixelMax = 511;
282 const int next_pixel_x =
283 std::clamp(pot_item.GetPixelX() + delta_pixel_x, 0, kRoomPixelMax);
284 const int next_pixel_y =
285 std::clamp(pot_item.GetPixelY() + delta_pixel_y, 0, kRoomPixelMax);
286 const int encoded_x = std::clamp(next_pixel_x / 4, 0, 255);
287 const int encoded_y = std::clamp(next_pixel_y / 16, 0, 255);
288 const uint16_t next_position =
289 static_cast<uint16_t>((encoded_y << 8) | encoded_x);
290 if (next_position == pot_item.position) {
291 return false;
292 }
293
295 pot_item.position = next_position;
296 room->MarkPotItemsDirty();
299 return true;
300}
301
302void ItemInteractionHandler::PlaceItemAtPosition(int canvas_x, int canvas_y) {
303 if (!HasValidContext())
304 return;
305
306 auto* room = GetCurrentRoom();
307 if (!room)
308 return;
309
310 int pixel_x = canvas_x;
311 int pixel_y = canvas_y;
312
313 // PotItem position encoding:
314 // high byte * 16 = Y, low byte * 4 = X
315 int encoded_x = pixel_x / 4;
316 int encoded_y = pixel_y / 16;
317
318 // Clamp to valid range
319 encoded_x = std::clamp(encoded_x, 0, 255);
320 encoded_y = std::clamp(encoded_y, 0, 255);
321
323
324 // Create the pot item
325 zelda3::PotItem new_item;
326 new_item.position = static_cast<uint16_t>((encoded_y << 8) | encoded_x);
327 new_item.item = preview_item_id_;
328
329 // Add item to room
330 room->GetPotItems().push_back(new_item);
331 room->MarkPotItemsDirty();
332
334}
335
336} // namespace yaze::editor
std::optional< ImVec2 > GetPointerScreenPosition() const
DungeonCanvasTransform GetCanvasTransform() const
bool IsWithinBounds(int canvas_x, int canvas_y) const
Check if coordinates are within room bounds.
zelda3::Room * GetCurrentRoom() const
Get current room (convenience method)
bool HasValidContext() const
Check if context is valid.
std::pair< int, int > ScreenToRoomPixelCoordinates(ImVec2 screen) const
ImVec2 RoomSizeToScreen(ImVec2 room_size) const
void DrawSelectionHighlight() override
Draw selection highlight for selected entities.
bool HandleClick(int canvas_x, int canvas_y) override
Handle mouse click at canvas position.
void DrawGhostPreview() override
Draw ghost preview during placement.
void BeginPlacement() override
Begin placement mode.
void PlaceItemAtPosition(int canvas_x, int canvas_y)
Place item at position.
void HandleDrag(ImVec2 current_pos, ImVec2 delta) override
Handle mouse drag.
std::optional< size_t > GetEntityAtPosition(int canvas_x, int canvas_y) const override
Get entity at canvas position.
bool NudgeSelected(int delta_pixel_x, int delta_pixel_y)
void HandleRelease() override
Handle mouse release.
void SelectItem(size_t index)
Select item at index.
const std::vector< PotItem > & GetPotItems() const
Definition room.h:366
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
const zelda3::Room * GetCurrentRoomConst() const
Get const pointer to current room.
void NotifyEntityChanged() const
Notify that entity has changed.
void NotifyInvalidateCache(MutationDomain domain=MutationDomain::kUnknown) const
Notify that cache invalidation is needed.
void NotifyMutation(MutationDomain domain=MutationDomain::kUnknown) const
Notify that a mutation is about to happen.
uint16_t position
Definition room.h:108