1 /** 2 Copyright: Copyright (c) 2015 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 module gui; 8 9 import std.algorithm; 10 import std.array; 11 import std.experimental.logger; 12 import std.format; 13 import std.process; 14 import std.range; 15 import std.stdio; 16 import std.string : format; 17 import std.typecons : Flag, Yes, No; 18 19 import dlib.math.vector; 20 import derelict.glfw3.glfw3; 21 import derelict.imgui.imgui; 22 import derelict.opengl3.gl3; 23 import anchovy.glfwwindow; 24 import voxelman.imgui_glfw; 25 import voxelman.utils.libloader; 26 import voxelman.utils.textformatter; 27 import voxelman.utils.linebuffer; 28 29 import launcher; 30 31 32 struct ItemList(T) 33 { 34 T[]* items; 35 size_t currentItem; 36 bool hasSelected() @property { 37 return currentItem < (*items).length; 38 } 39 T selected() @property { 40 if (currentItem < (*items).length) 41 return (*items)[currentItem]; 42 else if ((*items).length > 0) 43 return (*items)[$-1]; 44 else 45 return T.init; 46 } 47 48 void update() { 49 if (currentItem >= (*items).length) 50 currentItem = (*items).length-1; 51 if ((*items).length == 0) 52 currentItem = 0; 53 } 54 } 55 56 struct LauncherGui 57 { 58 bool show_test_window = true; 59 bool show_another_window = false; 60 float[3] clear_color = [0.3f, 0.4f, 0.6f]; 61 bool isRunning = true; 62 ImguiState igState; 63 GlfwWindow window; 64 65 Launcher launcher; 66 67 string pluginFolder = `./plugins`; 68 string pluginPackFolder = `./pluginpacks`; 69 ItemList!(PluginInfo*) plugins; 70 71 void init() 72 { 73 class ConciseLogger : FileLogger { 74 this(File file, const LogLevel lv = LogLevel.info) @safe { 75 super(file, lv); 76 } 77 78 override protected void beginLogMsg(string file, int line, string funcName, 79 string prettyFuncName, string moduleName, LogLevel logLevel, 80 Tid threadId, SysTime timestamp, Logger logger) 81 @safe {} 82 } 83 //auto file = File(filename, "w"); 84 auto logger = new MultiLogger; 85 //logger.insertLogger("fileLogger", new FileLogger(file)); 86 logger.insertLogger("stdoutLogger", new ConciseLogger(stdout)); 87 sharedLog = logger; 88 89 playMenu.init(&launcher); 90 codeMenu.init(&launcher); 91 refresh(); 92 93 window = new GlfwWindow(); 94 window.init(uvec2(800, 600), "Voxelman launcher"); 95 igState.init(window.handle); 96 window.keyPressed.connect(&igState.onKeyPressed); 97 window.keyReleased.connect(&igState.onKeyReleased); 98 window.charEntered.connect(&igState.charCallback); 99 window.mousePressed.connect(&igState.onMousePressed); 100 window.mouseReleased.connect(&igState.onMouseReleased); 101 window.wheelScrolled.connect((dvec2 s) => igState.scrollCallback(s.y)); 102 103 selectedMenu = SelectedMenu.code; 104 105 if (window is null) 106 isRunning = false; 107 108 setStyle(); 109 } 110 111 void run() 112 { 113 DerelictGL3.load(); 114 DerelictGLFW3.load(getLibName("", "glfw3")); 115 DerelictImgui.load(getLibName("", "cimgui")); 116 117 init(); 118 119 while(isRunning) 120 { 121 if (glfwWindowShouldClose(window.handle) && !launcher.anyProcessesRunning) 122 isRunning = false; 123 else 124 glfwSetWindowShouldClose(window.handle, false); 125 update(); 126 render(); 127 } 128 129 close(); 130 } 131 132 void refresh() 133 { 134 launcher.clear(); 135 launcher.setRootPath(pluginFolder, pluginPackFolder); 136 launcher.readPlugins(); 137 launcher.readPluginPacks(); 138 plugins.items = &launcher.plugins; 139 playMenu.refresh(); 140 codeMenu.refresh(); 141 } 142 143 void update() 144 { 145 launcher.update(); 146 window.processEvents(); 147 igState.newFrame(); 148 doGui(); 149 import core.thread; 150 Thread.sleep(15.msecs); 151 } 152 153 void render() 154 { 155 ImGuiIO* io = igGetIO(); 156 glViewport(0, 0, cast(int)io.DisplaySize.x, cast(int)io.DisplaySize.y); 157 glClearColor(clear_color[0], clear_color[1], clear_color[2], 0); 158 glClear(GL_COLOR_BUFFER_BIT); 159 igState.render(); 160 glfwSwapBuffers(window.handle); 161 } 162 163 void close() 164 { 165 window.releaseWindow; 166 igState.shutdown(); 167 glfwTerminate(); 168 } 169 170 void doGui() 171 { 172 //igPushStyleVar(ImGuiStyleVar_FrameRounding, 0f); 173 //igGetStyle().FrameRounding = 0.0f; 174 igSetNextWindowPos(ImVec2(0,0)); 175 igSetNextWindowSize(igGetIO().DisplaySize); 176 if (igBegin("Main", null, mainWindowFlags)) 177 { 178 drawMainMenu(); 179 igSameLine(); 180 drawMenuContent(); 181 182 igEnd(); 183 } 184 //igPopStyleVar(); 185 //igShowTestWindow(null); 186 } 187 188 enum SelectedMenu 189 { 190 play, 191 code, 192 conf 193 } 194 195 SelectedMenu selectedMenu; 196 PlayMenu playMenu; 197 CodeMenu codeMenu; 198 199 void drawMainMenu() 200 { 201 igBeginGroup(); 202 203 menuEntry("Play", SelectedMenu.play); 204 menuEntry("Code", SelectedMenu.code); 205 menuEntry("Conf", SelectedMenu.conf); 206 207 //if (igButton("Refresh")) 208 // refresh(); 209 igSpacing(); 210 if (igButton("Exit")) 211 isRunning = false; 212 igEndGroup(); 213 } 214 215 void menuEntry(string text, SelectedMenu select) 216 { 217 ImGuiStyle* style = igGetStyle(); 218 const ImVec4 color = style.Colors[ImGuiCol_Button]; 219 const ImVec4 colorActive = style.Colors[ImGuiCol_ButtonActive]; 220 const ImVec4 colorHover = style.Colors[ImGuiCol_ButtonHovered]; 221 222 if (selectedMenu == select) 223 { 224 style.Colors[ImGuiCol_Button] = colorActive; 225 style.Colors[ImGuiCol_ButtonActive] = colorActive; 226 style.Colors[ImGuiCol_ButtonHovered] = colorActive; 227 } 228 else 229 { 230 style.Colors[ImGuiCol_Button] = color; 231 style.Colors[ImGuiCol_ButtonActive] = colorActive; 232 style.Colors[ImGuiCol_ButtonHovered] = colorHover; 233 } 234 235 if (igButton(text.ptr)) selectedMenu = select; 236 237 style.Colors[ImGuiCol_Button] = color; 238 style.Colors[ImGuiCol_ButtonActive] = colorActive; 239 style.Colors[ImGuiCol_ButtonHovered] = colorHover; 240 } 241 242 void restoreStyle() 243 { 244 245 } 246 247 void drawMenuContent() 248 { 249 final switch(selectedMenu) with(SelectedMenu) 250 { 251 case play: 252 playMenu.draw(); 253 break; 254 case code: 255 codeMenu.draw(); 256 break; 257 case conf: 258 break; 259 } 260 } 261 } 262 263 struct PlayMenu 264 { 265 enum SelectedMenu 266 { 267 newGame, 268 connect, 269 load, 270 } 271 Launcher* launcher; 272 SelectedMenu selectedMenu; 273 ItemList!(PluginPack*) pluginPacks; 274 275 void init(Launcher* launcher) 276 { 277 this.launcher = launcher; 278 } 279 280 void refresh() 281 { 282 pluginPacks.items = &launcher.pluginPacks; 283 } 284 285 void draw() 286 { 287 pluginPacks.update(); 288 igBeginGroup(); 289 290 if (igButton("New")) 291 selectedMenu = SelectedMenu.newGame; 292 igSameLine(); 293 if (igButton("Connect")) 294 selectedMenu = SelectedMenu.connect; 295 igSameLine(); 296 if (igButton("Load")) 297 selectedMenu = SelectedMenu.load; 298 299 //igSeparator(); 300 301 if (selectedMenu == SelectedMenu.newGame) 302 drawNewGame(); 303 304 igEndGroup(); 305 } 306 307 void drawNewGame() 308 { 309 string pluginpack = "default"; 310 if (auto pack = pluginPacks.selected) 311 pluginpack = pack.id; 312 313 // ------------------------ PACKAGES ----------------------------------- 314 igBeginChild("packs", ImVec2(100, -igGetItemsLineHeightWithSpacing()), true); 315 foreach(int i, pluginPack; *pluginPacks.items) 316 { 317 igPushIdInt(cast(int)i); 318 immutable bool itemSelected = (i == pluginPacks.currentItem); 319 320 if (igSelectable(pluginPack.id.ptr, itemSelected)) 321 pluginPacks.currentItem = i; 322 323 igPopId(); 324 } 325 igEndChild(); 326 327 igSameLine(); 328 329 // ------------------------ PLUGINS ------------------------------------ 330 if (pluginPacks.hasSelected) 331 { 332 igBeginChild("pack's plugins", ImVec2(250, -igGetItemsLineHeightWithSpacing()), true); 333 foreach(int i, plugin; pluginPacks.selected.plugins) 334 { 335 igPushIdInt(cast(int)i); 336 igTextUnformatted(plugin.id.ptr, plugin.id.ptr+plugin.id.length); 337 igPopId(); 338 } 339 igEndChild(); 340 } 341 342 // ------------------------ BUTTONS ------------------------------------ 343 igBeginGroup(); 344 startButtons(launcher, pluginpack); 345 igSameLine(); 346 if (igButton("Stop")) 347 { 348 size_t numKilled = launcher.stopProcesses(); 349 launcher.appLog.put(format("killed %s processes\n", numKilled)); 350 } 351 igEndGroup(); 352 } 353 354 void pluginPackPlugins() 355 { 356 357 } 358 } 359 360 import std.traits : Parameters; 361 auto withWidth(float width, alias func)(auto ref Parameters!func args) 362 { 363 scope(exit) igPopItemWidth(); 364 igPushItemWidth(width); 365 return func(args); 366 } 367 368 void startButtons(Launcher* launcher, string pack) 369 { 370 static JobParams params; 371 params.pluginPack = pack; 372 373 igCheckbox("nodeps", cast(bool*)¶ms.nodeps); igSameLine(); 374 igCheckbox("force", cast(bool*)¶ms.force); igSameLine(); 375 igCheckbox("x64", cast(bool*)¶ms.arch64); igSameLine(); 376 igCheckbox("release", cast(bool*)¶ms.release); igSameLine(); 377 378 static int curJobType = 0; 379 withWidth!(100, igCombo2)("##job type", &curJobType, "Run\0Build\0Build & Run\0\0", 3); 380 igSameLine(); 381 params.jobType = cast(JobType)curJobType; 382 383 params.appType = AppType.client; 384 if (igButton("Client")) launcher.startJob(params); igSameLine(); 385 386 params.appType = AppType.server; 387 if (igButton("Server")) launcher.startJob(params); 388 } 389 390 struct CodeMenu 391 { 392 Launcher* launcher; 393 ItemList!(PluginPack*) pluginPacks; 394 395 void init(Launcher* launcher) 396 { 397 this.launcher = launcher; 398 } 399 400 void refresh() 401 { 402 pluginPacks.items = &launcher.pluginPacks; 403 } 404 405 bool getItem(int idx, const(char)** out_text) 406 { 407 *out_text = (*pluginPacks.items)[idx].id.ptr; 408 return true; 409 } 410 411 void draw() 412 { 413 igBeginGroup(); 414 withWidth!(200, igCombo3)( 415 "Pack", 416 cast(int*)&pluginPacks.currentItem, 417 &getter, &this, 418 cast(int)pluginPacks.items.length, -1); 419 igSameLine(); 420 startButtons(launcher, pluginPacks.selected.id); 421 422 foreach(job; launcher.jobs) drawJobLog(job); 423 424 igEndGroup(); 425 } 426 427 static extern (C) 428 bool getter(void* codeMenu, int idx, const(char)** out_text) 429 { 430 auto cm = cast(CodeMenu*)codeMenu; 431 return cm.getItem(idx, out_text); 432 } 433 } 434 435 void drawJobLog(J)(J job) 436 { 437 igPushIdPtr(job); 438 assert(job.command.ptr); 439 auto state = job.isRunning ? "[RUNNING] " : "[STOPPED] "; 440 auto textPtrs = makeFormattedText("%s%s\0", state, job.command); 441 442 if (igCollapsingHeader(textPtrs.start, null, true, true)) { 443 if (igButton("Close")) job.needsClose = true; 444 igSameLine(); 445 if (igButton("Clear")) job.messageWindow.lineBuffer.clear(); 446 igSameLine(); 447 if (igButton("Restart")) job.needsRestart = true; 448 igBeginChildEx(igGetIdPtr(job.command.ptr), ImVec2(0,350), true, ImGuiWindowFlags_HorizontalScrollbar); 449 job.messageWindow.draw(); 450 igEndChild(); 451 } 452 453 igPopId(); 454 } 455 456 void setStyle() 457 { 458 ImGuiStyle* style = igGetStyle(); 459 style.Colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); 460 style.Colors[ImGuiCol_WindowBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); 461 style.Colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.20f, 0.65f); 462 style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.12f); 463 style.Colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.39f); 464 style.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.80f); 465 style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.47f, 0.47f, 0.47f, 0.00f); 466 style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.55f, 0.55f, 0.55f, 1.00f); 467 style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.55f, 0.55f, 0.55f, 1.00f); 468 style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.55f, 0.55f, 0.55f, 1.00f); 469 style.Colors[ImGuiCol_ComboBg] = ImVec4(0.80f, 0.80f, 0.80f, 1.00f); 470 style.Colors[ImGuiCol_CheckMark] = ImVec4(0.36f, 0.40f, 0.71f, 0.60f); 471 style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.52f, 0.56f, 1.00f, 0.60f); 472 style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.36f, 0.40f, 0.71f, 0.60f); 473 style.Colors[ImGuiCol_Button] = ImVec4(0.52f, 0.56f, 1.00f, 0.60f); 474 style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.43f, 0.46f, 0.82f, 0.60f); 475 style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.37f, 0.40f, 0.71f, 0.60f); 476 style.Colors[ImGuiCol_TooltipBg] = ImVec4(0.86f, 0.86f, 0.86f, 0.90f); 477 style.Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); 478 style.WindowFillAlphaDefault = 1.0f; 479 } 480 481 enum mainWindowFlags = ImGuiWindowFlags_NoTitleBar | 482 ImGuiWindowFlags_NoResize | 483 ImGuiWindowFlags_NoMove | 484 ImGuiWindowFlags_NoCollapse | 485 ImGuiWindowFlags_NoSavedSettings; 486 //ImGuiWindowFlags_MenuBar;