1 /** 2 Copyright: Copyright (c) 2017-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 module gui2; 8 9 import std.format : formattedWrite; 10 import std.stdio; 11 import voxelman.math; 12 import voxelman.gui; 13 import voxelman.gui.textedit.lineedit; 14 import voxelman.gui.textedit.messagelog; 15 import voxelman.gui.textedit.textmodel; 16 import voxelman.gui.textedit.texteditorview; 17 import voxelman.graphics; 18 import voxelman.text.scale; 19 20 import launcher; 21 import voxelman.gui.guiapp; 22 23 class LauncherGui : GuiApp 24 { 25 Launcher launcher; 26 27 string pluginFolder = `./plugins`; 28 string pluginPackFolder = `./pluginpacks`; 29 string toolFolder = `./tools`; 30 string resFolder = `./res`; 31 32 AutoListModel!WorldList worldList; 33 AutoListModel!ServerList serverList; 34 WidgetProxy job_stack; 35 TextViewSettings textSettings; 36 bool isGuiDebuggerShown; 37 38 this(string title, ivec2 windowSize) 39 { 40 super(title, windowSize); 41 maxFps = 30; 42 launcher.init(); 43 launcher.setRootPath(pluginFolder, pluginPackFolder, toolFolder); 44 launcher.refresh(); 45 } 46 47 override void load(string[] args, string resPath) 48 { 49 super.load(args, resFolder); 50 textSettings = TextViewSettings(renderQueue.defaultFont); 51 createMain(guictx.getRoot(0)); 52 // showDebugInfo = true; 53 // isGuiDebuggerShown = true; 54 55 auto debugger_frame = createGuiDebugger(guictx.getRoot(1)); 56 debugger_frame.visible_if(() => isGuiDebuggerShown); 57 } 58 59 override void userPreUpdate(double delta) 60 { 61 launcher.update(); 62 } 63 64 override void closePressed() 65 { 66 if (!launcher.anyProcessesRunning) 67 { 68 isClosePressed = true; 69 } 70 } 71 72 void createMain(WidgetProxy root) 73 { 74 HLayout.attachTo(root, 0, padding4(0)); 75 76 WidgetProxy left_panel = PanelLogic.create(root, color_gray) 77 .minSize(ivec2(60, 0)) 78 .vexpand 79 .setVLayout(3, padding4(3, 0, 3, 3)); 80 81 WidgetProxy right_panel = VLayout.create(root, 0, padding4(0)).hvexpand; 82 83 left_panel.createIconTextButton("play", "Play", () => PagedWidget.switchPage(right_panel, 0)).hexpand; 84 left_panel.createIconTextButton("hammer", "Debug", () => PagedWidget.switchPage(right_panel, 1)).hexpand; 85 86 createPlay(right_panel); 87 createDebug(right_panel); 88 89 PagedWidget.convert(right_panel, 0); 90 } 91 92 WidgetProxy createPlay(WidgetProxy parent) 93 { 94 auto play_panel = HLayout.create(parent, 0, padding4(0)).hvexpand; 95 96 auto worlds_panel = VLayout.create(play_panel, 0, padding4(1)).hvexpand; 97 98 worldList = new AutoListModel!WorldList(WorldList(&launcher)); 99 auto list_worlds = ColumnListLogic.create(worlds_panel, worldList).minSize(260, 100).hvexpand; 100 101 WidgetProxy bottom_panel_worlds = HLayout.create(worlds_panel, 2, padding4(1)).hexpand.addBackground(color_gray); 102 bottom_panel_worlds.createTextButton("New", &newWorld); 103 bottom_panel_worlds.createTextButton("Remove", &removeWorld).visible_if(&worldList.hasSelected); 104 bottom_panel_worlds.createTextButton("Refresh", &refreshWorlds); 105 bottom_panel_worlds.hfill; 106 bottom_panel_worlds.createTextButton("Server", &startServer).visible_if(&worldList.hasSelected); 107 bottom_panel_worlds.createTextButton("Start", &startClient).visible_if(&worldList.hasSelected); 108 109 VLine.create(play_panel); 110 111 auto servers_panel = VLayout.create(play_panel, 0, padding4(1)).hvexpand; 112 serverList = new AutoListModel!ServerList(ServerList(&launcher)); 113 auto list_servers = ColumnListLogic.create(servers_panel, serverList).minSize(320, 100).hvexpand; 114 115 WidgetProxy bottom_panel_servers = HLayout.create(servers_panel, 2, padding4(1)).hexpand.addBackground(color_gray); 116 bottom_panel_servers.createTextButton("New", &newServer); 117 bottom_panel_servers.createTextButton("Remove", &removeServer).visible_if(&serverList.hasSelected); 118 bottom_panel_servers.hfill; 119 bottom_panel_servers.createTextButton("Connect", &connetToServer).visible_if(&serverList.hasSelected); 120 121 return play_panel; 122 } 123 124 WidgetProxy createDebug(WidgetProxy parent) 125 { 126 auto debug_panel = VLayout.create(parent, 0, padding4(0)).hvexpand; 127 auto top_buttons = HLayout.create(debug_panel, 2, padding4(1)).hexpand; 128 129 TextButtonLogic.create(top_buttons, "Client", &startClient_debug); 130 TextButtonLogic.create(top_buttons, "Server", &startServer_debug); 131 TextButtonLogic.create(top_buttons, "Combined", &startCombined_debug); 132 133 job_stack = VLayout.create(debug_panel, 0, padding4(0)).hvexpand; 134 135 return debug_panel; 136 } 137 138 void newWorld() {} 139 void removeWorld() {} 140 void refreshWorlds() { 141 launcher.refresh(); 142 } 143 void startServer() { 144 auto job = launcher.startServer(launcher.pluginPacks[0], launcher.saves[worldList.selectedRow]); 145 if (job) JobItemWidget.create(job_stack, job, &textSettings); 146 } 147 void startClient() { 148 auto job = launcher.startCombined(launcher.pluginPacks[0], launcher.saves[worldList.selectedRow]); 149 if (job) JobItemWidget.create(job_stack, job, &textSettings); 150 } 151 152 void newServer() { 153 154 } 155 void removeServer() {} 156 void connetToServer() { 157 launcher.connect(launcher.servers[serverList.selectedRow], launcher.pluginPacks[0]); } 158 159 void startClient_debug() { createJob(AppType.client); } 160 void startServer_debug() { createJob(AppType.server); } 161 void startCombined_debug() { createJob(AppType.combined); } 162 163 void createJob(AppType appType) 164 { 165 JobParams params; 166 params.appType = appType; 167 Job* job = launcher.createJob(params); 168 JobItemWidget.create(job_stack, job, &textSettings); 169 } 170 } 171 172 struct JobItemWidget 173 { 174 static: 175 WidgetProxy create(WidgetProxy parent, Job* job, TextViewSettingsRef textSettings) 176 { 177 parent.ctx.style.pushColor(color_wet_asphalt); 178 179 auto job_item = VLayout.create(parent, 0, padding4(0)).hvexpand; 180 auto top_buttons = HLayout.create(job_item, 2, padding4(3,3,1,1), Alignment.center).hexpand.addBorder(color_gray); 181 bool job_running() { return !job.isRunning && !job.needsRestart; } 182 183 void updateStatusText(WidgetProxy widget, ref GuiUpdateEvent event) 184 { 185 if (event.bubbling) return; 186 TextLogic.setText(widget, jobStateString(job)); 187 } 188 createText(top_buttons, jobStateString(job)).handlers(&updateStatusText); 189 190 createCheckButton(top_buttons, "nodeps", cast(bool*)&job.params.nodeps); 191 createCheckButton(top_buttons, "force", cast(bool*)&job.params.force); 192 createCheckButton(top_buttons, "x64", cast(bool*)&job.params.arch64); 193 DropDown.create(top_buttons, buildTypeUiOptions, 0, (size_t opt){job.params.buildType = cast(BuildType)opt;}); 194 DropDown.create(top_buttons, compilerUiOptions, 0, (size_t opt){job.params.compiler = cast(Compiler)opt;}); 195 createTextButton(top_buttons, "Clear", { job.msglog.clear; }); 196 createTextButton(top_buttons, "Close", { job.needsClose = true; }).visible_if(&job_running); 197 198 void startJob(JobType t)() { job.params.jobType = t; job.needsRestart = true; } 199 200 createTextButton(top_buttons, "Test", &startJob!(JobType.test)).visible_if(&job_running); 201 createTextButton(top_buttons, " Run ", &startJob!(JobType.run)).visible_if(&job_running); 202 createTextButton(top_buttons, "Build", &startJob!(JobType.compile)).visible_if(&job_running); 203 createTextButton(top_buttons, " B&R ", &startJob!(JobType.compileAndRun)).visible_if(&job_running); 204 createTextButton(top_buttons, "Stop", { job.sendCommand("stop"); }).visible_if_not(&job_running); 205 206 job.onClose ~= { job_item.ctx.removeWidget(job_item.wid); }; 207 208 job.msglog.setClipboard = parent.ctx.state.setClipboard; 209 auto msglogModel = new MessageLogTextModel(&job.msglog); 210 auto viewport = TextEditorViewportLogic.create(job_item, msglogModel, textSettings).hvexpand; 211 212 hline(job_item); 213 auto input = LineEdit.create(job_item).hexpand; 214 215 void enterHandler(string com) 216 { 217 sendCommand(job, com); 218 LineEdit.clear(input); 219 } 220 221 LineEdit.setEnterHandler(input, &enterHandler); 222 223 void autoscroll_enable() { viewport.get!TextEditorViewportData.autoscroll = true; } 224 bool autoscroll_enabled() { return viewport.get!TextEditorViewportData.autoscroll; } 225 226 top_buttons.hfill; 227 createTextButton(top_buttons, "Autoscroll", &autoscroll_enable).visible_if_not(&autoscroll_enabled); 228 229 autoscroll_enable(); 230 231 parent.ctx.style.popColor; 232 233 return job_item; 234 } 235 } 236 237 struct WorldList 238 { 239 Launcher* launcher; 240 WorldRow opIndex(size_t i) { return WorldRow(*launcher.saves[i]); } 241 size_t length() { return launcher.saves.length; } 242 } 243 244 struct WorldRow 245 { 246 this(SaveInfo info) { 247 this.filename = info.name; 248 this.fileSize = info.size; 249 } 250 @Column!WorldRow("Name", 200, (WorldRow r, scope SinkT s){ s(r.filename); }) 251 string filename; 252 253 @Column!WorldRow("Size", 60, (WorldRow r, scope SinkT s){ formattedWrite(s, "%sB", scaledNumberFmt(r.fileSize)); }) 254 ulong fileSize; 255 } 256 257 struct ServerList 258 { 259 Launcher* launcher; 260 ServerRow opIndex(size_t i) { return ServerRow(*launcher.servers[i]); } 261 size_t length() { return launcher.servers.length; } 262 } 263 264 struct ServerRow 265 { 266 this(ServerInfo info) { 267 this.name = info.name; 268 this.ip = info.ip; 269 this.port = info.port; 270 } 271 @Column!ServerRow("Name", 150, (ServerRow r, scope SinkT s){ s(r.name); }) 272 string name; 273 @Column!ServerRow("IP", 130, (ServerRow r, scope SinkT s){ s(r.ip); }) 274 string ip; 275 @Column!ServerRow("Port", 40, (ServerRow r, scope SinkT s){ formattedWrite(s, "%s", r.port); }) 276 ushort port; 277 } 278 279 // TODO 280 /// Returns a rect that is attached to button on the top or bottom 281 /// alignment preference chooses where a resulting rect should connect 282 /// If pref is min, then result.x == button.x 283 /// If pref is max, then result.endx == button.endx 284 irect choosePopupRectVert(irect button, irect totalArea, Alignment preference) 285 { 286 return irect(); 287 } 288 289 // TODO 290 /// Returns a rect that is attached to button on the left or right 291 irect choosePopupRectHori(irect button, irect totalArea) 292 { 293 return irect(); 294 }