5#include "imgui_widgets/utils.h"
36#include <filedialog/filedialog.h>
39#include <hpp/utility.hpp>
40#include <imgui/imgui.h>
41#include <imgui/imgui_internal.h>
42#include <imgui_widgets/imcoolbar.h>
48using namespace std::literals;
52fs::path pending_rename;
54auto get_new_file(
const fs::path& path,
const std::string&
name,
const std::string& ext =
"") -> fs::path
58 while(fs::exists(path / (fmt::format(
"{} ({})",
name.c_str(), i) + ext), err))
63 return path / (fmt::format(
"{} ({})",
name.c_str(), i) + ext);
66auto get_new_file_simple(
const fs::path& path,
const std::string&
name,
const std::string& ext =
"") -> fs::path
70 while(fs::exists(path / (fmt::format(
"{}{}",
name.c_str(), i) + ext), err))
75 return path / (fmt::format(
"{}{}",
name.c_str(), i) + ext);
78auto process_drag_drop_source(
const gfx::texture::ptr& preview,
const fs::path& absolute_path) ->
bool
80 if(ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
82 const auto filename = absolute_path.filename();
83 const std::string extension = filename.has_extension() ? filename.extension().string() :
"folder";
84 const std::string
id = absolute_path.string();
85 const std::string strfilename = filename.string();
86 ImVec2 item_size = {64, 64};
88 texture_size = ImMax(texture_size, item_size);
90 ImGui::ContentItem citem{};
92 citem.name = strfilename.c_str();
93 citem.texture_size = texture_size;
94 citem.image_size = item_size;
96 ImGui::ContentButtonItem(citem);
98 ImGui::SetDragDropPayload(extension.c_str(),
id.data(),
id.size());
99 ImGui::EndDragDropSource();
106void process_drag_drop_target(
const fs::path& absolute_path)
108 if(ImGui::BeginDragDropTarget())
110 if(ImGui::IsDragDropPayloadBeingAccepted())
112 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
116 ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
120 if(fs::is_directory(absolute_path, err))
124 const auto process_drop = [&absolute_path](
const std::string&
type)
126 auto payload = ImGui::AcceptDragDropPayload(
type.c_str());
127 if(payload !=
nullptr)
129 std::string data(
reinterpret_cast<const char*
>(payload->Data), std::size_t(payload->DataSize));
130 fs::path new_name = absolute_path / fs::path(data).filename();
135 if(!fs::exists(new_name, err))
137 fs::rename(data, new_name, err);
144 for(
const auto& asset_set : types)
146 for(
const auto&
type : asset_set)
148 if(process_drop(
type) !=
nullptr)
155 process_drop(
"folder");
159 auto payload = ImGui::AcceptDragDropPayload(
"entity");
160 if(payload !=
nullptr)
162 entt::handle dropped{};
163 std::memcpy(&dropped, payload->Data,
size_t(payload->DataSize));
167 auto& em = ctx.get_cached<editing_manager>();
169 auto do_action = [&](entt::handle dropped)
171 auto& comp = dropped.get<tag_component>();
172 auto prefab_path = absolute_path / fs::path(comp.name +
".pfb").make_preferred();
175 auto& am = ctx.get_cached<asset_manager>();
177 dropped.get_or_emplace<prefab_component>().source = am.get_asset<prefab>(
key.generic_string());
181 if(em.is_selected(dropped))
183 for(
auto e : em.try_get_selections_as<entt::handle>())
201 ImGui::EndDragDropTarget();
205auto draw_item(
const content_browser_item& item)
207 bool is_directory = item.entry.entry.is_directory();
208 const auto& absolute_path = item.entry.entry.path();
209 const auto&
name = item.entry.stem;
210 const auto& filename = item.entry.filename;
211 const auto& file_ext = item.entry.extension;
212 const auto& file_type =
ex::get_type(file_ext, is_directory);
213 enum class entry_action
223 auto duplicate_entry = [&]()
226 const auto available = get_new_file(absolute_path.parent_path(),
name, file_ext);
227 fs::copy(absolute_path, available, fs::copy_options::overwrite_existing, err);
230 bool is_popup_opened =
false;
231 entry_action action = entry_action::none;
233 bool open_rename_menu =
false;
235 ImGui::PushID(
name.c_str());
236 if(item.is_selected && !ImGui::IsAnyItemActive() && ImGui::IsWindowFocused())
240 open_rename_menu =
true;
245 action = entry_action::deleted;
250 action = entry_action::duplicate;
254 bool is_editing_label_after_create = pending_rename == absolute_path;
255 if(is_editing_label_after_create)
257 open_rename_menu =
true;
260 ImVec2 item_size = {item.size, item.size};
263 auto pos = ImGui::GetCursorScreenPos();
264 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
268 ImGui::ContentItem citem{};
270 citem.name =
name.c_str();
271 citem.type = file_type.c_str();
272 citem.type_font = file_type_font;
273 citem.texture_size = texture_size;
274 citem.image_size = item_size;
277 static ImGuiID last_double_clicked_id = 0;
278 static float last_double_click_time = -1.0f;
279 const float double_click_timeout = 0.5f;
281 ImGuiID current_id = ImGui::GetID(
name.c_str());
282 float current_time = ImGui::GetTime();
284 bool button_clicked =
false;
288 button_clicked = ImGui::ContentButtonItem(citem);
289 ImGui::DrawItemActivityOutline(ImGui::OutlineFlags_All);
295 auto spinner_size = item_size.x;
297 ImSpinner::Spinner<ImSpinner::SpinnerTypeT::e_st_eclipse>(
"spinner",
298 ImSpinner::Radius{spinner_size * 0.5f},
299 ImSpinner::Thickness{6.0f},
300 ImSpinner::Color{ImSpinner::white},
301 ImSpinner::Speed{6.0f});
304 pos.y += ImGui::GetItemRectSize().y;
306 ImGui::PopStyleVar();
309 bool is_double_clicked = ImGui::IsItemDoubleClicked(ImGuiMouseButton_Left);
310 if(is_double_clicked)
312 last_double_clicked_id = current_id;
313 last_double_click_time = current_time;
314 action = entry_action::double_clicked;
317 else if(button_clicked &&
318 !(last_double_clicked_id == current_id &&
319 current_time - last_double_click_time < double_click_timeout))
321 action = entry_action::clicked;
325 if(ImGui::IsItemFocused())
328 if(ImGui::IsItemFocusChanged() && !item.is_selected)
333 action = entry_action::clicked;
338 action = entry_action::double_clicked;
343 action = entry_action::none;
347 if(ImGui::IsItemHovered())
349 if(item.on_double_click)
351 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
355 ImGui::AddItemTooltipEx(
"%s", filename.c_str());
357 if(!file_type.empty())
360 ImGui::AddItemTooltipEx(
"%s", file_type.c_str());
364 auto input_buff = ImGui::CreateInputTextBuffer(
name);
366 if(ImGui::BeginPopupContextItem(
"ENTRY_CONTEXT_MENU"))
368 is_popup_opened =
true;
370 if(ImGui::Selectable(
"Open in Explorer"))
372 fs::show_in_graphical_env(absolute_path);
376 open_rename_menu =
true;
377 ImGui::CloseCurrentPopup();
382 action = entry_action::duplicate;
383 ImGui::CloseCurrentPopup();
388 action = entry_action::deleted;
389 ImGui::CloseCurrentPopup();
394 const float rename_field_width = 150.0f;
397 ImGui::OpenPopup(
"ENTRY_RENAME_MENU");
399 const auto& style = ImGui::GetStyle();
400 float rename_field_with_padding = rename_field_width + style.WindowPadding.x * 2.0f;
401 if(item.size < rename_field_with_padding)
403 auto diff = rename_field_with_padding - item.size;
404 pos.x -= diff * 0.5f;
407 ImGui::SetNextWindowPos(pos);
410 if(ImGui::BeginPopup(
"ENTRY_RENAME_MENU"))
412 is_popup_opened =
true;
415 ImGui::SetKeyboardFocusHere();
417 ImGui::PushItemWidth(rename_field_width);
419 if(ImGui::InputTextWidget(
"##NAME",
422 ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
424 action = entry_action::renamed;
425 ImGui::CloseCurrentPopup();
430 ImGui::ActivateItemByID(ImGui::GetItemID());
435 action = entry_action::deleted;
438 ImGui::PopItemWidth();
443 ImGui::SetItemFocusFrame();
448 ImGui::SetItemFocusFrame(ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 0.0f, 1.0f)));
453 action = entry_action::none;
465 case entry_action::clicked:
467 pending_rename.clear();
474 case entry_action::double_clicked:
476 pending_rename.clear();
478 if(item.on_double_click)
480 item.on_double_click();
484 case entry_action::renamed:
486 pending_rename.clear();
488 const std::string new_name = std::string(input_buff.data());
489 if(new_name !=
name && !new_name.empty())
493 item.on_rename(new_name);
498 case entry_action::deleted:
500 pending_rename.clear();
509 case entry_action::duplicate:
511 pending_rename.clear();
520 if(!process_drag_drop_source(item.icon, absolute_path))
522 process_drag_drop_target(absolute_path);
526 return is_popup_opened;
544 if(ImGui::Begin(
name,
nullptr))
550 handle_external_drop(ctx);
555void content_browser_panel::handle_external_drop(
rtti::context& ctx)
562 on_import(ctx, files, cache_.
get_path());
576 if(root_ != root_path || !fs::exists(cache_.
get_path(), err))
579 set_cache_path(root_);
582 if(!em.focused_data.focus_path.empty())
584 set_cache_path(em.focused_data.focus_path);
585 em.focused_data.focus_path.clear();
588 auto avail = ImGui::GetContentRegionAvail();
589 if(avail.x < 1.0f || avail.y < 1.0f)
594 if(ImGui::BeginChild(
"DETAILS_AREA",
595 avail * ImVec2(0.15f, 1.0f),
596 ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX))
600 if(fs::is_directory(root_path, err))
602 draw_details(ctx, root_path);
609 if(ImGui::BeginChild(
"EXPLORER"))
612 draw_as_explorer(ctx, root_path);
616 const auto& current_path = cache_.
get_path();
617 process_drag_drop_target(current_path);
625void content_browser_panel::draw_details(
rtti::context& ctx,
const fs::path& path)
628 ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth;
630 const auto& selected_path = cache_.
get_path();
631 if(selected_path == path)
633 flags |= ImGuiTreeNodeFlags_Selected;
638 ImGui::SetNextItemOpen(
true);
641 auto stem = path.stem();
642 bool open = ImGui::TreeNodeEx(fmt::format(
"{} {}",
ICON_MDI_FOLDER, stem.generic_string()).c_str(), flags);
643 process_drag_drop_target(path);
646 context_menu(ctx,
true, path);
648 const bool clicked = !ImGui::IsItemToggledOpen() && ImGui::IsItemClicked(ImGuiMouseButton_Left);
651 if (ImGui::IsItemFocused() && ImGui::IsItemFocusChanged())
654 set_cache_path(path);
659 const fs::directory_iterator it(path);
660 for(
const auto& p : it)
662 if(fs::is_directory(
p.status()))
664 const auto& path =
p.path();
665 draw_details(ctx, path);
674 set_cache_path(path);
679void content_browser_panel::draw_as_explorer(
rtti::context& ctx,
const fs::path& root_path)
683 auto& tm = ctx.
get_cached<thumbnail_manager>();
685 const float size = ImGui::GetFrameHeight() * 6.0f * scale_;
689 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() &&
691 hierarchy.size() > 1)
694 fs::path parent_path = cache_.
get_path().parent_path();
695 if (fs::exists(parent_path) && parent_path != cache_.
get_path())
697 set_cache_path(parent_path);
702 ImGui::DrawItemActivityOutline();
705 ImGui::SameLine(0.0f, 0.0f);
707 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
708 ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0.0f, 0.0f));
710 for(
const auto& dir : hierarchy)
712 const bool is_first = &dir == &hierarchy.front();
713 const bool is_last = &dir == &hierarchy.back();
718 ImGui::SameLine(0.0f, 0.0f);
719 ImGui::AlignTextToFramePadding();
720 ImGui::TextUnformatted(
"/");
721 ImGui::SameLine(0.0f, 0.0f);
729 auto filename = dir.filename().string();
732 filename = fmt::format(
"app:/{}", filename);
734 const bool clicked = ImGui::Button(filename.c_str());
747 process_drag_drop_target(dir);
749 ImGui::PopStyleVar(2);
752 ImGui::SameLine(0.0f, 0.0f);
753 ImGui::AlignedItem(1.0f,
754 ImGui::GetContentRegionAvail().
x,
758 ImGui::PushItemWidth(80.0f);
759 ImGui::SliderFloat(
"##scale", &scale_, 0.5f, 1.0f);
760 ImGui::SetItemTooltipEx(
"%s",
"Icons scale");
761 ImGui::PopItemWidth();
766 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
767 ImGuiWindowFlags_NoSavedSettings;
769 fs::path current_path = cache_.
get_path();
771 if(ImGui::BeginChild(
"assets_content", ImGui::GetContentRegionAvail(),
false, flags))
775 bool is_popup_opened =
false;
778 auto process_cache_entry = [&,
this](
const auto& cache_entry)
780 const auto& absolute_path = cache_entry.entry.path();
781 const auto&
name = cache_entry.stem;
782 const auto& filename = cache_entry.filename;
783 const auto& relative = cache_entry.protocol_path;
784 const auto& file_ext = cache_entry.extension;
786 content_browser_item item(cache_entry);
790 setup_rename_handler(item, absolute_path, file_ext);
813 using asset_t =
typename std::decay_t<
decltype(
tag)>
::type;
818 setup_asset_item<asset_t>(ctx, item, absolute_path, relative, file_ext);
819 is_popup_opened |= draw_item(item);
826 using entry_t = fs::path;
827 const entry_t&
entry = absolute_path;
828 item.icon = tm.get_thumbnail(
entry);
829 item.is_selected = em.is_selected(
entry);
830 item.is_focused = em.is_focused(
entry);
832 item.on_click = [&em,
entry]()
834 em.select(
entry, em.get_select_mode());
838 setup_delete_handler(item, relative, absolute_path,
entry, ctx);
841 setup_rename_handler(item, absolute_path, file_ext);
843 if(fs::is_directory(cache_entry.entry.status()))
845 item.on_double_click = [¤t_path, &em,
entry]()
847 current_path =
entry;
848 em.try_unselect<entry_t>();
852 is_popup_opened |= draw_item(item);
856 auto cache_size = cache_.
size();
858 if(!filter_.IsActive())
860 ImGui::ItemBrowser(
size,
864 auto& cache_entry = cache_[index];
865 process_cache_entry(cache_entry);
870 std::vector<fs::directory_cache::cache_entry> filtered_entries;
871 for(
size_t index = 0; index < cache_size; ++index)
873 const auto& cache_entry = cache_[index];
875 const auto&
name = cache_entry.stem;
876 const auto& filename = cache_entry.filename;
877 const auto& extension = cache_entry.extension;
879 if(filter_.PassFilter(
name.c_str()) ||
880 filter_.PassFilter(
ex::get_type(extension, cache_entry.entry.is_directory()).c_str()))
882 filtered_entries.emplace_back(cache_entry);
887 ImGui::ItemBrowser(
size,
888 filtered_entries.
size(),
891 auto& cache_entry = filtered_entries[index];
892 process_cache_entry(cache_entry);
898 context_menu(ctx,
false, cache_.
get_path());
900 set_cache_path(current_path);
904 handle_window_empty_click(ctx);
909void content_browser_panel::handle_window_empty_click(
rtti::context& ctx)
const
912 if(ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
914 if(!ImGui::IsAnyItemHovered())
921void content_browser_panel::context_menu(
rtti::context& ctx,
bool use_context_item,
const fs::path& target_path)
923 bool popup_opened =
false;
925 if (use_context_item)
927 popup_opened = ImGui::BeginPopupContextItem();
931 popup_opened = ImGui::BeginPopupContextWindowEx();
936 set_cache_path(target_path);
938 context_create_menu(ctx, target_path);
942 if(ImGui::Selectable(
"Open in Explorer"))
944 fs::show_in_graphical_env(target_path);
949 if(ImGui::Selectable(
"Import..."))
951 import(ctx, target_path);
953 ImGui::SetItemTooltipEx(
"If import asset consists of multiple files,\n"
954 "just copy paste all the files the data folder.\n"
955 "Preferably in a new folder. The importer will\n"
956 "automatically pick them up as dependencies.");
962void content_browser_panel::context_create_menu(
rtti::context& ctx,
const fs::path& target_path)
964 if(ImGui::BeginMenu(
"Create"))
966 if(ImGui::MenuItem(
"Folder"))
968 const auto available = get_new_file(target_path,
"New Folder");
970 fs::create_directory(available, ec);
974 pending_rename = available;
980 if(ImGui::MenuItem(
"C# Script"))
984 const auto available =
988 auto new_script_template =
990 fs::copy(new_script_template, available, ec);
994 pending_rename = available;
1008 auto new_mat_future = am.get_asset_from_instance<
material>(
key, std::make_shared<pbr_material>());
1012 pending_rename = available;
1021 const auto available =
1025 auto new_mat_future =
1026 am.get_asset_from_instance<physics_material>(
key, std::make_shared<physics_material>());
1030 pending_rename = available;
1041 const auto available =
1049 [&](
const fs::path& temp)
1057 pending_rename = available;
1066 const auto available =
1073 [&](
const fs::path& temp)
1081 pending_rename = available;
1089void content_browser_panel::set_cache_path(
const fs::path& path)
1100 if(!fs::equivalent(resolved, path, ec))
1109 if(!fs::exists(path, ec))
1122void content_browser_panel::import(
rtti::context& ctx,
const fs::path& target_path)
1124 std::vector<std::string> paths;
1125 if(native::open_files_dialog(paths, {}))
1127 on_import(ctx, paths, target_path);
1131void content_browser_panel::on_import(
rtti::context& ctx,
const std::vector<std::string>& paths,
const fs::path& target_path)
1135 for(
auto& path : paths)
1137 fs::path
p = fs::path(path).make_preferred();
1138 fs::path filename =
p.filename();
1141 auto task = ts.pool->schedule(
"Importing " + filename.extension().string(),
1142 [target_path](
const fs::path& path,
const fs::path& filename)
1145 fs::path dir = target_path / filename;
1146 asset_writer::atomic_copy_file(path, dir, err);
1153void content_browser_panel::prompt_delete_asset(
const std::string&
name,
const std::function<
void()>& on_delete)
1156 fmt::format(
"{}\n\nYou cannot undo the delete asset action.",
name),
1166template<
typename EntryType>
1167void content_browser_panel::setup_delete_handler(content_browser_item& item,
const std::string& relative,
1170 auto& em = ctx.
get_cached<editing_manager>();
1172 item.on_delete = [
this, relative, absolute_path, &em,
entry]()
1174 auto delete_impl = [&em, absolute_path,
entry]()
1177 fs::remove_all(absolute_path, err);
1181 this->prompt_delete_asset(relative, delete_impl);
1185void content_browser_panel::setup_rename_handler(content_browser_item& item,
const fs::path& absolute_path,
1186 const std::string& file_ext)
1188 item.on_rename = [absolute_path, file_ext](
const std::string& new_name)
1190 fs::path new_absolute_path = absolute_path;
1191 new_absolute_path.remove_filename();
1192 new_absolute_path /= new_name + file_ext;
1194 fs::rename(absolute_path, new_absolute_path, err);
1198template<
typename AssetType>
1199void content_browser_panel::setup_asset_item(
rtti::context& ctx, content_browser_item& item,
1200 const fs::path& absolute_path,
1201 const std::string& relative,
1202 const std::string& file_ext)
1205 auto& em = ctx.
get_cached<editing_manager>();
1206 auto& tm = ctx.
get_cached<thumbnail_manager>();
1209 const auto&
entry = am.find_asset<AssetType>(relative);
1211 item.icon = tm.get_thumbnail(
entry);
1212 item.is_selected = em.is_selected(
entry);
1213 item.is_focused = em.is_focused(
entry);
1214 item.is_loading = !
entry.is_ready();
1217 item.on_click = [&em,
entry]()
1219 em.select(
entry, em.get_select_mode());
1223 setup_delete_handler(item, relative, absolute_path,
entry, ctx);
1226 setup_rename_handler(item, absolute_path, file_ext);
1229 if constexpr(std::is_same_v<AssetType, scene_prefab>)
1231 item.on_double_click = [&ctx,
entry]()
1236 else if constexpr(std::is_same_v<AssetType, prefab>)
1238 item.on_double_click = [
this, &ctx,
entry]()
1240 auto& em_local = ctx.
get_cached<editing_manager>();
1243 bool auto_save = scene_panel.get_auto_save_prefab();
1244 em_local.enter_prefab_mode(ctx,
entry, auto_save);
1247 else if constexpr(std::is_same_v<AssetType, script> ||
1248 std::is_same_v<AssetType, gfx::shader> ||
1249 std::is_same_v<AssetType, style_sheet>)
1251 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 on_frame_ui_render(rtti::context &ctx, const char *name)
void deinit(rtti::context &ctx)
void init(rtti::context &ctx)
content_browser_panel(imgui_panels *parent)
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
#define ICON_MDI_FILE_SEARCH
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 PushWindowFontSize(int size)
void PushFont(Font::Enum _font)
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 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 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
Represents a handle to an asset, providing access and management functions.
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 &