258 UpdateWelcomeAccentPalette();
270 ImVec2 mouse_pos = ImGui::GetMousePos();
272 bool action_taken =
false;
275 ImGuiViewport* viewport = ImGui::GetMainViewport();
276 ImVec2 viewport_size = viewport->WorkSize;
281 if (dockspace_width < 200.0f) {
282 dockspace_x = viewport->WorkPos.x;
283 dockspace_width = viewport_size.x;
285 float dockspace_center_x = dockspace_x + dockspace_width / 2.0f;
286 float dockspace_center_y = viewport->WorkPos.y + viewport_size.y / 2.0f;
287 ImVec2 center(dockspace_center_x, dockspace_center_y);
291 const float font_scale = ImGui::GetFontSize() / 16.0f;
292 float width = std::clamp(dockspace_width * 0.70f, 560.0f * font_scale,
293 kWelcomeCardMaxWidth * font_scale);
294 float height = std::clamp(viewport_size.y * 0.68f, 400.0f * font_scale,
295 kWelcomeCardMaxHeight * font_scale);
299 width = std::min(width, std::max(1.0f, dockspace_width - 24.0f));
300 height = std::min(height, std::max(1.0f, viewport_size.y - 24.0f));
302 ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
303 ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
306 ImGuiWindowFlags window_flags =
307 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
308 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
309 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings;
315 const ImVec2 ambient_padding = ImGui::GetStyle().WindowPadding;
316 bool window_visible =
false;
319 ImGuiStyleVar_WindowPadding,
320 ImVec2(ambient_padding.x + 8.0f, ambient_padding.y + 6.0f));
325 window_visible = ImGui::Begin(
"##WelcomeScreen", p_open, window_flags);
328 if (window_visible) {
332 if (p_open !=
nullptr &&
333 ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
334 ImGui::IsKeyPressed(ImGuiKey_Escape,
false)) {
338 ImDrawList* bg_draw_list = ImGui::GetWindowDrawList();
339 ImVec2 window_pos = ImGui::GetWindowPos();
340 ImVec2 window_size = ImGui::GetWindowSize();
343 struct TriforceConfig {
347 float repel_distance;
350 TriforceConfig triforce_configs[] = {
351 {0.08f, 0.12f, 36.0f, 0.025f, 50.0f},
352 {0.92f, 0.15f, 34.0f, 0.022f, 50.0f},
353 {0.06f, 0.88f, 32.0f, 0.020f, 45.0f},
354 {0.94f, 0.85f, 34.0f, 0.023f, 50.0f},
355 {0.50f, 0.08f, 38.0f, 0.028f, 55.0f},
356 {0.50f, 0.92f, 32.0f, 0.020f, 45.0f},
362 float x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
363 float y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
376 for (
int i = 0; triforces_visible && i <
kNumTriforces; ++i) {
378 float base_x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
379 float base_y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
383 float time_offset = i * 1.2f;
384 float float_speed_x =
386 float float_speed_y =
388 float float_amount_x = (20.0f + (i % 2) * 10.0f) *
390 float float_amount_y =
394 float float_x = std::sin(
animation_time_ * float_speed_x + time_offset) *
403 float dist = std::sqrt(dx * dx + dy * dy);
411 target_pos.x += float_x;
412 target_pos.y += float_y;
417 float dir_x = dx / dist;
418 float dir_y = dy / dist;
421 float normalized_dist = dist / repel_radius;
422 float repel_strength = (1.0f - normalized_dist * normalized_dist) *
423 triforce_configs[i].repel_distance;
425 target_pos.x += dir_x * repel_strength;
426 target_pos.y += dir_y * repel_strength;
439 float adjusted_alpha =
441 if (adjusted_alpha < (1.0f / 255.0f)) {
444 float adjusted_size =
447 adjusted_size, adjusted_alpha, 0.0f);
467 float angle = (rand() % 360) * (
M_PI / 180.0f);
468 float speed = 20.0f + (rand() % 40);
470 ImVec2(std::cos(angle) * speed, std::sin(angle) * speed);
484 float dt = ImGui::GetIO().DeltaTime;
505 ImU32 particle_color = ImGui::GetColorU32(
506 ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, alpha));
507 bg_draw_list->AddCircleFilled(
particles_[i].position,
519 ImDrawList* draw_list = ImGui::GetWindowDrawList();
520 DrawAccentRule(draw_list);
522 ImGui::Dummy(ImVec2(0, ImGui::GetStyle().ItemSpacing.y));
527 const float footer_gap = ImGui::GetStyle().ItemSpacing.y;
528 const float footer_height = ImGui::GetFrameHeight() + 3.0f * footer_gap;
530 "WelcomeContent", ImVec2(0, -footer_height),
false,
531 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
532 const float content_width = ImGui::GetContentRegionAvail().x;
533 const float content_height = ImGui::GetContentRegionAvail().y;
534 const float layout_scale = ImGui::GetFontSize() / 16.0f;
535 const bool stacked_layout =
538 if (stacked_layout) {
548 std::clamp(280.0f * layout_scale, 260.0f * layout_scale,
549 std::min(320.0f * layout_scale, content_width * 0.42f));
551 "LeftPanel", ImVec2(left_width, 0),
false,
552 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
557 ImGui::SameLine(0.0f, 16.0f);
560 "RightPanel", ImVec2(0, 0),
false,
561 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
568 DrawAccentRule(draw_list);
569 ImGui::Dummy(ImVec2(0, footer_gap));
589 ImDrawList* draw_list = ImGui::GetWindowDrawList();
594 float header_alpha = header_progress;
595 float header_offset_y = (1.0f - header_progress) * 20.0f;
597 if (header_progress < 0.001f) {
600 const float title_line = ImGui::GetTextLineHeight() * 2.0f;
601 ImGui::Dummy(ImVec2(0, title_line + ImGui::GetTextLineHeightWithSpacing()));
610 constexpr float kTitleFontScale = 2.0f;
613 const float base_font_size = ImGui::GetStyle().FontSizeBase > 0.0f
614 ? ImGui::GetStyle().FontSizeBase
615 : ImGui::GetFontSize();
616 ImGui::PushFont(
nullptr, base_font_size * kTitleFontScale);
620 const float window_width = ImGui::GetWindowSize().x;
621 const float title_width = ImGui::CalcTextSize(title).x;
622 const float xPos = (window_width - title_width) * 0.5f;
625 ImVec2 cursor_pos = ImGui::GetCursorPos();
626 ImGui::SetCursorPos(ImVec2(xPos, cursor_pos.y - header_offset_y));
627 ImVec2 text_pos = ImGui::GetCursorScreenPos();
641 const float title_height = ImGui::GetTextLineHeight();
642 const ImVec4 window_bg = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
643 const float bg_luma =
644 0.299f * window_bg.x + 0.587f * window_bg.y + 0.114f * window_bg.z;
645 const bool light_ground = bg_luma > 0.5f;
647 ImVec4 glow = kTriforceGold;
648 float glow_alpha = 0.15f;
655 glow.w = glow_alpha * header_alpha;
656 const float glow_radius = title_height * 0.9f;
657 draw_list->AddCircleFilled(
658 ImVec2(text_pos.x + title_width * 0.5f, text_pos.y + title_height * 0.5f),
659 glow_radius, ImGui::GetColorU32(glow), 32);
662 ImVec4 title_color = kTriforceGold;
663 title_color.w *= header_alpha;
664 ImGui::TextColored(title_color,
"%s", title);
670 float subtitle_alpha = subtitle_progress;
673 const char* subtitle =
"Yet Another Zelda3 Editor";
674 const float subtitle_width = ImGui::CalcTextSize(subtitle).x;
675 ImGui::SetCursorPosX((window_width - subtitle_width) * 0.5f);
678 ImVec4(text_secondary.x, text_secondary.y, text_secondary.z,
679 text_secondary.w * subtitle_alpha),
715 float actions_alpha = actions_progress;
716 float actions_offset_x =
717 (1.0f - actions_progress) * -30.0f;
719 if (actions_progress < 0.001f) {
726 float indent = std::max(0.0f, -actions_offset_x);
728 ImGui::Indent(indent);
731 ImGui::TextColored(SectionHeadingColor(),
ICON_MD_BOLT " Start");
734 const float scale = ImGui::GetFontSize() / 16.0f;
743 const float density =
745 const float frame_height = ImGui::GetFrameHeight();
746 const float button_height = std::max(34.0f * density, frame_height * 1.5f);
747 const float secondary_height =
748 std::max(28.0f * density, frame_height * 1.25f);
749 const float action_width = ImGui::GetContentRegionAvail().x;
750 float button_width = action_width;
757 const float gap = ImGui::GetStyle().ItemSpacing.y;
758 const float heading_h = ImGui::GetTextLineHeightWithSpacing();
759 const float required_primary =
760 heading_h + gap + button_height + gap + button_height;
761 float budget = ImGui::GetContentRegionAvail().y - required_primary;
762 const float resume_cost = gap + secondary_height;
763 const float no_rom_label_cost = gap + heading_h;
764 const float no_rom_button_cost = gap + secondary_height;
766 bool show_resume = budget >= resume_cost;
768 budget -= resume_cost;
774 budget >= no_rom_label_cost + no_rom_count * no_rom_button_cost;
779 constexpr const char* open_tooltip =
ICON_MD_INFO " Open .sfc/.smc ROMs";
782 constexpr const char* open_tooltip =
783 ICON_MD_INFO " Open .sfc/.smc ROMs and .yaze/.yazeproj project files";
786 "welcome_screen",
"open_rom_or_project") &&
790 if (ImGui::IsItemHovered()) {
791 ImGui::SetTooltip(
"%s", open_tooltip);
797 ImVec2(button_width, button_height),
"welcome_screen",
802 if (ImGui::IsItemHovered()) {
805 " Create a new project for metadata, labels, and workflow settings");
814 const std::string resume_label = absl::StrFormat(
816 const std::string resume_path = last_recent->
filepath;
818 ImVec2(button_width, secondary_height),
819 "welcome_screen",
"resume_recent")) {
822 if (ImGui::IsItemHovered()) {
823 ImGui::SetTooltip(
"%s", resume_path.c_str());
832 const bool has_both_secondary = show_no_rom &&
835 const bool inline_secondary =
836 has_both_secondary && action_width >= 420.0f * scale;
837 const float secondary_width =
838 inline_secondary ? (button_width - ImGui::GetStyle().ItemSpacing.x) * 0.5f
844 ImVec2(secondary_width, secondary_height),
845 "welcome_screen",
"prototype_research")) {
848 if (ImGui::IsItemHovered()) {
851 " Open the Graphics editor for CGX, SCR, COL, BIN, and clipboard "
852 "work without loading a ROM");
854 if (inline_secondary) {
860 if (!inline_secondary) {
864 ImVec2(secondary_width, secondary_height),
865 "welcome_screen",
"assembly_editor")) {
868 if (ImGui::IsItemHovered()) {
871 " Open files or a folder for assembly work without loading a ROM");
877 ImGui::Unindent(indent);
901 if (recent_progress < 0.001f) {
909 const float header_spacing = ImGui::GetStyle().ItemSpacing.x;
911 ImGui::GetStyle().FramePadding.x * 2.0f;
913 ImGui::GetStyle().FramePadding.x * 2.0f;
914 const float total_width = manage_width + clear_width + header_spacing;
917 const float start_x = ImGui::GetCursorPosX();
918 const float right_edge = start_x + ImGui::GetContentRegionAvail().x;
919 const float button_start = std::max(start_x, right_edge - total_width);
920 ImGui::SetCursorPosX(button_start);
924 ImGui::BeginDisabled();
931 if (ImGui::IsItemHovered()) {
932 ImGui::SetTooltip(
"%s", tr(
"Manage projects"));
935 ImGui::EndDisabled();
937 ImGui::SameLine(0.0f, header_spacing);
942 if (ImGui::IsItemHovered()) {
943 ImGui::SetTooltip(
"%s", tr(
"Clear recent list"));
953 ImGui::TextWrapped(tr(
"No recent files yet. Open a ROM to begin."));
957 const float scale = ImGui::GetFontSize() / 16.0f;
958 const float row_height = std::max(44.0f, kRecentRowBaseHeight * scale);
959 const float row_gap = ImGui::GetStyle().ItemSpacing.y;
960 const float avail_h = ImGui::GetContentRegionAvail().y;
961 const float more_line = ImGui::GetTextLineHeightWithSpacing();
963 const size_t visible =
static_cast<size_t>(
965 row_height, row_gap, more_line));
966 for (
size_t i = 0; i < visible; ++i) {
968 ImVec2(ImGui::GetContentRegionAvail().x, row_height));
970 if (entries.size() > visible) {
973 ImGui::Text(tr(
"+%zu more in Manage"), entries.size() - visible);
1089 if (pending.path.empty())
1097 ImVec4 bg = warning_bg;
1100 const ImVec2 avail = ImGui::GetContentRegionAvail();
1101 const float row_height = ImGui::GetFrameHeight() + 6.0f;
1102 const ImVec2 cursor = ImGui::GetCursorScreenPos();
1103 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1104 draw_list->AddRectFilled(cursor,
1105 ImVec2(cursor.x + avail.x, cursor.y + row_height),
1106 ImGui::GetColorU32(bg), 4.0f);
1112 ImGui::SetCursorScreenPos(
1113 ImVec2(cursor.x + 8.0f,
1114 cursor.y + (row_height - ImGui::GetTextLineHeight()) * 0.5f));
1117 ImGui::Text(tr(
"Removed \"%s\""), pending.display_name.c_str());
1121 const float undo_width = ImGui::CalcTextSize(
ICON_MD_UNDO " Undo").x +
1122 ImGui::GetStyle().FramePadding.x * 2.0f;
1123 const float dismiss_width = ImGui::CalcTextSize(
ICON_MD_CLOSE).x +
1124 ImGui::GetStyle().FramePadding.x * 2.0f;
1125 const float spacing = ImGui::GetStyle().ItemSpacing.x;
1126 const float button_row = undo_width + dismiss_width + spacing;
1127 const float right_edge =
1128 ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x;
1129 ImGui::SetCursorPosX(
1130 std::max(ImGui::GetCursorPosX(), right_edge - button_row - 4.0f));
1136 ImGui::SameLine(0.0f, spacing);
1140 ImGui::Dummy(ImVec2(0, 2.0f));
1144 const ImVec2& card_size) {
1147 ImGui::PushID(index);
1152 const ImVec2 resolved_card_size = card_size;
1155 ImVec4 accent = kTriforceGold;
1158 }
else if (project.
item_type ==
"ROM") {
1159 accent = kHyruleGreen;
1160 }
else if (project.
item_type ==
"Project") {
1161 accent = kMasterSwordBlue;
1174 const ImVec4 theme_hovered =
1175 ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered);
1176 const ImVec4 theme_active = ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive);
1177 const ImVec4 kNoFill(0.0f, 0.0f, 0.0f, 0.0f);
1179 {ImGuiCol_HeaderHovered, kNoFill},
1180 {ImGuiCol_HeaderActive, kNoFill}});
1181 const bool is_activated = ImGui::Selectable(
1182 "##ProjectPanel",
false, ImGuiSelectableFlags_AllowDoubleClick,
1183 resolved_card_size);
1184 const bool is_hovered = ImGui::IsItemHovered();
1185 const bool is_held = ImGui::IsItemActive();
1186 const ImVec2 cursor_pos = ImGui::GetItemRectMin();
1187 const ImVec2 item_max = ImGui::GetItemRectMax();
1189 if (ImGui::BeginPopupContextItem(
"ProjectPanelMenu")) {
1194 const std::string new_path =
1196 if (!new_path.empty() && new_path != project.
filepath) {
1200 if (ImGui::IsItemHovered()) {
1201 ImGui::SetTooltip(tr(
1202 "Point at the new location for this file. Pin/rename/notes are "
1205 }
else if (can_open) {
1212 ImGui::BeginDisabled();
1214 ImGui::EndDisabled();
1228 ? project.
name.c_str()
1235 project.
notes.c_str());
1239 ImGui::SetClipboardText(project.
filepath.c_str());
1243 " Remove from Recents")) {
1249 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1251 constexpr float kCardRounding = 6.0f;
1255 surface = theme_active;
1256 }
else if (is_hovered) {
1257 surface = theme_hovered;
1259 draw_list->AddRectFilled(cursor_pos, item_max, ImGui::GetColorU32(surface),
1263 ? ImVec4(kHeartRed.x, kHeartRed.y, kHeartRed.z, 0.7f)
1264 : ImGui::GetStyleColorVec4(ImGuiCol_Border);
1265 draw_list->AddRect(cursor_pos, item_max, ImGui::GetColorU32(border),
1266 kCardRounding, 0, 1.0f);
1268 draw_list->AddRectFilled(cursor_pos,
1269 ImVec2(cursor_pos.x + 3.0f, item_max.y),
1270 ImGui::GetColorU32(accent), 2.0f);
1276 const float row_scale = ImGui::GetFontSize() / 16.0f;
1277 const float padding_x = 10.0f * row_scale;
1278 const float padding_y = 6.0f * row_scale;
1280 const float icon_radius =
1281 std::max(8.0f * row_scale, ImGui::GetTextLineHeight() * 0.7f);
1282 const float row_h = item_max.y - cursor_pos.y;
1283 const ImVec2 icon_center(cursor_pos.x + padding_x + icon_radius,
1284 cursor_pos.y + row_h * 0.5f);
1285 draw_list->AddCircleFilled(icon_center, icon_radius,
1286 ImGui::GetColorU32(accent), 20);
1288 const char* item_icon = project.
item_icon.empty()
1291 const ImVec2 icon_size = ImGui::CalcTextSize(item_icon);
1292 draw_list->AddText(ImVec2(icon_center.x - icon_size.x * 0.5f,
1293 icon_center.y - icon_size.y * 0.5f),
1294 ImGui::GetColorU32(text_primary), item_icon);
1296 const std::string badge_text =
1298 const ImVec2 badge_text_size = ImGui::CalcTextSize(badge_text.c_str());
1299 const float badge_pad_x = 5.0f;
1300 const float badge_pad_y = 1.0f;
1301 const ImVec2 badge_min(
1302 item_max.x - padding_x - badge_text_size.x - (badge_pad_x * 2.0f),
1303 cursor_pos.y + (row_h - badge_text_size.y - badge_pad_y * 2.0f) * 0.5f);
1304 const ImVec2 badge_max(
1305 badge_min.x + badge_text_size.x + (badge_pad_x * 2.0f),
1306 badge_min.y + badge_text_size.y + (badge_pad_y * 2.0f));
1307 draw_list->AddRectFilled(
1308 badge_min, badge_max,
1309 ImGui::GetColorU32(ImVec4(accent.x, accent.y, accent.z, 0.22f)), 3.0f);
1311 const float content_x = icon_center.x + icon_radius + 10.0f;
1312 const float content_right = badge_min.x - 8.0f;
1313 const float text_max_w = std::max(60.0f, content_right - content_x);
1314 const float line_h = ImGui::GetTextLineHeight();
1315 const float text_block_h = line_h * 2.0f + 2.0f;
1317 cursor_pos.y + std::max(padding_y, (row_h - text_block_h) * 0.5f);
1319 const std::string raw_name =
1323 const std::string visible_name = EllipsizeText(raw_name, text_max_w);
1324 draw_list->AddText(ImVec2(content_x, text_y),
1325 ImGui::GetColorU32(text_primary), visible_name.c_str());
1327 text_y += line_h + 2.0f;
1328 const std::string secondary =
1333 draw_list->AddText(ImVec2(content_x, text_y),
1334 ImGui::GetColorU32(text_secondary),
1335 EllipsizeText(secondary, text_max_w).c_str());
1337 ImVec2(badge_min.x + badge_pad_x, badge_min.y + badge_pad_y),
1338 ImGui::GetColorU32(text_primary), badge_text.c_str());
1346 ImGui::BeginTooltip();
1347 ImGui::TextUnformatted(project.
filepath.c_str());
1349 ImGui::TextColored(kTriforceGold,
1352 ImGui::TextColored(kHeartRed,
1355 ImGui::EndTooltip();