10#include "absl/strings/str_format.h"
11#include "imgui/imgui.h"
23 const size_t current_sprite_count = room ? room->GetSprites().size() : 0;
46 if (sprite_index.has_value()) {
50 ImVec2(
static_cast<float>(canvas_x),
static_cast<float>(canvas_y));
78 auto [tile_x, tile_y] =
86 const auto& sprites = room->GetSprites();
90 sprite.subtype(), sprite.layer(), sprite.key_drop());
102 if (!pointer_screen_pos.has_value())
106 const auto [canvas_x, canvas_y] =
116 const size_t current_sprite_count = room ? room->GetSprites().size() : 0;
121 ImVec2(
static_cast<float>(snapped_x),
static_cast<float>(snapped_y)));
124 const ImVec2 rect_max(rect_min.x + rect_size.x, rect_min.y + rect_size.y);
127 const ImVec4 base_color =
129 ImVec4 fill_color = base_color;
130 fill_color.w = 0.40f;
131 ImVec4 outline_color = base_color;
132 outline_color.w = 0.85f;
134 canvas->draw_list()->AddRectFilled(rect_min, rect_max,
135 ImGui::GetColorU32(fill_color));
136 canvas->draw_list()->AddRect(
137 rect_min, rect_max, ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
141 canvas->draw_list()->AddText(rect_min, ImGui::GetColorU32(theme.text_primary),
145 if (capacity_state != GhostCapacityState::kNormal &&
146 ImGui::IsMouseHoveringRect(rect_min, rect_max)) {
147 ImGui::SetTooltip(tr(
"Sprites: %zu/%zu\n%s"), current_sprite_count,
152 const std::string badge_text = absl::StrFormat(
155 ImVec2(rect_min.x, rect_max.y + 6.0f), theme,
156 capacity_state, badge_text);
167 const auto& sprites = room->GetSprites();
179 auto [tile_x, tile_y] =
188 ImDrawList* draw_list = ImGui::GetWindowDrawList();
190 const float scale = transform.
scale();
192 ImVec2(
static_cast<float>(pixel_x),
static_cast<float>(pixel_y)));
197 static float pulse = 0.0f;
198 pulse += ImGui::GetIO().DeltaTime * 3.0f;
199 float alpha = 0.5f + 0.3f * sinf(pulse);
201 ImU32 color = IM_COL32(0, 255, 0, 180);
203 (color & 0x00FFFFFF) | (
static_cast<ImU32
>(alpha * 100) << 24);
205 draw_list->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y),
207 draw_list->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), color, 0.0f,
211 ImVec2 text_pos(pos.x, pos.y - 14 * scale);
212 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 220),
"Sprite");
216 int canvas_x,
int canvas_y)
const {
226 for (
size_t i = 0; i < sprites.size(); ++i) {
227 const auto& sprite = sprites[i];
234 if (canvas_x >= sprite_x &&
236 canvas_y >= sprite_y &&
256 const std::vector<zelda3::Sprite>& candidate) {
261 const auto* source_context =
ctx_;
269 "Sprite room changed during edit preflight; repeat the edit");
286 auto candidate = room->GetSprites();
290 candidate.erase(candidate.begin() +
297 room->GetSprites() = std::move(candidate);
298 room->MarkSpritesDirty();
311 if (!room || room->GetSprites().empty()) {
320 room->GetSprites().clear();
321 room->MarkSpritesDirty();
338 auto& sprites = room->GetSprites();
344 const int64_t requested_x =
static_cast<int64_t
>(sprite.x()) + delta_x;
345 const int64_t requested_y =
static_cast<int64_t
>(sprite.y()) + delta_y;
346 const int next_x =
static_cast<int>(
348 const int next_y =
static_cast<int>(
350 if (next_x == sprite.x() && next_y == sprite.y()) {
355 sprite.subtype(), sprite.layer(), sprite.key_drop());
359 uint8_t
id,
int x,
int y,
int subtype,
int layer,
int key_drop) {
360 if (x < 0 || x > 31 || y < 0 || y > 31 || subtype < 0 || subtype > 31 ||
361 layer < 0 || layer > 1 || key_drop < 0 || key_drop > 2) {
362 return absl::InvalidArgumentError(
363 "Sprite fields are outside their ROM ranges");
367 const int first_byte = y | ((subtype & 0x18) << 2) | (layer << 7);
368 if (first_byte == 0xFF) {
369 return absl::InvalidArgumentError(
370 "Lower-layer sprites with subtype 24-31 cannot use Y 31: the ROM "
371 "reads that encoding as the end of the sprite list");
375 if (
id == 0xE4 && x == 0 && (y == 29 || y == 30) && subtype == 24 &&
377 return absl::InvalidArgumentError(
378 "This E4 position/subtype/layer encodes a hidden key marker; use "
379 "the owning sprite's Key drop property");
381 return absl::OkStatus();
385 int y,
int subtype,
int layer,
393 if (!room || index >= room->GetSprites().size()) {
396 const auto sprite = room->GetSprites()[index];
397 if (sprite.id() ==
id && sprite.x() == x && sprite.y() == y &&
398 sprite.subtype() == subtype && sprite.layer() == layer &&
399 sprite.key_drop() == key_drop) {
404 zelda3::Sprite updated(
id,
static_cast<uint8_t
>(x),
static_cast<uint8_t
>(y),
405 static_cast<uint8_t
>(subtype),
406 static_cast<uint8_t
>(layer));
409 auto candidate = room->GetSprites();
410 candidate[index] = std::move(updated);
416 room->GetSprites() = std::move(candidate);
417 room->MarkSpritesDirty();
453 static_cast<uint8_t
>(sprite_y), 0, 0);
454 auto candidate = room->GetSprites();
455 candidate.push_back(std::move(new_sprite));
464 room->GetSprites() = std::move(candidate);
465 room->MarkSpritesDirty();
472 int canvas_x,
int canvas_y)
const {
std::optional< ImVec2 > GetPointerScreenPosition() const
DungeonCanvasTransform GetCanvasTransform() const
void TriggerSuccessToast()
InteractionContext * ctx_
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.
GhostCapacityState GetPlacementGhostCapacityState() const
bool PreflightSprites(const std::vector< zelda3::Sprite > &candidate)
bool UpdateSprite(size_t index, uint8_t id, int x, int y, int subtype, int layer, int key_drop)
bool NudgeSelected(int delta_x, int delta_y)
void PlaceSpriteAtPosition(int canvas_x, int canvas_y)
Place sprite at position.
void ClearSelection()
Clear sprite selection.
PlacementCapacityState GhostCapacityState
void HandleRelease() override
Handle mouse release.
void SelectSprite(size_t index)
Select sprite at index.
std::optional< size_t > selected_sprite_index_
SpriteMutationPreflight sprite_mutation_preflight_
std::function< void(const absl::Status &) mutation_error_callback_)
std::optional< size_t > GetEntityAtPosition(int canvas_x, int canvas_y) const override
Get entity at canvas position.
void DrawGhostPreview() override
Draw ghost preview during placement.
absl::Status mutation_status_
static absl::Status ValidateSpriteProperties(uint8_t id, int x, int y, int subtype, int layer, int key_drop)
uint8_t preview_sprite_id_
bool sprite_placement_mode_
void DrawSelectionHighlight() override
Draw selection highlight for selected entities.
void HandleDrag(ImVec2 current_pos, ImVec2 delta) override
Handle mouse drag.
bool HandleClick(int canvas_x, int canvas_y) override
Handle mouse click at canvas position.
std::pair< int, int > CanvasToSpriteCoords(int canvas_x, int canvas_y) const
Convert canvas to sprite coordinates (16-pixel grid)
void BeginPlacement() override
Begin placement mode.
PlacementBlockReason placement_block_reason_
void DeleteSelected()
Delete selected sprite.
const std::vector< zelda3::Sprite > & GetSprites() const
A class for managing sprites in the overworld and underworld.
auto set_key_drop(int key)
auto set_deleted(bool deleted)
const AgentUITheme & GetTheme()
constexpr int kSpriteGridMax
constexpr int kSpriteTileSize
Editors are the view controllers for the application.
void DrawPlacementCapacityBadge(ImDrawList *draw_list, const ImVec2 &badge_min, const AgentUITheme &theme, PlacementCapacityState state, std::string_view primary_text)
ImVec4 GetPlacementAccentColor(const AgentUITheme &theme, PlacementCapacityState state, const ImVec4 &normal_color)
std::string_view GetPlacementCapacityTooltipSuffix(PlacementCapacityState state)
PlacementCapacityState GetPlacementCapacityState(size_t current_count, size_t max_count)
constexpr size_t kMaxTotalSprites
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.