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->AddLineV(origin.x + seam * scale, origin.y, origin.y + size.y,
412 for (
int seam = 512; seam < extent.height; seam += 512) {
413 draw->AddLineH(origin.x, origin.x + size.x, origin.y + seam * scale,
418 bool drew_map =
false;
420 const int world_base = shot.
area & 0x40;
421 const int parent_screen =
422 (extent.origin_y / 512) * 8 + extent.origin_x / 512;
423 for (
int dy = 0; dy < extent.height / 512; ++dy) {
424 for (
int dx = 0; dx < extent.width / 512; ++dx) {
426 source->AreaScreenBitmap(world_base + parent_screen + dx + dy * 8);
427 if (bitmap ==
nullptr) {
430 const ImVec2 p0(origin.x + dx * 512 * scale,
431 origin.y + dy * 512 * scale);
432 draw->AddImage((ImTextureID)(intptr_t)bitmap->texture(), p0,
433 ImVec2(p0.x + 512 * scale, p0.y + 512 * scale),
434 ImVec2(0, 0), ImVec2(1, 1),
435 ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
443 const ImVec2 view_min(origin.x + view.x, origin.y + view.y);
444 const ImVec2 view_max(view_min.x + view.width, view_min.y + view.height);
446 auto marker_pos = [&](
int x,
int y) {
448 return ImVec2(origin.x + p.x, origin.y + p.y);
450 auto near = [&](ImVec2 p) {
451 const float dx = mouse.x - p.x;
452 const float dy = mouse.y - p.y;
453 return dx * dx + dy * dy <= (kMarkerRadius + 3) * (kMarkerRadius + 3);
457 if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
459 if (near(marker_pos(shot.
link.
x, shot.
link.
y))) {
462 for (
int i = 0; i < static_cast<int>(shot.
actors.size()); ++i) {
463 if (near(marker_pos(shot.
actors[i].x, shot.
actors[i].y))) {
470 if (
drag_ ==
Drag::kNone && mouse.x >= view_min.x && mouse.x < view_max.x &&
471 mouse.y >= view_min.y && mouse.y < view_max.y) {
475 if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
479 ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) {
480 const ImVec2 delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left, 0);
481 const int dx =
static_cast<int>(std::lround(delta.x / scale));
482 const int dy =
static_cast<int>(std::lround(delta.y / scale));
483 if (dx != 0 || dy != 0) {
484 auto move = [&](
int& x,
int& y) {
485 x = std::clamp(x + dx, 0, 0xFFFF);
486 y = std::clamp(y + dy, 0, 0xFFFF);
499 ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);
505 const int step = ImGui::GetIO().KeyShift ? 8 : 1;
508 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
510 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow))
512 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
514 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
526 auto draw_marker = [&](
int x,
int y, ImVec4 color,
const std::string& label) {
528 const float alpha = inside ? 1.0f : 0.35f;
529 const ImVec2 p = marker_pos(x, y);
530 draw->AddCircleFilled(p, kMarkerRadius, ToU32(color, alpha));
531 draw->AddText(ImVec2(p.x + kMarkerRadius + 2, p.y - 7),
532 ToU32(ImGui::GetStyleColorVec4(ImGuiCol_Text), alpha),
536 absl::StrFormat(
"Link (%d)", shot.
link.
facing));
537 for (
const auto& actor : shot.
actors) {
539 absl::StrFormat(
"0x%02X", actor.sprite));
543 ImGui::Text(
"Mouse: world %d, %d screen %d, %d", mouse_world_x,
544 mouse_world_y, mouse_world_x - camera.x,
545 mouse_world_y - camera.y);
548 "Drag the viewport or a marker; arrow keys nudge "
553 "Outline only: open the Overworld editor to draw the area's screens.");
::yaze::project::YazeProject * current_project()
Get the current project instance.