7#include "imgui_widgets/utils.h"
42#include <filedialog/filedialog.h>
49#include <hpp/utility.hpp>
50#include <imgui/imgui.h>
51#include <imgui/imgui_internal.h>
52#include <imgui/imgui_internal.h>
53#include <imgui_widgets/imcoolbar.h>
59using namespace std::literals;
63fs::path pending_rename;
65auto get_new_file(
const fs::path& path,
const std::string&
name,
const std::string& ext =
"") -> fs::path
69 while(fs::exists(path / (fmt::format(
"{} ({})",
name.c_str(), i) + ext), err))
74 return path / (fmt::format(
"{} ({})",
name.c_str(), i) + ext);
77auto get_new_file_simple(
const fs::path& path,
const std::string&
name,
const std::string& ext =
"") -> fs::path
81 while(fs::exists(path / (fmt::format(
"{}{}",
name.c_str(), i) + ext), err))
86 return path / (fmt::format(
"{}{}",
name.c_str(), i) + ext);
93void sync_script_class_name(
const fs::path& script_path,
const std::string& old_stem,
const std::string& new_stem)
103 std::ifstream
input(script_path);
109 std::stringstream buffer;
110 buffer <<
input.rdbuf();
111 auto content = buffer.str();
114 const std::regex identifier(fmt::format(
"\\b{}\\b", old_stem));
115 if(!std::regex_search(content, identifier))
120 content = std::regex_replace(content, identifier, new_stem);
122 std::ofstream output(script_path);
129auto process_drag_drop_source(
const gfx::texture::ptr& preview,
const fs::path& absolute_path) ->
bool
131 if(ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
133 const auto filename = absolute_path.filename();
134 const std::string extension =
filename.has_extension() ?
filename.extension().string() :
"folder";
135 const std::string
id = absolute_path.string();
136 const std::string strfilename =
filename.string();
137 ImVec2 item_size = {64, 64};
139 texture_size = ImMax(texture_size, item_size);
141 ImGui::ContentItem citem{};
143 citem.name = strfilename.c_str();
144 citem.texture_size = texture_size;
145 citem.image_size = item_size;
147 ImGui::ContentButtonItem(citem);
149 ImGui::SetDragDropPayload(extension.c_str(),
id.data(),
id.size());
150 ImGui::EndDragDropSource();
157void process_drag_drop_target(
const fs::path& absolute_path)
159 if(ImGui::BeginDragDropTarget())
161 if(ImGui::IsDragDropPayloadBeingAccepted())
163 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
167 ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
171 if(fs::is_directory(absolute_path, err))
175 const auto process_drop = [&absolute_path](
const std::string&
type)
177 auto payload = ImGui::AcceptDragDropPayload(
type.c_str());
178 if(payload !=
nullptr)
180 std::string data(
reinterpret_cast<const char*
>(payload->Data), std::size_t(payload->DataSize));
181 fs::path new_name = absolute_path / fs::path(data).filename();
186 if(!fs::exists(new_name, err))
188 fs::rename(data, new_name, err);
195 for(
const auto& asset_set : types)
197 for(
const auto&
type : asset_set)
199 if(process_drop(
type) !=
nullptr)
206 process_drop(
"folder");
210 auto payload = ImGui::AcceptDragDropPayload(
"entity");
211 if(payload !=
nullptr)
213 entt::handle dropped{};
214 std::memcpy(&dropped, payload->Data,
size_t(payload->DataSize));
218 auto& em = ctx.get_cached<editing_manager>();
220 auto do_action = [&](entt::handle dropped)
222 auto& comp = dropped.get<tag_component>();
223 auto prefab_path = absolute_path / fs::path(comp.name +
".pfb").make_preferred();
226 auto& am = ctx.get_cached<asset_manager>();
228 dropped.get_or_emplace<prefab_component>().source = am.get_asset<prefab>(
key.generic_string());
232 if(em.is_selected(dropped))
234 for(
auto e : em.try_get_selections_as<entt::handle>())
252 ImGui::EndDragDropTarget();
257auto format_file_size(std::uintmax_t
bytes) -> std::string
259 constexpr std::array<const char*, 5> units{
"B",
"KB",
"MB",
"GB",
"TB"};
260 auto value =
static_cast<double>(
bytes);
262 while(value >= 1024.0 && unit < 4)
269 return fmt::format(
"{} {}",
bytes, units[0]);
271 return fmt::format(
"{:.1f} {}", value, units[unit]);
277struct asset_tooltip_style_scope
282 asset_tooltip_style_scope()
284 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10.0f);
285 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 12.0f));
286 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
287 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 5.0f));
289 ImVec4 window_bg = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
290 window_bg.x = std::min(window_bg.x + 0.035f, 1.0f);
291 window_bg.y = std::min(window_bg.y + 0.035f, 1.0f);
292 window_bg.z = std::min(window_bg.z + 0.035f, 1.0f);
293 ImGui::PushStyleColor(ImGuiCol_WindowBg, window_bg);
295 ImVec4 border = ImGui::GetStyleColorVec4(ImGuiCol_Border);
296 border.w = std::min(border.w * 1.35f, 1.0f);
297 ImGui::PushStyleColor(ImGuiCol_Border, border);
300 ~asset_tooltip_style_scope()
306 asset_tooltip_style_scope(
const asset_tooltip_style_scope&) =
delete;
307 asset_tooltip_style_scope& operator=(
const asset_tooltip_style_scope&) =
delete;
310auto draw_asset_tooltip_thumbnail(
const ImGui::ContentItem& citem, ImVec2 texture_size,
float thumb_side) ->
void
312 constexpr float thumb_rounding = 8.0f;
313 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, thumb_rounding);
314 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
315 ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 52));
316 if(ImGui::BeginChild(
"asset_tooltip_thumb",
317 ImVec2(thumb_side, thumb_side),
318 ImGuiChildFlags_None,
319 ImGuiWindowFlags_NoScrollbar))
321 ImGui::ImageWithAspect(citem.texId, texture_size, ImVec2(thumb_side, thumb_side), ImVec2(0.5f, 0.5f));
324 ImGui::PopStyleColor();
325 ImGui::PopStyleVar(2);
328auto draw_asset_tooltip_detail_row(
const char* label,
331 const std::string& value) ->
void
338 ImGui::AlignTextToFramePadding();
339 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
340 ImGui::TextUnformatted(label);
341 ImGui::PopStyleColor();
342 ImGui::SameLine(label_width);
343 ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + wrap_width);
344 ImGui::TextUnformatted(value.c_str());
345 ImGui::PopTextWrapPos();
352constexpr ImU32 content_caption_color = IM_COL32(224, 226, 231, 255);
356auto asset_type_accent(
const char*
type) -> ImU32
358 constexpr ImU32 fallback = IM_COL32(150, 150, 158, 255);
359 if(
type ==
nullptr ||
type[0] ==
'\0')
369 static constexpr std::array<type_color, 13> table{{
370 {
"Texture", IM_COL32(226, 96, 92, 255)},
371 {
"Material", IM_COL32(86, 180, 168, 255)},
372 {
"Physics Material", IM_COL32(214, 124, 72, 255)},
373 {
"Mesh", IM_COL32(234, 138, 64, 255)},
374 {
"Shader", IM_COL32(156, 116, 222, 255)},
375 {
"Prefab", IM_COL32(82, 179, 222, 255)},
376 {
"Scene", IM_COL32(232, 168, 70, 255)},
377 {
"Animation Clip", IM_COL32(124, 200, 96, 255)},
378 {
"Audio Clip", IM_COL32(220, 112, 178, 255)},
379 {
"Script", IM_COL32(94, 172, 206, 255)},
380 {
"Font", IM_COL32(186, 186, 196, 255)},
381 {
"UI Tree", IM_COL32(126, 138, 224, 255)},
382 {
"Style Sheet", IM_COL32(170, 134, 224, 255)},
385 for(
const auto&
entry : table)
400auto draw_content_card(
const ImGui::ContentItem& item,
bool selected) ->
bool
402 ImGuiWindow* window = ImGui::GetCurrentWindow();
403 if(window->SkipItems)
408 ImDrawList* draw_list = window->DrawList;
409 const ImGuiID
id = window->GetID(item.name);
411 constexpr float rounding = 6.0f;
412 const ImVec2 inner_pad(6.0f, 6.0f);
413 const float card_w = item.image_size.x > 0.0f ? item.image_size.x : ImGui::GetFrameHeight() * 4.0f;
414 const float content_w = card_w - inner_pad.x * 2.0f;
415 const float thumb_h = content_w;
417 const bool has_name = (item.name !=
nullptr) && (item.name[0] !=
'\0') && (item.name[0] !=
'#');
418 const bool has_type = (item.type !=
nullptr) && (item.type[0] !=
'\0') && (item.type[0] !=
'#');
419 const bool is_folder = has_type && (std::strcmp(item.type,
"Folder") == 0);
420 const bool show_accent = has_type && !is_folder;
424 const float base_font_size = ImGui::GetFontSize();
425 const float name_font_size = item.name_font !=
nullptr ? item.name_font->LegacySize : base_font_size;
426 const float type_font_size = name_font_size * 0.8f;
428 const auto line_height = [](ImFont*
font,
float size) ->
float
432 return ImGui::GetTextLineHeight();
435 const float height = ImGui::GetTextLineHeight();
442 const float name_h = line_height(item.name_font, name_font_size);
443 const float type_h = line_height(item.type_font, type_font_size);
445 const float accent_h = ImGui::GetStyle().SeparatorSize + 1;
446 constexpr float pad_thumb_to_accent = 4.0f;
447 constexpr float pad_accent_to_name = 4.0f;
448 constexpr float pad_name_to_type = 1.0f;
450 const float card_h = inner_pad.y + thumb_h + pad_thumb_to_accent + accent_h + pad_accent_to_name + name_h +
451 pad_name_to_type + type_h + inner_pad.y;
453 const ImVec2 card_min = window->DC.CursorPos;
454 const ImVec2 card_max = card_min + ImVec2(card_w, card_h);
455 const ImRect bb(card_min, card_max);
458 if(!ImGui::ItemAdd(bb,
id))
463 bool hovered =
false;
465 const bool pressed = ImGui::ButtonBehavior(bb,
id, &hovered, &held);
471 draw_list->AddRectFilled(card_min, card_max, ImGui::GetColorU32(ImGuiCol_Header), rounding);
472 draw_list->AddRect(card_min, card_max, ImGui::GetColorU32(ImGuiCol_NavCursor), rounding, 0, 1.5f);
474 else if(!is_folder || hovered || held)
477 ImU32 tile = IM_COL32(255, 255, 255, 10);
480 tile = IM_COL32(255, 255, 255, 32);
484 tile = IM_COL32(255, 255, 255, 20);
486 draw_list->AddRectFilled(card_min, card_max, tile, rounding);
490 const ImVec2 thumb_min = card_min + inner_pad;
491 const ImVec2 thumb_max = thumb_min + ImVec2(content_w, thumb_h);
494 ImVec2 img = item.texture_size;
495 if(img.x > 0.0f && img.y > 0.0f)
497 const float scale = ImMin(content_w / img.x, thumb_h / img.y);
500 const ImVec2 img_min(thumb_min.x + (content_w - img.x) * 0.5f, thumb_min.y + (thumb_h - img.y) * 0.5f);
501 const ImVec2 img_max = img_min + img;
502 draw_list->AddImageRounded(item.texId,
507 ImGui::GetColorU32(item.tint_col),
513 float cursor_y = thumb_max.y + pad_thumb_to_accent;
516 draw_list->AddRectFilled(ImVec2(thumb_min.x, cursor_y),
517 ImVec2(thumb_max.x, cursor_y + accent_h),
518 asset_type_accent(item.type),
521 cursor_y += accent_h + pad_accent_to_name;
524 const auto draw_centered_label = [&](
const char* text, ImFont*
font,
float font_size, ImU32
color) ->
void
530 ImVec2 ts = ImGui::CalcTextSize(text,
nullptr,
true);
531 ImVec2
start(thumb_min.x, cursor_y);
532 const float region_w = thumb_max.x - thumb_min.x;
535 start.x += (region_w - ts.x) * 0.5f;
537 ImGui::PushStyleColor(ImGuiCol_Text,
color);
538 ImGui::RenderTextEllipsis(draw_list,
540 ImVec2(thumb_max.x, cursor_y + ts.y),
545 ImGui::PopStyleColor();
554 draw_centered_label(item.name, item.name_font, name_font_size, ImGui::GetColorU32(ImGuiCol_Text));
556 cursor_y += name_h + pad_name_to_type;
558 if(has_type && show_accent)
560 draw_centered_label(item.type, item.type_font, type_font_size, content_caption_color);
566auto draw_item(
const content_browser_item& item)
568 bool is_directory = item.entry.entry.is_directory();
569 const auto& absolute_path = item.entry.entry.path();
570 const auto&
name = item.entry.stem;
571 const auto&
filename = item.entry.filename;
572 const auto& file_ext = item.entry.extension;
573 const auto& file_type =
ex::get_type(file_ext, is_directory);
575 enum class entry_action
586 auto duplicate_entry = [&]()
589 const auto available = get_new_file(absolute_path.parent_path(),
name, file_ext);
590 fs::copy(absolute_path, available, fs::copy_options::overwrite_existing, err);
593 bool is_popup_opened =
false;
594 entry_action action = entry_action::none;
596 bool open_rename_menu =
false;
598 ImGui::PushID(
name.c_str());
599 if(item.is_selected && !ImGui::IsAnyItemActive() && ImGui::IsWindowFocused())
603 open_rename_menu =
true;
608 action = entry_action::deleted;
613 action = entry_action::duplicate;
617 bool is_editing_label_after_create = pending_rename == absolute_path;
618 if(is_editing_label_after_create)
620 open_rename_menu =
true;
623 ImVec2 item_size = {item.size, item.size};
626 auto pos = ImGui::GetCursorScreenPos();
627 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
631 ImGui::ContentItem citem{};
633 citem.name =
name.c_str();
635 citem.type = file_type.c_str();
636 citem.type_font = file_type_font;
637 citem.texture_size = texture_size;
638 citem.image_size = item_size;
641 static ImGuiID last_double_clicked_id = 0;
642 static float last_double_click_time = -1.0f;
643 const float double_click_timeout = 0.5f;
645 ImGuiID current_id = ImGui::GetID(
name.c_str());
646 float current_time = ImGui::GetTime();
648 bool button_clicked =
false;
652 button_clicked = draw_content_card(citem, item.is_selected);
656 ImGui::DrawItemActivityOutline(ImGui::OutlineFlags_WhenActive | ImGui::OutlineFlags_HighlightActive);
661 auto spinner_size = item_size.x;
662 ImSpinner::Spinner<ImSpinner::SpinnerTypeT::e_st_eclipse>(
"spinner",
663 ImSpinner::Radius{spinner_size * 0.5f},
664 ImSpinner::Thickness{6.0f},
665 ImSpinner::Color{ImSpinner::white},
666 ImSpinner::Speed{6.0f});
669 pos.y += ImGui::GetItemRectSize().y;
671 ImGui::PopStyleVar();
674 bool is_double_clicked = ImGui::IsItemDoubleClicked(ImGuiMouseButton_Left);
675 if(is_double_clicked)
677 last_double_clicked_id = current_id;
678 last_double_click_time = current_time;
679 action = entry_action::double_clicked;
682 else if(button_clicked &&
683 !(last_double_clicked_id == current_id &&
684 current_time - last_double_click_time < double_click_timeout))
686 action = entry_action::clicked;
690 if(ImGui::IsItemFocused())
693 if(ImGui::IsItemFocusChanged() && !item.is_selected)
698 action = entry_action::clicked;
703 action = entry_action::double_clicked;
708 action = entry_action::none;
712 if(ImGui::IsItemHovered())
714 if(item.on_double_click)
716 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
720 const bool show_shift_preview_tooltip =
721 ImGui::IsItemHovered() && !item.is_loading && ImGui::GetIO().KeyShift;
722 if(show_shift_preview_tooltip)
724 ImGui::SetNextWindowViewportToCurrent();
725 ImGui::SetNextWindowPos(ImGui::GetIO().MousePos, ImGuiCond_None, ImVec2(0.5f, 1.0f));
726 asset_tooltip_style_scope tooltip_style;
728 if(ImGui::BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None))
730 constexpr float preview_scale = 2.75f;
731 constexpr float preview_max_side = 384.0f;
732 const float preview_side = ImClamp(item.size * preview_scale, item.size + 16.0f, preview_max_side);
733 ImGui::PushID(
"shift_thumbnail_preview");
734 ImGui::ContentItem preview_item = citem;
735 preview_item.image_size = ImVec2(preview_side, preview_side);
736 ImGui::ContentButtonItem(preview_item);
741 else if(!item.is_loading && ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
743 constexpr float thumb_side = 72.0f;
744 constexpr float wrap_width = 360.0f;
745 ImGui::SetNextWindowViewportToCurrent();
746 asset_tooltip_style_scope tooltip_style;
747 if(ImGui::BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None))
750 draw_asset_tooltip_thumbnail(citem, texture_size, thumb_side);
756 ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + wrap_width - thumb_side - ImGui::GetStyle().ItemSpacing.x);
757 ImGui::TextUnformatted(
name.c_str());
758 ImGui::PopTextWrapPos();
761 if(!file_type.empty())
764 ImGui::PushStyleColor(ImGuiCol_Text, content_caption_color);
765 ImGui::TextUnformatted(file_type.c_str());
766 ImGui::PopStyleColor();
773 ImGui::PushStyleColor(ImGuiCol_Separator, asset_type_accent(file_type.c_str()));
775 ImGui::PopStyleColor();
778 const float label_width = 130.0f;
780 draw_asset_tooltip_detail_row(
"Name", label_width, wrap_width,
filename);
781 draw_asset_tooltip_detail_row(
"Path", label_width, wrap_width, item.entry.protocol_path);
786 const auto bytes = fs::file_size(absolute_path, ec);
789 draw_asset_tooltip_detail_row(
"Disk Size", label_width, wrap_width, format_file_size(
bytes));
792 const auto compiled_path =
794 if(!compiled_path.empty())
797 if(fs::exists(compiled_path, ec))
799 const auto compiled_bytes = fs::file_size(compiled_path, ec);
802 draw_asset_tooltip_detail_row(
"Compiled Disk Size",
805 format_file_size(compiled_bytes));
813 draw_asset_tooltip_detail_row(
"UID", label_width, wrap_width,
description);
819 auto input_buff = ImGui::CreateInputTextBuffer(
name);
821 if(ImGui::BeginPopupContextItem(
"ENTRY_CONTEXT_MENU"))
823 is_popup_opened =
true;
829 fs::show_in_graphical_env(absolute_path);
842 open_rename_menu =
true;
843 ImGui::CloseCurrentPopup();
850 action = entry_action::duplicate;
851 ImGui::CloseCurrentPopup();
856 action = entry_action::deleted;
857 ImGui::CloseCurrentPopup();
863 const float rename_field_width = 150.0f;
866 ImGui::OpenPopup(
"ENTRY_RENAME_MENU");
868 const auto& style = ImGui::GetStyle();
869 float rename_field_with_padding = rename_field_width + style.WindowPadding.x * 2.0f;
870 if(item.size < rename_field_with_padding)
872 auto diff = rename_field_with_padding - item.size;
873 pos.x -= diff * 0.5f;
876 ImGui::SetNextWindowPos(pos);
879 if(ImGui::BeginPopup(
"ENTRY_RENAME_MENU"))
881 is_popup_opened =
true;
884 ImGui::SetKeyboardFocusHere();
886 ImGui::PushItemWidth(rename_field_width);
888 if(ImGui::InputTextWidget(
"##NAME",
891 ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
893 action = entry_action::renamed;
894 ImGui::CloseCurrentPopup();
899 ImGui::ActivateItemByID(ImGui::GetItemID());
904 action = entry_action::canceled;
907 ImGui::PopItemWidth();
912 ImGui::SetItemFocusFrame();
917 ImGui::SetItemFocusFrame(ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 0.0f, 1.0f)));
919 if(!ImGui::IsItemVisible())
921 ImGui::SetScrollHereY();
928 action = entry_action::none;
940 case entry_action::clicked:
942 pending_rename.clear();
949 case entry_action::double_clicked:
951 pending_rename.clear();
953 if(item.on_double_click)
955 item.on_double_click();
959 case entry_action::renamed:
961 pending_rename.clear();
963 const std::string new_name = std::string(input_buff.data());
964 if(new_name !=
name && !new_name.empty())
968 item.on_rename(new_name);
973 case entry_action::deleted:
975 pending_rename.clear();
984 case entry_action::duplicate:
986 pending_rename.clear();
991 case entry_action::canceled:
993 pending_rename.clear();
1004 if(!process_drag_drop_source(item.icon, absolute_path))
1006 process_drag_drop_target(absolute_path);
1010 return is_popup_opened;
1042 handle_external_drop(ctx);
1045void content_browser_panel::handle_external_drop(
rtti::context& ctx)
1052 on_import(ctx, files, cache_.
get_path());
1061 auto& pm = ctx.
get_cached<project_manager>();
1062 if(!pm.has_open_project())
1071 auto& em = ctx.
get_cached<editing_manager>();
1076 if(root_ != root_path || !fs::exists(cache_.
get_path(), err))
1079 set_cache_path(root_);
1082 if(!em.focused_data.focus_path.empty())
1084 set_cache_path(em.focused_data.focus_path);
1085 em.focused_data.focus_path.clear();
1088 auto avail = ImGui::GetContentRegionAvail();
1089 if(avail.x < 1.0f || avail.y < 1.0f)
1094 if(ImGui::BeginChild(
"DETAILS_AREA",
1095 avail * ImVec2(0.15f, 1.0f),
1096 ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX))
1100 if(fs::is_directory(root_path, err))
1102 draw_details(ctx, root_path);
1109 if(ImGui::BeginChild(
"EXPLORER"))
1112 draw_as_explorer(ctx, root_path);
1116 const auto& current_path = cache_.
get_path();
1117 process_drag_drop_target(current_path);
1124 draw_external_drop_overlay();
1127void content_browser_panel::draw_external_drop_overlay()
const
1134 ImGuiWindow* window = ImGui::GetCurrentWindow();
1135 if(window ==
nullptr)
1140 const ImRect bounds(window->InnerRect.Min, window->InnerRect.Max);
1143 if(bounds.GetWidth() < 1.0f || bounds.GetHeight() < 1.0f)
1149 ImDrawList* draw_list = ImGui::GetForegroundDrawList(window->Viewport);
1150 draw_list->PushClipRect(bounds.Min, bounds.Max,
true);
1152 draw_list->AddRectFilled(bounds.Min,
1154 ImGui::GetColorU32(ImGuiCol_ModalWindowDimBg, 0.72f));
1156 const ImU32 border_color = ImGui::GetColorU32(ImGuiCol_ButtonActive, 0.95f);
1157 draw_list->AddRect(bounds.Min, bounds.Max, border_color, 0.0f, 0, 2.0f);
1160 const std::string folder_line = fmt::format(
"Import into: {}", cache_.
get_path().generic_string());
1161 const char* hint =
"Release to add files to this folder";
1164 if(headline_font ==
nullptr)
1170 constexpr float card_padding = 28.0f;
1171 constexpr float line_spacing = 10.0f;
1173 const float headline_font_size = headline_font->LegacySize * 1.65f;
1174 const float body_font_size = body_font->LegacySize * 1.5f;
1176 const ImVec2 headline_size = headline_font->CalcTextSizeA(headline_font_size, FLT_MAX, 0.0f, headline);
1177 const ImVec2 folder_size = body_font->CalcTextSizeA(body_font_size, FLT_MAX, 0.0f, folder_line.c_str());
1178 const ImVec2 hint_size = body_font->CalcTextSizeA(body_font_size, FLT_MAX, 0.0f, hint);
1180 const float card_width =
1181 std::max({headline_size.x, folder_size.x, hint_size.x}) + card_padding * 2.0f;
1182 const float card_height =
1183 headline_size.y + folder_size.y + hint_size.y + line_spacing * 2.0f + card_padding * 2.0f;
1185 const ImVec2
center = bounds.GetCenter();
1186 const ImVec2 card_min(
center.x - card_width * 0.5f,
center.y - card_height * 0.5f);
1187 const ImVec2 card_max(
center.x + card_width * 0.5f,
center.y + card_height * 0.5f);
1189 draw_list->AddRectFilled(card_min, card_max, ImGui::GetColorU32(ImGuiCol_PopupBg, 0.98f), 8.0f);
1190 draw_list->AddRect(card_min, card_max, border_color, 8.0f, 0, 1.5f);
1192 ImVec2 text_pos(card_min.x + card_padding, card_min.y + card_padding);
1193 draw_list->AddText(headline_font,
1196 ImGui::GetColorU32(ImGuiCol_Text),
1199 text_pos.y += headline_size.y + line_spacing;
1200 draw_list->AddText(body_font,
1203 ImGui::GetColorU32(ImGuiCol_TextDisabled),
1204 folder_line.c_str());
1206 text_pos.y += folder_size.y + line_spacing;
1207 draw_list->AddText(body_font,
1210 ImGui::GetColorU32(ImGuiCol_TextDisabled),
1213 draw_list->PopClipRect();
1216void content_browser_panel::draw_details(
rtti::context& ctx,
const fs::path& path)
1219 ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth;
1221 const auto& selected_path = cache_.
get_path();
1222 if(selected_path == path)
1224 flags |= ImGuiTreeNodeFlags_Selected;
1229 ImGui::SetNextItemOpen(
true);
1232 auto stem = path.stem();
1233 bool open = ImGui::TreeNodeEx(fmt::format(
"{} {}",
ICON_MDI_FOLDER, stem.generic_string()).c_str(), flags);
1234 process_drag_drop_target(path);
1237 context_menu(ctx,
true, path);
1239 const bool clicked = !ImGui::IsItemToggledOpen() && ImGui::IsItemClicked(ImGuiMouseButton_Left);
1242 if (ImGui::IsItemFocused() && ImGui::IsItemFocusChanged())
1245 set_cache_path(path);
1250 const fs::directory_iterator it(path);
1251 for(
const auto& p : it)
1253 if(fs::is_directory(
p.status()))
1255 const auto& path =
p.path();
1256 draw_details(ctx, path);
1265 set_cache_path(path);
1270void content_browser_panel::draw_as_explorer(
rtti::context& ctx,
const fs::path& root_path)
1273 auto& em = ctx.
get_cached<editing_manager>();
1274 auto& tm = ctx.
get_cached<thumbnail_manager>();
1276 const float size = ImGui::GetFrameHeight() * 6.0f * scale_;
1280 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() &&
1282 hierarchy.size() > 1)
1285 fs::path parent_path = cache_.
get_path().parent_path();
1286 if (fs::exists(parent_path) && parent_path != cache_.
get_path())
1288 set_cache_path(parent_path);
1293 ImGui::DrawItemActivityOutline();
1296 ImGui::SameLine(0.0f, 0.0f);
1298 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
1299 ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0.0f, 0.0f));
1301 for(
const auto& dir : hierarchy)
1303 const bool is_first = &dir == &hierarchy.front();
1304 const bool is_last = &dir == &hierarchy.back();
1305 ImGui::PushID(
id++);
1309 ImGui::SameLine(0.0f, 0.0f);
1310 ImGui::AlignTextToFramePadding();
1311 ImGui::TextUnformatted(
"/");
1312 ImGui::SameLine(0.0f, 0.0f);
1320 auto filename = dir.filename().string();
1325 const bool clicked = ImGui::Button(
filename.c_str());
1335 set_cache_path(dir);
1338 process_drag_drop_target(dir);
1340 ImGui::PopStyleVar(2);
1343 ImGui::SameLine(0.0f, 0.0f);
1344 ImGui::AlignedItem(1.0f,
1345 ImGui::GetContentRegionAvail().
x,
1349 ImGui::PushItemWidth(80.0f);
1350 ImGui::KnobSliderScalarT(
"##scale", &scale_, 0.5f, 1.0f);
1351 ImGui::SetItemTooltipEx(
"%s",
"Icons scale");
1352 ImGui::PopItemWidth();
1357 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
1358 ImGuiWindowFlags_NoSavedSettings;
1360 fs::path current_path = cache_.
get_path();
1362 if(ImGui::BeginChild(
"assets_content", ImGui::GetContentRegionAvail(),
false, flags))
1365 bool is_popup_opened =
false;
1368 auto process_cache_entry = [&,
this](
const auto& cache_entry)
1370 const auto& absolute_path = cache_entry.entry.path();
1371 const auto&
name = cache_entry.stem;
1372 const auto&
filename = cache_entry.filename;
1373 const auto& relative = cache_entry.protocol_path;
1374 const auto& file_ext = cache_entry.extension;
1376 content_browser_item item(cache_entry);
1380 setup_rename_handler(item, absolute_path, file_ext);
1403 using asset_t =
typename std::decay_t<
decltype(
tag)>
::type;
1408 setup_asset_item<asset_t>(ctx, item, absolute_path, relative, file_ext);
1409 is_popup_opened |= draw_item(item);
1416 using entry_t = fs::path;
1417 const entry_t&
entry = absolute_path;
1418 item.icon = tm.get_thumbnail(
entry);
1419 item.is_selected = em.is_selected(
entry);
1420 item.is_focused = em.is_focused(
entry);
1422 item.on_click = [&em,
entry, &item]()
1424 bool is_directory = item.entry.entry.is_directory();
1425 const auto& file_ext = item.entry.extension;
1426 const auto& file_type =
ex::get_type(file_ext, is_directory);
1427 const auto&
name = item.entry.stem;
1428 em.select(
entry, em.get_select_mode(),
name +
" (" + file_type +
")");
1432 setup_delete_handler(item, relative, absolute_path,
entry, ctx);
1435 setup_rename_handler(item, absolute_path, file_ext);
1437 if(fs::is_directory(cache_entry.entry.status()))
1439 item.on_double_click = [¤t_path, &em,
entry]()
1441 current_path =
entry;
1442 em.try_unselect<entry_t>();
1446 is_popup_opened |= draw_item(item);
1450 auto cache_size = cache_.
size();
1452 if(!filter_.IsActive())
1454 ImGui::ItemBrowser(
size,
1458 auto& cache_entry = cache_[
index];
1459 process_cache_entry(cache_entry);
1464 std::vector<fs::directory_cache::cache_entry> filtered_entries;
1467 const auto& cache_entry = cache_[
index];
1469 const auto&
name = cache_entry.stem;
1470 const auto&
filename = cache_entry.filename;
1471 const auto& extension = cache_entry.extension;
1472 bool passed =
false;
1474 if(filter_.PassFilter(
name.c_str()))
1477 filtered_entries.emplace_back(cache_entry);
1482 if(filter_.PassFilter(
ex::get_type(extension, cache_entry.entry.is_directory()).c_str()))
1485 filtered_entries.emplace_back(cache_entry);
1491 const auto& metadata = am.get_metadata_for_path(cache_entry.entry.path()).meta;
1492 if(filter_.PassFilter(metadata.uid.to_string().c_str()))
1494 filtered_entries.emplace_back(cache_entry);
1501 ImGui::ItemBrowser(
size,
1502 filtered_entries.
size(),
1505 auto& cache_entry = filtered_entries[index];
1506 process_cache_entry(cache_entry);
1510 if(!is_popup_opened)
1512 context_menu(ctx,
false, cache_.
get_path());
1514 set_cache_path(current_path);
1517 handle_window_empty_click(ctx);
1522void content_browser_panel::handle_window_empty_click(
rtti::context& ctx)
const
1524 auto& em = ctx.
get_cached<editing_manager>();
1525 if(ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
1527 if(!ImGui::IsAnyItemHovered())
1534void content_browser_panel::context_menu(
rtti::context& ctx,
bool use_context_item,
const fs::path& target_path)
1536 const bool opened = use_context_item ? ImGui::BeginPopupContextItem()
1537 :
ImGui::BeginPopupContextWindow(nullptr, ImGuiPopupFlags_MouseButtonRight);
1546 set_cache_path(target_path);
1548 context_create_menu(ctx, target_path);
1553 fs::show_in_graphical_env(target_path);
1559 import(ctx, target_path);
1561 ImGui::SetItemTooltipEx(
"If import asset consists of multiple files,\n"
1562 "just copy paste all the files the data folder.\n"
1563 "Preferably in a new folder. The importer will\n"
1564 "automatically pick them up as dependencies.");
1569void content_browser_panel::context_create_menu(
rtti::context& ctx,
const fs::path& target_path)
1573 if(ImGui::MenuItem(
"Folder"))
1575 const auto available = get_new_file(target_path,
"New Folder");
1577 fs::create_directory(available, ec);
1581 pending_rename = available;
1587 if(ImGui::MenuItem(
"C# Script"))
1589 const auto available =
1602 pending_rename = available;
1616 auto new_mat_future = am.get_asset_from_instance<
material>(
key, std::make_shared<pbr_material>());
1620 pending_rename = available;
1629 const auto available =
1633 auto new_mat_future =
1634 am.get_asset_from_instance<physics_material>(
key, std::make_shared<physics_material>());
1638 pending_rename = available;
1649 const auto available =
1657 [&](
const fs::path& temp)
1665 pending_rename = available;
1674 const auto available =
1681 [&](
const fs::path& temp)
1689 pending_rename = available;
1697void content_browser_panel::set_cache_path(
const fs::path& path)
1708 if(!fs::equivalent(resolved, path, ec))
1717 if(!fs::exists(path, ec))
1730void content_browser_panel::import(
rtti::context& ctx,
const fs::path& target_path)
1732 std::vector<std::string> paths;
1733 if(native::open_files_dialog(paths, {}))
1735 on_import(ctx, paths, target_path);
1739void content_browser_panel::on_import(
rtti::context& ctx,
const std::vector<std::string>& paths,
const fs::path& target_path)
1744void content_browser_panel::prompt_delete_asset(
const std::string&
name,
const std::function<
void()>& on_delete)
1747 fmt::format(
"{}\n\nYou cannot undo the delete asset action.",
name),
1757template<
typename EntryType>
1758void content_browser_panel::setup_delete_handler(content_browser_item& item,
const std::string& relative,
1761 auto& em = ctx.
get_cached<editing_manager>();
1763 item.on_delete = [
this, relative, absolute_path, &em,
entry]()
1765 auto delete_impl = [&em, absolute_path,
entry]()
1768 fs::remove_all(absolute_path, err);
1772 this->prompt_delete_asset(relative, delete_impl);
1775 item.on_cancel = [
this, relative, absolute_path, &em,
entry]()
1778 fs::remove_all(absolute_path, err);
1783void content_browser_panel::setup_rename_handler(content_browser_item& item,
const fs::path& absolute_path,
1784 const std::string& file_ext)
1786 item.on_rename = [absolute_path, file_ext](
const std::string& new_name)
1788 fs::path new_absolute_path = absolute_path;
1789 new_absolute_path.remove_filename();
1790 new_absolute_path /= new_name + file_ext;
1792 fs::rename(absolute_path, new_absolute_path, err);
1796 sync_script_class_name(new_absolute_path, absolute_path.stem().string(), new_name);
1801template<
typename AssetType>
1802void content_browser_panel::setup_asset_item(
rtti::context& ctx, content_browser_item& item,
1803 const fs::path& absolute_path,
1804 const std::string& relative,
1805 const std::string& file_ext)
1808 auto& em = ctx.
get_cached<editing_manager>();
1809 auto& tm = ctx.
get_cached<thumbnail_manager>();
1812 const auto&
entry = am.find_asset<AssetType>(relative);
1814 item.description =
entry.uid().to_string();
1815 item.icon = tm.get_thumbnail(
entry);
1816 item.is_selected = em.is_selected(
entry);
1817 item.is_focused = em.is_focused(
entry);
1818 item.is_loading = !
entry.is_ready();
1821 item.on_click = [&em,
entry, &item]()
1823 bool is_directory = item.entry.entry.is_directory();
1824 const auto& file_ext = item.entry.extension;
1825 const auto& file_type =
ex::get_type(file_ext, is_directory);
1826 const auto&
name = item.entry.stem;
1828 em.select(
entry, em.get_select_mode(),
name +
" (" + file_type +
")");
1832 setup_delete_handler(item, relative, absolute_path,
entry, ctx);
1835 setup_rename_handler(item, absolute_path, file_ext);
1838 if constexpr(std::is_same_v<AssetType, scene_prefab>)
1840 item.on_double_click = [&ctx,
entry]()
1845 else if constexpr(std::is_same_v<AssetType, prefab>)
1847 item.on_double_click = [
this, &ctx,
entry]()
1849 auto& em_local = ctx.
get_cached<editing_manager>();
1852 bool auto_save = scene_panel.get_auto_save_prefab();
1853 em_local.enter_prefab_mode(ctx,
entry, auto_save);
1856 else if constexpr(std::is_same_v<AssetType, script> ||
1857 std::is_same_v<AssetType, gfx::shader> ||
1858 std::is_same_v<AssetType, style_sheet> ||
1859 std::is_same_v<AssetType, ui_tree>)
1861 item.on_double_click = [absolute_path]()
const fs::path & get_path() const
void set_path(const fs::path &path, const fs::pattern_filter &filter)
decltype(auto) size() const
Returns the size for the underlying cached container.
A filter that combines include and exclude patterns for file/directory filtering.
void add_exclude_pattern(const std::string &pattern)
Adds an exclude pattern to the filter.
void add_include_pattern(const std::string &pattern)
Adds an include pattern to the filter.
std::shared_ptr< texture > ptr
void draw_ui(rtti::context &ctx) override
void on_project_closed(rtti::context &ctx)
void deinit(rtti::context &ctx)
auto get_window_flags() const -> ImGuiWindowFlags override
content_browser_panel(imgui_panels *parent, const char *name)
void init(rtti::context &ctx)
auto get_scene_panel() -> scene_panel &
void clear_external_drop_files()
auto get_external_drop_files() const -> const std::vector< std::string > &
auto get_external_drop_in_progress() const -> bool
static constexpr int k_style_var_count
static constexpr int k_style_color_count
#define ICON_MDI_CONTENT_COPY
#define ICON_MDI_FOLDER_OPEN
#define ICON_MDI_FILE_SEARCH
std::vector< size_t > parent_
const fs::path * filename
ModalResult
Modal result flags for message box buttons.
auto ShowDeleteConfirmation(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a delete confirmation dialog with Delete/Cancel buttons.
void PushFont(Font::Enum _font)
auto BeginMenuIcon(const char *icon, const char *label, bool enabled) -> bool
ImTextureID ToId(gfx::texture_handle _handle, uint8_t _mip=0, uint8_t _flags=IMGUI_FLAGS_ALPHA_BLEND)
ImFont * GetFont(Font::Enum _font)
ImVec2 GetSize(const gfx::texture &tex, const ImVec2 &fallback={})
auto MenuItemIcon(const char *icon, const char *label, const char *shortcut, bool enabled) -> bool
auto get_all_formats() -> const std::vector< std::vector< std::string > > &
auto get_format(bool include_dot=true) -> std::string
auto get_type() -> const std::string &
auto is_format(const std::string &ex) -> bool
auto get_meta_format() -> const std::string &
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
bool is_any_parent_path(const path &parent, const path &child)
std::vector< path > split_until(const path &_path, const path &_predicate)
another.
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
auto is_valid_csharp_identifier(const std::string &name) -> bool
auto create_script_from_template(const fs::path &template_path, const fs::path &dst, std::string *error) -> bool
Instantiate a ScriptComponent template (.cs.in), substituting #SCRIPTNAME# with the destination file ...
void reimport_path(const fs::path &absolute_path)
auto can_reimport(const fs::path &absolute_path) -> bool
auto resolve_compiled_asset_path(const std::string &key, const std::string &source_extension) -> fs::path
auto atomic_save_to_file(const fs::path &key, const asset_handle< T > &obj) -> bool
void atomic_write_file(const fs::path &dst, const std::function< void(const fs::path &)> &callback, fs::error_code &ec) noexcept
constexpr ImGuiKey item_cancel
constexpr ImGuiKey delete_item
const ImGuiKeyCombination duplicate_item
constexpr ImGuiKey rename_item
constexpr ImGuiKey item_action_alt
constexpr ImGuiKey item_action
constexpr ImGuiKey navigate_back
std::vector< math::color > color
std::vector< float > scale
std::vector< math::vec3 > start
Thread-safe handle to an asset.
static auto import_files(rtti::context &ctx, const std::vector< std::string > &paths, const fs::path &target_path, bool async=true) -> std::vector< import_files_item >
Copy external files/folders into target_path (content-browser Import parity).
static void open_workspace_on_file(const fs::path &file, int line=0)
static auto open_scene_from_asset(rtti::context &ctx, const asset_handle< scene_prefab > &asset) -> bool
static auto context() -> rtti::context &
hpp::event< void(rtti::context &)> on_close_project