Unravel Engine C++ Reference
Loading...
Searching...
No Matches
editor_actions.cpp
Go to the documentation of this file.
1#include "editor_actions.h"
3#include "engine/ui/ui_tree.h"
4
14#include <engine/ecs/ecs.h>
15#include <engine/engine.h>
16#include <engine/events.h>
20#include <filedialog/filedialog.h>
22#include <filesystem/watcher.h>
23#include <filesystem>
25
26
28#include <string_utils/utils.h>
29
30namespace unravel
31{
32
33namespace
34{
35
36auto get_vscode_executable() -> fs::path
37{
38 fs::path executablePath;
39
40#if UNRAVEL_PLATFORM_WINDOWS
41 // Windows implementation
42 try
43 {
44 // Common installation paths
45 std::vector<fs::path> possiblePaths = {"C:\\Program Files\\Microsoft VS Code\\Code.exe",
46 "C:\\Program Files (x86)\\Microsoft VS Code\\Code.exe",
47 fs::path(std::getenv("LOCALAPPDATA")) / "Programs" /
48 "Microsoft VS Code" / "Code.exe"};
49
50 for(const auto& path : possiblePaths)
51 {
52 if(fs::exists(path))
53 {
54 executablePath = path;
55 break;
56 }
57 }
58
59 if(executablePath.empty())
60 {
61 // Search for Code.exe in the PATH environment variable
62 const char* pathEnv = std::getenv("PATH");
63 if(pathEnv)
64 {
65 std::string pathEnvStr(pathEnv);
66 std::stringstream ss(pathEnvStr);
67 std::string token;
68 while(std::getline(ss, token, ';'))
69 {
70 fs::path codePath = fs::path(token) / "Code.exe";
71 if(fs::exists(codePath))
72 {
73 executablePath = codePath;
74 break;
75 }
76 }
77 }
78
79 // If still not found, perform a recursive search in Program Files
80 if(executablePath.empty())
81 {
82 std::vector<fs::path> directoriesToSearch = {"C:\\Program Files",
83 "C:\\Program Files (x86)",
84 fs::path(std::getenv("LOCALAPPDATA")) / "Programs"};
85
86 for(const auto& dir : directoriesToSearch)
87 {
88 try
89 {
90 for(const auto& entry : fs::recursive_directory_iterator(dir))
91 {
92 if(entry.is_regular_file() && entry.path().filename() == "Code.exe")
93 {
94 executablePath = entry.path();
95 break;
96 }
97 }
98 if(!executablePath.empty())
99 {
100 break;
101 }
102 }
103 catch(const fs::filesystem_error&)
104 {
105 continue;
106 }
107 }
108 }
109 }
110 }
111 catch(const std::exception& e)
112 {
113 std::cerr << "Error finding VSCode executable path on Windows: " << e.what() << std::endl;
114 }
115
116#elif UNRAVEL_PLATFORM_OSX
117 // macOS implementation
118 try
119 {
120 // Common application bundle paths
121 std::vector<fs::path> possibleAppPaths = {"/Applications/Visual Studio Code.app",
122 "/Applications/Visual Studio Code - Insiders.app",
123 fs::path(std::getenv("HOME")) / "Applications" /
124 "Visual Studio Code.app"};
125
126 for(const auto& appPath : possibleAppPaths)
127 {
128 if(fs::exists(appPath))
129 {
130 // The executable is inside the app bundle
131 fs::path codeExecutable = appPath / "Contents" / "MacOS" / "Electron";
132 if(fs::exists(codeExecutable))
133 {
134 executablePath = codeExecutable;
135 break;
136 }
137 }
138 }
139
140 if(executablePath.empty())
141 {
142 // Search for 'code' in /usr/local/bin or /usr/bin
143 std::vector<fs::path> possibleLinks = {"/usr/local/bin/code", "/usr/bin/code"};
144 for(const auto& linkPath : possibleLinks)
145 {
146 if(fs::exists(linkPath))
147 {
148 // Resolve symlink
149 executablePath = fs::canonical(linkPath);
150 break;
151 }
152 }
153 }
154
155 if(executablePath.empty())
156 {
157 // Search in PATH environment variable
158 const char* pathEnv = std::getenv("PATH");
159 if(pathEnv)
160 {
161 std::string pathEnvStr(pathEnv);
162 std::stringstream ss(pathEnvStr);
163 std::string token;
164 while(std::getline(ss, token, ':'))
165 {
166 fs::path codePath = fs::path(token) / "code";
167 if(fs::exists(codePath))
168 {
169 executablePath = fs::canonical(codePath);
170 break;
171 }
172 }
173 }
174 }
175 }
176 catch(const std::exception& e)
177 {
178 std::cerr << "Error finding VSCode executable path on macOS: " << e.what() << std::endl;
179 }
180
181#elif UNRAVEL_PLATFORM_LINUX
182 // Linux implementation
183 try
184 {
185 // Search for 'code' executable in PATH
186 const char* pathEnv = std::getenv("PATH");
187 if(pathEnv)
188 {
189 std::string pathEnvStr(pathEnv);
190 std::stringstream ss(pathEnvStr);
191 std::string token;
192 while(std::getline(ss, token, ':'))
193 {
194 fs::path codePath = fs::path(token) / "code";
195 if(fs::exists(codePath) && fs::is_regular_file(codePath))
196 {
197 // Resolve symlink if necessary
198 executablePath = fs::canonical(codePath);
199 break;
200 }
201 }
202 }
203
204 if(executablePath.empty())
205 {
206 // Check common installation directories
207 std::vector<fs::path> possiblePaths = {
208 "/usr/bin/code",
209 "/bin/code",
210 "/sbin/code",
211 "/usr/share/code/bin/code",
212 "/usr/share/code-insiders/bin/code",
213 "/usr/local/share/code/bin/code",
214 "/opt/visual-studio-code/bin/code",
215 "/var/lib/flatpak/app/com.visualstudio.code/current/active/files/bin/code",
216 fs::path(std::getenv("HOME")) / ".vscode" / "bin" / "code"};
217
218 for(const auto& path : possiblePaths)
219 {
220 if(fs::exists(path))
221 {
222 executablePath = path;
223 break;
224 }
225 }
226 }
227 }
228 catch(const std::exception& e)
229 {
230 std::cerr << "Error finding VSCode executable path on Linux: " << e.what() << std::endl;
231 }
232
233#else
234#error "Unsupported operating system."
235#endif
236
237 return executablePath;
238}
239
240void remove_extensions(std::vector<std::vector<std::string>>& resourceExtensions,
241 const std::vector<std::string>& extsToRemove)
242{
243 // Convert extsToRemove to a set of lowercase strings
244 std::unordered_set<std::string> extsToRemoveSet;
245 for(const auto& ext : extsToRemove)
246 {
247 extsToRemoveSet.insert(string_utils::to_lower(ext));
248 }
249
250 for(auto outerIt = resourceExtensions.begin(); outerIt != resourceExtensions.end();)
251 {
252 std::vector<std::string>& innerVec = *outerIt;
253
254 innerVec.erase(std::remove_if(innerVec.begin(),
255 innerVec.end(),
256 [&extsToRemoveSet](const std::string& ext)
257 {
258 return extsToRemoveSet.find(string_utils::to_lower(ext)) !=
259 extsToRemoveSet.end();
260 }),
261 innerVec.end());
262
263 if(innerVec.empty())
264 {
265 outerIt = resourceExtensions.erase(outerIt);
266 }
267 else
268 {
269 ++outerIt;
270 }
271 }
272}
273void generate_workspace_file(const std::string& file_path,
274 const std::vector<std::vector<std::string>>& exclude_extensions,
275 const editor_settings& settings)
276{
277 // Start constructing the JSON content
278 std::ostringstream json_stream;
279
280 json_stream << "{\n";
281 json_stream << " \"folders\": [\n";
282 json_stream << " {\n";
283 json_stream << " \"path\": \"..\"\n";
284 json_stream << " }\n";
285 json_stream << " ],\n";
286 json_stream << " \"settings\": {\n";
287 json_stream << " \"dotnet.preferCSharpExtension\": true,\n";
288 json_stream << " \"files.exclude\": {\n";
289 json_stream << " \"**/.git\": true,\n";
290 json_stream << " \"**/.svn\": true";
291
292 // Add the exclude patterns from the provided extensions
293 for(const auto& extensions : exclude_extensions)
294 {
295 for(const auto& ext : extensions)
296 {
297 // Escape any special characters in the extension if necessary
298
299 // Create the pattern to exclude files with the given extension
300 std::string pattern = "**/*" + ext;
301
302 // Add a comma before each new entry
303 json_stream << ",\n";
304 json_stream << " \"" << pattern << "\": true";
305 }
306 }
307
308 // Close the files.exclude object and the settings object
309 json_stream << "\n";
310 json_stream << " }\n"; // End of "files.exclude"
311 json_stream << " }\n"; // End of "settings"
312
313 // Add the "extensions" section
314 json_stream << ",\n";
315 json_stream << " \"extensions\": {\n";
316 json_stream << " \"recommendations\": [\n";
317 json_stream << " \"ms-vscode.mono-debug\",\n";
318 json_stream << " \"ms-dotnettools.csharp\"\n";
319 json_stream << " ]\n";
320 json_stream << " }\n";
321
322 // Add the "launch" section
323 json_stream << ",\n";
324 json_stream << " \"launch\": {\n";
325 json_stream << " \"version\": \"0.2.0\",\n";
326 json_stream << " \"configurations\": [\n";
327 json_stream << " {\n";
328 json_stream << " \"name\": \"Attach to Mono\",\n";
329 json_stream << " \"request\": \"attach\",\n";
330 json_stream << " \"type\": \"mono\",\n";
331 json_stream << " \"address\": \"" << settings.debugger.ip << "\",\n";
332 json_stream << " \"port\": " << settings.debugger.port << "\n";
333 json_stream << " }\n";
334 json_stream << " ]\n";
335 json_stream << " }\n";
336
337 // Close the JSON object
338 json_stream << "}";
339
340 // Write the JSON string to a file
341 std::ofstream file(file_path);
342 if(file.is_open())
343 {
344 file << json_stream.str();
345 }
346
347 APPLOG_TRACE("Workspace {}", file_path);
348}
349
361void generate_csproj(const fs::path& source_directory,
362 const std::vector<fs::path>& external_dll_paths,
363 const fs::path& output_directory,
364 const std::string& project_name = "MyLibrary",
365 const std::string& dotnet_sdk_version = "7.0")
366{
367 // Ensure the output directory exists
368 try
369 {
370 fs::create_directories(output_directory);
371 }
372 catch(const fs::filesystem_error& e)
373 {
374 throw std::runtime_error("Failed to create output directory: " + std::string(e.what()));
375 }
376
377 // Verify that the source directory exists
378 if(!fs::exists(source_directory) || !fs::is_directory(source_directory))
379 {
380 throw std::runtime_error("Source directory does not exist or is not a directory: " + source_directory.string());
381 }
382
383 // Verify that all external DLLs exist and are files
384 for(const auto& dll_path : external_dll_paths)
385 {
386 if(!fs::exists(dll_path) || !fs::is_regular_file(dll_path))
387 {
388 throw std::runtime_error("External DLL does not exist or is not a file: " + dll_path.string());
389 }
390 }
391
392 // Collect all C# source files from the specified source directory
393 std::vector<fs::path> csharp_sources;
394 try
395 {
396 for(const auto& entry : fs::recursive_directory_iterator(source_directory))
397 {
398 if(entry.is_regular_file() && entry.path().extension() == ".cs")
399 {
400 // Compute the relative path from the source directory
401 fs::path relative_path = fs::relative(entry.path(), source_directory);
402 csharp_sources.push_back(relative_path);
403 }
404 }
405 }
406 catch(const fs::filesystem_error& e)
407 {
408 throw std::runtime_error("Error while iterating source directory: " + std::string(e.what()));
409 }
410
411 // Generate the list of source files for the .csproj file with <Link> elements (for virtual folders)
412 std::string csharp_source_items;
413 for(const auto& source_file : csharp_sources)
414 {
415 // Convert path to generic format (forward slashes)
416 std::string source_file_str = source_file.string();
417 fs::path full_physical_path = fs::absolute(source_directory / source_file);
418 std::string full_physical_path_str = full_physical_path.string();
419
420 // Construct the <Compile Include> with <Link>
421 csharp_source_items += " <Compile Include=\"" + full_physical_path_str + "\">\n";
422 csharp_source_items += " <Link>" + source_file_str + "</Link>\n";
423 csharp_source_items += " </Compile>\n";
424 }
425
426 // Generate external DLL references
427 std::string external_dll_references;
428 for(const auto& dll_path : external_dll_paths)
429 {
430 std::string dll_name = dll_path.filename().string();
431 fs::path dll_absolute_path = fs::absolute(dll_path);
432 std::string dll_absolute_path_str = dll_absolute_path.string(); // Forward slashes
433
434 external_dll_references += " <Reference Include=\"" + dll_name + "\">\n";
435 external_dll_references += " <HintPath>" + dll_absolute_path_str + "</HintPath>\n";
436 external_dll_references += " </Reference>\n";
437 }
438
439 // Build the .csproj content
440 std::string csproj_content;
441 csproj_content += "<Project Sdk=\"Microsoft.NET.Sdk\">\n";
442 csproj_content += " <PropertyGroup>\n";
443 csproj_content += " <TargetFramework>net" + dotnet_sdk_version + "</TargetFramework>\n";
444 csproj_content += " <OutputType>Library</OutputType>\n";
445 csproj_content +=
446 " <EnableDefaultCompileItems>false</EnableDefaultCompileItems>\n"; // Disable default .cs file inclusion
447 csproj_content += " </PropertyGroup>\n";
448 csproj_content += " <ItemGroup>\n";
449 csproj_content += csharp_source_items;
450 csproj_content += " </ItemGroup>\n";
451 csproj_content += " <ItemGroup>\n";
452 csproj_content += external_dll_references;
453 csproj_content += " </ItemGroup>\n";
454 csproj_content += "</Project>\n";
455
456 // Define the path to the .csproj file
457 fs::path csproj_path = output_directory / (project_name + ".csproj");
458
459 // Write the .csproj file
460 std::ofstream csproj_file(csproj_path);
461 if(!csproj_file.is_open())
462 {
463 APPLOG_ERROR("Failed to create .csproj file at {}", csproj_path.string());
464 return;
465 }
466
467 csproj_file << csproj_content;
468
469 APPLOG_TRACE("Generated {}", csproj_path.string());
470}
471
472void generate_csproj_legacy(const fs::path& source_directory,
473 const std::vector<fs::path>& external_dll_paths,
474 const fs::path& output_directory,
475 const std::string& project_name = "MyLibrary",
476 const std::string& dotnet_framework_version = "v4.7.1")
477{
478 auto uid = generate_uuid(project_name);
479 fs::path output_path = fs::path("temp") / "bin" / "Debug";
480 fs::path intermediate_output_path = fs::path("temp") / "obj" / "Debug";
481
482 // Ensure the output directory exists
483 try
484 {
485 fs::create_directories(output_directory);
486 }
487 catch(const fs::filesystem_error& e)
488 {
489 throw std::runtime_error("Failed to create output directory: " + std::string(e.what()));
490 }
491
492 // Verify that the source directory exists
493 if(!fs::exists(source_directory) || !fs::is_directory(source_directory))
494 {
495 throw std::runtime_error("Source directory does not exist or is not a directory: " + source_directory.string());
496 }
497
498 // Verify that all external DLLs exist and are files
499 for(const auto& dll_path : external_dll_paths)
500 {
501 if(!fs::exists(dll_path) || !fs::is_regular_file(dll_path))
502 {
503 throw std::runtime_error("External DLL does not exist or is not a file: " + dll_path.string());
504 }
505 }
506
507 // Collect all C# source files from the specified source directory
508 std::vector<fs::path> csharp_sources;
509 try
510 {
511 for(const auto& entry : fs::recursive_directory_iterator(source_directory))
512 {
513 if(entry.is_regular_file() && entry.path().extension() == ".cs")
514 {
515 // Compute the relative path from the output directory
516 fs::path relative_path = fs::relative(entry.path(), output_directory);
517 csharp_sources.push_back(relative_path);
518 }
519 }
520 }
521 catch(const fs::filesystem_error& e)
522 {
523 throw std::runtime_error("Error while iterating source directory: " + std::string(e.what()));
524 }
525
526 // Generate the list of source files for the .csproj file
527 std::string csharp_source_items;
528 for(const auto& source_file : csharp_sources)
529 {
530 // Convert path to generic format (forward slashes)
531 std::string source_file_str = source_file.string();
532 csharp_source_items += " <Compile Include=\"" + source_file_str + "\" />\n";
533 }
534
535 // Generate external DLL references
536 std::string external_dll_references;
537 for(const auto& dll_path : external_dll_paths)
538 {
539 std::string dll_name = dll_path.filename().string();
540 fs::path dll_absolute_path = fs::absolute(dll_path);
541 std::string dll_absolute_path_str = dll_absolute_path.string(); // Forward slashes
542
543 external_dll_references += " <Reference Include=\"" + dll_name + "\">\n";
544 external_dll_references += " <HintPath>" + dll_absolute_path_str + "</HintPath>\n";
545 external_dll_references += " <Private>False</Private>\n"; // Mimic Unity's references
546 external_dll_references += " </Reference>\n";
547 }
548
549 // Build the .csproj content
550 std::string csproj_content;
551 csproj_content += "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
552 csproj_content += "<Project ToolsVersion=\"4.0\" DefaultTargets=\"Build\" "
553 "xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n";
554 csproj_content += " <PropertyGroup>\n";
555 csproj_content += " <LangVersion>9.0</LangVersion>\n";
556 csproj_content += " </PropertyGroup>\n";
557 csproj_content += " <PropertyGroup>\n";
558 csproj_content += " <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n";
559 csproj_content += " <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n";
560 csproj_content += " <ProductVersion>10.0.20506</ProductVersion>\n";
561 csproj_content += " <SchemaVersion>2.0</SchemaVersion>\n";
562 csproj_content += " <RootNamespace></RootNamespace>\n";
563 csproj_content += " <ProjectGuid>{" + hpp::to_string_upper(uid) + "}</ProjectGuid>\n";
564 csproj_content += " <OutputType>Library</OutputType>\n";
565 csproj_content += " <AppDesignerFolder>Properties</AppDesignerFolder>\n";
566 csproj_content += " <AssemblyName>" + project_name + "</AssemblyName>\n";
567 csproj_content += " <TargetFrameworkVersion>" + dotnet_framework_version + "</TargetFrameworkVersion>\n";
568 csproj_content += " <FileAlignment>512</FileAlignment>\n";
569 csproj_content += " <BaseDirectory>.</BaseDirectory>\n";
570 csproj_content += " <OutputPath>" + output_path.string() + "</OutputPath>\n";
571 csproj_content +=
572 " <IntermediateOutputPath>" + intermediate_output_path.string() + "</IntermediateOutputPath>\n";
573
574 csproj_content += " </PropertyGroup>\n";
575
576 // Add other necessary PropertyGroups as needed (similar to the Unity example)
577 // ...
578
579 // ItemGroup for Compile (C# files)
580 csproj_content += " <ItemGroup>\n";
581 csproj_content += csharp_source_items;
582 csproj_content += " </ItemGroup>\n";
583
584 // ItemGroup for References
585 csproj_content += " <ItemGroup>\n";
586 csproj_content += external_dll_references;
587 csproj_content += " </ItemGroup>\n";
588
589 // Add other ItemGroups as needed (e.g., Analyzers, etc.)
590 // ...
591
592 // Import the C# targets
593 csproj_content += " <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n";
594
595 // Optionally add custom Targets
596 csproj_content += " <Target Name=\"GenerateTargetFrameworkMonikerAttribute\" />\n";
597
598 // Optionally add BeforeBuild and AfterBuild targets
599 csproj_content +=
600 " <!-- To modify your build process, add your task inside one of the targets below and uncomment it.\n";
601 csproj_content += " Other similar extension points exist, see Microsoft.Common.targets.\n";
602 csproj_content += " <Target Name=\"BeforeBuild\">\n";
603 csproj_content += " </Target>\n";
604 csproj_content += " <Target Name=\"AfterBuild\">\n";
605 csproj_content += " </Target>\n";
606 csproj_content += " -->\n";
607
608 csproj_content += "</Project>\n";
609
610 // Define the path to the .csproj file
611 fs::path csproj_path = output_directory / (project_name + ".csproj");
612
613 // Write the .csproj file
614 std::ofstream csproj_file(csproj_path);
615 if(!csproj_file.is_open())
616 {
617 APPLOG_ERROR("Failed to create .csproj file at {}", csproj_path.string());
618 return;
619 }
620
621 csproj_file << csproj_content;
622
623 APPLOG_TRACE("Generated {}", csproj_path.string());
624}
625
626auto trim_line = [](std::string& line)
627{
628 // Trim trailing spaces and \r
629 line.erase(std::find_if(line.rbegin(),
630 line.rend(),
631 [](char ch)
632 {
633 return !std::isspace(int(ch));
634 })
635 .base(),
636 line.end());
637};
638
639auto parse_line(std::string& line, const fs::path& fs_parent_path) -> bool
640{
641#if UNRAVEL_PLATFORM_WINDOWS
642 // parse dependencies output
643 if(line.find("[ApplicationDirectory]") != std::string::npos)
644 {
645 std::size_t pos = line.find(':');
646 if(pos != std::string::npos)
647 {
648 line = line.substr(pos + 2); // +2 to skip ": "
649 trim_line(line);
650
651 return true;
652 }
653 }
654#else
655 // parse ldd output
656 size_t pos = line.find("=> ");
657 if(pos != std::string::npos)
658 {
659 line = line.substr(pos + 3); // +3 to remove '=> '
660 size_t address_pos = line.find(" (0x");
661 if(address_pos != std::string::npos)
662 {
663 line = line.substr(0, address_pos); // remove the address
664 }
665
666 trim_line(line);
667
668 fs::path fs_path(line);
669
670 if(fs::exists(fs_path) && fs::exists(fs_parent_path))
671 {
672 if(fs::equivalent(fs_path.parent_path(), fs_parent_path))
673 {
674 return true;
675 }
676 }
677 }
678
679#endif
680 return false;
681}
682
683auto get_subprocess_params(const fs::path& file) -> std::vector<std::string>
684{
685 std::vector<std::string> params;
686
687#if UNRAVEL_PLATFORM_WINDOWS
688 params.emplace_back(fs::resolve_protocol("editor:/tools/dependencies/Dependencies.exe").string());
689 params.emplace_back("-modules");
690 params.emplace_back(file.string());
691
692#else
693
694 params.emplace_back("ldd");
695 params.emplace_back(file.string());
696#endif
697 return params;
698}
699
700auto parse_dependencies(const std::string& input, const fs::path& fs_parent_path) -> std::vector<std::string>
701{
702 std::vector<std::string> dependencies;
703 std::stringstream ss(input);
704 std::string line;
705
706 while(std::getline(ss, line))
707 {
708 if(parse_line(line, fs_parent_path))
709 {
710 dependencies.push_back(line);
711 }
712 }
713 return dependencies;
714}
715
716auto get_dependencies(const fs::path& file) -> std::vector<std::string>
717{
718 auto parent_path = file.parent_path();
719
720 auto params = get_subprocess_params(file);
721 auto result = subprocess::call(params);
722 return parse_dependencies(result.out_output, parent_path);
723}
724
725auto save_scene_impl(rtti::context& ctx, const fs::path& path) -> bool
726{
727 auto& ev = ctx.get_cached<events>();
728 if(ev.is_playing)
729 {
730 return false;
731 }
732
733 auto& ec = ctx.get_cached<ecs>();
734 if(asset_writer::atomic_save_to_file(path.string(), ec.get_scene()))
735 {
737
738 auto& em = ctx.get_cached<editing_manager>();
739 em.clear_unsaved_changes();
740 }
741
742 return true;
743}
744
745auto add_extension_if_missing(const std::string& p) -> fs::path
746{
747 fs::path def_path = p;
748 if(!ex::is_format<scene_prefab>(def_path.extension().generic_string()))
749 {
750 def_path.replace_extension(ex::get_format<scene_prefab>(false));
751 }
752
753 return def_path;
754}
755
756auto save_scene_as_impl(rtti::context& ctx, fs::path& path, const std::string& default_name = {}) -> bool
757{
758 auto& ev = ctx.get_cached<events>();
759 if(ev.is_playing)
760 {
761 return false;
762 }
763
764 auto& em = ctx.get_cached<editing_manager>();
765 if(em.is_prefab_mode())
766 {
767 em.save_prefab_changes(ctx);
768 return true;
769 }
770
771 auto save_path = fs::resolve_protocol("app:/data/").string();
772
773 if(!default_name.empty())
774 {
775 auto def_path = add_extension_if_missing(default_name);
776
777 save_path += def_path.string();
778 }
779
780 std::string picked;
781 if(native::save_file_dialog(picked,
783 "Scene files",
784 "Save scene as",
785 save_path))
786 {
787 auto& em = ctx.get_cached<editing_manager>();
788
789 path = add_extension_if_missing(picked);
790
791 return save_scene_impl(ctx, path);
792 }
793
794 return false;
795}
796
797void try_delete_empty_parents(const fs::path& start, const fs::path& root, fs::error_code& ec)
798{
799 fs::path current = start.parent_path();
800 while(current != root && fs::is_empty(current, ec))
801 {
802 APPLOG_TRACE("Removing Empty Parent Directory {}", current.generic_string());
803 fs::remove(current, ec);
804 current = current.parent_path();
805 }
806}
807
808void remove_unreferenced_files(const fs::path& root)
809{
810 fs::error_code ec;
811 const fs::recursive_directory_iterator end;
812
813 std::vector<fs::path> deleted_dirs;
814
815 // First pass: remove matching script files
816 {
817 fs::recursive_directory_iterator it(root, ec);
818 while(it != end)
819 {
820 const fs::path current_path = it->path();
821 ++it;
822
823 for(const auto& type : ex::get_suported_formats<script>())
824 {
825 auto ext = fs::reduce_trailing_extensions(current_path).extension().generic_string();
826 if(ext == type)
827 {
828 APPLOG_TRACE("Removing Script {}", current_path.generic_string());
829 fs::remove(current_path, ec);
830 deleted_dirs.push_back(current_path.parent_path());
831 break;
832 }
833 }
834 }
835 }
836
837 // Second pass: remove now-empty directories
838 {
839 fs::recursive_directory_iterator it(root, ec);
840 while(it != end)
841 {
842 const fs::path current_path = it->path();
843 ++it;
844
845 if(fs::is_directory(current_path, ec) && fs::is_empty(current_path, ec))
846 {
847 APPLOG_TRACE("Removing Empty Directory {}", current_path.generic_string());
848 fs::remove(current_path, ec);
849 deleted_dirs.push_back(current_path.parent_path());
850 }
851 }
852 }
853
854 // Deduplicate deleted parent paths and sort deepest first
855 std::sort(deleted_dirs.begin(), deleted_dirs.end());
856 deleted_dirs.erase(std::unique(deleted_dirs.begin(), deleted_dirs.end()), deleted_dirs.end());
857 std::sort(deleted_dirs.begin(),
858 deleted_dirs.end(),
859 [](const fs::path& a, const fs::path& b)
860 {
861 return a.string().size() > b.string().size();
862 });
863
864 // Final cleanup: walk up and try deleting empty parents
865 for(const auto& path : deleted_dirs)
866 {
867 try_delete_empty_parents(path, root, ec);
868 }
869}
870
871} // namespace
872
874{
875 auto& ev = ctx.get_cached<events>();
876 if(ev.is_playing)
877 {
878 return false;
879 }
880 prompt_save_scene(ctx, [&ctx]() {
881 auto& em = ctx.get_cached<editing_manager>();
882 em.clear();
883
884 auto& ec = ctx.get_cached<ecs>();
885 ec.unload_scene();
886
887 defaults::create_default_3d_scene(ctx, ec.get_scene());
888 });
889
890 return true;
891}
893{
894 auto& ev = ctx.get_cached<events>();
895 if(ev.is_playing)
896 {
897 ev.set_play_mode(ctx, false);
898 }
899
900 std::string picked;
901 if(native::open_file_dialog(picked,
903 "Scene files",
904 "Open scene",
905 fs::resolve_protocol("app:/data/").string()))
906 {
907 auto path = fs::convert_to_protocol(picked);
908 if(ex::is_format<scene_prefab>(path.extension().generic_string()))
909 {
910 auto& am = ctx.get_cached<asset_manager>();
911 auto asset = am.get_asset<scene_prefab>(path.string());
912
913 return open_scene_from_asset(ctx, asset);
914 }
915 }
916 return false;
917}
918
920{
921 return prompt_save_scene(ctx, [&ctx, asset]() {
922 auto& em = ctx.get_cached<editing_manager>();
923 em.clear();
924
925 auto& ec = ctx.get_cached<ecs>();
926 ec.unload_scene();
927
928 auto& scene = ec.get_scene();
929 bool loaded = scene.load_from(asset);
930
931 if(loaded)
932 {
933 em.sync_prefab_instances(ctx, &scene);
934 }
935
936 if(!loaded)
937 {
939 }
940 });
941}
943{
944 auto& ec = ctx.get_cached<ecs>();
945 auto& scene = ec.get_scene();
946 auto& em = ctx.get_cached<editing_manager>();
947
948 if(em.is_prefab_mode())
949 {
950 em.save_prefab_changes(ctx);
951 return true;
952 }
953
954 if(!scene.source)
955 {
956 fs::path picked;
957 if(save_scene_as_impl(ctx, picked, "Scene3D"))
958 {
959 auto path = fs::convert_to_protocol(picked);
960
961 auto& am = ctx.get_cached<asset_manager>();
962 scene.source = am.get_asset<scene_prefab>(path.string());
963 return true;
964 }
965 }
966 else
967 {
968 auto path = fs::resolve_protocol(scene.source.id());
969 return save_scene_impl(ctx, path);
970 }
971
972 return false;
973}
975{
976 auto& ec = ctx.get_cached<ecs>();
977 auto& scene = ec.get_scene();
978
979 fs::path p;
980 return save_scene_as_impl(ctx, p, scene.source.name());
981}
982
983auto editor_actions::prompt_save_scene(rtti::context& ctx, const std::function<void()>& on_continue) -> bool
984{
985 auto& ev = ctx.get_cached<events>();
986 if(ev.is_playing)
987 {
988 on_continue();
989 return false;
990 }
991
992 auto& em = ctx.get_cached<editing_manager>();
993 if(!em.has_unsaved_changes())
994 {
995 on_continue();
996 return true;
997 }
998
999 ImBox::ShowSaveConfirmation("Save scene?",
1000 "Do you want to save the changes you made?",
1001 [&ctx, on_continue](ImBox::ModalResult result)
1002 {
1003 if(result == ImBox::ModalResult::Save)
1004 {
1005 save_scene(ctx);
1006 }
1007
1008 if(result != ImBox::ModalResult::Cancel)
1009 {
1010 on_continue();
1011 }
1012 });
1013
1014 return true;
1015}
1016
1018{
1019 auto& ev = ctx.get_cached<events>();
1020 if(ev.is_playing)
1021 {
1022 return false;
1023 }
1024
1025 prompt_save_scene(ctx, [&ctx]() {
1026 auto& pm = ctx.get_cached<project_manager>();
1027 pm.close_project(ctx);
1028 });
1029
1030 return true;
1031}
1032
1034{
1035 auto& ev = ctx.get_cached<events>();
1036 if(ev.is_playing)
1037 {
1038 return false;
1039 }
1040 auto& pm = ctx.get_cached<project_manager>();
1041 if(!pm.has_open_project())
1042 {
1043 return false;
1044 }
1045 auto project_path = fs::resolve_protocol("app:/");
1046
1047 pm.close_project(ctx);
1048 return pm.open_project(ctx, project_path);
1049}
1050
1052{
1053 auto call_params = params.deploy_location / (std::string("game") + fs::executable_extension());
1054 subprocess::call(call_params.string());
1055}
1056
1058 const deploy_settings& params) -> std::map<std::string, tpp::shared_future<void>>
1059{
1060 auto& th = ctx.get_cached<threader>();
1061
1062 std::map<std::string, tpp::shared_future<void>> jobs;
1063 std::vector<tpp::shared_future<void>> jobs_seq;
1064
1065 fs::error_code ec;
1066
1067 auto& am = ctx.get_cached<asset_manager>();
1068
1069 auto& pm = ctx.get_cached<project_manager>();
1070 auto project_name = pm.get_name();
1071
1072 // am.get_database("engine:/")
1073
1074 if(params.deploy_dependencies)
1075 {
1076 APPLOG_INFO("Clearing {}", params.deploy_location.generic_string());
1077 fs::remove_all(params.deploy_location, ec);
1078 fs::create_directories(params.deploy_location, ec);
1079
1080 auto job =
1081 th.pool
1082 ->schedule("Deploying Dependencies",
1083 [params, project_name]()
1084 {
1085 APPLOG_INFO("Deploying Dependencies...");
1086
1087 fs::path app_executable =
1088 fs::resolve_protocol("binary:/game" + fs::executable_extension());
1089 auto deps = get_dependencies(app_executable);
1090
1091 fs::error_code ec;
1092 for(const auto& dep : deps)
1093 {
1094 APPLOG_TRACE("Copying {} -> {}",
1095 fs::path(dep).generic_string(),
1096 params.deploy_location.generic_string());
1097 fs::copy(dep, params.deploy_location, fs::copy_options::overwrite_existing, ec);
1098 }
1099
1100 auto executable_path = params.deploy_location / (project_name + fs::executable_extension());
1101
1102 APPLOG_TRACE("Copying {} -> {}",
1103 app_executable.generic_string(),
1104 params.deploy_location.generic_string());
1105 fs::copy(app_executable, executable_path, fs::copy_options::overwrite_existing, ec);
1106
1107 APPLOG_INFO("Deploying Dependencies - Done");
1108 })
1109 .share();
1110 jobs["Deploying Dependencies"] = job;
1111 jobs_seq.emplace_back(job);
1112 }
1113
1114 {
1115 auto job = th.pool
1116 ->schedule("Deploying Project Settings",
1117 [params]()
1118 {
1119 APPLOG_INFO("Deploying Project Settings...");
1120
1121 auto data = fs::resolve_protocol("app:/settings");
1122 fs::path dst = params.deploy_location / "data" / "app" / "settings";
1123
1124 fs::error_code ec;
1125
1126 APPLOG_TRACE("Clearing {}", dst.generic_string());
1127 fs::remove_all(dst, ec);
1128 fs::create_directories(dst, ec);
1129
1130 APPLOG_TRACE("Copying {} -> {}", data.generic_string(), dst.generic_string());
1131 fs::copy(data, dst, fs::copy_options::recursive, ec);
1132
1133 APPLOG_INFO("Deploying Project Settings - Done");
1134 })
1135 .share();
1136
1137 jobs["Deploying Project Settings"] = job;
1138 jobs_seq.emplace_back(job);
1139 }
1140
1141 {
1142 auto job =
1143 th.pool
1144 ->schedule(
1145 "Deploying Project Data",
1146 [params, &am]()
1147 {
1148 APPLOG_INFO("Deploying Project Data...");
1149
1150 fs::error_code ec;
1151 {
1153 fs::path cached_data =
1154 params.deploy_location / "data" / "app" / ex::get_compiled_directory_no_slash();
1155
1156 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1157 fs::remove_all(cached_data, ec);
1158 fs::create_directories(cached_data, ec);
1159
1160 APPLOG_TRACE("Copying {} -> {}", data.generic_string(), cached_data.generic_string());
1161 fs::copy(data, cached_data, fs::copy_options::recursive, ec);
1162
1163 remove_unreferenced_files(cached_data);
1164 }
1165
1166 {
1167 fs::path cached_data = params.deploy_location / "data" / "app" / "assets.pack";
1168 APPLOG_TRACE("Creating Asset Pack -> {}", cached_data.generic_string());
1169 am.save_database("app:/", cached_data);
1170 }
1171
1172 APPLOG_INFO("Deploying Project Data - Done");
1173 })
1174 .share();
1175
1176 jobs["Deploying Project Data"] = job;
1177 jobs_seq.emplace_back(job);
1178 }
1179
1180 {
1181 auto job =
1182 th.pool
1183 ->schedule(
1184 "Deploying Engine Data",
1185 [params, &am]()
1186 {
1187 APPLOG_INFO("Deploying Engine Data...");
1188
1189 fs::error_code ec;
1190 {
1191 fs::path cached_data =
1192 params.deploy_location / "data" / "engine" / ex::get_compiled_directory_no_slash();
1193 auto data = fs::resolve_protocol(ex::get_compiled_directory("engine"));
1194
1195 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1196 fs::remove_all(cached_data, ec);
1197 fs::create_directories(cached_data, ec);
1198
1199 APPLOG_TRACE("Copying {} -> {}", data.generic_string(), cached_data.generic_string());
1200 fs::copy(data, cached_data, fs::copy_options::recursive, ec);
1201
1202 remove_unreferenced_files(cached_data);
1203 }
1204
1205 {
1206 fs::path cached_data = params.deploy_location / "data" / "engine" / "assets.pack";
1207 APPLOG_TRACE("Creating Asset Pack -> {}", cached_data.generic_string());
1208 am.save_database("engine:/", cached_data);
1209 }
1210
1211 APPLOG_INFO("Deploying Engine Data - Done");
1212 })
1213 .share();
1214 jobs["Deploying Engine Data..."] = job;
1215 jobs_seq.emplace_back(job);
1216 }
1217
1218 {
1219 auto job =
1220 th.pool
1221 ->schedule(
1222 "Deploying Mono",
1223 [params, &am, &ctx]()
1224 {
1225 APPLOG_INFO("Deploying Mono...");
1226
1227 auto paths = script_system::find_mono(ctx);
1228 fs::path assembly_path = mono::get_core_assembly_path();
1229 fs::path assembly_dir = assembly_path.parent_path().parent_path();
1230 fs::path lib_version = assembly_dir.filename();
1231
1232 fs::error_code ec;
1233
1234 {
1235 fs::path cached_data = params.deploy_location / "data" / "engine" / "mono" / "lib";
1236 cached_data /= "mono";
1237
1238 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1239 fs::remove_all(cached_data, ec);
1240
1241 // cached_data /= lib_version;
1242
1243 fs::create_directories(cached_data, ec);
1244
1245 APPLOG_TRACE("Copying {} -> {}",
1246 assembly_dir.generic_string(),
1247 cached_data.generic_string());
1248 fs::copy(assembly_dir, cached_data, fs::copy_options::recursive, ec);
1249 }
1250
1251 fs::path config_dir = paths.config_dir;
1252 config_dir /= "mono";
1253
1254 {
1255 fs::path cached_data = params.deploy_location / "data" / "engine" / "mono" / "etc";
1256 cached_data /= "mono";
1257
1258 APPLOG_TRACE("Clearing {}", cached_data.generic_string());
1259 fs::remove_all(cached_data, ec);
1260 fs::create_directories(cached_data, ec);
1261
1262 APPLOG_TRACE("Copying {} -> {}", config_dir.generic_string(), cached_data.generic_string());
1263 fs::copy(config_dir, cached_data, fs::copy_options::recursive, ec);
1264 }
1265
1266 APPLOG_INFO("Deploying Mono - Done");
1267 })
1268 .share();
1269 jobs["Deploying Mono..."] = job;
1270 jobs_seq.emplace_back(job);
1271 }
1272
1273 tpp::when_all(std::begin(jobs_seq), std::end(jobs_seq))
1274 .then(tpp::this_thread::get_id(),
1275 [params](auto f)
1276 {
1277 if(params.deploy_and_run)
1278 {
1279 run_project(params);
1280 }
1281 else
1282 {
1283 fs::show_in_graphical_env(params.deploy_location);
1284 }
1285 });
1286
1287 return jobs;
1288}
1289
1291{
1292 auto& ctx = engine::context();
1293 auto& pm = ctx.get_cached<project_manager>();
1294 auto project_name = pm.get_name();
1295 const auto& editor_settings = pm.get_editor_settings();
1296
1297 fs::error_code err;
1298
1299 auto workspace_folder = fs::resolve_protocol("app:/.vscode");
1300 fs::create_directories(workspace_folder, err);
1301
1302 auto formats = ex::get_all_formats();
1303 formats.emplace_back(std::vector<std::string>{".meta"});
1304 remove_extensions(formats, ex::get_suported_formats<gfx::shader>());
1305 remove_extensions(formats, ex::get_suported_formats<script>());
1306 remove_extensions(formats, ex::get_suported_formats<ui_tree>());
1307 remove_extensions(formats, ex::get_suported_formats<style_sheet>());
1308
1309 auto workspace_file = workspace_folder / fmt::format("{}-workspace.code-workspace", project_name);
1310 generate_workspace_file(workspace_file.string(), formats, editor_settings);
1311
1312 auto source_path = fs::resolve_protocol("app:/data");
1313
1314 auto engine_dep = fs::resolve_protocol(script_system::get_lib_compiled_key("engine"));
1315
1316 auto output_path = fs::resolve_protocol("app:/");
1317
1318 generate_csproj_legacy(source_path, {engine_dep}, output_path, project_name);
1319}
1320
1321void editor_actions::open_workspace_on_file(const fs::path& file, int line)
1322{
1323 auto& ctx = engine::context();
1324 auto& pm = ctx.get_cached<project_manager>();
1325 auto project_name = pm.get_name();
1326 auto vscode_exe = pm.get_editor_settings().external_tools.vscode_executable;
1327 tpp::async(
1328 [vscode_exe, project_name, file, line]()
1329 {
1330 auto external_tool = vscode_exe;
1331 if(external_tool.empty())
1332 {
1333 external_tool = get_vscode_executable();
1334 }
1335
1336 static const char* tool = "[Visual Studio Code]";
1337 static const char* setup_hint = "Edit -> Editor Settings -> External Tools";
1338
1339 if(external_tool.empty())
1340 {
1341 APPLOG_ERROR("Cannot locate external tool {}", tool);
1342 APPLOG_ERROR("To configure {} visit : {}", tool, setup_hint);
1343 return;
1344 }
1345 auto workspace_key = fmt::format("app:/.vscode/{}-workspace.code-workspace", project_name);
1346 auto workspace_path = fs::resolve_protocol(workspace_key);
1347
1348 auto result = subprocess::call(external_tool.string(),
1349 {workspace_path.string(), "-g", fmt::format("{}:{}", file.string(), line)});
1350
1351 if(result.retcode != 0)
1352 {
1353 APPLOG_ERROR("Cannot open external tool {} for file {}", tool, external_tool.string(), file.string());
1354 APPLOG_ERROR("To configure {} visit : {}", tool, setup_hint);
1355 }
1356 });
1357}
1358
1360{
1361 auto& ctx = engine::context();
1362 auto& am = ctx.get_cached<asset_manager>();
1363 auto shaders = am.get_assets<gfx::shader>();
1365 for(auto& asset : shaders)
1366 {
1367 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string());
1368 fs::watcher::touch(path, false);
1369 }
1371}
1372
1374{
1375 auto& ctx = engine::context();
1376 auto& am = ctx.get_cached<asset_manager>();
1377 auto textures = am.get_assets<gfx::texture>();
1379 for(auto& asset : textures)
1380 {
1381 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string());
1382 fs::watcher::touch(path, false);
1383 }
1385}
1386
1388{
1389 auto& ctx = engine::context();
1390 auto& am = ctx.get_cached<asset_manager>();
1392 {
1393 auto assets = am.get_assets<ui_tree>();
1394 for(auto& asset : assets)
1395 {
1396 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string());
1397 fs::watcher::touch(path, false);
1398 }
1399 }
1400 {
1401 auto assets = am.get_assets<style_sheet>();
1402 for(auto& asset : assets)
1403 {
1404 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string());
1405 fs::watcher::touch(path, false);
1406 }
1407 }
1408
1410}
1412{
1413 auto& ctx = engine::context();
1414 auto& am = ctx.get_cached<asset_manager>();
1415 auto scripts = am.get_assets<script>();
1417 for(auto& asset : scripts)
1418 {
1419 auto path = fs::absolute(fs::resolve_protocol(asset.id()).string());
1420 fs::watcher::touch(path, false);
1421 }
1423}
1430} // namespace unravel
entt::handle b
manifold_type type
entt::handle a
static void resume()
Definition watcher.cpp:491
static void pause()
Definition watcher.cpp:475
static void touch(const fs::path &path, bool recursive, fs::file_time_type time=fs::now())
Sets the last modification time of a file or directory. by default sets the time to the current time.
Definition watcher.cpp:436
Manages assets, including loading, unloading, and storage.
auto get_asset(const std::string &key, load_flags flags=load_flags::standard) -> asset_handle< T >
Gets an asset by its key.
auto get_assets(const std::string &group={}) const -> std::vector< asset_handle< T > >
Gets all assets in a specified group.
auto get_name() const -> const std::string &
void close_project(rtti::context &ctx)
@ ImGuiToastType_Success
#define APPLOG_ERROR(...)
Definition logging.h:20
#define APPLOG_INFO(...)
Definition logging.h:18
#define APPLOG_TRACE(...)
Definition logging.h:17
ModalResult
Modal result flags for message box buttons.
auto ShowSaveConfirmation(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a save confirmation dialog with Save/Don't Save/Cancel buttons.
NOTIFY_INLINE void PushNotification(const ImGuiToast &toast)
Insert a new toast in the list.
auto get_suported_formats< gfx::shader >() -> const std::vector< std::string > &
auto get_all_formats() -> const std::vector< std::vector< std::string > > &
auto get_format(bool include_dot=true) -> std::string
auto get_compiled_directory_no_slash(const std::string &prefix={}) -> std::string
auto is_format(const std::string &ex) -> bool
auto get_compiled_directory(const std::string &prefix={}) -> std::string
auto get_suported_formats() -> const std::vector< std::string > &
auto get_suported_formats_with_wildcard() -> std::vector< std::string >
path reduce_trailing_extensions(const path &_path)
another.
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
void end(encoder *_encoder)
Definition graphics.cpp:270
auto start(seq_action action, const seq_scope_policy &scope_policy, hpp::source_location location) -> seq_id_t
Starts a new action.
Definition seq.cpp:8
auto to_lower(const std::string &str) -> std::string
Definition utils.cpp:42
auto call(const std::vector< std::string > &args_array) -> call_result
auto atomic_save_to_file(const fs::path &key, const asset_handle< T > &obj) -> bool
auto generate_uuid() -> hpp::uuid
Definition uuid.cpp:25
Represents a handle to an asset, providing access and management functions.
auto get_cached() -> T &
Definition context.hpp:49
static void create_default_3d_scene(rtti::context &ctx, scene &scn)
Creates a default 3D scene.
Definition defaults.cpp:615
fs::path deploy_location
Definition deploy.h:11
Manages the entity-component-system (ECS) operations for the ACE framework.
Definition ecs.h:12
void unload_scene()
Unloads the current scene.
Definition ecs.cpp:25
static auto close_project(rtti::context &ctx) -> bool
static void open_workspace_on_file(const fs::path &file, int line=0)
static auto open_scene(rtti::context &ctx) -> bool
static auto open_scene_from_asset(rtti::context &ctx, const asset_handle< scene_prefab > &asset) -> bool
static void run_project(const deploy_settings &params)
static auto prompt_save_scene(rtti::context &ctx, const std::function< void()> &on_continue) -> bool
static auto save_scene(rtti::context &ctx) -> bool
static auto new_scene(rtti::context &ctx) -> bool
static auto reload_project(rtti::context &ctx) -> bool
static auto save_scene_as(rtti::context &ctx) -> bool
static auto deploy_project(rtti::context &ctx, const deploy_settings &params) -> std::map< std::string, tpp::shared_future< void > >
static void generate_script_workspace()
static auto context() -> rtti::context &
Definition engine.cpp:115
Represents a scene-specific prefab. Inherits from the generic prefab structure.
Definition prefab.h:31
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:21
asset_handle< scene_prefab > source
The source prefab asset handle for the scene.
Definition scene.h:112
auto load_from(const asset_handle< scene_prefab > &pfb) -> bool
Loads a scene from a prefab asset.
Definition scene.cpp:176
static auto get_lib_compiled_key(const std::string &protocol) -> std::string
static auto find_mono(const rtti::context &ctx) -> mono::compiler_paths
Represents a UI style sheet asset (CSS/RCSS document).
Definition style_sheet.h:26
Represents a UI visual tree asset (HTML/RML document).
Definition ui_tree.h:25