yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
welcome_screen.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <cmath>
6#include <filesystem>
7#include <fstream>
8
9#include "absl/strings/str_format.h"
10#include "absl/time/clock.h"
11#include "absl/time/time.h"
12#include "app/gui/core/icons.h"
14#include "app/platform/timing.h"
15#include "core/project.h"
16#include "imgui/imgui.h"
17#include "imgui/imgui_internal.h"
18#include "util/file_util.h"
19
20#ifndef M_PI
21#define M_PI 3.14159265358979323846
22#endif
23
24namespace yaze {
25namespace editor {
26
27namespace {
28
29// Get Zelda-inspired colors from theme or use fallback
30ImVec4 GetThemedColor(const char* color_name, const ImVec4& fallback) {
31 auto& theme_mgr = gui::ThemeManager::Get();
32 const auto& theme = theme_mgr.GetCurrentTheme();
33
34 // TODO: Fix this
35 // Map color names to theme colors
36 // if (strcmp(color_name, "triforce_gold") == 0) {
37 // return theme.accent.to_im_vec4();
38 // } else if (strcmp(color_name, "hyrule_green") == 0) {
39 // return theme.success.to_im_vec4();
40 // } else if (strcmp(color_name, "master_sword_blue") == 0) {
41 // return theme.info.to_im_vec4();
42 // } else if (strcmp(color_name, "ganon_purple") == 0) {
43 // return theme.secondary.to_im_vec4();
44 // } else if (strcmp(color_name, "heart_red") == 0) {
45 // return theme.error.to_im_vec4();
46 // } else if (strcmp(color_name, "spirit_orange") == 0) {
47 // return theme.warning.to_im_vec4();
48 // }
49
50 return fallback;
51}
52
53// Zelda-inspired color palette (fallbacks)
54const ImVec4 kTriforceGoldFallback = ImVec4(1.0f, 0.843f, 0.0f, 1.0f);
55const ImVec4 kHyruleGreenFallback = ImVec4(0.133f, 0.545f, 0.133f, 1.0f);
56const ImVec4 kMasterSwordBlueFallback = ImVec4(0.196f, 0.6f, 0.8f, 1.0f);
57const ImVec4 kGanonPurpleFallback = ImVec4(0.502f, 0.0f, 0.502f, 1.0f);
58const ImVec4 kHeartRedFallback = ImVec4(0.863f, 0.078f, 0.235f, 1.0f);
59const ImVec4 kSpiritOrangeFallback = ImVec4(1.0f, 0.647f, 0.0f, 1.0f);
60const ImVec4 kShadowPurpleFallback = ImVec4(0.416f, 0.353f, 0.804f, 1.0f);
61
62// Active colors (updated each frame from theme)
70
72 const std::filesystem::file_time_type& ftime) {
73 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
74 ftime - std::filesystem::file_time_type::clock::now() +
75 std::chrono::system_clock::now());
76 auto now = std::chrono::system_clock::now();
77 auto diff = std::chrono::duration_cast<std::chrono::hours>(now - sctp);
78
79 int hours = diff.count();
80 if (hours < 24) {
81 return "Today";
82 } else if (hours < 48) {
83 return "Yesterday";
84 } else if (hours < 168) {
85 int days = hours / 24;
86 return absl::StrFormat("%d days ago", days);
87 } else if (hours < 720) {
88 int weeks = hours / 168;
89 return absl::StrFormat("%d week%s ago", weeks, weeks > 1 ? "s" : "");
90 } else {
91 int months = hours / 720;
92 return absl::StrFormat("%d month%s ago", months, months > 1 ? "s" : "");
93 }
94}
95
96// Draw a pixelated triforce in the background (ALTTP style)
97void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size,
98 float alpha, float glow) {
99 // Make it pixelated - round size to nearest 4 pixels
100 size = std::round(size / 4.0f) * 4.0f;
101
102 // Calculate triangle points with pixel-perfect positioning
103 auto triangle = [&](ImVec2 center, float s, ImU32 color) {
104 // Round to pixel boundaries for crisp edges
105 float half_s = s / 2.0f;
106 float tri_h = s * 0.866f; // Height of equilateral triangle
107
108 // Fixed: Proper equilateral triangle with apex at top
109 ImVec2 p1(std::round(center.x),
110 std::round(center.y - tri_h / 2.0f)); // Top apex
111 ImVec2 p2(std::round(center.x - half_s),
112 std::round(center.y + tri_h / 2.0f)); // Bottom left
113 ImVec2 p3(std::round(center.x + half_s),
114 std::round(center.y + tri_h / 2.0f)); // Bottom right
115
116 draw_list->AddTriangleFilled(p1, p2, p3, color);
117 };
118
119 ImU32 gold = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha));
120
121 // Proper triforce layout with three triangles
122 float small_size = size / 2.0f;
123 float small_height = small_size * 0.866f;
124
125 // Top triangle (centered above)
126 triangle(ImVec2(pos.x, pos.y), small_size, gold);
127
128 // Bottom left triangle
129 triangle(ImVec2(pos.x - small_size / 2.0f, pos.y + small_height), small_size,
130 gold);
131
132 // Bottom right triangle
133 triangle(ImVec2(pos.x + small_size / 2.0f, pos.y + small_height), small_size,
134 gold);
135}
136
137} // namespace
138
142
143bool WelcomeScreen::Show(bool* p_open) {
144 // Update theme colors each frame
145 kTriforceGold = GetThemedColor("triforce_gold", kTriforceGoldFallback);
146 kHyruleGreen = GetThemedColor("hyrule_green", kHyruleGreenFallback);
147 kMasterSwordBlue =
148 GetThemedColor("master_sword_blue", kMasterSwordBlueFallback);
149 kGanonPurple = GetThemedColor("ganon_purple", kGanonPurpleFallback);
150 kHeartRed = GetThemedColor("heart_red", kHeartRedFallback);
151 kSpiritOrange = GetThemedColor("spirit_orange", kSpiritOrangeFallback);
152
154
155 // Get mouse position for interactive triforce movement
156 ImVec2 mouse_pos = ImGui::GetMousePos();
157
158 bool action_taken = false;
159
160 // Center the window within the dockspace region (accounting for sidebars)
161 ImGuiViewport* viewport = ImGui::GetMainViewport();
162 ImVec2 viewport_size = viewport->WorkSize;
163
164 // Calculate the dockspace region (excluding sidebars)
165 float dockspace_x = viewport->WorkPos.x + left_offset_;
166 float dockspace_width = viewport_size.x - left_offset_ - right_offset_;
167 float dockspace_center_x = dockspace_x + dockspace_width / 2.0f;
168 float dockspace_center_y = viewport->WorkPos.y + viewport_size.y / 2.0f;
169 ImVec2 center(dockspace_center_x, dockspace_center_y);
170
171 // Size based on dockspace region, not full viewport
172 float width = std::min(dockspace_width * 0.85f, 1400.0f);
173 float height = std::min(viewport_size.y * 0.85f, 900.0f);
174
175 ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
176 ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
177
178 // CRITICAL: Override ImGui's saved window state from imgui.ini
179 // Without this, ImGui will restore the last saved state (hidden/collapsed)
180 // even when our logic says the window should be visible
182 ImGui::SetNextWindowCollapsed(false); // Force window to be expanded
183 // Don't steal focus - allow menu bar to remain clickable
184 first_show_attempt_ = false;
185 }
186
187 // Window flags: allow menu bar to be clickable by not bringing to front
188 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse |
189 ImGuiWindowFlags_NoResize |
190 ImGuiWindowFlags_NoMove |
191 ImGuiWindowFlags_NoBringToFrontOnFocus |
192 ImGuiWindowFlags_NoTitleBar;
193
194 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20, 20));
195
196 if (ImGui::Begin("##WelcomeScreen", p_open, window_flags)) {
197 ImDrawList* bg_draw_list = ImGui::GetWindowDrawList();
198 ImVec2 window_pos = ImGui::GetWindowPos();
199 ImVec2 window_size = ImGui::GetWindowSize();
200
201 // Interactive scattered triforces (react to mouse position)
202 struct TriforceConfig {
203 float x_pct, y_pct; // Base position (percentage of window)
204 float size;
205 float alpha;
206 float repel_distance; // How far they move away from mouse
207 };
208
209 TriforceConfig triforce_configs[] = {
210 {0.08f, 0.12f, 36.0f, 0.025f, 50.0f}, // Top left corner
211 {0.92f, 0.15f, 34.0f, 0.022f, 50.0f}, // Top right corner
212 {0.06f, 0.88f, 32.0f, 0.020f, 45.0f}, // Bottom left
213 {0.94f, 0.85f, 34.0f, 0.023f, 50.0f}, // Bottom right
214 {0.50f, 0.08f, 38.0f, 0.028f, 55.0f}, // Top center
215 {0.50f, 0.92f, 32.0f, 0.020f, 45.0f}, // Bottom center
216 };
217
218 // Initialize base positions on first frame
220 for (int i = 0; i < kNumTriforces; ++i) {
221 float x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
222 float y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
223 triforce_base_positions_[i] = ImVec2(x, y);
225 }
227 }
228
229 // Update triforce positions based on mouse interaction + floating animation
230 for (int i = 0; i < kNumTriforces; ++i) {
231 // Update base position in case window moved/resized
232 float base_x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
233 float base_y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
234 triforce_base_positions_[i] = ImVec2(base_x, base_y);
235
236 // Slow, subtle floating animation
237 float time_offset = i * 1.2f; // Offset each triforce's animation
238 float float_speed_x =
239 (0.15f + (i % 2) * 0.1f) * triforce_speed_multiplier_; // Very slow
240 float float_speed_y =
241 (0.12f + ((i + 1) % 2) * 0.08f) * triforce_speed_multiplier_;
242 float float_amount_x = (20.0f + (i % 2) * 10.0f) *
243 triforce_size_multiplier_; // Smaller amplitude
244 float float_amount_y =
245 (25.0f + ((i + 1) % 2) * 15.0f) * triforce_size_multiplier_;
246
247 // Create gentle orbital motion
248 float float_x = std::sin(animation_time_ * float_speed_x + time_offset) *
249 float_amount_x;
250 float float_y =
251 std::cos(animation_time_ * float_speed_y + time_offset * 1.2f) *
252 float_amount_y;
253
254 // Calculate distance from mouse
255 float dx = triforce_base_positions_[i].x - mouse_pos.x;
256 float dy = triforce_base_positions_[i].y - mouse_pos.y;
257 float dist = std::sqrt(dx * dx + dy * dy);
258
259 // Calculate repulsion offset with stronger effect
260 ImVec2 target_pos = triforce_base_positions_[i];
261 float repel_radius =
262 200.0f; // Larger radius for more visible interaction
263
264 // Add floating motion to base position
265 target_pos.x += float_x;
266 target_pos.y += float_y;
267
268 // Apply mouse repulsion if enabled
269 if (triforce_mouse_repel_enabled_ && dist < repel_radius && dist > 0.1f) {
270 // Normalize direction away from mouse
271 float dir_x = dx / dist;
272 float dir_y = dy / dist;
273
274 // Much stronger repulsion when closer with exponential falloff
275 float normalized_dist = dist / repel_radius;
276 float repel_strength = (1.0f - normalized_dist * normalized_dist) *
277 triforce_configs[i].repel_distance;
278
279 target_pos.x += dir_x * repel_strength;
280 target_pos.y += dir_y * repel_strength;
281 }
282
283 // Smooth interpolation to target position (faster response)
284 // Use TimingManager for accurate delta time
285 float lerp_speed = 8.0f * yaze::TimingManager::Get().GetDeltaTime();
286 triforce_positions_[i].x +=
287 (target_pos.x - triforce_positions_[i].x) * lerp_speed;
288 triforce_positions_[i].y +=
289 (target_pos.y - triforce_positions_[i].y) * lerp_speed;
290
291 // Draw at current position with alpha multiplier
292 float adjusted_alpha =
293 triforce_configs[i].alpha * triforce_alpha_multiplier_;
294 float adjusted_size =
295 triforce_configs[i].size * triforce_size_multiplier_;
296 DrawTriforceBackground(bg_draw_list, triforce_positions_[i],
297 adjusted_size, adjusted_alpha, 0.0f);
298 }
299
300 // Update and draw particle system
301 if (particles_enabled_) {
302 // Spawn new particles
303 static float spawn_accumulator = 0.0f;
304 spawn_accumulator += ImGui::GetIO().DeltaTime * particle_spawn_rate_;
305 while (spawn_accumulator >= 1.0f &&
307 // Find inactive particle slot
308 for (int i = 0; i < kMaxParticles; ++i) {
309 if (particles_[i].lifetime <= 0.0f) {
310 // Spawn from random triforce
311 int source_triforce = rand() % kNumTriforces;
312 particles_[i].position = triforce_positions_[source_triforce];
313
314 // Random direction and speed
315 float angle = (rand() % 360) * (M_PI / 180.0f);
316 float speed = 20.0f + (rand() % 40);
318 ImVec2(std::cos(angle) * speed, std::sin(angle) * speed);
319
320 particles_[i].size = 2.0f + (rand() % 4);
321 particles_[i].alpha = 0.4f + (rand() % 40) / 100.0f;
322 particles_[i].max_lifetime = 2.0f + (rand() % 30) / 10.0f;
325 break;
326 }
327 }
328 spawn_accumulator -= 1.0f;
329 }
330
331 // Update and draw particles
332 float dt = ImGui::GetIO().DeltaTime;
333 for (int i = 0; i < kMaxParticles; ++i) {
334 if (particles_[i].lifetime > 0.0f) {
335 // Update lifetime
336 particles_[i].lifetime -= dt;
337 if (particles_[i].lifetime <= 0.0f) {
339 continue;
340 }
341
342 // Update position
343 particles_[i].position.x += particles_[i].velocity.x * dt;
344 particles_[i].position.y += particles_[i].velocity.y * dt;
345
346 // Fade out near end of life
347 float life_ratio =
349 float alpha =
351
352 // Draw particle as small golden circle
353 ImU32 particle_color =
354 ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha));
355 bg_draw_list->AddCircleFilled(particles_[i].position,
356 particles_[i].size, particle_color, 8);
357 }
358 }
359 }
360
361 DrawHeader();
362
363 ImGui::Spacing();
364 ImGui::Spacing();
365
366 // Main content area with subtle gradient separator
367 ImDrawList* draw_list = ImGui::GetWindowDrawList();
368 ImVec2 separator_start = ImGui::GetCursorScreenPos();
369 ImVec2 separator_end(separator_start.x + ImGui::GetContentRegionAvail().x,
370 separator_start.y + 1);
371 ImVec4 gold_faded = kTriforceGold;
372 gold_faded.w = 0.3f;
373 ImVec4 blue_faded = kMasterSwordBlue;
374 blue_faded.w = 0.3f;
375 draw_list->AddRectFilledMultiColor(
376 separator_start, separator_end, ImGui::GetColorU32(gold_faded),
377 ImGui::GetColorU32(blue_faded), ImGui::GetColorU32(blue_faded),
378 ImGui::GetColorU32(gold_faded));
379
380 ImGui::Dummy(ImVec2(0, 10));
381
382 ImGui::BeginChild("WelcomeContent", ImVec2(0, -60), false);
383
384 // Left side - Quick Actions & Templates
385 ImGui::BeginChild("LeftPanel",
386 ImVec2(ImGui::GetContentRegionAvail().x * 0.3f, 0), true,
387 ImGuiWindowFlags_NoScrollbar);
389 ImGui::Spacing();
390
391 // Subtle separator
392 ImVec2 sep_start = ImGui::GetCursorScreenPos();
393 draw_list->AddLine(
394 sep_start,
395 ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
396 ImGui::GetColorU32(
397 ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)),
398 1.0f);
399
400 ImGui::Dummy(ImVec2(0, 5));
402 ImGui::EndChild();
403
404 ImGui::SameLine();
405
406 // Right side - Recent Projects & What's New
407 ImGui::BeginChild("RightPanel", ImVec2(0, 0), true);
409 ImGui::Spacing();
410
411 // Subtle separator
412 sep_start = ImGui::GetCursorScreenPos();
413 draw_list->AddLine(
414 sep_start,
415 ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
416 ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y,
417 kMasterSwordBlue.z, 0.2f)),
418 1.0f);
419
420 ImGui::Dummy(ImVec2(0, 5));
421 DrawWhatsNew();
422 ImGui::EndChild();
423
424 ImGui::EndChild();
425
426 // Footer with subtle gradient
427 ImVec2 footer_start = ImGui::GetCursorScreenPos();
428 ImVec2 footer_end(footer_start.x + ImGui::GetContentRegionAvail().x,
429 footer_start.y + 1);
430 ImVec4 red_faded = kHeartRed;
431 red_faded.w = 0.3f;
432 ImVec4 green_faded = kHyruleGreen;
433 green_faded.w = 0.3f;
434 draw_list->AddRectFilledMultiColor(
435 footer_start, footer_end, ImGui::GetColorU32(red_faded),
436 ImGui::GetColorU32(green_faded), ImGui::GetColorU32(green_faded),
437 ImGui::GetColorU32(red_faded));
438
439 ImGui::Dummy(ImVec2(0, 5));
441 }
442 ImGui::End();
443
444 ImGui::PopStyleVar();
445
446 return action_taken;
447}
448
450 animation_time_ += ImGui::GetIO().DeltaTime;
451
452 // Update hover scale for cards (smooth interpolation)
453 for (int i = 0; i < 6; ++i) {
454 float target = (hovered_card_ == i) ? 1.03f : 1.0f;
456 (target - card_hover_scale_[i]) * ImGui::GetIO().DeltaTime * 10.0f;
457 }
458
459 // Note: Triforce positions and particles are updated in Show() based on mouse
460 // position
461}
462
464 recent_projects_.clear();
465
466 // Use the ProjectManager singleton to get recent files
468 auto recent_files = manager.GetRecentFiles(); // Copy to allow modification
469
470 std::vector<std::string> files_to_remove;
471
472 for (const auto& filepath : recent_files) {
474 break;
475
476 std::filesystem::path path(filepath);
477
478 // Skip and mark for removal if file doesn't exist
479 if (!std::filesystem::exists(path)) {
480 files_to_remove.push_back(filepath);
481 continue;
482 }
483
484 RecentProject project;
485 project.filepath = filepath;
486 project.name = path.filename().string();
487
488 // Get file modification time
489 try {
490 auto ftime = std::filesystem::last_write_time(path);
491 project.last_modified = GetRelativeTimeString(ftime);
492 project.rom_title = "ALTTP ROM";
493 } catch (const std::filesystem::filesystem_error&) {
494 // File became inaccessible between exists() check and last_write_time()
495 files_to_remove.push_back(filepath);
496 continue;
497 }
498
499 recent_projects_.push_back(project);
500 }
501
502 // Remove missing files from the recent files manager
503 for (const auto& missing_file : files_to_remove) {
504 manager.RemoveFile(missing_file);
505 }
506
507 // Save updated list if we removed any files
508 if (!files_to_remove.empty()) {
509 manager.Save();
510 }
511}
512
514 ImDrawList* draw_list = ImGui::GetWindowDrawList();
515
516 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font
517
518 // Simple centered title
519 const char* title = ICON_MD_CASTLE " yaze";
520 auto windowWidth = ImGui::GetWindowSize().x;
521 auto textWidth = ImGui::CalcTextSize(title).x;
522 float xPos = (windowWidth - textWidth) * 0.5f;
523
524 ImGui::SetCursorPosX(xPos);
525 ImVec2 text_pos = ImGui::GetCursorScreenPos();
526
527 // Subtle static glow behind text
528 float glow_size = 30.0f;
529 ImU32 glow_color = ImGui::GetColorU32(
530 ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f));
531 draw_list->AddCircleFilled(
532 ImVec2(text_pos.x + textWidth / 2, text_pos.y + 15), glow_size,
533 glow_color, 32);
534
535 // Simple gold color for title
536 ImGui::TextColored(kTriforceGold, "%s", title);
537 ImGui::PopFont();
538
539 // Static subtitle
540 const char* subtitle = "Yet Another Zelda3 Editor";
541 textWidth = ImGui::CalcTextSize(subtitle).x;
542 ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);
543
544 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", subtitle);
545
546 // Small decorative triforces flanking the title (static, transparent)
547 // Positioned well away from text to avoid crowding
548 ImVec2 left_tri_pos(xPos - 80, text_pos.y + 20);
549 ImVec2 right_tri_pos(xPos + textWidth + 50, text_pos.y + 20);
550 DrawTriforceBackground(draw_list, left_tri_pos, 20, 0.12f, 0.0f);
551 DrawTriforceBackground(draw_list, right_tri_pos, 20, 0.12f, 0.0f);
552
553 ImGui::Spacing();
554}
555
557 ImGui::TextColored(kSpiritOrange, ICON_MD_BOLT " Quick Actions");
558 ImGui::Spacing();
559
560 float button_width = ImGui::GetContentRegionAvail().x;
561
562 // Animated button colors (compact height)
563 auto draw_action_button = [&](const char* icon, const char* text,
564 const ImVec4& color, bool enabled,
565 std::function<void()> callback) {
566 ImGui::PushStyleColor(
567 ImGuiCol_Button,
568 ImVec4(color.x * 0.6f, color.y * 0.6f, color.z * 0.6f, 0.8f));
569 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
570 ImVec4(color.x, color.y, color.z, 1.0f));
571 ImGui::PushStyleColor(
572 ImGuiCol_ButtonActive,
573 ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, 1.0f));
574
575 if (!enabled)
576 ImGui::BeginDisabled();
577
578 bool clicked =
579 ImGui::Button(absl::StrFormat("%s %s", icon, text).c_str(),
580 ImVec2(button_width, 38)); // Reduced from 45 to 38
581
582 if (!enabled)
583 ImGui::EndDisabled();
584
585 ImGui::PopStyleColor(3);
586
587 if (clicked && enabled && callback) {
588 callback();
589 }
590
591 return clicked;
592 };
593
594 // Open ROM button - Green like finding an item
595 if (draw_action_button(ICON_MD_FOLDER_OPEN, "Open ROM", kHyruleGreen, true,
597 // Handled by callback
598 }
599 if (ImGui::IsItemHovered()) {
600 ImGui::SetTooltip(ICON_MD_INFO " Open an existing ALTTP ROM file");
601 }
602
603 ImGui::Spacing();
604
605 // New Project button - Gold like getting a treasure
606 if (draw_action_button(ICON_MD_ADD_CIRCLE, "New Project", kTriforceGold, true,
608 // Handled by callback
609 }
610 if (ImGui::IsItemHovered()) {
611 ImGui::SetTooltip(ICON_MD_INFO " Create a new ROM hacking project");
612 }
613
614 ImGui::Spacing();
615
616 // AI Agent button - Purple like magic
617 if (draw_action_button(ICON_MD_SMART_TOY, "AI Agent", kGanonPurple, true,
619 // Handled by callback
620 }
621 if (ImGui::IsItemHovered()) {
622 ImGui::SetTooltip(ICON_MD_INFO " Open AI Agent for natural language ROM editing");
623 }
624}
625
627 ImGui::TextColored(kMasterSwordBlue, ICON_MD_HISTORY " Recent Projects");
628 ImGui::Spacing();
629
630 if (recent_projects_.empty()) {
631 // Simple empty state
632 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f));
633
634 ImVec2 cursor = ImGui::GetCursorPos();
635 ImGui::SetCursorPosX(cursor.x + ImGui::GetContentRegionAvail().x * 0.3f);
636 ImGui::TextColored(
637 ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.8f),
639 ImGui::SetCursorPosX(cursor.x);
640
641 ImGui::TextWrapped(
642 "No recent projects yet.\nOpen a ROM to begin your adventure!");
643 ImGui::PopStyleColor();
644 return;
645 }
646
647 // Grid layout for project cards (compact)
648 float card_width = 220.0f; // Reduced for compactness
649 float card_height = 95.0f; // Reduced for less scrolling
650 int columns =
651 std::max(1, (int)(ImGui::GetContentRegionAvail().x / (card_width + 12)));
652
653 for (size_t i = 0; i < recent_projects_.size(); ++i) {
654 if (i % columns != 0) {
655 ImGui::SameLine();
656 }
658 }
659}
660
661void WelcomeScreen::DrawProjectPanel(const RecentProject& project, int index) {
662 ImGui::BeginGroup();
663
664 ImVec2 card_size(200, 95); // Compact size
665 ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
666
667 // Subtle hover scale (only on actual hover, no animation)
668 float scale = card_hover_scale_[index];
669 if (scale != 1.0f) {
670 ImVec2 center(cursor_pos.x + card_size.x / 2,
671 cursor_pos.y + card_size.y / 2);
672 cursor_pos.x = center.x - (card_size.x * scale) / 2;
673 cursor_pos.y = center.y - (card_size.y * scale) / 2;
674 card_size.x *= scale;
675 card_size.y *= scale;
676 }
677
678 // Draw card background with subtle gradient
679 ImDrawList* draw_list = ImGui::GetWindowDrawList();
680
681 // Gradient background
682 ImU32 color_top = ImGui::GetColorU32(ImVec4(0.15f, 0.20f, 0.25f, 1.0f));
683 ImU32 color_bottom = ImGui::GetColorU32(ImVec4(0.10f, 0.15f, 0.20f, 1.0f));
684 draw_list->AddRectFilledMultiColor(
685 cursor_pos,
686 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), color_top,
687 color_top, color_bottom, color_bottom);
688
689 // Static themed border
690 ImVec4 border_color_base = (index % 3 == 0) ? kHyruleGreen
691 : (index % 3 == 1) ? kMasterSwordBlue
692 : kTriforceGold;
693 ImU32 border_color = ImGui::GetColorU32(ImVec4(
694 border_color_base.x, border_color_base.y, border_color_base.z, 0.5f));
695
696 draw_list->AddRect(
697 cursor_pos,
698 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
699 border_color, 6.0f, 0, 2.0f);
700
701 // Make the card clickable
702 ImGui::SetCursorScreenPos(cursor_pos);
703 ImGui::InvisibleButton(absl::StrFormat("ProjectPanel_%d", index).c_str(),
704 card_size);
705 bool is_hovered = ImGui::IsItemHovered();
706 bool is_clicked = ImGui::IsItemClicked();
707
709 is_hovered ? index : (hovered_card_ == index ? -1 : hovered_card_);
710
711 // Subtle hover glow (no particles)
712 if (is_hovered) {
713 ImU32 hover_color = ImGui::GetColorU32(
714 ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f));
715 draw_list->AddRectFilled(
716 cursor_pos,
717 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
718 hover_color, 6.0f);
719 }
720
721 // Draw content (tighter layout)
722 ImVec2 content_pos(cursor_pos.x + 8, cursor_pos.y + 8);
723
724 // Icon with colored background circle (compact)
725 ImVec2 icon_center(content_pos.x + 13, content_pos.y + 13);
726 ImU32 icon_bg = ImGui::GetColorU32(border_color_base);
727 draw_list->AddCircleFilled(icon_center, 15, icon_bg, 24);
728
729 // Center the icon properly
730 ImVec2 icon_size = ImGui::CalcTextSize(ICON_MD_VIDEOGAME_ASSET);
731 ImGui::SetCursorScreenPos(
732 ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2));
733 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1));
734 ImGui::Text(ICON_MD_VIDEOGAME_ASSET);
735 ImGui::PopStyleColor();
736
737 // Project name (compact, shorten if too long)
738 ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 32, content_pos.y + 8));
739 ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 8);
740 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font
741 std::string short_name = project.name;
742 if (short_name.length() > 22) {
743 short_name = short_name.substr(0, 19) + "...";
744 }
745 ImGui::TextColored(kTriforceGold, "%s", short_name.c_str());
746 ImGui::PopFont();
747 ImGui::PopTextWrapPos();
748
749 // ROM title (compact)
750 ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 35));
751 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.65f, 0.65f, 0.65f, 1.0f));
752 ImGui::Text(ICON_MD_GAMEPAD " %s", project.rom_title.c_str());
753 ImGui::PopStyleColor();
754
755 // Path in card (compact)
756 ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 58));
757 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
758 std::string short_path = project.filepath;
759 if (short_path.length() > 26) {
760 short_path = "..." + short_path.substr(short_path.length() - 23);
761 }
762 ImGui::Text(ICON_MD_FOLDER " %s", short_path.c_str());
763 ImGui::PopStyleColor();
764
765 // Tooltip
766 if (is_hovered) {
767 ImGui::BeginTooltip();
768 ImGui::TextColored(kMasterSwordBlue, ICON_MD_INFO " Project Details");
769 ImGui::Separator();
770 ImGui::Text("Name: %s", project.name.c_str());
771 ImGui::Text("ROM: %s", project.rom_title.c_str());
772 ImGui::Text("Path: %s", project.filepath.c_str());
773 ImGui::Separator();
774 ImGui::TextColored(kTriforceGold, ICON_MD_TOUCH_APP " Click to open");
775 ImGui::EndTooltip();
776 }
777
778 // Handle click
779 if (is_clicked && open_project_callback_) {
781 }
782
783 ImGui::EndGroup();
784}
785
787 // Header with visual settings button
788 float content_width = ImGui::GetContentRegionAvail().x;
789 ImGui::TextColored(kGanonPurple, ICON_MD_LAYERS " Project Templates");
790 ImGui::SameLine(content_width - 25);
791 if (ImGui::SmallButton(show_triforce_settings_ ? ICON_MD_CLOSE
792 : ICON_MD_TUNE)) {
794 }
795 if (ImGui::IsItemHovered()) {
796 ImGui::SetTooltip(ICON_MD_AUTO_AWESOME " Visual Effects Settings");
797 }
798
799 ImGui::Spacing();
800
801 // Visual effects settings panel (when opened)
803 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.18f, 0.15f, 0.22f, 0.4f));
804 ImGui::BeginChild("VisualSettingsCompact", ImVec2(0, 115), true,
805 ImGuiWindowFlags_NoScrollbar);
806 {
807 ImGui::TextColored(kGanonPurple, ICON_MD_AUTO_AWESOME " Visual Effects");
808 ImGui::Spacing();
809
810 ImGui::Text(ICON_MD_OPACITY " Visibility");
811 ImGui::SetNextItemWidth(-1);
812 ImGui::SliderFloat("##visibility", &triforce_alpha_multiplier_, 0.0f,
813 3.0f, "%.1fx");
814
815 ImGui::Text(ICON_MD_SPEED " Speed");
816 ImGui::SetNextItemWidth(-1);
817 ImGui::SliderFloat("##speed", &triforce_speed_multiplier_, 0.05f, 1.0f,
818 "%.2fx");
819
820 ImGui::Checkbox(ICON_MD_MOUSE " Mouse Interaction",
822 ImGui::SameLine();
823 ImGui::Checkbox(ICON_MD_AUTO_FIX_HIGH " Particles", &particles_enabled_);
824
825 if (ImGui::SmallButton(ICON_MD_REFRESH " Reset")) {
830 particles_enabled_ = true;
832 }
833 }
834 ImGui::EndChild();
835 ImGui::PopStyleColor();
836 ImGui::Spacing();
837 }
838
839 ImGui::Spacing();
840
841 struct Template {
842 const char* icon;
843 const char* name;
844 const char* description;
845 const char* template_id;
846 ImVec4 color;
847 };
848
849 Template templates[] = {
850 {ICON_MD_COTTAGE, "Vanilla ROM Hack",
851 "Standard editing without custom ASM", "Vanilla ROM Hack", kHyruleGreen},
852 {ICON_MD_MAP, "ZSCustomOverworld v3",
853 "Full overworld expansion features", "ZSCustomOverworld v3 (Recommended)",
854 kMasterSwordBlue},
855 {ICON_MD_LAYERS, "ZSCustomOverworld v2",
856 "Basic overworld expansion", "ZSCustomOverworld v2", kShadowPurple},
857 {ICON_MD_SHUFFLE, "Randomizer Compatible",
858 "Minimal custom features for rando", "Randomizer Compatible",
859 kSpiritOrange},
860 };
861
862 for (int i = 0; i < 4; ++i) {
863 bool is_selected = (selected_template_ == i);
864
865 // Subtle selection highlight (no animation)
866 if (is_selected) {
867 ImGui::PushStyleColor(
868 ImGuiCol_Header,
869 ImVec4(templates[i].color.x * 0.6f, templates[i].color.y * 0.6f,
870 templates[i].color.z * 0.6f, 0.6f));
871 }
872
873 if (ImGui::Selectable(
874 absl::StrFormat("%s %s", templates[i].icon, templates[i].name)
875 .c_str(),
876 is_selected)) {
878 }
879
880 if (is_selected) {
881 ImGui::PopStyleColor();
882 }
883
884 if (ImGui::IsItemHovered()) {
885 ImGui::SetTooltip("%s %s\n%s", ICON_MD_INFO, templates[i].name,
886 templates[i].description);
887 }
888 }
889
890 ImGui::Spacing();
891
892 // Use Template button - enabled and functional
893 ImGui::PushStyleColor(ImGuiCol_Button,
894 ImVec4(kSpiritOrange.x * 0.6f, kSpiritOrange.y * 0.6f,
895 kSpiritOrange.z * 0.6f, 0.8f));
896 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kSpiritOrange);
897 ImGui::PushStyleColor(ImGuiCol_ButtonActive,
898 ImVec4(kSpiritOrange.x * 1.2f, kSpiritOrange.y * 1.2f,
899 kSpiritOrange.z * 1.2f, 1.0f));
900
901 if (ImGui::Button(
902 absl::StrFormat("%s Use Template", ICON_MD_ROCKET_LAUNCH).c_str(),
903 ImVec2(-1, 30))) {
904 // Trigger template-based project creation
907 } else if (new_project_callback_) {
908 // Fallback to regular new project if template callback not set
910 }
911 }
912
913 ImGui::PopStyleColor(3);
914
915 if (ImGui::IsItemHovered()) {
916 ImGui::SetTooltip("%s Create new project with '%s' template\nThis will "
917 "open a ROM and apply the template settings.",
918 ICON_MD_INFO, templates[selected_template_].name);
919 }
920}
921
923 // Static tip (or could rotate based on session start time rather than
924 // animation)
925 const char* tips[] = {
926 "Press Ctrl+Shift+P to open the command palette",
927 "Use z3ed agent for AI-powered ROM editing (Ctrl+Shift+A)",
928 "Enable ZSCustomOverworld in Debug menu for expanded features",
929 "Check the Performance Dashboard for optimization insights",
930 "Collaborate in real-time with yaze-server"};
931 int tip_index = 0; // Show first tip, or could be random on screen open
932
933 ImGui::Text(ICON_MD_LIGHTBULB);
934 ImGui::SameLine();
935 ImGui::TextColored(kTriforceGold, "Tip:");
936 ImGui::SameLine();
937 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), "%s", tips[tip_index]);
938
939 ImGui::SameLine(ImGui::GetWindowWidth() - 220);
940 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 0.5f));
941 if (ImGui::SmallButton(
942 absl::StrFormat("%s Don't show again", ICON_MD_CLOSE).c_str())) {
943 manually_closed_ = true;
944 }
945 ImGui::PopStyleColor();
946}
947
949 ImGui::TextColored(kHeartRed, ICON_MD_NEW_RELEASES " What's New");
950 ImGui::Spacing();
951
952 // Version badge (no animation)
953 ImGui::TextColored(kMasterSwordBlue, ICON_MD_VERIFIED "yaze v%s",
955 ImGui::Spacing();
956
957 // Feature list with icons and colors
958 struct Feature {
959 const char* icon;
960 const char* title;
961 const char* desc;
962 ImVec4 color;
963 };
964
965 Feature features[] = {
966 {ICON_MD_MUSIC_NOTE, "Music Editor",
967 "Complete SPC music editing with piano roll and tracker views", kTriforceGold},
968 {ICON_MD_PIANO, "Piano Roll & Playback",
969 "Visual note editing with authentic N-SPC audio preview", kMasterSwordBlue},
970 {ICON_MD_SPEAKER, "Instrument Editor",
971 "Edit ADSR envelopes, samples, and instrument banks", kHyruleGreen},
972 {ICON_MD_PSYCHOLOGY, "AI Agent Integration",
973 "Natural language ROM editing with z3ed agent", kGanonPurple},
974 {ICON_MD_SPEED, "Performance Improvements",
975 "Improved graphics arena and faster loading", kSpiritOrange},
976 };
977
978 for (const auto& feature : features) {
979 ImGui::Bullet();
980 ImGui::SameLine();
981 ImGui::TextColored(feature.color, "%s ", feature.icon);
982 ImGui::SameLine();
983 ImGui::TextColored(ImVec4(0.95f, 0.95f, 0.95f, 1.0f), "%s", feature.title);
984
985 ImGui::Indent(25);
986 ImGui::TextColored(ImVec4(0.65f, 0.65f, 0.65f, 1.0f), "%s", feature.desc);
987 ImGui::Unindent(25);
988 ImGui::Spacing();
989 }
990
991 ImGui::Spacing();
992 ImGui::PushStyleColor(
993 ImGuiCol_Button,
994 ImVec4(kMasterSwordBlue.x * 0.6f, kMasterSwordBlue.y * 0.6f,
995 kMasterSwordBlue.z * 0.6f, 0.8f));
996 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kMasterSwordBlue);
997 if (ImGui::Button(
998 absl::StrFormat("%s View Full Changelog", ICON_MD_OPEN_IN_NEW)
999 .c_str())) {
1000 // Open changelog or GitHub releases
1001 }
1002 ImGui::PopStyleColor(2);
1003}
1004
1005} // namespace editor
1006} // namespace yaze
static TimingManager & Get()
Definition timing.h:20
float GetDeltaTime() const
Get the last frame's delta time in seconds.
Definition timing.h:60
std::function< void()> open_rom_callback_
std::function< void()> new_project_callback_
void RefreshRecentProjects()
Refresh recent projects list from the project manager.
static constexpr int kNumTriforces
std::vector< RecentProject > recent_projects_
ImVec2 triforce_base_positions_[kNumTriforces]
Particle particles_[kMaxParticles]
void DrawProjectPanel(const RecentProject &project, int index)
std::function< void(const std::string &) new_project_with_template_callback_)
void UpdateAnimations()
Update animation time for dynamic effects.
static constexpr int kMaxParticles
bool Show(bool *p_open)
Show the welcome screen.
ImVec2 triforce_positions_[kNumTriforces]
std::function< void(const std::string &) open_project_callback_)
static constexpr int kMaxRecentProjects
std::function< void()> open_agent_callback_
static ThemeManager & Get()
static RecentFilesManager & GetInstance()
Definition project.h:310
#define YAZE_VERSION_STRING
Definition yaze.h:43
#define ICON_MD_ROCKET_LAUNCH
Definition icons.h:1612
#define ICON_MD_SHUFFLE
Definition icons.h:1738
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_PIANO
Definition icons.h:1462
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_LIGHTBULB
Definition icons.h:1083
#define ICON_MD_NEW_RELEASES
Definition icons.h:1291
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_AUTO_AWESOME
Definition icons.h:214
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_SPEED
Definition icons.h:1817
#define ICON_MD_VERIFIED
Definition icons.h:2055
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:218
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1264
#define ICON_MD_LAYERS
Definition icons.h:1068
#define ICON_MD_SPEAKER
Definition icons.h:1812
#define ICON_MD_PSYCHOLOGY
Definition icons.h:1523
#define ICON_MD_BOLT
Definition icons.h:282
#define ICON_MD_TOUCH_APP
Definition icons.h:2000
#define ICON_MD_MOUSE
Definition icons.h:1251
#define ICON_MD_FOLDER
Definition icons.h:809
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_GAMEPAD
Definition icons.h:866
#define ICON_MD_COTTAGE
Definition icons.h:480
#define ICON_MD_ADD_CIRCLE
Definition icons.h:95
#define ICON_MD_SMART_TOY
Definition icons.h:1781
#define ICON_MD_OPACITY
Definition icons.h:1351
#define ICON_MD_HISTORY
Definition icons.h:946
#define ICON_MD_EXPLORE
Definition icons.h:705
ImVec4 GetThemedColor(const char *color_name, const ImVec4 &fallback)
std::string GetRelativeTimeString(const std::filesystem::file_time_type &ftime)
void DrawTriforceBackground(ImDrawList *draw_list, ImVec2 pos, float size, float alpha, float glow)
Information about a recently used project.
#define M_PI