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 }