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 "core/project.h"
13#include "app/platform/timing.h"
14#include "app/gui/core/icons.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
71std::string GetRelativeTimeString(const std::filesystem::file_time_type& ftime) {
72 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
73 ftime - std::filesystem::file_time_type::clock::now() +
74 std::chrono::system_clock::now());
75 auto now = std::chrono::system_clock::now();
76 auto diff = std::chrono::duration_cast<std::chrono::hours>(now - sctp);
77
78 int hours = diff.count();
79 if (hours < 24) {
80 return "Today";
81 } else if (hours < 48) {
82 return "Yesterday";
83 } else if (hours < 168) {
84 int days = hours / 24;
85 return absl::StrFormat("%d days ago", days);
86 } else if (hours < 720) {
87 int weeks = hours / 168;
88 return absl::StrFormat("%d week%s ago", weeks, weeks > 1 ? "s" : "");
89 } else {
90 int months = hours / 720;
91 return absl::StrFormat("%d month%s ago", months, months > 1 ? "s" : "");
92 }
93}
94
95// Draw a pixelated triforce in the background (ALTTP style)
96void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size, float alpha, float glow) {
97 // Make it pixelated - round size to nearest 4 pixels
98 size = std::round(size / 4.0f) * 4.0f;
99
100 // Calculate triangle points with pixel-perfect positioning
101 auto triangle = [&](ImVec2 center, float s, ImU32 color) {
102 // Round to pixel boundaries for crisp edges
103 float half_s = s / 2.0f;
104 float tri_h = s * 0.866f; // Height of equilateral triangle
105
106 // Fixed: Proper equilateral triangle with apex at top
107 ImVec2 p1(std::round(center.x), std::round(center.y - tri_h / 2.0f)); // Top apex
108 ImVec2 p2(std::round(center.x - half_s), std::round(center.y + tri_h / 2.0f)); // Bottom left
109 ImVec2 p3(std::round(center.x + half_s), std::round(center.y + tri_h / 2.0f)); // Bottom right
110
111 draw_list->AddTriangleFilled(p1, p2, p3, color);
112 };
113
114 ImU32 gold = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha));
115
116 // Proper triforce layout with three triangles
117 float small_size = size / 2.0f;
118 float small_height = small_size * 0.866f;
119
120 // Top triangle (centered above)
121 triangle(ImVec2(pos.x, pos.y), small_size, gold);
122
123 // Bottom left triangle
124 triangle(ImVec2(pos.x - small_size / 2.0f, pos.y + small_height), small_size, gold);
125
126 // Bottom right triangle
127 triangle(ImVec2(pos.x + small_size / 2.0f, pos.y + small_height), small_size, gold);
128}
129
130} // namespace
131
135
136bool WelcomeScreen::Show(bool* p_open) {
137 // Update theme colors each frame
138 kTriforceGold = GetThemedColor("triforce_gold", kTriforceGoldFallback);
139 kHyruleGreen = GetThemedColor("hyrule_green", kHyruleGreenFallback);
140 kMasterSwordBlue = GetThemedColor("master_sword_blue", kMasterSwordBlueFallback);
141 kGanonPurple = GetThemedColor("ganon_purple", kGanonPurpleFallback);
142 kHeartRed = GetThemedColor("heart_red", kHeartRedFallback);
143 kSpiritOrange = GetThemedColor("spirit_orange", kSpiritOrangeFallback);
144
146
147 // Get mouse position for interactive triforce movement
148 ImVec2 mouse_pos = ImGui::GetMousePos();
149
150 bool action_taken = false;
151
152 // Center the window with responsive size (80% of viewport, max 1400x900)
153 ImGuiViewport* viewport = ImGui::GetMainViewport();
154 ImVec2 center = viewport->GetCenter();
155 ImVec2 viewport_size = viewport->Size;
156
157 float width = std::min(viewport_size.x * 0.8f, 1400.0f);
158 float height = std::min(viewport_size.y * 0.85f, 900.0f);
159
160 ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
161 ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
162
163 // CRITICAL: Override ImGui's saved window state from imgui.ini
164 // Without this, ImGui will restore the last saved state (hidden/collapsed)
165 // even when our logic says the window should be visible
167 ImGui::SetNextWindowCollapsed(false); // Force window to be expanded
168 ImGui::SetNextWindowFocus(); // Bring window to front
169 first_show_attempt_ = false;
170 }
171
172 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse |
173 ImGuiWindowFlags_NoResize |
174 ImGuiWindowFlags_NoMove;
175
176 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20, 20));
177
178 if (ImGui::Begin("##WelcomeScreen", p_open, window_flags)) {
179 ImDrawList* bg_draw_list = ImGui::GetWindowDrawList();
180 ImVec2 window_pos = ImGui::GetWindowPos();
181 ImVec2 window_size = ImGui::GetWindowSize();
182
183 // Interactive scattered triforces (react to mouse position)
184 struct TriforceConfig {
185 float x_pct, y_pct; // Base position (percentage of window)
186 float size;
187 float alpha;
188 float repel_distance; // How far they move away from mouse
189 };
190
191 TriforceConfig triforce_configs[] = {
192 {0.08f, 0.12f, 36.0f, 0.025f, 50.0f}, // Top left corner
193 {0.92f, 0.15f, 34.0f, 0.022f, 50.0f}, // Top right corner
194 {0.06f, 0.88f, 32.0f, 0.020f, 45.0f}, // Bottom left
195 {0.94f, 0.85f, 34.0f, 0.023f, 50.0f}, // Bottom right
196 {0.50f, 0.08f, 38.0f, 0.028f, 55.0f}, // Top center
197 {0.50f, 0.92f, 32.0f, 0.020f, 45.0f}, // Bottom center
198 };
199
200 // Initialize base positions on first frame
202 for (int i = 0; i < kNumTriforces; ++i) {
203 float x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
204 float y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
205 triforce_base_positions_[i] = ImVec2(x, y);
207 }
209 }
210
211 // Update triforce positions based on mouse interaction + floating animation
212 for (int i = 0; i < kNumTriforces; ++i) {
213 // Update base position in case window moved/resized
214 float base_x = window_pos.x + window_size.x * triforce_configs[i].x_pct;
215 float base_y = window_pos.y + window_size.y * triforce_configs[i].y_pct;
216 triforce_base_positions_[i] = ImVec2(base_x, base_y);
217
218 // Slow, subtle floating animation
219 float time_offset = i * 1.2f; // Offset each triforce's animation
220 float float_speed_x = (0.15f + (i % 2) * 0.1f) * triforce_speed_multiplier_; // Very slow
221 float float_speed_y = (0.12f + ((i + 1) % 2) * 0.08f) * triforce_speed_multiplier_;
222 float float_amount_x = (20.0f + (i % 2) * 10.0f) * triforce_size_multiplier_; // Smaller amplitude
223 float float_amount_y = (25.0f + ((i + 1) % 2) * 15.0f) * triforce_size_multiplier_;
224
225 // Create gentle orbital motion
226 float float_x = std::sin(animation_time_ * float_speed_x + time_offset) * float_amount_x;
227 float float_y = std::cos(animation_time_ * float_speed_y + time_offset * 1.2f) * float_amount_y;
228
229 // Calculate distance from mouse
230 float dx = triforce_base_positions_[i].x - mouse_pos.x;
231 float dy = triforce_base_positions_[i].y - mouse_pos.y;
232 float dist = std::sqrt(dx * dx + dy * dy);
233
234 // Calculate repulsion offset with stronger effect
235 ImVec2 target_pos = triforce_base_positions_[i];
236 float repel_radius = 200.0f; // Larger radius for more visible interaction
237
238 // Add floating motion to base position
239 target_pos.x += float_x;
240 target_pos.y += float_y;
241
242 // Apply mouse repulsion if enabled
243 if (triforce_mouse_repel_enabled_ && dist < repel_radius && dist > 0.1f) {
244 // Normalize direction away from mouse
245 float dir_x = dx / dist;
246 float dir_y = dy / dist;
247
248 // Much stronger repulsion when closer with exponential falloff
249 float normalized_dist = dist / repel_radius;
250 float repel_strength = (1.0f - normalized_dist * normalized_dist) * triforce_configs[i].repel_distance;
251
252 target_pos.x += dir_x * repel_strength;
253 target_pos.y += dir_y * repel_strength;
254 }
255
256 // Smooth interpolation to target position (faster response)
257 // Use TimingManager for accurate delta time
258 float lerp_speed = 8.0f * yaze::TimingManager::Get().GetDeltaTime();
259 triforce_positions_[i].x += (target_pos.x - triforce_positions_[i].x) * lerp_speed;
260 triforce_positions_[i].y += (target_pos.y - triforce_positions_[i].y) * lerp_speed;
261
262 // Draw at current position with alpha multiplier
263 float adjusted_alpha = triforce_configs[i].alpha * triforce_alpha_multiplier_;
264 float adjusted_size = triforce_configs[i].size * triforce_size_multiplier_;
265 DrawTriforceBackground(bg_draw_list, triforce_positions_[i],
266 adjusted_size, adjusted_alpha, 0.0f);
267 }
268
269 // Update and draw particle system
270 if (particles_enabled_) {
271 // Spawn new particles
272 static float spawn_accumulator = 0.0f;
273 spawn_accumulator += ImGui::GetIO().DeltaTime * particle_spawn_rate_;
274 while (spawn_accumulator >= 1.0f && active_particle_count_ < kMaxParticles) {
275 // Find inactive particle slot
276 for (int i = 0; i < kMaxParticles; ++i) {
277 if (particles_[i].lifetime <= 0.0f) {
278 // Spawn from random triforce
279 int source_triforce = rand() % kNumTriforces;
280 particles_[i].position = triforce_positions_[source_triforce];
281
282 // Random direction and speed
283 float angle = (rand() % 360) * (M_PI / 180.0f);
284 float speed = 20.0f + (rand() % 40);
285 particles_[i].velocity = ImVec2(std::cos(angle) * speed, std::sin(angle) * speed);
286
287 particles_[i].size = 2.0f + (rand() % 4);
288 particles_[i].alpha = 0.4f + (rand() % 40) / 100.0f;
289 particles_[i].max_lifetime = 2.0f + (rand() % 30) / 10.0f;
292 break;
293 }
294 }
295 spawn_accumulator -= 1.0f;
296 }
297
298 // Update and draw particles
299 float dt = ImGui::GetIO().DeltaTime;
300 for (int i = 0; i < kMaxParticles; ++i) {
301 if (particles_[i].lifetime > 0.0f) {
302 // Update lifetime
303 particles_[i].lifetime -= dt;
304 if (particles_[i].lifetime <= 0.0f) {
306 continue;
307 }
308
309 // Update position
310 particles_[i].position.x += particles_[i].velocity.x * dt;
311 particles_[i].position.y += particles_[i].velocity.y * dt;
312
313 // Fade out near end of life
314 float life_ratio = particles_[i].lifetime / particles_[i].max_lifetime;
315 float alpha = particles_[i].alpha * life_ratio * triforce_alpha_multiplier_;
316
317 // Draw particle as small golden circle
318 ImU32 particle_color = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha));
319 bg_draw_list->AddCircleFilled(particles_[i].position, particles_[i].size, particle_color, 8);
320 }
321 }
322 }
323
324 DrawHeader();
325
326 ImGui::Spacing();
327 ImGui::Spacing();
328
329 // Main content area with subtle gradient separator
330 ImDrawList* draw_list = ImGui::GetWindowDrawList();
331 ImVec2 separator_start = ImGui::GetCursorScreenPos();
332 ImVec2 separator_end(separator_start.x + ImGui::GetContentRegionAvail().x, separator_start.y + 1);
333 ImVec4 gold_faded = kTriforceGold;
334 gold_faded.w = 0.3f;
335 ImVec4 blue_faded = kMasterSwordBlue;
336 blue_faded.w = 0.3f;
337 draw_list->AddRectFilledMultiColor(
338 separator_start, separator_end,
339 ImGui::GetColorU32(gold_faded),
340 ImGui::GetColorU32(blue_faded),
341 ImGui::GetColorU32(blue_faded),
342 ImGui::GetColorU32(gold_faded));
343
344 ImGui::Dummy(ImVec2(0, 10));
345
346 ImGui::BeginChild("WelcomeContent", ImVec2(0, -60), false);
347
348 // Left side - Quick Actions & Templates
349 ImGui::BeginChild("LeftPanel", ImVec2(ImGui::GetContentRegionAvail().x * 0.3f, 0), true,
350 ImGuiWindowFlags_NoScrollbar);
352 ImGui::Spacing();
353
354 // Subtle separator
355 ImVec2 sep_start = ImGui::GetCursorScreenPos();
356 draw_list->AddLine(
357 sep_start,
358 ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
359 ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)),
360 1.0f);
361
362 ImGui::Dummy(ImVec2(0, 5));
364 ImGui::EndChild();
365
366 ImGui::SameLine();
367
368 // Right side - Recent Projects & What's New
369 ImGui::BeginChild("RightPanel", ImVec2(0, 0), true);
371 ImGui::Spacing();
372
373 // Subtle separator
374 sep_start = ImGui::GetCursorScreenPos();
375 draw_list->AddLine(
376 sep_start,
377 ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y),
378 ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y, kMasterSwordBlue.z, 0.2f)),
379 1.0f);
380
381 ImGui::Dummy(ImVec2(0, 5));
382 DrawWhatsNew();
383 ImGui::EndChild();
384
385 ImGui::EndChild();
386
387 // Footer with subtle gradient
388 ImVec2 footer_start = ImGui::GetCursorScreenPos();
389 ImVec2 footer_end(footer_start.x + ImGui::GetContentRegionAvail().x, footer_start.y + 1);
390 ImVec4 red_faded = kHeartRed;
391 red_faded.w = 0.3f;
392 ImVec4 green_faded = kHyruleGreen;
393 green_faded.w = 0.3f;
394 draw_list->AddRectFilledMultiColor(
395 footer_start, footer_end,
396 ImGui::GetColorU32(red_faded),
397 ImGui::GetColorU32(green_faded),
398 ImGui::GetColorU32(green_faded),
399 ImGui::GetColorU32(red_faded));
400
401 ImGui::Dummy(ImVec2(0, 5));
403 }
404 ImGui::End();
405
406 ImGui::PopStyleVar();
407
408 return action_taken;
409}
410
412 animation_time_ += ImGui::GetIO().DeltaTime;
413
414 // Update hover scale for cards (smooth interpolation)
415 for (int i = 0; i < 6; ++i) {
416 float target = (hovered_card_ == i) ? 1.03f : 1.0f;
417 card_hover_scale_[i] += (target - card_hover_scale_[i]) * ImGui::GetIO().DeltaTime * 10.0f;
418 }
419
420 // Note: Triforce positions and particles are updated in Show() based on mouse position
421}
422
424 recent_projects_.clear();
425
426 // Use the ProjectManager singleton to get recent files
428
429 for (const auto& filepath : recent_files) {
430 if (recent_projects_.size() >= kMaxRecentProjects) break;
431
432 RecentProject project;
433 project.filepath = filepath;
434
435 // Extract filename
436 std::filesystem::path path(filepath);
437 project.name = path.filename().string();
438
439 // Get file modification time if it exists
440 if (std::filesystem::exists(path)) {
441 auto ftime = std::filesystem::last_write_time(path);
442 project.last_modified = GetRelativeTimeString(ftime);
443 project.rom_title = "ALTTP ROM";
444 } else {
445 project.last_modified = "File not found";
446 project.rom_title = "Missing";
447 }
448
449 recent_projects_.push_back(project);
450 }
451}
452
454 ImDrawList* draw_list = ImGui::GetWindowDrawList();
455
456 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font
457
458 // Simple centered title
459 const char* title = ICON_MD_CASTLE " yaze";
460 auto windowWidth = ImGui::GetWindowSize().x;
461 auto textWidth = ImGui::CalcTextSize(title).x;
462 float xPos = (windowWidth - textWidth) * 0.5f;
463
464 ImGui::SetCursorPosX(xPos);
465 ImVec2 text_pos = ImGui::GetCursorScreenPos();
466
467 // Subtle static glow behind text
468 float glow_size = 30.0f;
469 ImU32 glow_color = ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f));
470 draw_list->AddCircleFilled(
471 ImVec2(text_pos.x + textWidth / 2, text_pos.y + 15),
472 glow_size,
473 glow_color,
474 32);
475
476 // Simple gold color for title
477 ImGui::TextColored(kTriforceGold, "%s", title);
478 ImGui::PopFont();
479
480 // Static subtitle
481 const char* subtitle = "Yet Another Zelda3 Editor";
482 textWidth = ImGui::CalcTextSize(subtitle).x;
483 ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);
484
485 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", subtitle);
486
487 // Small decorative triforces flanking the title (static, transparent)
488 // Positioned well away from text to avoid crowding
489 ImVec2 left_tri_pos(xPos - 80, text_pos.y + 20);
490 ImVec2 right_tri_pos(xPos + textWidth + 50, text_pos.y + 20);
491 DrawTriforceBackground(draw_list, left_tri_pos, 20, 0.12f, 0.0f);
492 DrawTriforceBackground(draw_list, right_tri_pos, 20, 0.12f, 0.0f);
493
494 ImGui::Spacing();
495}
496
498 ImGui::TextColored(kSpiritOrange, ICON_MD_BOLT " Quick Actions");
499 ImGui::Spacing();
500
501 float button_width = ImGui::GetContentRegionAvail().x;
502
503 // Animated button colors (compact height)
504 auto draw_action_button = [&](const char* icon, const char* text,
505 const ImVec4& color, bool enabled,
506 std::function<void()> callback) {
507 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x * 0.6f, color.y * 0.6f, color.z * 0.6f, 0.8f));
508 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 1.0f));
509 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, 1.0f));
510
511 if (!enabled) ImGui::BeginDisabled();
512
513 bool clicked = ImGui::Button(absl::StrFormat("%s %s", icon, text).c_str(),
514 ImVec2(button_width, 38)); // Reduced from 45 to 38
515
516 if (!enabled) ImGui::EndDisabled();
517
518 ImGui::PopStyleColor(3);
519
520 if (clicked && enabled && callback) {
521 callback();
522 }
523
524 return clicked;
525 };
526
527 // Open ROM button - Green like finding an item
528 if (draw_action_button(ICON_MD_FOLDER_OPEN, "Open ROM", kHyruleGreen, true, open_rom_callback_)) {
529 // Handled by callback
530 }
531 if (ImGui::IsItemHovered()) {
532 ImGui::SetTooltip(ICON_MD_INFO " Open an existing ALTTP ROM file");
533 }
534
535 ImGui::Spacing();
536
537 // New Project button - Gold like getting a treasure
538 if (draw_action_button(ICON_MD_ADD_CIRCLE, "New Project", kTriforceGold, true, new_project_callback_)) {
539 // Handled by callback
540 }
541 if (ImGui::IsItemHovered()) {
542 ImGui::SetTooltip(ICON_MD_INFO " Create a new ROM hacking project");
543 }
544}
545
547 ImGui::TextColored(kMasterSwordBlue, ICON_MD_HISTORY " Recent Projects");
548 ImGui::Spacing();
549
550 if (recent_projects_.empty()) {
551 // Simple empty state
552 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f));
553
554 ImVec2 cursor = ImGui::GetCursorPos();
555 ImGui::SetCursorPosX(cursor.x + ImGui::GetContentRegionAvail().x * 0.3f);
556 ImGui::TextColored(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.8f),
558 ImGui::SetCursorPosX(cursor.x);
559
560 ImGui::TextWrapped("No recent projects yet.\nOpen a ROM to begin your adventure!");
561 ImGui::PopStyleColor();
562 return;
563 }
564
565 // Grid layout for project cards (compact)
566 float card_width = 220.0f; // Reduced for compactness
567 float card_height = 95.0f; // Reduced for less scrolling
568 int columns = std::max(1, (int)(ImGui::GetContentRegionAvail().x / (card_width + 12)));
569
570 for (size_t i = 0; i < recent_projects_.size(); ++i) {
571 if (i % columns != 0) {
572 ImGui::SameLine();
573 }
575 }
576}
577
578void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) {
579 ImGui::BeginGroup();
580
581 ImVec2 card_size(200, 95); // Compact size
582 ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
583
584 // Subtle hover scale (only on actual hover, no animation)
585 float scale = card_hover_scale_[index];
586 if (scale != 1.0f) {
587 ImVec2 center(cursor_pos.x + card_size.x / 2, cursor_pos.y + card_size.y / 2);
588 cursor_pos.x = center.x - (card_size.x * scale) / 2;
589 cursor_pos.y = center.y - (card_size.y * scale) / 2;
590 card_size.x *= scale;
591 card_size.y *= scale;
592 }
593
594 // Draw card background with subtle gradient
595 ImDrawList* draw_list = ImGui::GetWindowDrawList();
596
597 // Gradient background
598 ImU32 color_top = ImGui::GetColorU32(ImVec4(0.15f, 0.20f, 0.25f, 1.0f));
599 ImU32 color_bottom = ImGui::GetColorU32(ImVec4(0.10f, 0.15f, 0.20f, 1.0f));
600 draw_list->AddRectFilledMultiColor(
601 cursor_pos,
602 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
603 color_top, color_top, color_bottom, color_bottom);
604
605 // Static themed border
606 ImVec4 border_color_base = (index % 3 == 0) ? kHyruleGreen :
607 (index % 3 == 1) ? kMasterSwordBlue : kTriforceGold;
608 ImU32 border_color = ImGui::GetColorU32(
609 ImVec4(border_color_base.x, border_color_base.y, border_color_base.z, 0.5f));
610
611 draw_list->AddRect(cursor_pos,
612 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
613 border_color, 6.0f, 0, 2.0f);
614
615 // Make the card clickable
616 ImGui::SetCursorScreenPos(cursor_pos);
617 ImGui::InvisibleButton(absl::StrFormat("ProjectCard_%d", index).c_str(), card_size);
618 bool is_hovered = ImGui::IsItemHovered();
619 bool is_clicked = ImGui::IsItemClicked();
620
621 hovered_card_ = is_hovered ? index : (hovered_card_ == index ? -1 : hovered_card_);
622
623 // Subtle hover glow (no particles)
624 if (is_hovered) {
625 ImU32 hover_color = ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f));
626 draw_list->AddRectFilled(cursor_pos,
627 ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y),
628 hover_color, 6.0f);
629 }
630
631 // Draw content (tighter layout)
632 ImVec2 content_pos(cursor_pos.x + 8, cursor_pos.y + 8);
633
634 // Icon with colored background circle (compact)
635 ImVec2 icon_center(content_pos.x + 13, content_pos.y + 13);
636 ImU32 icon_bg = ImGui::GetColorU32(border_color_base);
637 draw_list->AddCircleFilled(icon_center, 15, icon_bg, 24);
638
639 // Center the icon properly
640 ImVec2 icon_size = ImGui::CalcTextSize(ICON_MD_VIDEOGAME_ASSET);
641 ImGui::SetCursorScreenPos(ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2));
642 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1));
643 ImGui::Text(ICON_MD_VIDEOGAME_ASSET);
644 ImGui::PopStyleColor();
645
646 // Project name (compact, shorten if too long)
647 ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 32, content_pos.y + 8));
648 ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 8);
649 ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font
650 std::string short_name = project.name;
651 if (short_name.length() > 22) {
652 short_name = short_name.substr(0, 19) + "...";
653 }
654 ImGui::TextColored(kTriforceGold, "%s", short_name.c_str());
655 ImGui::PopFont();
656 ImGui::PopTextWrapPos();
657
658 // ROM title (compact)
659 ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 35));
660 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.65f, 0.65f, 0.65f, 1.0f));
661 ImGui::Text(ICON_MD_GAMEPAD " %s", project.rom_title.c_str());
662 ImGui::PopStyleColor();
663
664 // Path in card (compact)
665 ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 58));
666 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
667 std::string short_path = project.filepath;
668 if (short_path.length() > 26) {
669 short_path = "..." + short_path.substr(short_path.length() - 23);
670 }
671 ImGui::Text(ICON_MD_FOLDER " %s", short_path.c_str());
672 ImGui::PopStyleColor();
673
674 // Tooltip
675 if (is_hovered) {
676 ImGui::BeginTooltip();
677 ImGui::TextColored(kMasterSwordBlue, ICON_MD_INFO " Project Details");
678 ImGui::Separator();
679 ImGui::Text("Name: %s", project.name.c_str());
680 ImGui::Text("ROM: %s", project.rom_title.c_str());
681 ImGui::Text("Path: %s", project.filepath.c_str());
682 ImGui::Separator();
683 ImGui::TextColored(kTriforceGold, ICON_MD_TOUCH_APP " Click to open");
684 ImGui::EndTooltip();
685 }
686
687 // Handle click
688 if (is_clicked && open_project_callback_) {
690 }
691
692 ImGui::EndGroup();
693}
694
696 // Header with visual settings button
697 float content_width = ImGui::GetContentRegionAvail().x;
698 ImGui::TextColored(kGanonPurple, ICON_MD_LAYERS " Templates");
699 ImGui::SameLine(content_width - 25);
700 if (ImGui::SmallButton(show_triforce_settings_ ? ICON_MD_CLOSE : ICON_MD_TUNE)) {
702 }
703 if (ImGui::IsItemHovered()) {
704 ImGui::SetTooltip(ICON_MD_AUTO_AWESOME " Visual Effects Settings");
705 }
706
707 ImGui::Spacing();
708
709 // Visual effects settings panel (when opened)
711 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.18f, 0.15f, 0.22f, 0.4f));
712 ImGui::BeginChild("VisualSettingsCompact", ImVec2(0, 115), true, ImGuiWindowFlags_NoScrollbar);
713 {
714 ImGui::TextColored(kGanonPurple, ICON_MD_AUTO_AWESOME " Visual Effects");
715 ImGui::Spacing();
716
717 ImGui::Text(ICON_MD_OPACITY " Visibility");
718 ImGui::SetNextItemWidth(-1);
719 ImGui::SliderFloat("##visibility", &triforce_alpha_multiplier_, 0.0f, 3.0f, "%.1fx");
720
721 ImGui::Text(ICON_MD_SPEED " Speed");
722 ImGui::SetNextItemWidth(-1);
723 ImGui::SliderFloat("##speed", &triforce_speed_multiplier_, 0.05f, 1.0f, "%.2fx");
724
725 ImGui::Checkbox(ICON_MD_MOUSE " Mouse Interaction", &triforce_mouse_repel_enabled_);
726 ImGui::SameLine();
727 ImGui::Checkbox(ICON_MD_AUTO_FIX_HIGH " Particles", &particles_enabled_);
728
729 if (ImGui::SmallButton(ICON_MD_REFRESH " Reset")) {
734 particles_enabled_ = true;
736 }
737 }
738 ImGui::EndChild();
739 ImGui::PopStyleColor();
740 ImGui::Spacing();
741 }
742
743 ImGui::Spacing();
744
745 struct Template {
746 const char* icon;
747 const char* name;
748 ImVec4 color;
749 };
750
751 Template templates[] = {
752 {ICON_MD_COTTAGE, "Vanilla ALTTP", kHyruleGreen},
753 {ICON_MD_MAP, "ZSCustomOverworld v3", kMasterSwordBlue},
754 };
755
756 for (int i = 0; i < 2; ++i) {
757 bool is_selected = (selected_template_ == i);
758
759 // Subtle selection highlight (no animation)
760 if (is_selected) {
761 ImGui::PushStyleColor(ImGuiCol_Header,
762 ImVec4(templates[i].color.x * 0.6f, templates[i].color.y * 0.6f,
763 templates[i].color.z * 0.6f, 0.6f));
764 }
765
766 if (ImGui::Selectable(absl::StrFormat("%s %s", templates[i].icon, templates[i].name).c_str(),
767 is_selected)) {
769 }
770
771 if (is_selected) {
772 ImGui::PopStyleColor();
773 }
774
775 if (ImGui::IsItemHovered()) {
776 ImGui::SetTooltip(ICON_MD_STAR " Start with a %s template", templates[i].name);
777 }
778 }
779
780 ImGui::Spacing();
781 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(kSpiritOrange.x * 0.6f, kSpiritOrange.y * 0.6f, kSpiritOrange.z * 0.6f, 0.8f));
782 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kSpiritOrange);
783 ImGui::BeginDisabled(true);
784 ImGui::Button(absl::StrFormat("%s Use Template", ICON_MD_ROCKET_LAUNCH).c_str(),
785 ImVec2(-1, 30)); // Reduced from 35 to 30
786 ImGui::EndDisabled();
787 ImGui::PopStyleColor(2);
788}
789
791 // Static tip (or could rotate based on session start time rather than animation)
792 const char* tips[] = {
793 "Press Ctrl+Shift+P to open the command palette",
794 "Use z3ed agent for AI-powered ROM editing (Ctrl+Shift+A)",
795 "Enable ZSCustomOverworld in Debug menu for expanded features",
796 "Check the Performance Dashboard for optimization insights",
797 "Collaborate in real-time with yaze-server"
798 };
799 int tip_index = 0; // Show first tip, or could be random on screen open
800
801 ImGui::Text(ICON_MD_LIGHTBULB);
802 ImGui::SameLine();
803 ImGui::TextColored(kTriforceGold, "Tip:");
804 ImGui::SameLine();
805 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), "%s", tips[tip_index]);
806
807 ImGui::SameLine(ImGui::GetWindowWidth() - 220);
808 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 0.5f));
809 if (ImGui::SmallButton(absl::StrFormat("%s Don't show again", ICON_MD_CLOSE).c_str())) {
810 manually_closed_ = true;
811 }
812 ImGui::PopStyleColor();
813}
814
816 ImGui::TextColored(kHeartRed, ICON_MD_NEW_RELEASES " What's New");
817 ImGui::Spacing();
818
819 // Version badge (no animation)
820 ImGui::TextColored(kMasterSwordBlue, ICON_MD_VERIFIED "yaze v%s", YAZE_VERSION_STRING);
821 ImGui::Spacing();
822
823 // Feature list with icons and colors
824 struct Feature {
825 const char* icon;
826 const char* title;
827 const char* desc;
828 ImVec4 color;
829 };
830
831 Feature features[] = {
832 {ICON_MD_PSYCHOLOGY, "AI Agent Integration",
833 "Natural language ROM editing with z3ed agent", kGanonPurple},
834 {ICON_MD_CLOUD_SYNC, "Collaboration Features",
835 "Real-time ROM collaboration via yaze-server", kMasterSwordBlue},
836 {ICON_MD_HISTORY, "Version Management",
837 "ROM snapshots, rollback, corruption detection", kHyruleGreen},
838 {ICON_MD_PALETTE, "Enhanced Palette Editor",
839 "Advanced color tools with ROM palette browser", kSpiritOrange},
840 {ICON_MD_SPEED, "Performance Improvements",
841 "Faster dungeon loading with parallel processing", kTriforceGold},
842 };
843
844 for (const auto& feature : features) {
845 ImGui::Bullet();
846 ImGui::SameLine();
847 ImGui::TextColored(feature.color, "%s ", feature.icon);
848 ImGui::SameLine();
849 ImGui::TextColored(ImVec4(0.95f, 0.95f, 0.95f, 1.0f), "%s", feature.title);
850
851 ImGui::Indent(25);
852 ImGui::TextColored(ImVec4(0.65f, 0.65f, 0.65f, 1.0f), "%s", feature.desc);
853 ImGui::Unindent(25);
854 ImGui::Spacing();
855 }
856
857 ImGui::Spacing();
858 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(kMasterSwordBlue.x * 0.6f, kMasterSwordBlue.y * 0.6f, kMasterSwordBlue.z * 0.6f, 0.8f));
859 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kMasterSwordBlue);
860 if (ImGui::Button(absl::StrFormat("%s View Full Changelog", ICON_MD_OPEN_IN_NEW).c_str())) {
861 // Open changelog or GitHub releases
862 }
863 ImGui::PopStyleColor(2);
864}
865
866} // namespace editor
867} // namespace yaze
static TimingManager & Get()
Definition timing.h:19
float GetDeltaTime() const
Get the last frame's delta time in seconds.
Definition timing.h:59
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 UpdateAnimations()
Update animation time for dynamic effects.
static constexpr int kMaxParticles
bool Show(bool *p_open)
Show the welcome screen.
ImVec2 triforce_positions_[kNumTriforces]
static constexpr int kMaxRecentProjects
std::function< void(const std::string &)> open_project_callback_
void DrawProjectCard(const RecentProject &project, int index)
static ThemeManager & Get()
static RecentFilesManager & GetInstance()
Definition project.h:244
const std::vector< std::string > & GetRecentFiles() const
Definition project.h:272
#define YAZE_VERSION_STRING
Definition yaze.h:32
#define ICON_MD_ROCKET_LAUNCH
Definition icons.h:1610
#define ICON_MD_FOLDER_OPEN
Definition icons.h:811
#define ICON_MD_INFO
Definition icons.h:991
#define ICON_MD_LIGHTBULB
Definition icons.h:1081
#define ICON_MD_CLOUD_SYNC
Definition icons.h:427
#define ICON_MD_STAR
Definition icons.h:1846
#define ICON_MD_NEW_RELEASES
Definition icons.h:1289
#define ICON_MD_TUNE
Definition icons.h:2020
#define ICON_MD_REFRESH
Definition icons.h:1570
#define ICON_MD_MAP
Definition icons.h:1171
#define ICON_MD_AUTO_AWESOME
Definition icons.h:212
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2074
#define ICON_MD_SPEED
Definition icons.h:1815
#define ICON_MD_VERIFIED
Definition icons.h:2053
#define ICON_MD_CASTLE
Definition icons.h:378
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:216
#define ICON_MD_LAYERS
Definition icons.h:1066
#define ICON_MD_PSYCHOLOGY
Definition icons.h:1521
#define ICON_MD_BOLT
Definition icons.h:280
#define ICON_MD_TOUCH_APP
Definition icons.h:1998
#define ICON_MD_MOUSE
Definition icons.h:1249
#define ICON_MD_FOLDER
Definition icons.h:807
#define ICON_MD_PALETTE
Definition icons.h:1368
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1352
#define ICON_MD_CLOSE
Definition icons.h:416
#define ICON_MD_GAMEPAD
Definition icons.h:864
#define ICON_MD_COTTAGE
Definition icons.h:478
#define ICON_MD_ADD_CIRCLE
Definition icons.h:93
#define ICON_MD_OPACITY
Definition icons.h:1349
#define ICON_MD_HISTORY
Definition icons.h:944
#define ICON_MD_EXPLORE
Definition icons.h:703
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)
Main namespace for the application.
Definition controller.cc:20
Information about a recently used project.
#define M_PI