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. #ifdef USE_MICROSOFT_WINDOWS
  40. this->executableFilePath = string_combine(extensionlessProjectPath, U".exe");
  41. #else
  42. this->executableFilePath = extensionlessProjectPath;
  43. #endif
  44. if (file_getEntryType(this->executableFilePath) != EntryType::File) {
  45. this->executableFilePath = U"";
  46. }
  47. String descriptionPath = file_combinePaths(projectFolderPath, U"Description.txt");
  48. if (file_getEntryType(descriptionPath) == EntryType::File) {
  49. this->description = string_load(descriptionPath);
  50. } else {
  51. this->description = string_combine(U"Project at ", projectFolderPath, U" did not have any Description.txt to display!");
  52. }
  53. String previewPath = file_combinePaths(projectFolderPath, U"Preview.jpg");
  54. if (file_getEntryType(previewPath) == EntryType::File) {
  55. this->preview = image_load_RgbaU8(previewPath);
  56. } else {
  57. previewPath = file_combinePaths(projectFolderPath, U"Preview.gif");
  58. if (file_getEntryType(previewPath) == EntryType::File) {
  59. this->preview = image_load_RgbaU8(previewPath);
  60. } else {
  61. this->preview = OrderedImageRgbaU8();
  62. }
  63. }
  64. }
  65. static ReadableString findParent(const ReadableString& startPath, const ReadableString& parentName) {
  66. int64_t pathEndIndex = -1; // Last character of path leading to Source.
  67. file_getPathEntries(startPath, [&pathEndIndex, &parentName](ReadableString entry, int64_t firstIndex, int64_t lastIndex) {
  68. if (string_match(entry, parentName)) {
  69. pathEndIndex = lastIndex;
  70. }
  71. });
  72. if (pathEndIndex == -1) {
  73. throwError(U"Could not find the Source folder with SDK examples.");
  74. return startPath;
  75. } else {
  76. return string_until(startPath, pathEndIndex);
  77. }
  78. }
  79. static void findProjects(const ReadableString& folderPath) {
  80. file_getFolderContent(folderPath, [](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) {
  81. if (entryType == EntryType::Folder) {
  82. findProjects(entryPath);
  83. } else if (entryType == EntryType::File) {
  84. ReadableString extension = string_upperCase(file_getExtension(entryName));
  85. Project newProject = Project(entryPath);
  86. // If we find a project within folderPath...
  87. if (string_match(extension, U"DSRPROJ")) {
  88. // ...and the folder is not namned wizard...
  89. if (!string_match(newProject.title, U"Wizard")) {
  90. // ...then add it to the list of projects.
  91. projects.push(newProject);
  92. }
  93. }
  94. }
  95. });
  96. }
  97. // Returns true iff the interface needs to be redrawn.
  98. static bool updateInterface(bool forceUpdate) {
  99. bool needToDraw = false;
  100. int projectIndex = component_getProperty_integer(projectList, U"SelectedIndex", true);
  101. //Application name from project name?
  102. if (projectIndex >= 0 && projectIndex < projects.length()) {
  103. DsrProcessStatus newStatus = process_getStatus(projects[projectIndex].programHandle);
  104. DsrProcessStatus lastStatus = projects[projectIndex].lastStatus;
  105. if (newStatus != lastStatus || forceUpdate) {
  106. if (newStatus == DsrProcessStatus::Running) {
  107. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" is running."));
  108. } else if (newStatus == DsrProcessStatus::Crashed) {
  109. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" crashed."));
  110. } else if (newStatus == DsrProcessStatus::Completed) {
  111. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" terminated safely."));
  112. } else if (newStatus == DsrProcessStatus::NotStarted) {
  113. component_setProperty_string(descriptionLabel, U"Text", projects[projectIndex].description);
  114. }
  115. needToDraw = true;
  116. projects[projectIndex].lastStatus = newStatus;
  117. }
  118. component_setProperty_image(previewPicture, U"Image", projects[projectIndex].preview, false);
  119. bool foundExecutable = string_length(projects[projectIndex].executableFilePath) > 0;
  120. component_setProperty_integer(launchButton, U"Visible", foundExecutable);
  121. }
  122. return needToDraw;
  123. }
  124. static void selectProject(int64_t projectIndex) {
  125. // Don't trigger new events if the selected index is already updated manually.
  126. if (projectIndex != component_getProperty_integer(projectList, U"SelectedIndex", true)) {
  127. component_setProperty_integer(projectList, U"SelectedIndex", projectIndex, false);
  128. }
  129. updateInterface(true);
  130. }
  131. static void populateInterface() {
  132. for (int p = 0; p < projects.length(); p++) {
  133. component_call(projectList, U"PushElement", projects[p].title);
  134. }
  135. selectProject(0);
  136. }
  137. DSR_MAIN_CALLER(dsrMain)
  138. void dsrMain(List<String> args) {
  139. // Get the application folder.
  140. String applicationFolder = file_getApplicationFolder();
  141. String mediaFolder = file_combinePaths(applicationFolder, U"media");
  142. // Start sound.
  143. sound_initialize();
  144. boomSound = loadSoundFromFile(file_combinePaths(mediaFolder, U"Boom.wav"));
  145. // Create a window.
  146. window = window_create(U"DFPSR wizard application", 800, 600);
  147. // Create components using the layout.
  148. window_loadInterfaceFromFile(window, file_combinePaths(mediaFolder, U"Interface.lof"));
  149. // Create a virtual machine with reusable image generating functions.
  150. // The same Media Machine Code (*.mmc) can be used for multiple themes.
  151. MediaMachine machine = machine_create(string_load(file_combinePaths(mediaFolder, U"Drawing.mmc")));
  152. // Use the virtual machine with a specific style referring to the functions in machine.
  153. window_applyTheme(window, theme_createFromFile(machine, file_combinePaths(mediaFolder, U"Theme.ini")));
  154. // Find components.
  155. projectList = window_findComponentByName(window, U"projectList");
  156. launchButton = window_findComponentByName(window, U"launchButton");
  157. descriptionLabel = window_findComponentByName(window, U"descriptionLabel");
  158. previewPicture = window_findComponentByName(window, U"previewPicture");
  159. // Find projects to showcase.
  160. // On systems that don't allow getting the application's folder, the program must be started somewhere within the Source folder.
  161. String sourceFolder = findParent(applicationFolder, U"Source");
  162. findProjects(file_combinePaths(sourceFolder, U"SDK"));
  163. findProjects(file_combinePaths(sourceFolder, U"templates"));
  164. populateInterface();
  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. }