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*)&params.nodeps); igSameLine();
374 	igCheckbox("force", cast(bool*)&params.force); igSameLine();
375 	igCheckbox("x64", cast(bool*)&params.arch64); igSameLine();
376 	igCheckbox("release", cast(bool*)&params.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;