259 UpdateWelcomeAccentPalette();
271 ImVec2 mouse_pos = ImGui::GetMousePos();
273 bool action_taken =
false;
276 ImGuiViewport* viewport = ImGui::GetMainViewport();
277 ImVec2 viewport_size = viewport->WorkSize;
282 if (dockspace_width < 200.0f) {
283 dockspace_x = viewport->WorkPos.x;
284 dockspace_width = viewport_size.x;
286 float dockspace_center_x = dockspace_x + dockspace_width / 2.0f;
287 float dockspace_center_y = viewport->WorkPos.y + viewport_size.y / 2.0f;
288 ImVec2 center(dockspace_center_x, dockspace_center_y);
292 const float font_scale = ImGui::GetFontSize() / 16.0f;
293 float width = std::clamp(dockspace_width * 0.70f, 560.0f * font_scale,
294 kWelcomeCardMaxWidth * font_scale);
295 float height = std::clamp(viewport_size.y * 0.68f, 400.0f * font_scale,
296 kWelcomeCardMaxHeight * font_scale);
300 width = std::min(width, std::max(1.0f, dockspace_width - 24.0f));
301 height = std::min(height, std::max(1.0f, viewport_size.y - 24.0f));
303 ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
304 ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
307 ImGuiWindowFlags window_flags =
308 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
309 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
310 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings;
316 const ImVec2 ambient_padding = ImGui::GetStyle().WindowPadding;
317 bool window_visible =
false;
320 ImGuiStyleVar_WindowPadding,
321 ImVec2(ambient_padding.x + 8.0f, ambient_padding.y + 6.0f));
326 window_visible = ImGui::Begin(
"##WelcomeScreen", p_open, window_flags);
329 if (window_visible) {
333 if (p_open !=
nullptr &&
334 ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
335 ImGui::IsKeyPressed(ImGuiKey_Escape,
false)) {
339 ImDrawList* bg_draw_list = ImGui::GetWindowDrawList();
340 ImVec2 window_pos = ImGui::GetWindowPos();
341 ImVec2 window_size = ImGui::GetWindowSize();
344 struct TriforceConfig {
348 float repel_distance;
351 TriforceConfig triforce_configs[] = {
352 {0.08f, 0.12f, 36.0f, 0.025f, 50.0f},
353 {0.92f, 0.15f, 34.0f, 0.022f, 50.0f},
354 {0.06f, 0.88f, 32.0f, 0.020f, 45.0f},
355 {0.94f, 0.85f, 34.0f, 0.023f, 50.0f},
356 {0.50f, 0.08f, 38.0f, 0.028f, 55.0f},
357 {0.50f, 0.92f, 32.0f, 0.020f, 45.0f},
363 float x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
364 float y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
377 for (
int i = 0; triforces_visible && i <
kNumTriforces; ++i) {
379 float base_x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
380 float base_y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
384 float time_offset = i * 1.2f;
385 float float_speed_x =
387 float float_speed_y =
389 float float_amount_x = (20.0f + (i % 2) * 10.0f) *
391 float float_amount_y =
395 float float_x = std::sin(
animation_time_ * float_speed_x + time_offset) *
404 float dist = std::sqrt(dx * dx + dy * dy);
412 target_pos.x += float_x;
413 target_pos.y += float_y;
418 float dir_x = dx / dist;
419 float dir_y = dy / dist;
422 float normalized_dist = dist / repel_radius;
423 float repel_strength = (1.0f - normalized_dist * normalized_dist) *
424 triforce_configs[i].repel_distance;
426 target_pos.x += dir_x * repel_strength;
427 target_pos.y += dir_y * repel_strength;
440 float adjusted_alpha =
442 if (adjusted_alpha < (1.0f / 255.0f)) {
445 float adjusted_size =
448 adjusted_size, adjusted_alpha, 0.0f);
468 float angle = (rand() % 360) * (
M_PI / 180.0f);
469 float speed = 20.0f + (rand() % 40);
471 ImVec2(std::cos(angle) * speed, std::sin(angle) * speed);
485 float dt = ImGui::GetIO().DeltaTime;
506 ImU32 particle_color = ImGui::GetColorU32(
507 ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, alpha));
508 bg_draw_list->AddCircleFilled(
particles_[i].position,
520 ImDrawList* draw_list = ImGui::GetWindowDrawList();
521 DrawAccentRule(draw_list);
523 ImGui::Dummy(ImVec2(0, ImGui::GetStyle().ItemSpacing.y));
528 const float footer_gap = ImGui::GetStyle().ItemSpacing.y;
529 const float footer_height = ImGui::GetFrameHeight() + 3.0f * footer_gap;
531 "WelcomeContent", ImVec2(0, -footer_height),
false,
532 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
533 const float content_width = ImGui::GetContentRegionAvail().x;
534 const float content_height = ImGui::GetContentRegionAvail().y;
535 const float layout_scale = ImGui::GetFontSize() / 16.0f;
536 const bool stacked_layout =
539 if (stacked_layout) {
549 std::clamp(280.0f * layout_scale, 260.0f * layout_scale,
550 std::min(320.0f * layout_scale, content_width * 0.42f));
552 "LeftPanel", ImVec2(left_width, 0),
false,
553 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
558 ImGui::SameLine(0.0f, 16.0f);
561 "RightPanel", ImVec2(0, 0),
false,
562 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
569 DrawAccentRule(draw_list);
570 ImGui::Dummy(ImVec2(0, footer_gap));
590 ImDrawList* draw_list = ImGui::GetWindowDrawList();
595 float header_alpha = header_progress;
596 float header_offset_y = (1.0f - header_progress) * 20.0f;
598 if (header_progress < 0.001f) {
601 const float title_line = ImGui::GetTextLineHeight() * 2.0f;
602 ImGui::Dummy(ImVec2(0, title_line + ImGui::GetTextLineHeightWithSpacing()));
611 constexpr float kTitleFontScale = 2.0f;
614 const float base_font_size = ImGui::GetStyle().FontSizeBase > 0.0f
615 ? ImGui::GetStyle().FontSizeBase
616 : ImGui::GetFontSize();
617 ImGui::PushFont(
nullptr, base_font_size * kTitleFontScale);
621 const float window_width = ImGui::GetWindowSize().x;
622 const float title_width = ImGui::CalcTextSize(title).x;
623 const float xPos = (window_width - title_width) * 0.5f;
626 ImVec2 cursor_pos = ImGui::GetCursorPos();
627 ImGui::SetCursorPos(ImVec2(xPos, cursor_pos.y - header_offset_y));
628 ImVec2 text_pos = ImGui::GetCursorScreenPos();
642 const float title_height = ImGui::GetTextLineHeight();
643 const ImVec4 window_bg = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
644 const float bg_luma =
645 0.299f * window_bg.x + 0.587f * window_bg.y + 0.114f * window_bg.z;
646 const bool light_ground = bg_luma > 0.5f;
648 ImVec4 glow = kTriforceGold;
649 float glow_alpha = 0.15f;
656 glow.w = glow_alpha * header_alpha;
657 const float glow_radius = title_height * 0.9f;
658 draw_list->AddCircleFilled(
659 ImVec2(text_pos.x + title_width * 0.5f, text_pos.y + title_height * 0.5f),
660 glow_radius, ImGui::GetColorU32(glow), 32);
663 ImVec4 title_color = kTriforceGold;
664 title_color.w *= header_alpha;
665 ImGui::TextColored(title_color,
"%s", title);
671 float subtitle_alpha = subtitle_progress;
674 const char* subtitle =
"Yet Another Zelda3 Editor";
675 const float subtitle_width = ImGui::CalcTextSize(subtitle).x;
676 ImGui::SetCursorPosX((window_width - subtitle_width) * 0.5f);
679 ImVec4(text_secondary.x, text_secondary.y, text_secondary.z,
680 text_secondary.w * subtitle_alpha),
716 float actions_alpha = actions_progress;
717 float actions_offset_x =
718 (1.0f - actions_progress) * -30.0f;
720 if (actions_progress < 0.001f) {
727 float indent = std::max(0.0f, -actions_offset_x);
729 ImGui::Indent(indent);
732 ImGui::TextColored(SectionHeadingColor(),
ICON_MD_BOLT " Start");
735 const float scale = ImGui::GetFontSize() / 16.0f;
744 const float density =
746 const float frame_height = ImGui::GetFrameHeight();
747 const float button_height = std::max(34.0f * density, frame_height * 1.5f);
748 const float secondary_height =
749 std::max(28.0f * density, frame_height * 1.25f);
750 const float action_width = ImGui::GetContentRegionAvail().x;
751 float button_width = action_width;
758 const float gap = ImGui::GetStyle().ItemSpacing.y;
759 const float heading_h = ImGui::GetTextLineHeightWithSpacing();
760 const float required_primary =
761 heading_h + gap + button_height + gap + button_height;
762 float budget = ImGui::GetContentRegionAvail().y - required_primary;
763 const float resume_cost = gap + secondary_height;
764 const float no_rom_label_cost = gap + heading_h;
765 const float no_rom_button_cost = gap + secondary_height;
767 bool show_resume = budget >= resume_cost;
769 budget -= resume_cost;
775 budget >= no_rom_label_cost + no_rom_count * no_rom_button_cost;
780 constexpr const char* open_tooltip =
ICON_MD_INFO " Open .sfc/.smc ROMs";
783 constexpr const char* open_tooltip =
784 ICON_MD_INFO " Open .sfc/.smc ROMs and .yaze/.yazeproj project files";
787 "welcome_screen",
"open_rom_or_project") &&
791 if (ImGui::IsItemHovered()) {
792 ImGui::SetTooltip(
"%s", open_tooltip);
798 ImVec2(button_width, button_height),
"welcome_screen",
803 if (ImGui::IsItemHovered()) {
806 " Create a new project for metadata, labels, and workflow settings");
815 const std::string resume_label = absl::StrFormat(
817 const std::string resume_path = last_recent->
filepath;
819 ImVec2(button_width, secondary_height),
820 "welcome_screen",
"resume_recent")) {
823 if (ImGui::IsItemHovered()) {
824 ImGui::SetTooltip(
"%s", resume_path.c_str());
833 const bool has_both_secondary = show_no_rom &&
836 const bool inline_secondary =
837 has_both_secondary && action_width >= 420.0f * scale;
838 const float secondary_width =
839 inline_secondary ? (button_width - ImGui::GetStyle().ItemSpacing.x) * 0.5f
845 ImVec2(secondary_width, secondary_height),
846 "welcome_screen",
"prototype_research")) {
849 if (ImGui::IsItemHovered()) {
852 " Open the Graphics editor for CGX, SCR, COL, BIN, and clipboard "
853 "work without loading a ROM");
855 if (inline_secondary) {
861 if (!inline_secondary) {
865 ImVec2(secondary_width, secondary_height),
866 "welcome_screen",
"assembly_editor")) {
869 if (ImGui::IsItemHovered()) {
872 " Open files or a folder for assembly work without loading a ROM");
878 ImGui::Unindent(indent);
902 if (recent_progress < 0.001f) {
910 const float header_spacing = ImGui::GetStyle().ItemSpacing.x;
912 ImGui::GetStyle().FramePadding.x * 2.0f;
914 ImGui::GetStyle().FramePadding.x * 2.0f;
915 const float total_width = manage_width + clear_width + header_spacing;
918 const float start_x = ImGui::GetCursorPosX();
919 const float right_edge = start_x + ImGui::GetContentRegionAvail().x;
920 const float button_start = std::max(start_x, right_edge - total_width);
921 ImGui::SetCursorPosX(button_start);
925 ImGui::BeginDisabled();
932 if (ImGui::IsItemHovered()) {
933 ImGui::SetTooltip(
"%s", tr(
"Manage projects"));
936 ImGui::EndDisabled();
938 ImGui::SameLine(0.0f, header_spacing);
943 if (ImGui::IsItemHovered()) {
944 ImGui::SetTooltip(
"%s", tr(
"Clear recent list"));
954 ImGui::TextWrapped(tr(
"No recent files yet. Open a ROM to begin."));
958 const float scale = ImGui::GetFontSize() / 16.0f;
959 const float row_height = std::max(44.0f, kRecentRowBaseHeight * scale);
960 const float row_gap = ImGui::GetStyle().ItemSpacing.y;
961 const float avail_h = ImGui::GetContentRegionAvail().y;
962 const float more_line = ImGui::GetTextLineHeightWithSpacing();
964 const size_t visible =
static_cast<size_t>(
966 row_height, row_gap, more_line));
967 for (
size_t i = 0; i < visible; ++i) {
969 ImVec2(ImGui::GetContentRegionAvail().x, row_height));
971 if (entries.size() > visible) {
974 ImGui::Text(tr(
"+%zu more in Manage"), entries.size() - visible);
1090 if (pending.path.empty())
1098 ImVec4 bg = warning_bg;
1101 const ImVec2 avail = ImGui::GetContentRegionAvail();
1102 const float row_height = ImGui::GetFrameHeight() + 6.0f;
1103 const ImVec2 cursor = ImGui::GetCursorScreenPos();
1104 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1105 draw_list->AddRectFilled(cursor,
1106 ImVec2(cursor.x + avail.x, cursor.y + row_height),
1107 ImGui::GetColorU32(bg), 4.0f);
1113 ImGui::SetCursorScreenPos(
1114 ImVec2(cursor.x + 8.0f,
1115 cursor.y + (row_height - ImGui::GetTextLineHeight()) * 0.5f));
1118 ImGui::Text(tr(
"Removed \"%s\""), pending.display_name.c_str());
1122 const float undo_width = ImGui::CalcTextSize(
ICON_MD_UNDO " Undo").x +
1123 ImGui::GetStyle().FramePadding.x * 2.0f;
1124 const float dismiss_width = ImGui::CalcTextSize(
ICON_MD_CLOSE).x +
1125 ImGui::GetStyle().FramePadding.x * 2.0f;
1126 const float spacing = ImGui::GetStyle().ItemSpacing.x;
1127 const float button_row = undo_width + dismiss_width + spacing;
1128 const float right_edge =
1129 ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x;
1130 ImGui::SetCursorPosX(
1131 std::max(ImGui::GetCursorPosX(), right_edge - button_row - 4.0f));
1137 ImGui::SameLine(0.0f, spacing);
1141 ImGui::Dummy(ImVec2(0, 2.0f));
1145 const ImVec2& card_size) {
1148 ImGui::PushID(index);
1153 const ImVec2 resolved_card_size = card_size;
1156 ImVec4 accent = kTriforceGold;
1159 }
else if (project.
item_type ==
"ROM") {
1160 accent = kHyruleGreen;
1161 }
else if (project.
item_type ==
"Project") {
1162 accent = kMasterSwordBlue;
1175 const ImVec4 theme_hovered =
1176 ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered);
1177 const ImVec4 theme_active = ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive);
1178 const ImVec4 kNoFill(0.0f, 0.0f, 0.0f, 0.0f);
1180 {ImGuiCol_HeaderHovered, kNoFill},
1181 {ImGuiCol_HeaderActive, kNoFill}});
1182 const bool is_activated = ImGui::Selectable(
1183 "##ProjectPanel",
false, ImGuiSelectableFlags_AllowDoubleClick,
1184 resolved_card_size);
1185 const bool is_hovered = ImGui::IsItemHovered();
1186 const bool is_held = ImGui::IsItemActive();
1187 const ImVec2 cursor_pos = ImGui::GetItemRectMin();
1188 const ImVec2 item_max = ImGui::GetItemRectMax();
1191 std::vector<gui::MenuItemSpec> items;
1195 gui::MenuItemSpec locate(
"Locate...", ICON_MD_SEARCH, [&]() {
1196 const std::string new_path =
1197 util::FileDialogWrapper::ShowOpenFileDialog();
1198 if (!new_path.empty() && new_path != project.filepath) {
1199 recent_projects_model_.RelinkRecent(project.filepath, new_path);
1203 tr(
"Point at the new location for this file. Pin/rename/notes are "
1205 items.push_back(std::move(locate));
1206 }
else if (can_open) {
1216 items.back().separator_after =
true;
1227 ? project.
name.c_str()
1230 items.emplace_back(
"Edit Notes...",
ICON_MD_NOTE, [&]() {
1234 project.
notes.c_str());
1236 items.back().separator_after =
true;
1242 items.back().separator_after =
true;
1246 auto remove_recent = [&]() {
1259 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1261 constexpr float kCardRounding = 6.0f;
1265 surface = theme_active;
1266 }
else if (is_hovered) {
1267 surface = theme_hovered;
1269 draw_list->AddRectFilled(cursor_pos, item_max, ImGui::GetColorU32(surface),
1273 ? ImVec4(kHeartRed.x, kHeartRed.y, kHeartRed.z, 0.7f)
1274 : ImGui::GetStyleColorVec4(ImGuiCol_Border);
1275 draw_list->AddRect(cursor_pos, item_max, ImGui::GetColorU32(border),
1276 kCardRounding, 0, 1.0f);
1278 draw_list->AddRectFilled(cursor_pos,
1279 ImVec2(cursor_pos.x + 3.0f, item_max.y),
1280 ImGui::GetColorU32(accent), 2.0f);
1286 const float row_scale = ImGui::GetFontSize() / 16.0f;
1287 const float padding_x = 10.0f * row_scale;
1288 const float padding_y = 6.0f * row_scale;
1290 const float icon_radius =
1291 std::max(8.0f * row_scale, ImGui::GetTextLineHeight() * 0.7f);
1292 const float row_h = item_max.y - cursor_pos.y;
1293 const ImVec2 icon_center(cursor_pos.x + padding_x + icon_radius,
1294 cursor_pos.y + row_h * 0.5f);
1295 draw_list->AddCircleFilled(icon_center, icon_radius,
1296 ImGui::GetColorU32(accent), 20);
1298 const char* item_icon = project.
item_icon.empty()
1301 const ImVec2 icon_size = ImGui::CalcTextSize(item_icon);
1302 draw_list->AddText(ImVec2(icon_center.x - icon_size.x * 0.5f,
1303 icon_center.y - icon_size.y * 0.5f),
1304 ImGui::GetColorU32(text_primary), item_icon);
1306 const std::string badge_text =
1308 const ImVec2 badge_text_size = ImGui::CalcTextSize(badge_text.c_str());
1309 const float badge_pad_x = 5.0f;
1310 const float badge_pad_y = 1.0f;
1311 const ImVec2 badge_min(
1312 item_max.x - padding_x - badge_text_size.x - (badge_pad_x * 2.0f),
1313 cursor_pos.y + (row_h - badge_text_size.y - badge_pad_y * 2.0f) * 0.5f);
1314 const ImVec2 badge_max(
1315 badge_min.x + badge_text_size.x + (badge_pad_x * 2.0f),
1316 badge_min.y + badge_text_size.y + (badge_pad_y * 2.0f));
1317 draw_list->AddRectFilled(
1318 badge_min, badge_max,
1319 ImGui::GetColorU32(ImVec4(accent.x, accent.y, accent.z, 0.22f)), 3.0f);
1321 const float content_x = icon_center.x + icon_radius + 10.0f;
1322 const float content_right = badge_min.x - 8.0f;
1323 const float text_max_w = std::max(60.0f, content_right - content_x);
1324 const float line_h = ImGui::GetTextLineHeight();
1325 const float text_block_h = line_h * 2.0f + 2.0f;
1327 cursor_pos.y + std::max(padding_y, (row_h - text_block_h) * 0.5f);
1329 const std::string raw_name =
1333 const std::string visible_name = EllipsizeText(raw_name, text_max_w);
1334 draw_list->AddText(ImVec2(content_x, text_y),
1335 ImGui::GetColorU32(text_primary), visible_name.c_str());
1337 text_y += line_h + 2.0f;
1338 const std::string secondary =
1343 draw_list->AddText(ImVec2(content_x, text_y),
1344 ImGui::GetColorU32(text_secondary),
1345 EllipsizeText(secondary, text_max_w).c_str());
1347 ImVec2(badge_min.x + badge_pad_x, badge_min.y + badge_pad_y),
1348 ImGui::GetColorU32(text_primary), badge_text.c_str());
1356 ImGui::BeginTooltip();
1357 ImGui::TextUnformatted(project.
filepath.c_str());
1359 ImGui::TextColored(kTriforceGold,
1362 ImGui::TextColored(kHeartRed,
1365 ImGui::EndTooltip();
1376void WelcomeScreen::DrawFooterBar() {
1381 kEntryStaggerDelay);
1382 if (progress < 0.001f) {
1387 const ImGuiStyle& style = ImGui::GetStyle();
1391 ImGui::TextColored(text_secondary,
"%s", version.c_str());
1392 ImGui::SameLine(0.0f, style.ItemSpacing.x);
1395 constexpr char kReleaseNotesUrl[] =
1396 "https://github.com/scawful/yaze/blob/master/docs/public/"
1398 release_notes_open_failed_ = !
gui::OpenUrl(kReleaseNotesUrl);
1400 if (ImGui::IsItemHovered()) {
1401 ImGui::SetTooltip(
"%s", tr(
"Open the release notes in your browser"));
1404 if (coming_from_zscream_callback_) {
1405 ImGui::SameLine(0.0f, style.ItemSpacing.x);
1407 coming_from_zscream_callback_();
1409 if (ImGui::IsItemHovered()) {
1411 "%s", tr(
"Map familiar ZScream tools to yaze editors and projects"));
1415 const std::string close_label =
1417 const float close_width =
1418 ImGui::CalcTextSize(close_label.c_str()).x + 2.0f * style.FramePadding.x;
1423 const std::string notice =
1424 release_notes_open_failed_
1426 tr(
"Could not open the browser; see "
1427 "docs/public/release-notes.md"))
1430 ImGui::SameLine(0.0f, style.ItemSpacing.x);
1431 const float notice_start = ImGui::GetCursorPosX();
1432 const float right_edge = notice_start + ImGui::GetContentRegionAvail().x;
1433 const float notice_width =
1434 right_edge - close_width - style.ItemSpacing.x - notice_start;
1435 if (!notice.empty() && notice_width > 0.0f) {
1436 const std::string visible = EllipsizeText(notice, notice_width);
1439 "%s", visible.c_str());
1440 if (visible != notice && ImGui::IsItemHovered()) {
1441 ImGui::SetTooltip(
"%s", notice.c_str());
1444 if (notice_width > 0.0f) {
1445 ImGui::SameLine(right_edge - close_width);
1447 if (ImGui::SmallButton(close_label.c_str())) {
1448 manually_closed_ =
true;