main.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. 
  2. // TODO:
  3. // * Create a reusable file explorer component with lots of features and settings.
  4. // Then use it to select a cloning destination for new projects.
  5. #include "../../DFPSR/includeFramework.h"
  6. #include "../../SDK/SoundEngine/soundEngine.h"
  7. using namespace dsr;
  8. // Global
  9. bool running = true;
  10. Window window;
  11. // Visual components
  12. Component selectPanel;
  13. Component previewPicture;
  14. Component descriptionLabel;
  15. Component projectList;
  16. Component launchButton;
  17. Component cloneButton;
  18. Component clonePanel;
  19. Component sourceBox;
  20. Component targetBox;
  21. Component nameBox;
  22. Component cancelCloneButton;
  23. Component acceptCloneButton;
  24. // Media
  25. int boomSound;
  26. struct Project {
  27. String projectFilePath;
  28. String executableFilePath;
  29. String title; // To display
  30. String description; // To show when selected
  31. DsrProcess programHandle;
  32. DsrProcessStatus lastStatus = DsrProcessStatus::NotStarted;
  33. OrderedImageRgbaU8 preview;
  34. Project(const ReadableString &projectFilePath);
  35. };
  36. List<Project> projects;
  37. Project::Project(const ReadableString &projectFilePath)
  38. : projectFilePath(projectFilePath) {
  39. String projectFolderPath = file_getRelativeParentFolder(projectFilePath);
  40. String extensionlessProjectPath = file_getExtensionless(projectFilePath);
  41. this->title = file_getPathlessName(extensionlessProjectPath);
  42. #ifdef USE_MICROSOFT_WINDOWS
  43. this->executableFilePath = string_combine(extensionlessProjectPath, U".exe");
  44. #else
  45. this->executableFilePath = extensionlessProjectPath;
  46. #endif
  47. if (file_getEntryType(this->executableFilePath) != EntryType::File) {
  48. this->executableFilePath = U"";
  49. }
  50. String descriptionPath = file_combinePaths(projectFolderPath, U"Description.txt");
  51. if (file_getEntryType(descriptionPath) == EntryType::File) {
  52. this->description = string_load(descriptionPath);
  53. } else {
  54. this->description = string_combine(U"Project at ", projectFolderPath, U" did not have any Description.txt to display!");
  55. }
  56. String previewPath = file_combinePaths(projectFolderPath, U"Preview.jpg");
  57. if (file_getEntryType(previewPath) == EntryType::File) {
  58. this->preview = image_load_RgbaU8(previewPath);
  59. } else {
  60. previewPath = file_combinePaths(projectFolderPath, U"Preview.gif");
  61. if (file_getEntryType(previewPath) == EntryType::File) {
  62. this->preview = image_load_RgbaU8(previewPath);
  63. } else {
  64. this->preview = OrderedImageRgbaU8();
  65. }
  66. }
  67. }
  68. static ReadableString findParent(const ReadableString& startPath, const ReadableString& parentName) {
  69. int64_t pathEndIndex = -1; // Last character of path leading to Source.
  70. file_getPathEntries(startPath, [&pathEndIndex, &parentName](ReadableString entry, int64_t firstIndex, int64_t lastIndex) {
  71. if (string_match(entry, parentName)) {
  72. pathEndIndex = lastIndex;
  73. }
  74. });
  75. if (pathEndIndex == -1) {
  76. throwError(U"Could not find the Source folder with SDK examples.");
  77. return startPath;
  78. } else {
  79. return string_until(startPath, pathEndIndex);
  80. }
  81. }
  82. static void findProjects(const ReadableString& folderPath) {
  83. file_getFolderContent(folderPath, [](const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType) {
  84. if (entryType == EntryType::Folder) {
  85. findProjects(entryPath);
  86. } else if (entryType == EntryType::File) {
  87. ReadableString extension = string_upperCase(file_getExtension(entryName));
  88. Project newProject = Project(entryPath);
  89. // If we find a project within folderPath...
  90. if (string_match(extension, U"DSRPROJ")) {
  91. // ...and the folder is not namned wizard...
  92. if (!string_match(newProject.title, U"Wizard")) {
  93. // ...then add it to the list of projects.
  94. projects.push(newProject);
  95. }
  96. }
  97. }
  98. });
  99. }
  100. // Returns true iff the interface needs to be redrawn.
  101. static bool updateInterface(bool forceUpdate) {
  102. bool needToDraw = false;
  103. int projectIndex = component_getProperty_integer(projectList, U"SelectedIndex", true);
  104. //Application name from project name?
  105. if (projectIndex >= 0 && projectIndex < projects.length()) {
  106. DsrProcessStatus newStatus = process_getStatus(projects[projectIndex].programHandle);
  107. DsrProcessStatus lastStatus = projects[projectIndex].lastStatus;
  108. if (newStatus != lastStatus || forceUpdate) {
  109. if (newStatus == DsrProcessStatus::Running) {
  110. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" is running."));
  111. } else if (newStatus == DsrProcessStatus::Crashed) {
  112. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" crashed."));
  113. } else if (newStatus == DsrProcessStatus::Completed) {
  114. component_setProperty_string(descriptionLabel, U"Text", string_combine(projects[projectIndex].title, U" terminated safely."));
  115. } else if (newStatus == DsrProcessStatus::NotStarted) {
  116. component_setProperty_string(descriptionLabel, U"Text", projects[projectIndex].description);
  117. }
  118. needToDraw = true;
  119. projects[projectIndex].lastStatus = newStatus;
  120. }
  121. component_setProperty_image(previewPicture, U"Image", projects[projectIndex].preview, false);
  122. bool foundExecutable = string_length(projects[projectIndex].executableFilePath) > 0;
  123. component_setProperty_integer(launchButton, U"Visible", foundExecutable);
  124. }
  125. return needToDraw;
  126. }
  127. static void selectProject(int64_t projectIndex) {
  128. // Don't trigger new events if the selected index is already updated manually.
  129. if (projectIndex != component_getProperty_integer(projectList, U"SelectedIndex", true)) {
  130. component_setProperty_integer(projectList, U"SelectedIndex", projectIndex, false);
  131. }
  132. updateInterface(true);
  133. }
  134. static void populateInterface() {
  135. for (int p = 0; p < projects.length(); p++) {
  136. component_call(projectList, U"PushElement", projects[p].title);
  137. }
  138. selectProject(0);
  139. }
  140. static void cloneProject(const ReadableString &toolPath, const ReadableString &sourceFolderPath, const ReadableString &targetFolderPath, const ReadableString &projectName) {
  141. printText(U"Cloning project from ", sourceFolderPath, U" to ", targetFolderPath, U" using project name ", projectName, U"\n");
  142. if (file_getEntryType(toolPath) != EntryType::File) {
  143. throwError(U"Could not find the cloning tool at ", toolPath, U"! Make sure that it is compiled and located where it should be.\n");
  144. }
  145. // TODO: Create a waiting panel to allow showing the progress of a process while waiting for results.
  146. DsrProcess process = process_execute(toolPath, List<String>(U"-s", sourceFolderPath, U"-t", targetFolderPath, U"-n", projectName));
  147. while (true) {
  148. DsrProcessStatus status = process_getStatus(process);
  149. if (status == DsrProcessStatus::Completed) {
  150. printText(U"Done cloning the project.\n");
  151. break;
  152. } else if (status == DsrProcessStatus::Crashed) {
  153. printText(U"The cloning tool failed!\n");
  154. break;
  155. } else if (status == DsrProcessStatus::NotStarted) {
  156. printText(U"Failed to start the cloning tool!\n");
  157. break;
  158. }
  159. time_sleepSeconds(0.001);
  160. }
  161. }
  162. DSR_MAIN_CALLER(dsrMain)
  163. void dsrMain(List<String> args) {
  164. // Get the application folder.
  165. String applicationFolder = file_getApplicationFolder();
  166. String mediaFolder = file_combinePaths(applicationFolder, U"media");
  167. // Start sound.
  168. soundEngine_initialize();
  169. boomSound = soundEngine_loadSoundFromFile(file_combinePaths(mediaFolder, U"Boom.wav"));
  170. // Create a window.
  171. window = window_create(U"DFPSR wizard application", 800, 600);
  172. // Create components using the layout.
  173. window_loadInterfaceFromFile(window, file_combinePaths(mediaFolder, U"Interface.lof"));
  174. // Create a virtual machine with reusable image generating functions.
  175. // The same Media Machine Code (*.mmc) can be used for multiple themes.
  176. MediaMachine machine = machine_create(string_load(file_combinePaths(mediaFolder, U"Drawing.mmc")));
  177. // Use the virtual machine with a specific style referring to the functions in machine.
  178. window_applyTheme(window, theme_createFromFile(machine, file_combinePaths(mediaFolder, U"Theme.ini")));
  179. // Find components.
  180. projectList = window_findComponentByName(window, U"projectList");
  181. launchButton = window_findComponentByName(window, U"launchButton");
  182. cloneButton = window_findComponentByName(window, U"cloneButton");
  183. sourceBox = window_findComponentByName(window, U"sourceBox");
  184. targetBox = window_findComponentByName(window, U"targetBox");
  185. nameBox = window_findComponentByName(window, U"nameBox");
  186. cancelCloneButton = window_findComponentByName(window, U"cancelCloneButton");
  187. acceptCloneButton = window_findComponentByName(window, U"acceptCloneButton");
  188. selectPanel = window_findComponentByName(window, U"selectPanel");
  189. clonePanel = window_findComponentByName(window, U"clonePanel");
  190. descriptionLabel = window_findComponentByName(window, U"descriptionLabel");
  191. previewPicture = window_findComponentByName(window, U"previewPicture");
  192. // Find projects to showcase.
  193. // On systems that don't allow getting the application's folder, the program must be started somewhere within the Source folder.
  194. String sourceFolder = findParent(applicationFolder, U"Source");
  195. findProjects(file_combinePaths(sourceFolder, U"SDK"));
  196. findProjects(file_combinePaths(sourceFolder, U"templates"));
  197. populateInterface();
  198. // Bind methods to events.
  199. window_setKeyboardEvent(window, [](const KeyboardEvent& event) {
  200. DsrKey key = event.dsrKey;
  201. if (event.keyboardEventType == KeyboardEventType::KeyDown) {
  202. if (key == DsrKey_Escape) {
  203. running = false;
  204. }
  205. }
  206. });
  207. component_setPressedEvent(cloneButton, []() {
  208. // Get the project index.
  209. int projectIndex = component_getProperty_integer(projectList, U"SelectedIndex", true);
  210. // Check if the project index is valid.
  211. if (projectIndex >= 0 && projectIndex < projects.length()) {
  212. ReadableString projectFilePath = projects[projectIndex].projectFilePath;
  213. ReadableString sourceFolder = file_getAbsoluteParentFolder(projectFilePath);
  214. ReadableString projectName = file_getExtensionless(file_getPathlessName(projectFilePath));
  215. // Show the clone panel and fill in some information.
  216. component_setProperty_string(sourceBox, U"Text", sourceFolder, true);
  217. component_setProperty_string(targetBox, U"Text", U"?", true);
  218. component_setProperty_string(nameBox, U"Text", projectName, true);
  219. component_setProperty_integer(selectPanel, U"Visible", 0, true);
  220. component_setProperty_integer(clonePanel, U"Visible", 1, true);
  221. soundEngine_playSound(boomSound, false);
  222. }
  223. });
  224. component_setPressedEvent(cancelCloneButton, []() {
  225. soundEngine_playSound(boomSound, false);
  226. // Show the select panel.
  227. component_setProperty_integer(selectPanel, U"Visible", 1, true);
  228. component_setProperty_integer(clonePanel, U"Visible", 0, true);
  229. });
  230. component_setPressedEvent(acceptCloneButton, [applicationFolder]() {
  231. soundEngine_playSound(boomSound, false);
  232. // Try to clone the selected project.
  233. // TODO: Can a reusable function generate executable filenames without hardcoding them in each program?
  234. #ifdef USE_MICROSOFT_WINDOWS
  235. String cloneExecutableFile = U"Clone.exe";
  236. #else
  237. String cloneExecutableFile = U"Clone";
  238. #endif
  239. cloneProject(
  240. file_combinePaths(applicationFolder, U"..", U"processing", U"cloneProject", cloneExecutableFile),
  241. component_getProperty_string(sourceBox, U"Text", true),
  242. component_getProperty_string(targetBox, U"Text", true),
  243. component_getProperty_string(nameBox , U"Text", true)
  244. );
  245. // Show the select panel.
  246. component_setProperty_integer(selectPanel, U"Visible", 1, true);
  247. component_setProperty_integer(clonePanel, U"Visible", 0, true);
  248. });
  249. component_setPressedEvent(launchButton, []() {
  250. soundEngine_playSound(boomSound, false);
  251. int projectIndex = component_getProperty_integer(projectList, U"SelectedIndex", true);
  252. //Application name from project name?
  253. if (projectIndex >= 0 && projectIndex < projects.length()) {
  254. if (file_getEntryType(projects[projectIndex].executableFilePath) != EntryType::File) {
  255. // Could not find the application.
  256. component_setProperty_string(descriptionLabel, U"Text", string_combine(U"Could not find the executable at ", projects[projectIndex].executableFilePath, U"!\n"), true);
  257. } else if (process_getStatus(projects[projectIndex].programHandle) != DsrProcessStatus::Running) {
  258. // Select input arguments.
  259. List<String> arguments;
  260. if (string_match(projects[projectIndex].title, U"BasicCLI")) {
  261. // Give some random arguments to the CLI template, so that it will do something more than just printing "Hello World".
  262. arguments.push(U"1");
  263. arguments.push(U"TWO");
  264. arguments.push(U"three");
  265. arguments.push(U"Four");
  266. }
  267. // Launch the application from the project's folder.
  268. file_setCurrentPath(file_getAbsoluteParentFolder(projects[projectIndex].executableFilePath));
  269. projects[projectIndex].programHandle = process_execute(projects[projectIndex].executableFilePath, arguments);
  270. updateInterface(true);
  271. }
  272. }
  273. });
  274. component_setSelectEvent(projectList, [](int64_t index) {
  275. soundEngine_playSound(boomSound, false);
  276. selectProject(index);
  277. });
  278. window_setCloseEvent(window, []() {
  279. running = false;
  280. });
  281. // Execute.
  282. soundEngine_playSound(boomSound, false);
  283. while(running) {
  284. // Wait for actions so that we don't render until an action has been recieved.
  285. // This will save battery on laptops for applications that don't require animation.
  286. while (!(window_executeEvents(window) || updateInterface(false))) {
  287. time_sleepSeconds(0.01);
  288. }
  289. // Draw interface.
  290. window_drawComponents(window);
  291. // Show the final image.
  292. window_showCanvas(window);
  293. }
  294. // Close sound.
  295. soundEngine_terminate();
  296. }