Unravel Engine C++ Reference
Loading...
Searching...
No Matches
hub.cpp
Go to the documentation of this file.
1#include "hub.h"
2#include "imgui_widgets/utils.h"
3#include <editor/events.h>
6#include <engine/engine.h>
7#include <engine/events.h>
9#include <hpp/optional.hpp>
10
11#include <filedialog/filedialog.h>
12#include <imgui/imgui.h>
13#include <imgui_widgets/markdown.h>
15#include <memory>
16
17namespace unravel
18{
19
20namespace
21{
22struct project_item
23{
25 float scale = 1.0f;
26 std::string tag{};
27 std::string name{};
28};
29
30void draw_item(const std::vector<project_item>& v, std::function<void(ImVec2)> callback)
31{
32 ImGui::BeginGroup();
33
34 auto pos = ImGui::GetCursorPos();
35
36 float height = 0;
37 for(const auto& item : v)
38 {
39 if(item.font != ImGui::Font::Count)
40 {
41 ImGui::PushFont(item.font);
42 }
43 if(item.scale > 0)
44 {
46 }
47 height += ImGui::GetFrameHeightWithSpacing();
48
49 if(item.scale > 0)
50 {
52 }
53
54 if(item.font != ImGui::Font::Count)
55 {
56 ImGui::PopFont();
57 }
58 }
59 ImVec2 item_size(ImGui::GetContentRegionAvail().x, height);
60
61 callback(item_size);
62 bool hovered = ImGui::IsItemHovered();
63 ImGui::SetCursorPos(pos);
64 ImGui::Dummy({});
65 ImGui::Indent();
66
67 // ImGui::BeginGroup();
68 // {
69 // ImGui::AlignTextToFramePadding();
70 // ImGui::NewLine();
71
72 // ImGui::AlignTextToFramePadding();
73 // ImGui::TextUnformatted(fmt::format("{} {}.", 0, ICON_MDI_FOLDER).c_str());
74
75 // ImGui::AlignTextToFramePadding();
76 // ImGui::NewLine();
77 // }
78 // ImGui::EndGroup();
79 // ImGui::SameLine(0.0f, 1.0f);
80 ImGui::BeginGroup();
81
82 size_t i = 0;
83 for(const auto& item : v)
84 {
85 ImGui::AlignTextToFramePadding();
86 ImGui::Text("%s", item.tag.c_str());
87
88 ImGui::SameLine();
89
90 if(item.font != ImGui::Font::Count)
91 {
92 ImGui::PushFont(item.font);
93 }
94 if(item.scale > 0)
95 {
97 }
98
99 ImGui::AlignTextToFramePadding();
100 if(i == 0 && hovered)
101 {
102 ImGui::TextLink(fmt::format("{}", item.name).c_str());
103 }
104 else
105 {
106 ImGui::Text("%s", item.name.c_str());
107 }
108
109 if(item.scale > 0)
110 {
112 }
113
114 if(item.font != ImGui::Font::Count)
115 {
116 ImGui::PopFont();
117 }
118
119 i++;
120 }
121 ImGui::EndGroup();
122 ImGui::Unindent();
123
124 ImGui::EndGroup();
125}
126} // namespace
127
128auto hub::draw_project_card(const std::string& id,
129 const std::string& name,
130 const std::string& directory,
131 const std::chrono::system_clock::time_point& last_modified,
132 bool is_selected,
133 bool enable_interaction,
134 float form_width) -> bool
135{
136 // Create project card with auto-resize height
137 ImVec2 card_size(form_width, 0);
138
139 // Card background and interaction styling
140 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f);
141 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16, 12));
142 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
143
144 bool is_hovered = false;
145
146 // Different background color for selected projects
147 if(is_selected)
148 {
149 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.4f));
150 }
151 else
152 {
153 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg, 0.6f));
154 }
155
156 if(ImGui::BeginChild(id.c_str(), card_size, ImGuiChildFlags_FrameStyle| ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY))
157 {
158 is_hovered = ImGui::IsWindowHovered();
159
160 // Enhanced hover effect with gradient
161 if(is_hovered)
162 {
163 ImDrawList* draw_list = ImGui::GetWindowDrawList();
164 ImVec2 p_min = ImGui::GetWindowPos();
165 ImVec2 p_max = ImVec2(p_min.x + ImGui::GetWindowSize().x, p_min.y + ImGui::GetWindowSize().y);
166 draw_list->AddRectFilled(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonHovered, 0.4f), 8.0f);
167
168 // Add subtle border highlight
169 if(is_selected)
170 {
171 draw_list->AddRect(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.8f), 8.0f, 0, 2.0f);
172 }
173 else
174 {
175 draw_list->AddRect(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.6f), 8.0f, 0, 2.0f);
176 }
177 }
178 else if(is_selected)
179 {
180 // Add selection border even when not hovered
181 ImDrawList* draw_list = ImGui::GetWindowDrawList();
182 ImVec2 p_min = ImGui::GetWindowPos();
183 ImVec2 p_max = ImVec2(p_min.x + ImGui::GetWindowSize().x, p_min.y + ImGui::GetWindowSize().y);
184 draw_list->AddRect(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.6f), 8.0f, 0, 1.5f);
185 }
186
187 // Project content layout with proper internal padding
188 ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 8, ImGui::GetCursorPosY() + 4));
189 ImGui::BeginGroup();
190 {
191 // Project name (large, bold) with enhanced styling
193
195 if(is_hovered || is_selected)
196 {
197 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_ButtonActive));
198 ImGui::Text("%s", name.c_str());
199 ImGui::PopStyleColor();
200 }
201 else
202 {
203 ImGui::Text("%s", name.c_str());
204 }
206 ImGui::PopFont();
207
208 ImGui::Spacing();
209
210 // Project location and date in improved horizontal layout
211 ImGui::BeginGroup();
212 {
213 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.9f));
214
215 // Location on the left
216 ImGui::Text(ICON_MDI_FOLDER " %s", directory.c_str());
217
218 // Date aligned to the right
219 ImGui::SameLine();
220 try
221 {
222 auto date_text = fmt::format(ICON_MDI_CLOCK_OUTLINE " {:%m/%d/%Y}", last_modified);
223 float date_width = ImGui::CalcTextSize(date_text.c_str()).x;
224 float available_width = ImGui::GetContentRegionAvail().x;
225
226 ImGui::AlignedItem(1.0f,
227 available_width,
228 date_width,
229 [&]()
230 {
231 ImGui::Text("%s", date_text.c_str());
232 });
233 }
234 FMT_CATCH(...)
235 {
236 const char* unknown_text = ICON_MDI_CLOCK_OUTLINE " Unknown";
237 float date_width = ImGui::CalcTextSize(unknown_text).x;
238 float available_width = ImGui::GetContentRegionAvail().x;
239
240 ImGui::AlignedItem(1.0f,
241 available_width,
242 date_width,
243 [&]()
244 {
245 ImGui::Text("%s", unknown_text);
246 });
247 }
248
249 ImGui::PopStyleColor();
250 }
251 ImGui::EndGroup();
252 }
253 ImGui::EndGroup();
254 }
255 ImGui::EndChild();
256
257 ImGui::PopStyleVar(3);
258 ImGui::PopStyleColor();
259
260 return is_hovered && enable_interaction;
261}
262
264{
265 auto& ui_ev = ctx.get_cached<ui_events>();
266 auto& ev = ctx.get_cached<events>();
267
268 ev.on_frame_update.connect(sentinel_, this, &hub::on_frame_update);
269 ev.on_frame_before_render.connect(sentinel_, this, &hub::on_frame_before_render);
270 ev.on_frame_render.connect(sentinel_, this, &hub::on_frame_render);
271 ev.on_play_begin.connect(sentinel_, -999, this, &hub::on_play_begin);
272 ev.on_script_recompile.connect(sentinel_, 10000, this, &hub::on_script_recompile);
273 ev.on_os_event.connect(sentinel_, 10000, this, &hub::on_os_event);
274
275 ui_ev.on_frame_ui_render.connect(sentinel_, this, &hub::on_frame_ui_render);
276}
277
278auto hub::init(rtti::context& ctx) -> bool
279{
280 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
281
282 panels_.init(ctx);
283
284 return true;
285}
286
287auto hub::deinit(rtti::context& ctx) -> bool
288{
289 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
290
291 panels_.deinit(ctx);
292
293 return true;
294}
295
296void hub::open_project_settings(rtti::context& ctx, const std::string& hint)
297{
298 auto& pm = ctx.get_cached<project_manager>();
299
300 if(!pm.has_open_project())
301 {
302 return;
303 }
304
305 panels_.get_project_settings_panel().show(true, hint);
306}
307
308void hub::on_frame_update(rtti::context& ctx, delta_t dt)
309{
310 auto& pm = ctx.get_cached<project_manager>();
311
312 if(!pm.has_open_project())
313 {
314 return;
315 }
316 panels_.on_frame_update(ctx, dt);
317}
318
319void hub::on_frame_before_render(rtti::context& ctx, delta_t dt)
320{
321 auto& pm = ctx.get_cached<project_manager>();
322
323 if(!pm.has_open_project())
324 {
325 return;
326 }
327 panels_.on_frame_before_render(ctx, dt);
328}
329
330void hub::on_frame_render(rtti::context& ctx, delta_t dt)
331{
332 auto& pm = ctx.get_cached<project_manager>();
333
334 if(!pm.has_open_project())
335 {
336 return;
337 }
338 panels_.on_frame_render(ctx, dt);
339}
340
341void hub::on_frame_ui_render(rtti::context& ctx, delta_t dt)
342{
343 auto& pm = ctx.get_cached<project_manager>();
344
345 if(!pm.has_open_project())
346 {
347 on_start_page_render(ctx);
348 }
349 else
350 {
351 on_opened_project_render(ctx);
352 }
353}
354
355void hub::on_script_recompile(rtti::context& ctx, const std::string& protocol, uint64_t version)
356{
357 panels_.get_console_log_panel().on_recompile();
358}
359
360void hub::on_play_begin(rtti::context& ctx)
361{
362 panels_.get_console_log_panel().on_play();
363}
364
365void hub::on_os_event(rtti::context& ctx, os::event& e)
366{
367 auto& pm = ctx.get_cached<project_manager>();
368 if(!pm.has_open_project())
369 {
370 return;
371 }
372
373 if(e.type == os::events::drop_position)
374 {
375 panels_.set_external_drop_position(ImVec2{e.drop.x, e.drop.y});
376 }
377 else if(e.type == os::events::drop_begin)
378 {
379 panels_.set_external_drop_in_progress(true);
380 }
381 else if(e.type == os::events::drop_file)
382 {
383 panels_.add_external_drop_file(e.drop.data);
384 }
385 else if(e.type == os::events::drop_complete)
386 {
387 panels_.set_external_drop_in_progress(false);
388 }
389 else if(e.type == os::events::window)
390 {
391 if(e.window.type == os::window_event_id::close)
392 {
393 auto window_id = e.window.window_id;
394
395 auto& rend = ctx.get_cached<renderer>();
396 auto render_window = rend.get_main_window();
397 if(render_window)
398 {
399 if(render_window->get_window().get_id() == window_id)
400 {
403 });
404
405 e = {};
406 }
407 }
408 }
409 }
410}
411
412void hub::on_opened_project_render(rtti::context& ctx)
413{
414 panels_.on_frame_ui_render(ctx);
415}
416
417void hub::on_start_page_render(rtti::context& ctx)
418{
419 const ImGuiViewport* viewport = ImGui::GetMainViewport();
420 ImGui::SetNextWindowPos(viewport->WorkPos);
421 ImGui::SetNextWindowSize(viewport->WorkSize);
422 ImGui::SetNextWindowViewport(viewport->ID);
423 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
424 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
425
426 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking;
427 window_flags |=
428 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
429 window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
430 ImGui::Begin("START PAGE", nullptr, window_flags);
431 ImGui::PopStyleVar(2);
432
433 ImGui::OpenPopup("PROJECTS");
434
435 // Calculate popup size with proper margins - reduced size
436 ImVec2 viewport_size = ImGui::GetMainViewport()->Size;
437 ImVec2 popup_size = ImVec2(viewport_size.x * 0.5f, viewport_size.y * 0.5f);
438 ImGui::SetNextWindowSize(popup_size, ImGuiCond_Appearing);
439
440 // Add moderate padding to the popup window
441 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(24.0f, 20.0f));
442 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12.0f, 10.0f));
443
444 if(ImGui::BeginPopupModal("PROJECTS", nullptr, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoTitleBar))
445 {
446 switch(current_view_)
447 {
448 case view_state::projects_list:
449 render_projects_list_view(ctx);
450 break;
451 case view_state::new_project_creator:
452 render_new_project_creator_view(ctx);
453 break;
454 case view_state::project_remover:
455 render_project_remover_view(ctx);
456 break;
457 }
458
459 ImGui::EndPopup();
460 }
461
462 ImGui::PopStyleVar(2);
463
464 ImGui::End();
465}
466
467void hub::render_projects_list_view(rtti::context& ctx)
468{
469 auto& pm = ctx.get_cached<project_manager>();
470
471 auto on_open_project = [&](const std::string& p)
472 {
473 auto path = fs::path(p).make_preferred();
474 pm.open_project(ctx, path);
475 };
476
477 // Header section with improved styling
478 ImGui::BeginGroup();
479 {
480
481 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.8f));
482 ImGui::Text("Open an existing project or create a new one");
483 ImGui::PopStyleColor();
484 }
485 ImGui::EndGroup();
486
487 ImGui::Spacing();
488 ImGui::Spacing();
489
490 // Subtle separator with custom styling
491 ImGui::PushStyleColor(ImGuiCol_Separator, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.3f));
492 ImGui::Separator();
493 ImGui::PopStyleColor();
494
495 ImGui::Spacing();
496 ImGui::Spacing();
497
498 // Main content area with improved sidebar layout
499 float sidebar_width = 200.0f;
500 float content_spacing = 20.0f;
501 float content_width = ImGui::GetContentRegionAvail().x - sidebar_width - content_spacing;
502
503 // Projects list area with enhanced styling
504 ImGui::BeginGroup();
505 {
507 ImGui::Text("Projects");
508 ImGui::PopFont();
509
510 ImGui::Spacing();
511
512 // Projects container with modern card layout
513 ImGuiWindowFlags flags = ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoSavedSettings;
514
515 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16.0f, 16.0f));
516 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 12.0f));
517 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f);
518 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_WindowBg, 0.8f));
519
520 if(ImGui::BeginChild("projects_content", ImVec2(content_width, ImGui::GetContentRegionAvail().y - 24), ImGuiChildFlags_Borders, flags))
521 {
522 const auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
523
524 if(recent_projects.empty())
525 {
526 // Enhanced empty state
527 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 60);
528 ImGui::BeginGroup();
529 {
530 float center_x = (ImGui::GetContentRegionAvail().x - 200) * 0.5f;
531 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + center_x);
532
534 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
535 ImGui::Text("No recent projects found");
536 ImGui::PopStyleColor();
537 ImGui::PopFont();
538
539 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + center_x - 30);
540 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.7f));
541 ImGui::Text("Create a new project or browse for an existing one");
542 ImGui::PopStyleColor();
543 }
544 ImGui::EndGroup();
545 }
546 else
547 {
548 for(size_t i = 0; i < recent_projects.size(); ++i)
549 {
550 const auto& prj = recent_projects[i];
551 auto p = fs::path(prj);
552
553 // Get project metadata
554 fs::error_code ec;
555 auto ftime = fs::last_write_time(p / "settings" / "settings.cfg", ec);
556 auto system_time = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
557 ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
558
559 auto name = p.stem().string();
560 auto dir = p.parent_path().string();
561
562 // Check if this project is selected
563 bool is_selected = (selected_project_ == prj.string());
564
565 // Draw project card using our shared function
566 bool is_hovered = draw_project_card(
567 fmt::format("project_card_{}", i),
568 name,
569 dir,
570 system_time,
571 is_selected,
572 true,
573 content_width
574 );
575
576 // Handle interactions
577 if(is_hovered)
578 {
579 // Single click - select project
580 if(ImGui::IsMouseClicked(0))
581 {
582 selected_project_ = prj.string();
583 }
584
585 // Double click - open project
586 if(ImGui::IsMouseDoubleClicked(0))
587 {
588 on_open_project(prj.string());
589 }
590
591 // Right click - open context menu
592 if(ImGui::IsMouseClicked(1))
593 {
594 selected_project_ = prj.string();
595 ImGui::OpenPopup(fmt::format("project_context_menu_{}", i).c_str());
596 }
597 }
598
599 // Context menu
600 if(ImGui::BeginPopup(fmt::format("project_context_menu_{}", i).c_str()))
601 {
603 ImGui::Text("%s", name.c_str());
604 ImGui::PopFont();
605 ImGui::Separator();
606
607 if(ImGui::MenuItem("Open Project"))
608 {
609 on_open_project(prj.string());
610 }
611
612 ImGui::Separator();
613
614 if(ImGui::MenuItem("Remove from Recents"))
615 {
616 auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
617 auto it = std::find(recent_projects.begin(), recent_projects.end(), fs::path(prj.string()));
618 if(it != recent_projects.end())
619 {
620 recent_projects.erase(it);
621 pm.save_editor_settings();
622 if(selected_project_ == prj.string())
623 {
624 selected_project_.clear();
625 }
626 }
627 }
628
629 if(ImGui::MenuItem("Delete Project Folder"))
630 {
631 // TODO: Implement folder deletion with confirmation dialog
632 auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
633 auto it = std::find(recent_projects.begin(), recent_projects.end(), fs::path(prj.string()));
634 if(it != recent_projects.end())
635 {
636 recent_projects.erase(it);
637 pm.save_editor_settings();
638 if(selected_project_ == prj.string())
639 {
640 selected_project_.clear();
641 }
642 }
643 }
644
645 ImGui::EndPopup();
646 }
647 }
648 }
649 }
650 ImGui::EndChild();
651
652 ImGui::PopStyleVar(3);
653 ImGui::PopStyleColor();
654 }
655 ImGui::EndGroup();
656
657 ImGui::SameLine(0, content_spacing);
658
659 // Enhanced actions sidebar
660 ImGui::BeginGroup();
661 {
663 ImGui::Text("Actions");
664 ImGui::PopFont();
665
666 ImGui::Spacing();
667
668 // New Project button with enhanced styling
669 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
670 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.9f));
671 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.1f));
672 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.2f));
674
675 if(ImGui::Button("New Project", ImVec2(sidebar_width, 45)))
676 {
677 current_view_ = view_state::new_project_creator;
678 project_name_ = "";
679 project_directory_ = "";
680 }
681
682 ImGui::PopFont();
683 ImGui::PopStyleColor(3);
684
685 ImGui::Spacing();
686
687 // Open Selected button - only enabled when a project is selected
688 bool has_selection = !selected_project_.empty();
689 if(!has_selection)
690 {
691 ImGui::BeginDisabled();
692 }
693
694 if(ImGui::Button("Open Selected", ImVec2(sidebar_width, 35)))
695 {
696 if(has_selection)
697 {
698 on_open_project(selected_project_);
699 }
700 }
701
702 if(!has_selection)
703 {
704 ImGui::EndDisabled();
705 }
706
707 if(has_selection)
708 {
709 auto project_name = fs::path(selected_project_).stem().string();
710 ImGui::SetItemTooltipEx("Open: %s", project_name.c_str());
711 }
712
713 ImGui::Spacing();
714
715 // Browse for Project button (external folder picker)
716 if(ImGui::Button("Browse for Project", ImVec2(sidebar_width, 35)))
717 {
718 std::string path;
719 if(native::pick_folder_dialog(path))
720 {
721 on_open_project(path);
722 }
723 }
724
725 ImGui::Spacing();
726
727 // Remove Selected button - only enabled when a project is selected
728 if(!has_selection)
729 {
730 ImGui::BeginDisabled();
731 }
732
733 if(ImGui::Button("Remove Selected", ImVec2(sidebar_width, 35)))
734 {
735 if(has_selection)
736 {
737 project_to_remove_ = selected_project_;
738 current_view_ = view_state::project_remover;
739 }
740 }
741
742 if(!has_selection)
743 {
744 ImGui::EndDisabled();
745 }
746
747 if(has_selection)
748 {
749 auto project_name = fs::path(selected_project_).stem().string();
750 ImGui::SetItemTooltipEx("Remove: %s", project_name.c_str());
751 }
752
753 ImGui::PopStyleVar();
754
755 ImGui::Spacing();
756 ImGui::Spacing();
757
758 // Additional info section with better styling
759 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.8f));
761 ImGui::TextWrapped("Click to select, double-click to open, or right-click for options.");
762 ImGui::PopFont();
763 ImGui::PopStyleColor();
764 }
765 ImGui::EndGroup();
766}
767
768void hub::render_new_project_creator_view(rtti::context& ctx)
769{
770 auto& pm = ctx.get_cached<project_manager>();
771
772 auto on_create_project = [&](const std::string& p)
773 {
774 auto path = fs::path(p).make_preferred();
775 pm.create_project(ctx, path);
776 };
777
778 // Header section with title and back button
779 ImGui::BeginGroup();
780 {
782 ImGui::Text("Create New Project");
783 ImGui::PopFont();
784
785 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 80);
786 if(ImGui::Button("Back", ImVec2(80, 0)))
787 {
788 current_view_ = view_state::projects_list;
789 project_name_.clear();
790 project_directory_.clear();
791 }
792 }
793 ImGui::EndGroup();
794
795 ImGui::Separator();
796 ImGui::Spacing();
797 ImGui::Spacing();
798
799 // Center the form content
800 float form_width = 600.0f;
801 float center_offset = (ImGui::GetContentRegionAvail().x - form_width) * 0.5f;
802 if(center_offset > 0)
803 {
804 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + center_offset);
805 }
806
807 ImGui::BeginGroup();
808 {
809 // Project Name Section
811 ImGui::Text("Project Name");
812 ImGui::PopFont();
813 ImGui::Spacing();
814
815 ImGui::SetNextItemWidth(form_width);
816 ImGui::InputTextWidget("##project_name", project_name_, false);
817
818 if(project_name_.empty())
819 {
820 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
821 ImGui::Text("Enter a name for your project");
822 ImGui::PopStyleColor();
823 }
824
825 ImGui::Spacing();
826 ImGui::Spacing();
827 ImGui::Spacing();
828
829 // Directory Section
831 ImGui::Text("Project Location");
832 ImGui::PopFont();
833 ImGui::Spacing();
834
835 ImGui::BeginGroup();
836 {
837 float button_width = 40.0f;
838 float input_width = form_width - button_width - ImGui::GetStyle().ItemSpacing.x;
839
840 ImGui::SetNextItemWidth(input_width);
841 ImGui::InputTextWidget("##project_directory", project_directory_, false);
842
843 ImGui::SameLine();
844 if(ImGui::Button(ICON_MDI_FOLDER_OPEN "##dir_picker", ImVec2(button_width, 0)))
845 {
846 std::string picked_dir;
847 if(native::pick_folder_dialog(picked_dir))
848 {
849 project_directory_ = picked_dir;
850 }
851 }
852 ImGui::SetItemTooltipEx("Browse for folder...");
853 }
854 ImGui::EndGroup();
855 }
856 ImGui::EndGroup();
857
858 if(project_directory_.empty())
859 {
860 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
861 float text_width = ImGui::CalcTextSize("Choose where to create your project").x;
862 float available_width = ImGui::GetContentRegionAvail().x;
863 ImGui::AlignedItem(0.5f, available_width, text_width, [&]() {
864 ImGui::Text("Choose where to create your project");
865 });
866 ImGui::PopStyleColor();
867 }
868 else
869 {
870 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
871 std::string text = fmt::format("Project will be created at: {}", (fs::path(project_directory_) / project_name_).string());
872 float text_width = ImGui::CalcTextSize(text.c_str()).x;
873 float available_width = ImGui::GetContentRegionAvail().x;
874 ImGui::AlignedItem(0.5f, available_width, text_width, [&]() {
875 ImGui::Text("%s", text.c_str());
876 });
877 ImGui::PopStyleColor();
878 }
879
880 ImGui::Spacing();
881 ImGui::Spacing();
882 ImGui::Spacing();
883 ImGui::Spacing();
884
885 // Buttons Section
886 bool can_create = !project_name_.empty() && !project_directory_.empty();
887
888 // Center the buttons
889 float button_width = 120.0f;
890 float buttons_total_width = button_width * 2 + ImGui::GetStyle().ItemSpacing.x;
891 float button_center_offset = (form_width - buttons_total_width) * 0.5f;
892
893 //ImGui::SetCursorPosX(ImGui::GetCursorPosX() + button_center_offset);
894
895 ImGui::AlignedItem(0.5f,
896 ImGui::GetContentRegionAvail().x,
897 buttons_total_width,
898 [&]()
899 {
900 ImGui::BeginGroup();
901
902 // Create button with different styling based on state
903 if(can_create)
904 {
905 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive));
906 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.2f));
907 }
908 else
909 {
910 ImGui::BeginDisabled();
911 }
912
913 if(ImGui::Button("Create Project", ImVec2(button_width, 35)))
914 {
915 if(can_create)
916 {
917 auto project_path = fs::path(project_directory_) / project_name_;
918 on_create_project(project_path.string());
919 ImGui::CloseCurrentPopup();
920 current_view_ = view_state::projects_list;
921 project_name_.clear();
922 project_directory_.clear();
923 }
924 }
925
926 if(can_create)
927 {
928 ImGui::PopStyleColor(2);
929 }
930 else
931 {
932 ImGui::EndDisabled();
933 }
934
935 ImGui::SameLine();
936
937 if(ImGui::Button("Cancel", ImVec2(button_width, 35)))
938 {
939 current_view_ = view_state::projects_list;
940 project_name_.clear();
941 project_directory_.clear();
942 }
943
944 ImGui::EndGroup();
945 });
946
947
948}
949
950void hub::render_project_remover_view(rtti::context& ctx)
951{
952 auto& pm = ctx.get_cached<project_manager>();
953
954 // Header section with title and back button
955 ImGui::BeginGroup();
956 {
958 ImGui::Text("Remove Project");
959 ImGui::PopFont();
960
961 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 80);
962 if(ImGui::Button("Back", ImVec2(80, 0)))
963 {
964 current_view_ = view_state::projects_list;
965 project_to_remove_.clear();
966 }
967 }
968 ImGui::EndGroup();
969
970 ImGui::Separator();
971 ImGui::Spacing();
972 ImGui::Spacing();
973
974 // Center the form content with increased width
975 float form_width = ImGui::GetContentRegionAvail().x * 0.8f; // Increased from 500.0f
976 float center_offset = (ImGui::GetContentRegionAvail().x - form_width) * 0.5f;
977 if(center_offset > 0)
978 {
979 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + center_offset);
980 }
981
982 ImGui::BeginGroup();
983 {
984 // Project selection section if no project is pre-selected
985 if(project_to_remove_.empty())
986 {
988 ImGui::Text("Select Project to Remove");
989 ImGui::PopFont();
990 ImGui::Spacing();
991
992 // Project selection dropdown
993 const auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
994 if(!recent_projects.empty())
995 {
996 static int selected_project = 0;
997
998 // Create project names for combo
999 std::vector<std::string> project_names;
1000 for(const auto& prj : recent_projects)
1001 {
1002 auto p = fs::path(prj);
1003 project_names.push_back(p.stem().string());
1004 }
1005
1006 ImGui::SetNextItemWidth(form_width);
1007 if(ImGui::BeginCombo("##project_select", project_names[selected_project].c_str()))
1008 {
1009 for(int i = 0; i < project_names.size(); ++i)
1010 {
1011 bool is_selected = (selected_project == i);
1012 if(ImGui::Selectable(project_names[i].c_str(), is_selected))
1013 {
1014 selected_project = i;
1015 project_to_remove_ = recent_projects[i].string();
1016 }
1017 if(is_selected)
1018 {
1019 ImGui::SetItemDefaultFocus();
1020 }
1021 }
1022 ImGui::EndCombo();
1023 }
1024
1025 if(selected_project >= 0 && selected_project < recent_projects.size())
1026 {
1027 project_to_remove_ = recent_projects[selected_project].string();
1028 }
1029 }
1030
1031 ImGui::Spacing();
1032 ImGui::Spacing();
1033 ImGui::Spacing();
1034 }
1035
1036 // Project info section using the same card design as recent projects
1037 if(!project_to_remove_.empty())
1038 {
1039 auto project_path = fs::path(project_to_remove_);
1040 auto project_name = project_path.stem().string();
1041 auto project_dir = project_path.parent_path().string();
1042
1043 // Get project metadata like in the recent view
1044 fs::error_code ec;
1045 auto ftime = fs::last_write_time(project_path / "settings" / "settings.cfg", ec);
1046 auto system_time = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
1047 ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
1048
1050 ImGui::Text("Project Information");
1051 ImGui::PopFont();
1052 ImGui::Spacing();
1053
1054 // Use the shared project card function with interaction disabled
1055 draw_project_card(
1056 "project_card_removal",
1057 project_name,
1058 project_dir,
1059 system_time,
1060 false, // Not selectable in this view
1061 false, // Disable interaction
1062 form_width
1063 );
1064
1065 ImGui::Spacing();
1066 ImGui::Spacing();
1067 ImGui::Spacing();
1068
1069 // Warning section with better styling
1071 ImGui::Text("Choose Removal Action");
1072 ImGui::PopFont();
1073 ImGui::Spacing();
1074
1075 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.9f));
1076 ImGui::TextWrapped("Select how you want to remove this project from your workspace:");
1077 ImGui::PopStyleColor();
1078
1079 ImGui::Spacing();
1080 ImGui::Spacing();
1081 ImGui::Spacing();
1082
1083 // Action buttons section with improved layout and wider buttons
1084 float button_spacing = 15.0f;
1085 float total_spacing = button_spacing * 2;
1086 float button_width = (form_width - total_spacing) / 3.0f; // Increased because form_width is larger
1087 float button_height = 55.0f; // Increased height for better text visibility
1088
1089 // Remove from recents button (safe action) - primary
1090 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
1091 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.8f));
1092 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.0f));
1093 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.2f));
1095
1096 if(ImGui::Button("Remove from Recents", ImVec2(button_width, button_height)))
1097 {
1098 auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
1099 auto it = std::find(recent_projects.begin(), recent_projects.end(), fs::path(project_to_remove_));
1100 if(it != recent_projects.end())
1101 {
1102 recent_projects.erase(it);
1103 pm.save_editor_settings();
1104 }
1105 current_view_ = view_state::projects_list;
1106 project_to_remove_.clear();
1107 }
1108
1109 ImGui::PopFont();
1110 ImGui::PopStyleColor(3);
1111
1112 ImGui::SetItemTooltipEx("Remove from recent projects list\n(Project folder remains untouched)");
1113
1114
1115 ImGui::SameLine(0, button_spacing);
1116
1117 // Delete folder button (dangerous action) - red color scheme
1118 ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(180, 60, 60, 255)); // Red base color
1119 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(220, 80, 80, 255)); // Brighter red on hover
1120 ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(255, 100, 100, 255)); // Even brighter red when pressed
1122
1123 if(ImGui::Button("Delete Folder", ImVec2(button_width, button_height)))
1124 {
1125
1126 auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
1127 auto it = std::find(recent_projects.begin(), recent_projects.end(), fs::path(project_to_remove_));
1128 if(it != recent_projects.end())
1129 {
1130 recent_projects.erase(it);
1131 pm.save_editor_settings();
1132 }
1133
1134 fs::remove_all(project_to_remove_);
1135
1136 current_view_ = view_state::projects_list;
1137 project_to_remove_.clear();
1138 }
1139
1140 ImGui::PopFont();
1141 ImGui::PopStyleColor(3);
1142
1143 ImGui::SetItemTooltipEx("DANGER: Permanently delete the entire\nproject folder and all its contents");
1144
1145
1146 ImGui::SameLine(0, button_spacing);
1147
1148 // Cancel button
1149 if(ImGui::Button("Cancel", ImVec2(button_width, button_height)))
1150 {
1151 current_view_ = view_state::projects_list;
1152 project_to_remove_.clear();
1153 }
1154
1155 ImGui::PopStyleVar();
1156
1157 ImGui::SetItemTooltipEx("Return to projects list without making changes");
1158
1159
1160 ImGui::Spacing();
1161 ImGui::Spacing();
1162
1163 // Additional safety information
1164 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.7f));
1166 ImGui::TextWrapped("Tip: 'Remove from Recents' is the safer option if you want to keep the project files.");
1167 ImGui::PopFont();
1168 ImGui::PopStyleColor();
1169 }
1170 }
1171 ImGui::EndGroup();
1172}
1173
1174} // namespace unravel
void open_project_settings(rtti::context &ctx, const std::string &hint)
Definition hub.cpp:296
auto deinit(rtti::context &ctx) -> bool
Definition hub.cpp:287
auto init(rtti::context &ctx) -> bool
Definition hub.cpp:278
hub(rtti::context &ctx)
Definition hub.cpp:263
void set_external_drop_in_progress(bool in_progress)
Definition panel.cpp:186
void add_external_drop_file(const std::string &file)
Definition panel.cpp:206
void on_frame_ui_render(rtti::context &ctx)
Definition panel.cpp:85
auto get_project_settings_panel() -> project_settings_panel &
Definition panel.cpp:146
void on_frame_update(rtti::context &ctx, delta_t dt)
Definition panel.cpp:67
void on_frame_render(rtti::context &ctx, delta_t dt)
Definition panel.cpp:79
void on_frame_before_render(rtti::context &ctx, delta_t dt)
Definition panel.cpp:73
auto get_console_log_panel() -> console_log_panel &
Definition panel.cpp:166
void set_external_drop_position(ImVec2 pos)
Definition panel.cpp:196
std::chrono::duration< float > delta_t
float scale
Definition hub.cpp:25
ImGui::Font::Enum font
Definition hub.cpp:24
std::string name
Definition hub.cpp:27
std::string tag
Definition hub.cpp:26
#define ICON_MDI_CLOCK_OUTLINE
#define ICON_MDI_FOLDER
#define ICON_MDI_FOLDER_OPEN
#define APPLOG_TRACE(...)
Definition logging.h:17
void PushFont(Font::Enum _font)
Definition imgui.cpp:617
void PopWindowFontScale()
Definition imgui.cpp:705
void PushWindowFontScale(float scale)
Definition imgui.cpp:692
float y
float x
auto get_cached() -> T &
Definition context.hpp:49
static auto prompt_save_scene(rtti::context &ctx, const std::function< void()> &on_continue) -> bool
static auto interrupt() -> bool
Definition engine.cpp:477
hpp::event< void(rtti::context &, delta_t)> on_frame_ui_render
Definition events.h:12