main.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. 
  2. // TODO:
  3. // * A catalogue of SDK examples with images and descriptions loaded automatically from their folder.
  4. // Offer one-click build and execution of SDK examples on multiple platforms, while explaining how the building works.
  5. // How can the file library execute other applications and scripts in a portable way when scripts need to select a terminal application to execute them?
  6. // Maybe call the builder as a static library and have it call the compiler directly in a simulated terminal window embedded into the wizard, instead of using unreliable scripts?
  7. // * Let the user browse a file system and select a location for a new or existing project.
  8. // Should a multi-frame tab container be created to allow having multiple frames in the same container?
  9. // Can let frames have a caption for when used within a container.
  10. #include "../../DFPSR/includeFramework.h"
  11. #include "sound.h"
  12. using namespace dsr;
  13. // Global
  14. bool running = true;
  15. Window window;
  16. // Visual components
  17. Component projectList;
  18. Component launchButton;
  19. Component descriptionLabel;
  20. Component previewPicture;
  21. // Media
  22. int boomSound;
  23. struct Project {
  24. String projectFilePath;
  25. String executableFilePath;
  26. String title; // To display
  27. String description; // To show when selected
  28. DsrProcess programHandle;
  29. DsrProcessStatus lastStatus = DsrProcessStatus::NotStarted;
  30. OrderedImageRgbaU8 preview;
  31. Project(const ReadableString &projectFilePath);
  32. };
  33. List<Project> projects;
  34. Project::Project(const ReadableString &projectFilePath)
  35. : projectFilePath(projectFilePath) {
  36. String projectFolderPath = file_getRelativeParentFolder(projectFilePath);
  37. String extensionlessProjectPath = file_getExtensionless(projectFilePath);
  38. this->title = file_getPathlessName(extensionlessProjectPath);
  39. // TODO: Get the native extension for each type of file? .exe, .dll, .so...
  40. #ifdef USE_MICROSOFT_WINDOWS
  41. this->executableFilePath = string_combine(extensionlessProjectPath, U".exe");
  42. #else
  43. this->executableFilePath = extensionlessProjectPath;
  44. #endif
  45. if (file_getEntryType(this->executableFilePath) != EntryType::File) {
  46. this->executableFilePath = U"";
  47. }
  48. String descriptionPath = file_combinePaths(projectFolderPath, U"Description.txt");
  49. if (file_getEntryType(descriptionPath) == EntryType::File) {
  50. this->description = string_load(descriptionPath);
  51. } else {
  52. this->description = string_combine(U"Project at ", projectFolderPath, U" did not have any Description.txt to display!");
  53. }
  54. String previewPath = file_combinePaths(projectFolderPath, U"Preview.jpg");
  55. if (file_getEntryType(previewPath) == EntryType::File) {
  56. this->preview = image_load_RgbaU8(previewPath);
  57. } else {
  58. previewPath = file_combinePaths(projectFolderPath, U"Preview.gif");
  59. if (file_getEntryType(previewPath) == EntryType::File) {
  60. this->preview = image_load_RgbaU8(previewPath);
  61. } else {
  62. this->preview = OrderedImageRgbaU8();
  63. }
  64. }
  65. }
  66. static ReadableString findParent(const ReadableString& startPath, const ReadableString& parentName) {
  67. int64_t pathEndIndex = -1; // Last character of path leading to Source.
  68. file_getPathEntries(startPath, [&pathEndIndex, &parentName](ReadableString entry, int64_t firstIndex, int64_t lastIndex) {
  69. if (string_match(entry, parentName)) {
  70. pathEndIndex = lastIndex;
  71. }
  72. });
  73. if (pathEndIndex == -1) {
  74. throwError(U"Could not find the Source folder with SDK examples.");
  75. return startPath;
  76. } else {
  77. return string_until(startPath, pathEndIndex);
  78. }
  79. }
  80. static void findProjects(const ReadableString& folderPath) {
  81. file_getFolderContent(folderPath, [](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) {
  82. if (entryType == EntryType::Folder) {
  83. findProjects(entryPath);
  84. } else if (entryType == EntryType::File) {
  85. ReadableString extension = string_upperCase(file_getExtension(entryName));
  86. Project newProject = Project(entryPath);
  87. // If we find a project within folderPath...
  88. if (string_match(extension, U"DSRPROJ")) {
  89. // ...and the folder is not namned wizard...
  90. if (!string_match(newProject.title, U"Wizard")) {
  91. // ...then add it to the list of projects.
  92. projects.push(newProject);
  93. }
  94. }
  95. }
  96. });
  97. }
  98. // Returns true iff the interface needs to be redrawn.
  99. static bool updateInterface(bool forceUpdate) {
  100. bool needToDraw = false;
  101. int projectIndex = component_getProperty_integer(projectList, U"SelectedIndex", true);
  102. //Application name from project name?
  103. if (projectIndex >= 0 && projectIndex < projects.length()) {
  104. DsrProcessStatus newStatus = process_getStatus(projects[projectIndex].programHandle);
  105. DsrProcessStatus lastStatus = projects[projectIndex].lastStatus;
  106. if (newStatus != lastStatus || forceUpdate) {
  107. if (newStatus == DsrProcessStatus::Running) {
  108. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" is running."));
  109. } else if (newStatus == DsrProcessStatus::Crashed) {
  110. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" crashed."));
  111. } else if (newStatus == DsrProcessStatus::Completed) {
  112. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" terminated safely."));
  113. } else if (newStatus == DsrProcessStatus::NotStarted) {
  114. component_setProperty_string(descriptionLabel, U"Text", projects[projectIndex].description);
  115. }
  116. needToDraw = true;
  117. projects[projectIndex].lastStatus = newStatus;
  118. }
  119. component_setProperty_image(previewPicture, U"Image", projects[projectIndex].preview, false);
  120. bool foundExecutable = string_length(projects[projectIndex].executableFilePath) > 0;
  121. component_setProperty_integer(launchButton, U"Visible", foundExecutable);
  122. }
  123. return needToDraw;
  124. }
  125. static void selectProject(int64_t projectIndex) {
  126. // Don't trigger new events if the selected index is already updated manually.
  127. if (projectIndex != component_getProperty_integer(projectList, U"SelectedIndex", true)) {
  128. component_setProperty_integer(projectList, U"SelectedIndex", projectIndex, false);
  129. }
  130. updateInterface(true);
  131. }
  132. static void populateInterface(const ReadableString& folderPath) {
  133. findProjects(folderPath);
  134. for (int p = 0; p < projects.length(); p++) {
  135. component_call(projectList, U"PushElement", projects[p].title);
  136. }
  137. selectProject(0);
  138. }
  139. DSR_MAIN_CALLER(dsrMain)
  140. void dsrMain(List<String> args) {
  141. // Get the application folder.
  142. String applicationFolder = file_getApplicationFolder();
  143. String mediaFolder = file_combinePaths(applicationFolder, U"media");
  144. // Start sound.
  145. sound_initialize();
  146. boomSound = loadSoundFromFile(file_combinePaths(mediaFolder, U"Boom.wav"));
  147. // Create a window.
  148. window = window_create(U"DFPSR wizard application", 800, 600);
  149. // Create components using the layout.
  150. window_loadInterfaceFromFile(window, file_combinePaths(mediaFolder, U"Interface.lof"));
  151. // Create a virtual machine with reusable image generating functions.
  152. // The same Media Machine Code (*.mmc) can be used for multiple themes.
  153. MediaMachine machine = machine_create(string_load(file_combinePaths(mediaFolder, U"Drawing.mmc")));
  154. // Use the virtual machine with a specific style referring to the functions in machine.
  155. window_applyTheme(window, theme_createFromFile(machine, file_combinePaths(mediaFolder, U"Theme.ini")));
  156. // Find components.
  157. projectList = window_findComponentByName(window, U"projectList");
  158. launchButton = window_findComponentByName(window, U"launchButton");
  159. descriptionLabel = window_findComponentByName(window, U"descriptionLabel");
  160. previewPicture = window_findComponentByName(window, U"previewPicture");
  161. // Find projects to showcase.
  162. // On systems that don't allow getting the application's folder, the program must be started somewhere within the Source folder.
  163. String sourceFolder = findParent(applicationFolder, U"Source");
  164. populateInterface(sourceFolder);
  165. // Bind methods to events.
  166. window_setKeyboardEvent(window, [](const KeyboardEvent& event) {
  167. DsrKey key = event.dsrKey;
  168. if (event.keyboardEventType == KeyboardEventType::KeyDown) {
  169. if (key == DsrKey_Escape) {
  170. running = false;
  171. }
  172. }
  173. });
  174. component_setPressedEvent(launchButton, []() {
  175. // TODO: Implement building and running of the selected project.
  176. playSound(boomSound, false, 1.0, 1.0, 0.7);
  177. int projectIndex = component_getProperty_integer(projectList, U"SelectedIndex", true);
  178. //Application name from project name?
  179. if (projectIndex >= 0 && projectIndex < projects.length()) {
  180. if (file_getEntryType(projects[projectIndex].executableFilePath) != EntryType::File) {
  181. // Could not find the application.
  182. component_setProperty_string(descriptionLabel, U"Text", string_combine(U"Could not find the executable at ", projects[projectIndex].executableFilePath, U"!\n"), true);
  183. } else if (process_getStatus(projects[projectIndex].programHandle) != DsrProcessStatus::Running) {
  184. // Select input arguments.
  185. List<String> arguments;
  186. if (string_match(projects[projectIndex].title, U"BasicCLI")) {
  187. // Give some random arguments to the CLI template, so that it will do something more than just printing "Hello World".
  188. arguments.push(U"1");
  189. arguments.push(U"TWO");
  190. arguments.push(U"three");
  191. arguments.push(U"Four");
  192. }
  193. // Launch the application.
  194. projects[projectIndex].programHandle = process_execute(projects[projectIndex].executableFilePath, arguments);
  195. updateInterface(true);
  196. }
  197. }
  198. });
  199. component_setSelectEvent(projectList, [](int64_t index) {
  200. playSound(boomSound, false, 0.5, 0.5, 0.5);
  201. selectProject(index);
  202. });
  203. window_setCloseEvent(window, []() {
  204. running = false;
  205. });
  206. // Execute.
  207. playSound(boomSound, false, 1.0, 1.0, 0.25);
  208. while(running) {
  209. // Wait for actions so that we don't render until an action has been recieved.
  210. // This will save battery on laptops for applications that don't require animation.
  211. while (!(window_executeEvents(window) || updateInterface(false))) {
  212. time_sleepSeconds(0.01);
  213. }
  214. // Fill the background.
  215. AlignedImageRgbaU8 canvas = window_getCanvas(window);
  216. image_fill(canvas, ColorRgbaI32(64, 64, 64, 255));
  217. // Draw interface.
  218. window_drawComponents(window);
  219. // Show the final image.
  220. window_showCanvas(window);
  221. }
  222. // Close sound.
  223. sound_terminate();
  224. }