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