5#include "absl/strings/str_format.h"
9#include "imgui/imgui.h"
44 "Rectangle mode used for single object selection");
53 int room_min_x,
int room_min_y,
int room_max_x,
int room_max_y,
54 const std::vector<zelda3::RoomObject>& objects,
SelectionMode mode) {
56 int min_x = std::min(room_min_x, room_max_x);
57 int max_x = std::max(room_min_x, room_max_x);
58 int min_y = std::min(room_min_y, room_max_y);
59 int max_y = std::max(room_min_y, room_max_y);
67 for (
size_t i = 0; i < objects.size(); ++i) {
88 for (
size_t i = 0; i < object_count; ++i) {
95 const std::vector<zelda3::RoomObject>& objects) {
97 for (
size_t i = 0; i < objects.size(); ++i) {
121 std::vector<size_t> result;
124 result.push_back(idx);
151 "UpdateRectangleSelection called when not active");
160 const std::vector<zelda3::RoomObject>& objects,
SelectionMode mode) {
163 "EndRectangleSelection called when not active");
168 auto [start_room_x, start_room_y] =
170 auto [end_room_x, end_room_y] =
190 return {min_x, min_y, max_x, max_y};
199 const int width = std::abs(max_x - min_x);
200 const int height = std::abs(max_y - min_y);
201 return (width >= min_pixels) || (height >= min_pixels);
209 gui::Canvas* canvas,
const std::vector<zelda3::RoomObject>& objects,
216 ImDrawList* draw_list = ImGui::GetWindowDrawList();
219 const float scale = transform.
scale();
222 if (index >= objects.size()) {
226 const auto&
object = objects[index];
234 int pixel_height = 0;
235 if (bounds_calculator) {
236 auto [dx, dy, w, h] = bounds_calculator(
object);
243 auto [tile_x, tile_y, tile_width, tile_height] =
GetObjectBounds(
object);
244 offset_x = (tile_x -
object.x_) * 8;
245 offset_y = (tile_y -
object.y_) * 8;
246 pixel_width = tile_width * 8;
247 pixel_height = tile_height * 8;
254 ImVec2(
static_cast<float>(obj_x),
static_cast<float>(obj_y)));
256 static_cast<float>(pixel_width),
static_cast<float>(pixel_height)));
257 ImVec2 obj_end(obj_start.x + obj_size.x, obj_start.y + obj_size.y);
260 constexpr float margin = 2.0f;
261 obj_start.x -= margin;
262 obj_start.y -= margin;
271 0.6f + 0.4f * std::sin(
static_cast<float>(ImGui::GetTime()) * 6.0f);
272 ImVec4 pulsing_color = theme.selection_primary;
273 pulsing_color.w = 0.4f + 0.4f * pulse;
275 ImU32 border_color = ImGui::GetColorU32(theme.selection_primary);
276 ImU32 pulse_color_u32 = ImGui::GetColorU32(pulsing_color);
278 draw_list->AddRect(obj_start, obj_end, border_color, 0.0f, 0, 2.0f);
279 draw_list->AddRect(obj_start, obj_end, pulse_color_u32, 0.0f, 0, 4.0f);
282 float handle_size = 6.0f * std::max(1.0f, scale * 0.5f);
283 ImU32 handle_color = ImGui::GetColorU32(theme.selection_handle);
284 ImU32 handle_outline = ImGui::GetColorU32(ImVec4(0, 0, 0, 0.8f));
286 auto draw_handle = [&](ImVec2 pos) {
287 draw_list->AddRectFilled(
288 ImVec2(pos.x - handle_size / 2, pos.y - handle_size / 2),
289 ImVec2(pos.x + handle_size / 2, pos.y + handle_size / 2),
292 ImVec2(pos.x - handle_size / 2, pos.y - handle_size / 2),
293 ImVec2(pos.x + handle_size / 2, pos.y + handle_size / 2),
294 handle_outline, 2.0f, 0, 1.0f);
297 draw_handle(obj_start);
298 draw_handle(ImVec2(obj_end.x, obj_start.y));
299 draw_handle(ImVec2(obj_start.x, obj_end.y));
300 draw_handle(obj_end);
307 int layer =
object.GetLayerValue();
311 return theme.dungeon_outline_layer0;
313 return theme.dungeon_outline_layer1;
315 return theme.dungeon_outline_layer2;
317 return theme.status_inactive;
327 ImDrawList* draw_list = ImGui::GetWindowDrawList();
335 ImVec2(
static_cast<float>(min_x),
static_cast<float>(min_y)));
337 ImVec2(
static_cast<float>(max_x),
static_cast<float>(max_y)));
341 ImU32 border_color = ImGui::ColorConvertFloat4ToU32(
342 ImVec4(theme.selection_secondary.x, theme.selection_secondary.y,
343 theme.selection_secondary.z, 0.85f));
345 ImU32 fill_color = ImGui::ColorConvertFloat4ToU32(
346 ImVec4(theme.selection_secondary.x, theme.selection_secondary.y,
347 theme.selection_secondary.z, 0.15f));
349 draw_list->AddRectFilled(box_start, box_end, fill_color);
350 draw_list->AddRect(box_start, box_end, border_color, 0.0f, 0, 2.0f);
360 return {room_x * 8, room_y * 8};
366 return {canvas_x / 8, canvas_y / 8};
385 int min_x,
int min_y,
int max_x,
397 int obj_min_x = obj_x;
398 int obj_max_x = obj_x + obj_width - 1;
399 int obj_min_y = obj_y;
400 int obj_max_y = obj_y + obj_height - 1;
403 bool x_overlap = (obj_min_x <= max_x) && (obj_max_x >= min_x);
404 bool y_overlap = (obj_min_y <= max_y) && (obj_max_y >= min_y);
406 return x_overlap && y_overlap;
418 return object.GetLayerValue() ==
kLayer2;
bool IsObjectInRectangle(const zelda3::RoomObject &object, int min_x, int min_y, int max_x, int max_y) const
std::tuple< int, int, int, int > GetRectangleSelectionBounds() const
Get rectangle selection bounds in canvas coordinates.
void UpdateRectangleSelection(int canvas_x, int canvas_y)
Update rectangle selection endpoint.
bool rectangle_selection_active_
static std::pair< int, int > RoomToCanvasCoordinates(int room_x, int room_y)
Convert room tile coordinates to canvas pixel coordinates.
void DrawSelectionHighlights(gui::Canvas *canvas, const std::vector< zelda3::RoomObject > &objects, std::function< std::tuple< int, int, int, int >(const zelda3::RoomObject &)> bounds_calculator)
Draw selection highlights for all selected objects.
std::function< void()> selection_changed_callback_
static constexpr int kMaskLayer
bool IsObjectSelected(size_t index) const
Check if an object is selected.
std::set< size_t > selected_indices_
std::vector< size_t > GetSelectedIndices() const
Get all selected object indices.
static std::tuple< int, int, int, int > GetObjectBounds(const zelda3::RoomObject &object)
Calculate the bounding box of an object.
bool IsRectangleLargeEnough(int min_pixels) const
Check if rectangle selection exceeds a minimum pixel size.
void SelectAll(size_t object_count)
Select all objects in the current room.
static std::pair< int, int > CanvasToRoomCoordinates(int canvas_x, int canvas_y)
Convert canvas pixel coordinates to room tile coordinates.
void SelectObject(size_t index, SelectionMode mode=SelectionMode::Single)
Select a single object by index.
void ClearSelection()
Clear all selections.
void EndRectangleSelection(const std::vector< zelda3::RoomObject > &objects, SelectionMode mode=SelectionMode::Single)
Complete rectangle selection operation.
void NotifySelectionChanged()
void BeginRectangleSelection(int canvas_x, int canvas_y)
Begin a rectangle selection operation.
bool PassesLayerFilter(const zelda3::RoomObject &object) const
static constexpr int kLayer2
ImVec4 GetLayerTypeColor(const zelda3::RoomObject &object) const
Get selection highlight color based on object layer and type.
static constexpr int kLayerAll
void CancelRectangleSelection()
Cancel rectangle selection without modifying selection.
std::optional< size_t > GetPrimarySelection() const
Get the primary selected object (first in selection)
void DrawRectangleSelectionBox(gui::Canvas *canvas)
Draw the active rectangle selection box.
void SelectObjectsInRect(int room_min_x, int room_min_y, int room_max_x, int room_max_y, const std::vector< zelda3::RoomObject > &objects, SelectionMode mode=SelectionMode::Single)
Select multiple objects within a rectangle.
Modern, robust canvas for drawing and manipulating graphics.
auto global_scale() const
static DimensionService & Get()
std::tuple< int, int, int, int > GetHitTestBounds(const RoomObject &obj) const
#define LOG_ERROR(category, format,...)
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.