102 if (!path.has_value()) {
107 status_ = std::string(text.status().message());
112 std::filesystem::create_directories(path->parent_path(), ec);
114 .
subject =
"cutscene shots", .published_file =
"cutscene shots"};
117 status_ = std::string(lock.status().message());
125 std::optional<std::string> on_disk;
127 std::ifstream in(*path, std::ios::binary);
129 on_disk = std::string((std::istreambuf_iterator<char>(in)), {});
134 "%s changed outside yaze since it was loaded. Reload before saving.",
140 if (on_disk.has_value()) {
141 std::vector<core::SourceArtifactUpdate> updates;
143 .
target = *path, .before = on_disk, .after = *text});
145 **lock, std::move(updates),
150 const std::filesystem::path temp =
151 path->parent_path() / (path->filename().string() +
".yaze-new");
153 std::ofstream out(temp, std::ios::binary | std::ios::trunc);
156 status = absl::InternalError(
157 absl::StrFormat(
"Could not write %s", temp.string()));
161 std::filesystem::rename(temp, *path, ec);
163 status = absl::InternalError(absl::StrFormat(
164 "Could not create %s: %s", path->string(), ec.message()));
167 std::filesystem::remove(temp, ec);
170 status_ = std::string(status.message());
389 const float avail = std::max(ImGui::GetContentRegionAvail().x, 128.0f);
390 const float scale = std::min(1.0f, avail /
static_cast<float>(extent.width));
391 const ImVec2 size(extent.width * scale, extent.height * scale);
392 const ImVec2 origin = ImGui::GetCursorScreenPos();
393 ImDrawList* draw = ImGui::GetWindowDrawList();
395 ImGui::InvisibleButton(
"##cutscene_canvas", size);
396 const bool hovered = ImGui::IsItemHovered();
397 const ImVec2 mouse = ImGui::GetIO().MousePos;
398 const int mouse_world_x =
400 static_cast<int>(std::floor((mouse.x - origin.x) / scale));
401 const int mouse_world_y =
403 static_cast<int>(std::floor((mouse.y - origin.y) / scale));
406 draw->AddRectFilled(origin, ImVec2(origin.x + size.x, origin.y + size.y),
407 ToU32(ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)));
408 for (
int seam = 512; seam < extent.width; seam += 512) {
409 draw->AddLine(ImVec2(origin.x + seam * scale, origin.y),
410 ImVec2(origin.x + seam * scale, origin.y + size.y),
413 for (
int seam = 512; seam < extent.height; seam += 512) {
414 draw->AddLine(ImVec2(origin.x, origin.y + seam * scale),
415 ImVec2(origin.x + size.x, origin.y + seam * scale),
420 bool drew_map =
false;
422 const int world_base = shot.
area & 0x40;
423 const int parent_screen =
424 (extent.origin_y / 512) * 8 + extent.origin_x / 512;
425 for (
int dy = 0; dy < extent.height / 512; ++dy) {
426 for (
int dx = 0; dx < extent.width / 512; ++dx) {
428 source->AreaScreenBitmap(world_base + parent_screen + dx + dy * 8);
429 if (bitmap ==
nullptr) {
432 const ImVec2 p0(origin.x + dx * 512 * scale,
433 origin.y + dy * 512 * scale);
434 draw->AddImage((ImTextureID)(intptr_t)bitmap->texture(), p0,
435 ImVec2(p0.x + 512 * scale, p0.y + 512 * scale),
436 ImVec2(0, 0), ImVec2(1, 1),
437 ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
445 const ImVec2 view_min(origin.x + view.x, origin.y + view.y);
446 const ImVec2 view_max(view_min.x + view.width, view_min.y + view.height);
448 auto marker_pos = [&](
int x,
int y) {
450 return ImVec2(origin.x + p.x, origin.y + p.y);
452 auto near = [&](ImVec2 p) {
453 const float dx = mouse.x - p.x;
454 const float dy = mouse.y - p.y;
455 return dx * dx + dy * dy <= (kMarkerRadius + 3) * (kMarkerRadius + 3);
459 if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
461 if (near(marker_pos(shot.
link.
x, shot.
link.
y))) {
464 for (
int i = 0; i < static_cast<int>(shot.
actors.size()); ++i) {
465 if (near(marker_pos(shot.
actors[i].x, shot.
actors[i].y))) {
472 if (
drag_ ==
Drag::kNone && mouse.x >= view_min.x && mouse.x < view_max.x &&
473 mouse.y >= view_min.y && mouse.y < view_max.y) {
477 if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
481 ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) {
482 const ImVec2 delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left, 0);
483 const int dx =
static_cast<int>(std::lround(delta.x / scale));
484 const int dy =
static_cast<int>(std::lround(delta.y / scale));
485 if (dx != 0 || dy != 0) {
486 auto move = [&](
int& x,
int& y) {
487 x = std::clamp(x + dx, 0, 0xFFFF);
488 y = std::clamp(y + dy, 0, 0xFFFF);
501 ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);
507 const int step = ImGui::GetIO().KeyShift ? 8 : 1;
510 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
512 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow))
514 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
516 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
528 auto draw_marker = [&](
int x,
int y, ImVec4 color,
const std::string& label) {
530 const float alpha = inside ? 1.0f : 0.35f;
531 const ImVec2 p = marker_pos(x, y);
532 draw->AddCircleFilled(p, kMarkerRadius, ToU32(color, alpha));
533 draw->AddText(ImVec2(p.x + kMarkerRadius + 2, p.y - 7),
534 ToU32(ImGui::GetStyleColorVec4(ImGuiCol_Text), alpha),
538 absl::StrFormat(
"Link (%d)", shot.
link.
facing));
539 for (
const auto& actor : shot.
actors) {
541 absl::StrFormat(
"0x%02X", actor.sprite));
545 ImGui::Text(
"Mouse: world %d, %d screen %d, %d", mouse_world_x,
546 mouse_world_y, mouse_world_x - camera.x,
547 mouse_world_y - camera.y);
550 "Drag the viewport or a marker; arrow keys nudge "
555 "Outline only: open the Overworld editor to draw the area's screens.");