2#include "imgui_widgets/utils.h"
15#include <hpp/optional.hpp>
18#include <filedialog/filedialog.h>
19#include <imgui/imgui.h>
20#include <imgui_widgets/markdown.h>
36void draw_item(
const std::vector<project_item>&
v, std::function<
void(ImVec2)> callback)
40 auto pos = ImGui::GetCursorPos();
43 for(
const auto& item :
v)
53 height += ImGui::GetFrameHeightWithSpacing();
65 ImVec2 item_size(ImGui::GetContentRegionAvail().
x,
height);
68 bool hovered = ImGui::IsItemHovered();
69 ImGui::SetCursorPos(pos);
89 for(
const auto& item :
v)
91 ImGui::AlignTextToFramePadding();
92 ImGui::Text(
"%s", item.tag.c_str());
105 ImGui::AlignTextToFramePadding();
106 if(i == 0 && hovered)
108 ImGui::TextLink(fmt::format(
"{}", item.name).c_str());
112 ImGui::Text(
"%s", item.name.c_str());
134auto hub::draw_project_card(
const std::string&
id,
135 const std::string&
name,
136 const std::string& directory,
137 const std::chrono::system_clock::time_point& last_modified,
138 const std::string& engine_version,
140 bool enable_interaction,
141 float form_width) ->
bool
144 ImVec2 card_size(form_width, 0);
147 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f);
148 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16, 12));
149 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
151 bool is_hovered =
false;
156 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.4f));
160 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg, 0.6f));
163 if(ImGui::BeginChild(
id.c_str(), card_size, ImGuiChildFlags_FrameStyle| ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY))
165 is_hovered = ImGui::IsWindowHovered();
170 ImDrawList* draw_list = ImGui::GetWindowDrawList();
171 ImVec2 p_min = ImGui::GetWindowPos();
172 ImVec2 p_max = ImVec2(p_min.x + ImGui::GetWindowSize().x, p_min.y + ImGui::GetWindowSize().y);
173 draw_list->AddRectFilled(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonHovered, 0.4f), 8.0f);
178 draw_list->AddRect(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.8f), 8.0f, 0, 2.0f);
182 draw_list->AddRect(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.6f), 8.0f, 0, 2.0f);
188 ImDrawList* draw_list = ImGui::GetWindowDrawList();
189 ImVec2 p_min = ImGui::GetWindowPos();
190 ImVec2 p_max = ImVec2(p_min.x + ImGui::GetWindowSize().x, p_min.y + ImGui::GetWindowSize().y);
191 draw_list->AddRect(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.6f), 8.0f, 0, 1.5f);
195 ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 8, ImGui::GetCursorPosY() + 4));
205 if(is_hovered || is_selected)
207 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_ButtonActive));
208 ImGui::Text(
"%s",
name.c_str());
209 ImGui::PopStyleColor();
213 ImGui::Text(
"%s",
name.c_str());
223 const float name_row_height = ImGui::GetItemRectSize().y;
225 if(!engine_version.empty())
228 const float engine_width = ImGui::CalcTextSize(engine_text.c_str()).x;
231 const float available_w = ImGui::GetContentRegionAvail().x;
232 const float y_offset = (name_row_height - ImGui::GetTextLineHeight()) * 0.5f;
234 ImGui::AlignedItem(1.0f,
241 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + y_offset);
243 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.9f));
244 ImGui::TextUnformatted(engine_text.c_str());
245 ImGui::PopStyleColor();
253 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.9f));
261 const float date_width = ImGui::CalcTextSize(date_text.c_str()).x;
262 const float available_width = ImGui::GetContentRegionAvail().x;
264 ImGui::AlignedItem(1.0f,
269 ImGui::TextUnformatted(date_text.c_str());
275 const float date_width = ImGui::CalcTextSize(unknown_text).x;
276 const float available_width = ImGui::GetContentRegionAvail().x;
278 ImGui::AlignedItem(1.0f,
283 ImGui::TextUnformatted(unknown_text);
287 ImGui::PopStyleColor();
293 ImGui::PopStyleVar(3);
294 ImGui::PopStyleColor();
296 return is_hovered && enable_interaction;
299auto hub::draw_sample_card(
const std::string&
id,
300 const std::string&
name,
302 const std::string& url,
303 float form_width) ->
bool
305 ImVec2 card_size(form_width, 0);
307 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f);
308 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16, 12));
309 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
310 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg, 0.6f));
312 bool is_hovered =
false;
314 if(ImGui::BeginChild(
id.c_str(), card_size, ImGuiChildFlags_FrameStyle | ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY))
316 is_hovered = ImGui::IsWindowHovered();
320 ImDrawList* draw_list = ImGui::GetWindowDrawList();
321 ImVec2 p_min = ImGui::GetWindowPos();
322 ImVec2 p_max = ImVec2(p_min.x + ImGui::GetWindowSize().x, p_min.y + ImGui::GetWindowSize().y);
323 draw_list->AddRectFilled(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonHovered, 0.4f), 8.0f);
324 draw_list->AddRect(p_min, p_max, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.6f), 8.0f, 0, 2.0f);
327 ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 8, ImGui::GetCursorPosY() + 4));
334 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_ButtonActive));
335 ImGui::Text(
"%s",
name.c_str());
336 ImGui::PopStyleColor();
340 ImGui::Text(
"%s",
name.c_str());
347 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.9f));
349 ImGui::PopStyleColor();
355 ImGui::PopStyleVar(3);
356 ImGui::PopStyleColor();
358 if(is_hovered && ImGui::IsMouseClicked(0))
360 ImGui::OpenInShell(url.c_str());
365 ImGui::SetItemTooltipEx(
"Click to open download link: %s", url.c_str());
376 ev.on_project_opened.connect(sentinel_,
this, &hub::on_project_opened);
377 ev.on_frame_update.connect(sentinel_,
this, &hub::on_frame_update);
378 ev.on_frame_before_render.connect(sentinel_,
this, &hub::on_frame_before_render);
379 ev.on_frame_render.connect(sentinel_,
this, &hub::on_frame_render);
380 ev.on_play_before_begin.connect(sentinel_, -998,
this, &hub::on_play_before_begin);
381 ev.on_play_begin.connect(sentinel_, -999,
this, &hub::on_play_begin);
382 ev.on_play_after_end.connect(sentinel_, -999,
this, &hub::on_play_after_end);
384 ev.on_script_recompile.connect(sentinel_, 10000,
this, &hub::on_script_recompile);
385 ev.on_os_event.connect(sentinel_, 10000,
this, &hub::on_os_event);
392 APPLOG_TRACE(
"{}::{}", hpp::type_name_str(*
this), __func__);
401 APPLOG_TRACE(
"{}::{}", hpp::type_name_str(*
this), __func__);
412 if(!pm.has_open_project())
431 if(!pm.has_open_project())
442 if(!pm.has_open_project())
454 if(!pm.has_open_project())
465 if(!pm.has_open_project())
467 on_start_page_render(ctx);
471 on_opened_project_render(ctx);
477 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.f);
478 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(43.f / 255.f, 43.f / 255.f, 43.f / 255.f, 100.f / 255.f));
482 ImGui::PopStyleVar(1);
483 ImGui::PopStyleColor(1);
485 if(ImGui::IsKeyPressed(ImGuiKey_F11))
500 if(
auto* game_window = ImGui::FindWindowByName(
GAME_VIEW))
502 ImGui::FocusWindow(game_window);
509 ImGui::FocusWindow(ImGui::FindWindowByName(
GAME_VIEW));
516 ImGui::FocusWindow(ImGui::FindWindowByName(
SCENE_VIEW));
522 if(!pm.has_open_project())
527 if(
e.type == os::events::drop_position)
531 else if(
e.type == os::events::drop_begin)
535 else if(
e.type == os::events::drop_file)
539 else if(
e.type == os::events::drop_complete)
543 else if(
e.type == os::events::window)
545 if(
e.window.type == os::window_event_id::close)
547 auto window_id =
e.window.window_id;
550 auto render_window = rend.get_main_window();
553 if(render_window->get_window().get_id() == window_id)
573 const ImGuiViewport* viewport = ImGui::GetMainViewport();
574 ImGui::SetNextWindowPos(viewport->WorkPos);
575 ImGui::SetNextWindowSize(viewport->WorkSize);
576 ImGui::SetNextWindowViewport(viewport->ID);
577 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
578 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
579 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.08f, 1.0f));
581 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking;
583 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
584 window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
585 ImGui::Begin(
"START PAGE",
nullptr, window_flags);
586 ImGui::PopStyleVar(2);
587 ImGui::PopStyleColor();
601 const ImVec2 viewport_size = ImGui::GetMainViewport()->Size;
602 const ImVec2 chooser_size(viewport_size.x * 0.55f, viewport_size.y * 0.58f);
603 const ImVec2 avail = ImGui::GetContentRegionAvail();
604 const ImVec2 cursor = ImGui::GetCursorPos();
605 ImGui::SetCursorPos(ImVec2(cursor.x + std::max(0.0f, (avail.x - chooser_size.x) * 0.5f),
606 cursor.y + std::max(0.0f, (avail.y - chooser_size.y) * 0.5f)));
608 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(32.0f, 28.0f));
609 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12.0f, 10.0f));
610 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
611 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_PopupBg));
613 if(ImGui::BeginChild(
"PROJECTS", chooser_size, ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding))
615 switch(current_view_)
617 case view_state::projects_list:
618 render_projects_list_view(ctx);
620 case view_state::new_project_creator:
621 render_new_project_creator_view(ctx);
623 case view_state::project_remover:
624 render_project_remover_view(ctx);
626 case view_state::project_samples:
627 render_project_samples_view(ctx);
633 ImGui::PopStyleColor();
634 ImGui::PopStyleVar(3);
643 auto on_open_project = [&](
const std::string&
p)
646 auto project_path = fs::path(p).make_preferred();
648 const auto queue_open = [&ctx, &pm, &em, project_path]()
650 em.queue_action(
"Open Project", [&ctx, &pm, project_path]()
652 pm.open_project(ctx, project_path);
665 const auto report = pm.inspect_project(project_path);
677 std::string cancel_log;
681 title =
"Unrecognized project folder";
682 message =
"The selected folder does not contain a project.cfg file.\n\n"
683 " Path: " + project_path.string() +
"\n"
684 " Running engine: " +
running +
"\n\n"
685 "This is either a legacy project created before project.cfg was introduced, "
686 "or a folder that isn't an Unravel project at all.\n\n"
687 "If you proceed, the engine will assume it is a project and create a fresh "
688 "project.cfg inside it.\n\n"
689 "Open this folder as a project?";
690 cancel_log =
"Project open cancelled by user (no project.cfg).";
694 const std::string opened_by = report.on_disk.engine_version_opened.original.empty()
695 ? report.on_disk.engine_version_opened.to_string()
696 : report.on_disk.engine_version_opened.original;
698 title =
"Project engine-version mismatch";
699 message =
"This project was last opened with an older engine version than the one you are running.\n\n"
700 " Project saved by: " + opened_by +
"\n"
701 " Running engine: " +
running +
"\n\n"
702 "Opening it may cause data loss if the on-disk format has changed since then.\n"
703 "It is strongly recommended to back up the project folder yourself before proceeding.\n\n"
704 "Open the project anyway?";
705 cancel_log =
"Project open cancelled by user (engine-version mismatch).";
724 auto title_text =
"Unravel Engine";
725 float title_w = ImGui::CalcTextSize(title_text).x;
726 float region_w = ImGui::GetContentRegionAvail().x;
727 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (region_w - title_w) * 0.5f);
728 ImGui::TextUnformatted(title_text);
735 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.50f, 0.50f, 0.50f, 1.0f));
736 auto subtitle_text =
"Open an existing project or create a new one";
737 float subtitle_w = ImGui::CalcTextSize(subtitle_text).x;
738 float region_w = ImGui::GetContentRegionAvail().x;
739 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (region_w - subtitle_w) * 0.5f);
740 ImGui::TextUnformatted(subtitle_text);
741 ImGui::PopStyleColor();
744 ImGui::Spacing(); ImGui::Spacing();
746 ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(0.25f, 0.25f, 0.25f, 1.0f));
748 ImGui::PopStyleColor();
750 ImGui::Spacing(); ImGui::Spacing();
753 float sidebar_width = 200.0f;
754 float content_spacing = 20.0f;
755 float content_width = ImGui::GetContentRegionAvail().x - sidebar_width - content_spacing;
761 ImGui::Text(
"Projects");
767 ImGuiWindowFlags flags = ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoSavedSettings;
769 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16.0f, 16.0f));
770 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 12.0f));
771 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f);
772 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_WindowBg, 0.8f));
774 if(ImGui::BeginChild(
"projects_content", ImVec2(content_width, ImGui::GetContentRegionAvail().
y - 24), ImGuiChildFlags_Borders, flags))
776 const auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
778 if(recent_projects.empty())
781 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 60);
784 float center_x = (ImGui::GetContentRegionAvail().x - 200) * 0.5f;
785 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + center_x);
788 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
789 ImGui::Text(
"No recent projects found");
790 ImGui::PopStyleColor();
793 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + center_x - 30);
794 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.7f));
795 ImGui::Text(
"Create a new project or browse for an existing one");
796 ImGui::PopStyleColor();
802 for(
size_t i = 0;
i < recent_projects.size(); ++
i)
804 const auto& prj = recent_projects[
i];
805 auto p = fs::path(prj);
809 auto ftime = fs::last_write_time(p /
"settings" /
"settings.cfg", ec);
810 auto system_time = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
811 ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
813 auto name =
p.stem().string();
814 auto dir =
p.parent_path().string();
819 std::string engine_version_str;
820 const auto project_report = pm.inspect_project(p);
823 const auto& ver = project_report.on_disk.engine_version_opened;
824 engine_version_str = ver.original.empty() ? ver.to_string() : ver.original;
828 bool is_selected = (selected_project_ == prj.string());
831 bool is_hovered = draw_project_card(
832 fmt::format(
"project_card_{}", i),
846 if(ImGui::IsMouseClicked(0))
848 selected_project_ = prj.string();
852 if(ImGui::IsMouseDoubleClicked(0))
854 on_open_project(prj.string());
858 if(ImGui::IsMouseClicked(1))
860 selected_project_ = prj.string();
861 ImGui::OpenPopup(fmt::format(
"project_context_menu_{}", i).c_str());
866 if(ImGui::BeginPopup(fmt::format(
"project_context_menu_{}", i).c_str()))
869 ImGui::Text(
"%s",
name.c_str());
873 if(ImGui::MenuItem(
"Open Project"))
875 on_open_project(prj.string());
880 if(ImGui::MenuItem(
"Remove from Recents"))
882 auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
883 auto it = std::find(recent_projects.begin(), recent_projects.end(), fs::path(prj.string()));
884 if(it != recent_projects.end())
886 recent_projects.erase(it);
887 pm.save_editor_settings();
888 if(selected_project_ == prj.string())
890 selected_project_.clear();
895 if(ImGui::MenuItem(
"Delete Project Folder"))
898 auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
899 auto it = std::find(recent_projects.begin(), recent_projects.end(), fs::path(prj.string()));
900 if(it != recent_projects.end())
902 recent_projects.erase(it);
903 pm.save_editor_settings();
904 if(selected_project_ == prj.string())
906 selected_project_.clear();
918 ImGui::PopStyleVar(3);
919 ImGui::PopStyleColor();
923 ImGui::SameLine(0, content_spacing);
929 ImGui::Text(
"Actions");
935 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
936 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.9f));
937 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.1f));
938 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.2f));
941 if(ImGui::Button(
"New Project", ImVec2(sidebar_width, 45)))
943 current_view_ = view_state::new_project_creator;
945 project_directory_ =
"";
949 ImGui::PopStyleColor(3);
954 bool has_selection = !selected_project_.empty();
957 ImGui::BeginDisabled();
960 if(ImGui::Button(
"Open Selected", ImVec2(sidebar_width, 35)))
964 on_open_project(selected_project_);
970 ImGui::EndDisabled();
975 auto project_name = fs::path(selected_project_).stem().string();
976 ImGui::SetItemTooltipEx(
"Open: %s", project_name.c_str());
982 if(ImGui::Button(
"Browse for Project", ImVec2(sidebar_width, 35)))
985 if(native::pick_folder_dialog(path))
987 on_open_project(path);
996 ImGui::BeginDisabled();
999 if(ImGui::Button(
"Remove Selected", ImVec2(sidebar_width, 35)))
1003 project_to_remove_ = selected_project_;
1004 current_view_ = view_state::project_remover;
1010 ImGui::EndDisabled();
1015 auto project_name = fs::path(selected_project_).stem().string();
1016 ImGui::SetItemTooltipEx(
"Remove: %s", project_name.c_str());
1022 if(ImGui::Button(
"Samples", ImVec2(sidebar_width, 35)))
1024 current_view_ = view_state::project_samples;
1026 ImGui::SetItemTooltipEx(
"Browse sample projects to download");
1028 ImGui::PopStyleVar();
1034 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.8f));
1036 ImGui::TextWrapped(
"Click to select, double-click to open, or right-click for options.");
1038 ImGui::PopStyleColor();
1052 static const sample_item samples[] = {
1054 "Full demo project with PBR rendering, physics, audio, and scripting examples.",
1055 "https://github.com/unravel-dev/DemoProject"},
1056 {
"UnravelEngine Repository",
1057 "Source code and documentation for the UnravelEngine.",
1058 "https://github.com/unravel-dev/UnravelEngine"},
1060 "C++ engine API documentation.",
1061 "https://unravel-dev.github.io/UnravelEngine/engine-api/html/"},
1062 {
"Scripting API Docs",
1063 "C# scripting API documentation.",
1064 "https://unravel-dev.github.io/UnravelEngine/script-api/html/"},
1068 ImGui::BeginGroup();
1071 ImGui::Text(
"Samples");
1074 ImGui::SameLine(ImGui::GetContentRegionAvail().
x - 80);
1075 if(ImGui::Button(
"Back", ImVec2(80, 0)))
1077 current_view_ = view_state::projects_list;
1083 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.8f));
1084 ImGui::Text(
"Click a sample to open its download or documentation link in your browser.");
1085 ImGui::PopStyleColor();
1089 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16.0f, 16.0f));
1090 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 12.0f));
1091 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f);
1092 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_WindowBg, 0.8f));
1094 float content_width = ImGui::GetContentRegionAvail().x;
1096 if(ImGui::BeginChild(
"samples_content", ImVec2(content_width, ImGui::GetContentRegionAvail().
y - 24), ImGuiChildFlags_Borders))
1098 for(
size_t i = 0;
i < std::size(samples); ++
i)
1100 const auto&
s = samples[
i];
1101 draw_sample_card(fmt::format(
"sample_card_{}", i),
s.name,
s.description,
s.url, content_width - 32.0f);
1106 ImGui::PopStyleColor();
1107 ImGui::PopStyleVar(3);
1110void hub::render_new_project_creator_view(
rtti::context& ctx)
1112 auto& pm = ctx.
get_cached<project_manager>();
1113 auto& em = ctx.
get_cached<editing_manager>();
1115 auto on_create_project = [&](
const std::string&
p)
1117 em.queue_action(
"Create Project", [&ctx, &pm, p]()
1119 auto path = fs::path(p).make_preferred();
1120 pm.create_project(ctx, path);
1125 ImGui::BeginGroup();
1128 ImGui::Text(
"Create New Project");
1131 ImGui::SameLine(ImGui::GetContentRegionAvail().
x - 80);
1132 if(ImGui::Button(
"Back", ImVec2(80, 0)))
1134 current_view_ = view_state::projects_list;
1135 project_name_.clear();
1136 project_directory_.clear();
1146 float form_width = 600.0f;
1147 float center_offset = (ImGui::GetContentRegionAvail().x - form_width) * 0.5f;
1148 if(center_offset > 0)
1150 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + center_offset);
1153 ImGui::BeginGroup();
1157 ImGui::Text(
"Project Name");
1161 ImGui::SetNextItemWidth(form_width);
1162 ImGui::InputTextWidget(
"##project_name", project_name_,
false);
1164 if(project_name_.empty())
1166 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
1167 ImGui::Text(
"Enter a name for your project");
1168 ImGui::PopStyleColor();
1177 ImGui::Text(
"Project Location");
1181 ImGui::BeginGroup();
1183 float button_width = 40.0f;
1184 float input_width = form_width - button_width - ImGui::GetStyle().ItemSpacing.x;
1186 ImGui::SetNextItemWidth(input_width);
1187 ImGui::InputTextWidget(
"##project_directory", project_directory_,
false);
1192 std::string picked_dir;
1193 if(native::pick_folder_dialog(picked_dir))
1195 project_directory_ = picked_dir;
1198 ImGui::SetItemTooltipEx(
"Browse for folder...");
1204 if(project_directory_.empty())
1206 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
1207 float text_width = ImGui::CalcTextSize(
"Choose where to create your project").x;
1208 float available_width = ImGui::GetContentRegionAvail().x;
1209 ImGui::AlignedItem(0.5f, available_width, text_width, [&]() {
1210 ImGui::Text(
"Choose where to create your project");
1212 ImGui::PopStyleColor();
1216 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled));
1217 std::string text = fmt::format(
"Project will be created at: {}", (fs::path(project_directory_) / project_name_).
string());
1218 float text_width = ImGui::CalcTextSize(text.c_str()).x;
1219 float available_width = ImGui::GetContentRegionAvail().x;
1220 ImGui::AlignedItem(0.5f, available_width, text_width, [&]() {
1221 ImGui::Text(
"%s", text.c_str());
1223 ImGui::PopStyleColor();
1232 bool can_create = !project_name_.empty() && !project_directory_.empty();
1235 float button_width = 120.0f;
1236 float buttons_total_width = button_width * 2 + ImGui::GetStyle().ItemSpacing.x;
1237 float button_center_offset = (form_width - buttons_total_width) * 0.5f;
1241 ImGui::AlignedItem(0.5f,
1242 ImGui::GetContentRegionAvail().
x,
1243 buttons_total_width,
1246 ImGui::BeginGroup();
1251 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive));
1252 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.2f));
1256 ImGui::BeginDisabled();
1259 if(ImGui::Button(
"Create Project", ImVec2(button_width, 35)))
1263 auto project_path = fs::path(project_directory_) / project_name_;
1264 on_create_project(project_path.string());
1265 current_view_ = view_state::projects_list;
1266 project_name_.clear();
1267 project_directory_.clear();
1273 ImGui::PopStyleColor(2);
1277 ImGui::EndDisabled();
1282 if(ImGui::Button(
"Cancel", ImVec2(button_width, 35)))
1284 current_view_ = view_state::projects_list;
1285 project_name_.clear();
1286 project_directory_.clear();
1297 auto& pm = ctx.
get_cached<project_manager>();
1300 ImGui::BeginGroup();
1303 ImGui::Text(
"Remove Project");
1306 ImGui::SameLine(ImGui::GetContentRegionAvail().
x - 80);
1307 if(ImGui::Button(
"Back", ImVec2(80, 0)))
1309 current_view_ = view_state::projects_list;
1310 project_to_remove_.clear();
1320 float form_width = ImGui::GetContentRegionAvail().x * 0.8f;
1321 float center_offset = (ImGui::GetContentRegionAvail().x - form_width) * 0.5f;
1322 if(center_offset > 0)
1324 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + center_offset);
1327 ImGui::BeginGroup();
1330 if(project_to_remove_.empty())
1333 ImGui::Text(
"Select Project to Remove");
1338 const auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
1339 if(!recent_projects.empty())
1341 static int selected_project = 0;
1344 std::vector<std::string> project_names;
1345 for(
const auto& prj : recent_projects)
1347 auto p = fs::path(prj);
1348 project_names.push_back(
p.stem().string());
1351 ImGui::SetNextItemWidth(form_width);
1352 if(ImGui::BeginCombo(
"##project_select", project_names[selected_project].c_str()))
1354 for(
int i = 0;
i < project_names.size(); ++
i)
1356 bool is_selected = (selected_project ==
i);
1357 if(ImGui::Selectable(project_names[i].c_str(), is_selected))
1359 selected_project =
i;
1360 project_to_remove_ = recent_projects[
i].string();
1364 ImGui::SetItemDefaultFocus();
1370 if(selected_project >= 0 && selected_project < recent_projects.size())
1372 project_to_remove_ = recent_projects[selected_project].string();
1382 if(!project_to_remove_.empty())
1384 auto project_path = fs::path(project_to_remove_);
1385 auto project_name = project_path.stem().string();
1386 auto project_dir = project_path.parent_path().string();
1390 auto ftime = fs::last_write_time(project_path /
"settings" /
"settings.cfg", ec);
1391 auto system_time = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
1392 ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
1394 std::string engine_version_str;
1395 const auto project_report = pm.inspect_project(project_path);
1398 const auto& ver = project_report.on_disk.engine_version_opened;
1399 engine_version_str = ver.original.empty() ? ver.to_string() : ver.original;
1403 ImGui::Text(
"Project Information");
1409 "project_card_removal",
1425 ImGui::Text(
"Choose Removal Action");
1429 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.9f));
1430 ImGui::TextWrapped(
"Select how you want to remove this project from your workspace:");
1431 ImGui::PopStyleColor();
1438 float button_spacing = 15.0f;
1439 float total_spacing = button_spacing * 2;
1440 float button_width = (form_width - total_spacing) / 3.0f;
1441 float button_height = 55.0f;
1444 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
1445 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.8f));
1446 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.0f));
1447 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_ButtonActive, 1.2f));
1450 if(ImGui::Button(
"Remove from Recents", ImVec2(button_width, button_height)))
1452 auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
1453 auto it = std::find(recent_projects.begin(), recent_projects.end(), fs::path(project_to_remove_));
1454 if(it != recent_projects.end())
1456 recent_projects.erase(it);
1457 pm.save_editor_settings();
1459 current_view_ = view_state::projects_list;
1460 project_to_remove_.clear();
1464 ImGui::PopStyleColor(3);
1466 ImGui::SetItemTooltipEx(
"Remove from recent projects list\n(Project folder remains untouched)");
1469 ImGui::SameLine(0, button_spacing);
1472 ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(180, 60, 60, 255));
1473 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(220, 80, 80, 255));
1474 ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(255, 100, 100, 255));
1477 if(ImGui::Button(
"Delete Folder", ImVec2(button_width, button_height)))
1480 auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
1481 auto it = std::find(recent_projects.begin(), recent_projects.end(), fs::path(project_to_remove_));
1482 if(it != recent_projects.end())
1484 recent_projects.erase(it);
1485 pm.save_editor_settings();
1488 fs::remove_all(project_to_remove_);
1490 current_view_ = view_state::projects_list;
1491 project_to_remove_.clear();
1495 ImGui::PopStyleColor(3);
1497 ImGui::SetItemTooltipEx(
"DANGER: Permanently delete the entire\nproject folder and all its contents");
1500 ImGui::SameLine(0, button_spacing);
1503 if(ImGui::Button(
"Cancel", ImVec2(button_width, button_height)))
1505 current_view_ = view_state::projects_list;
1506 project_to_remove_.clear();
1509 ImGui::PopStyleVar();
1511 ImGui::SetItemTooltipEx(
"Return to projects list without making changes");
1518 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.7f));
1520 ImGui::TextWrapped(
"Tip: 'Remove from Recents' is the safer option if you want to keep the project files.");
1522 ImGui::PopStyleColor();
static void render()
Draw the modal. Call every frame from the main UI render loop.
void open_project_settings(rtti::context &ctx, const std::string &hint)
auto deinit(rtti::context &ctx) -> bool
auto init(rtti::context &ctx) -> bool
void set_external_drop_in_progress(bool in_progress)
void add_external_drop_file(const std::string &file)
void on_frame_ui_render(rtti::context &ctx)
auto get_project_settings_panel() -> project_settings_panel &
auto get_scene_panel() -> scene_panel &
void on_frame_update(rtti::context &ctx, delta_t dt)
void on_frame_render(rtti::context &ctx, delta_t dt)
auto get_game_panel() -> game_panel &
auto get_dockspace() -> dockspace &
void on_frame_before_render(rtti::context &ctx, delta_t dt)
auto get_console_log_panel() -> console_log_panel &
void set_external_drop_position(ImVec2 pos)
std::chrono::duration< float > delta_t
#define ICON_MDI_CLOCK_OUTLINE
#define ICON_MDI_ENGINE_OUTLINE
#define ICON_MDI_FOLDER_OPEN
#define APPLOG_TRACE(...)
ModalResult
Modal result flags for message box buttons.
auto ShowConfirmation(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback, MessageType type) -> std::shared_ptr< MsgBox >
Show a confirmation dialog with OK/Cancel buttons.
auto IsConfirmation(ModalResult result) -> bool
auto RenderMessageBoxes() -> void
Render all message boxes (call this in your main render loop)
void PushFont(Font::Enum _font)
NOTIFY_INLINE void PushNotification(const ImGuiToast &toast)
Insert a new toast in the list.
void PopWindowFontScale()
NOTIFY_INLINE void RenderNotifications()
Render toasts, call at the end of your rendering!
void PushWindowFontScale(float scale)
auto get_current() -> engine_version
std::vector< float > scale
#define APP_SCOPE_PERF(name_literal)
Create a scoped performance timer that records to the timeline profiler. Only accepts string literals...
static auto prompt_save_scene(rtti::context &ctx, const std::function< void()> &on_continue) -> bool
static auto interrupt() -> bool
hpp::event< void(rtti::context &, delta_t)> on_frame_ui_render