1 /++
2  + Authors: Stephan Dilly, lastname dot firstname at gmail dot com
3  + Copyright: MIT
4  +/
5  module unecht.core.components.editor.editorGui;
6 
7 version(UEIncludeEditor):
8 
9 import unecht.core.component;
10 import unecht.core.components._editor;
11 import unecht.core.components.sceneNode;
12 import unecht.core.components.editor.ui.assetView;
13 import unecht.core.components.editor.ui.menuSystem;
14 import unecht.core.components.editor.ui.console;
15 import unecht.core.components.editor.ui.referenceEditor;
16 import unecht.core.components.editor.ui.dragDropEditor;
17 import unecht.core.components.editor.ui.fileDialog;
18 
19 import unecht.core.components.internal.gui:UEGui;
20 
21 import derelict.imgui.imgui;
22 
23 ///
24 final class UEEditorGUI : UEComponent
25 {
26 	mixin(UERegisterObject!());
27 
28 	UEEditorMenuBar menuBar;
29 	UEEditorAssetView assetView;
30 	UEEditorConsole console;
31 	UEReferenceEditor referenceEditor;
32 	UEDragDropEditor dragDropEditor;
33 	UEFileDialog fileDialog;
34 
35 	override void onCreate() {
36 		super.onCreate;
37 
38 		assetView = entity.addComponent!UEEditorAssetView;
39 		menuBar = entity.addComponent!UEEditorMenuBar;
40 		console = entity.addComponent!UEEditorConsole;
41 		referenceEditor = entity.addComponent!UEReferenceEditor;
42 		dragDropEditor = entity.addComponent!UEDragDropEditor;
43 		fileDialog = entity.addComponent!UEFileDialog;
44 	}
45 
46 	//TODO: #127
47 	void render()
48 	{
49 		import unecht.ue:ue;
50 
51 		{
52 			const height = igGetItemsLineHeightWithSpacing();
53 			igSetNextWindowPos(ImVec2(0,ue.application.framebufferSize.height-height),ImGuiSetCond_Always);
54 
55 			igPushStyleColor(ImGuiCol_WindowBg, ImVec4(1,1,1,0));
56 			igBegin("editor",null,
57 				ImGuiWindowFlags_AlwaysAutoResize|
58 				ImGuiWindowFlags_NoTitleBar|
59 				ImGuiWindowFlags_NoMove);
60 
61 			scope(exit)
62 			{
63 				igEnd();
64 				igPopStyleColor();
65 			}
66 
67 			import std.format:format;
68 			UEGui.Text(format("EditorMode (%s with F1) [%0.1f fps]",EditorRootComponent.visible?"hide":"show",igGetIO().Framerate));
69 		}
70 
71 		if(EditorRootComponent.visible)
72 		{
73 			menuBar.render();
74 			if(showHirarchie)
75 			{
76 				renderScene();
77 				renderInspector();
78 			}
79 			if(showDebug)
80 				renderDebug();
81 
82 			assetView.render(sceneWindowHeight);
83 
84 			if(console.enabled)
85 				console.render();
86 
87 			referenceEditor.render();
88 
89 			dragDropEditor.render();
90 
91 			fileDialog.render();
92 		}
93 	}
94 
95 	private static void renderDebug()
96 	{
97 		igBegin("debug", &showDebug);
98 		scope(exit) igEnd();
99 
100 		import unecht.core.profiler:UEProfiling;
101 
102 		igPlotLines("framestimes",UEProfiling.frameTimes.ptr,cast(int)UEProfiling.frameTimes.length,0,null,float.max,float.max,ImVec2(0,100));
103 		igPlotLines("fps",UEProfiling.framerates.ptr,cast(int)UEProfiling.framerates.length,0,null,float.max,float.max,ImVec2(0,100));
104 	}
105 
106 	///
107 	package static bool showHirarchie = true;
108 	package static bool showDebug = false;
109 
110 	private static float sceneWindowWidth;
111 	private static float sceneWindowHeight;
112 	///
113 	private static void renderScene()
114 	{
115 		import unecht.ue:ue;
116 
117 		const top = igGetItemsLineHeightWithSpacing();
118 		igSetNextWindowPos(ImVec2(0,top), ImGuiSetCond_Always);
119 
120 		igBegin("scene",null,ImGuiWindowFlags_NoMove);
121 		scope(exit){igEnd();}
122 
123 		foreach(n; ue.scene.root.children)
124 		{
125 			renderSceneNode(n);
126 		}
127 
128 		sceneWindowWidth = igGetWindowWidth();
129 		sceneWindowHeight = igGetWindowHeight();
130 	}
131 
132 	///
133 	private static void renderSceneNode(UESceneNode _node)
134 	{
135 		if(_node.hideInHirarchie && !showHiddenNodes)
136 			return;
137 
138 		const canExpand = _node.children.length>0;
139 
140 		if(canExpand)
141 		{
142 			if(_node.hideInHirarchie)
143 				igPushStyleColor(ImGuiCol_Text, ImVec4(0.9f,0.9f,0.9f,0.5f));
144 
145 			const expanded = UEGui.TreeNode(cast(void*)(_node.entity), _node.entity.name);
146 
147 			if(_node.hideInHirarchie)
148 				igPopStyleColor();
149 
150 			if(igIsItemActive())
151 			{
152 				if(EditorRootComponent.currentEntity !is _node.entity)
153 					EditorRootComponent.selectEntity(_node.entity);
154 			}
155 			if(igIsItemHovered() && igIsMouseDoubleClicked(0))
156 				EditorRootComponent.lookAtNode(_node);
157 
158 			if(!expanded)
159 				return;
160 
161 			foreach(n; _node.children)
162 			{
163 				renderSceneNode(n);
164 			}
165 
166 			igTreePop();
167 		}
168 		else
169 		{
170 			if(_node.hideInHirarchie)
171 				igPushStyleColor(ImGuiCol_Text, ImVec4(0.9f,0.9f,0.9f,0.5f));
172 
173 			igBullet();
174 			igPushIdPtr(cast(void*)(_node.entity));
175 			auto isSelected = EditorRootComponent.currentEntity is _node.entity;
176 			if(UEGui.Selectable(_node.entity.name,isSelected))
177 			{
178 				if(isSelected)
179 					EditorRootComponent.selectEntity(null);
180 				else
181 					EditorRootComponent.selectEntity(_node.entity);
182 			}
183 			igPopId();
184 
185 			if(_node.hideInHirarchie)
186 				igPopStyleColor();
187 
188 			if(igIsItemHovered() && igIsMouseDoubleClicked(0))
189 				EditorRootComponent.lookAtNode(_node);
190 		}
191 	}
192 
193 	///
194 	private static void renderInspector()
195 	{
196 		if(!EditorRootComponent.currentEntity)
197 			return;
198 
199 		const top = igGetItemsLineHeightWithSpacing();
200 		igSetNextWindowPos(ImVec2(sceneWindowWidth+1,top),ImGuiSetCond_Always);
201 		bool closed;
202 		igBegin("inspector",&closed,
203 			ImGuiWindowFlags_AlwaysAutoResize|
204 			ImGuiWindowFlags_NoCollapse|
205 			ImGuiWindowFlags_NoMove|
206 			ImGuiWindowFlags_NoResize);
207 
208 		scope(exit)igEnd();
209 
210 		if(closed)
211 		{
212 			EditorRootComponent.selectEntity(null);
213 			return;
214 		}
215 
216 		string name = EditorRootComponent.currentEntity.name;
217 		UEGui.InputText("name",name);
218 		EditorRootComponent.currentEntity.name = name;
219 
220 		foreach(int i, c; EditorRootComponent.currentEntity.components)
221 		{
222 			const isSceneNode = i == 0;
223 			renderInspectorComponent(c,isSceneNode);
224 		}
225 
226 		renderInspectorFooter();
227 
228 		if(component2Remove)
229 		{
230 			//TODO: broken ?
231 			component2Remove.entity.removeComponent(component2Remove);
232 			component2Remove = null;
233 		}
234 		if(componentToAdd.length>0)
235 		{
236 			EditorRootComponent.currentEntity.addComponent(componentToAdd);
237 			componentToAdd.length = 0;
238 		}
239 	}
240 
241 	///
242 	private static void renderInspectorComponent(UEComponent c, bool isSceneNode)
243 	{
244 		auto openNode = false;
245 
246 		if(!isSceneNode)
247 		{
248 			igPushIdPtr(cast(void*)c);
249 			openNode = UEGui.TreeNode(c.typename);
250 		}
251 		else
252 		{
253 			UEGui.Text("UESceneNode");
254 		}
255 
256 		if(openNode || isSceneNode)
257 		{
258 			if(!openNode)
259 				igIndent();
260 
261 			renderInspectorSameline(c,!isSceneNode);
262 
263 			import unecht.core.componentManager;
264 			if(auto renderer = c.typename in UEComponentsManager.editors)
265 			{
266 				renderer.render(c);
267 			}
268 
269 			if(openNode)
270 				igTreePop();
271 			else
272 				igUnindent();
273 		}
274 		else
275 		{
276 			renderInspectorSameline(c, !isSceneNode);
277 		}
278 
279 		if(isSceneNode)
280 			igSeparator();
281 		else
282 			igPopId();
283 	}
284 
285 	static bool showHiddenNodes=false;
286 	@MenuItem("view/show hidden nodes")
287 	static private void toggleShowHiddenNodes()
288 	{
289 		showHiddenNodes = !showHiddenNodes;
290 	}
291 
292 	static UEComponent componentEdit;
293 	private static void renderInspectorSameline(UEComponent c, bool allowEdit)
294 	{
295 		auto subtext = " ";
296 		if(c.enabled)
297 			subtext = "X";
298 
299 		ImVec2 size;
300 		igGetWindowContentRegionMax(&size);
301 		const wndWidth = cast(int)size.x;
302 
303 		igCalcTextSize(&size,"#");
304 		const charWidth = 2 + cast(int)size.x*2;
305 
306 		if(allowEdit)
307 		{
308 			igSameLine(wndWidth-charWidth*2);
309 			if(UEGui.SmallButton("#"))
310 			{
311 				componentEdit = c;
312 				igOpenPopup("compedit");
313 			}
314 
315 			renderComponentEdit();
316 		}
317 
318 		igSameLine(wndWidth - charWidth);
319 		if(UEGui.SmallButton(subtext))
320 			c.enabled = !c.enabled;
321 	}
322 
323 	static UEComponent component2Remove;
324 
325 	private static void renderComponentEdit()
326 	{
327 		bool menuOpen = igBeginPopup("compedit");
328 		scope(exit){ if(menuOpen) igEndPopup(); }
329 
330 		if(!menuOpen && !componentEdit)
331 		{
332 			igCloseCurrentPopup();
333 			componentEdit = null;
334 			return;
335 		}
336 		if(menuOpen)
337 		{
338 			UEGui.Text("edit");
339 			igSeparator();
340 
341 			if(UEGui.Button("remove"))
342 			{
343 				component2Remove = componentEdit;
344 				componentEdit = null;
345 				igCloseCurrentPopup();
346 			}
347 		}
348 	}
349 
350 	static string componentToAdd;
351 
352 	private static void renderInspectorFooter()
353 	{
354 		igSeparator();
355 		if(UEGui.Button("add  component..."))
356 			igOpenPopup("addcomp");
357 
358 		static string filterString;
359 
360 		bool menuOpen=igBeginPopup("addcomp");
361 		scope(exit){if(menuOpen) igEndPopup();}
362 		if(!menuOpen)
363 		{
364 			filterString.length=0;
365 			return;
366 		}
367 		else
368 		{
369 			UEGui.InputText("filter",filterString);
370 			igSeparator();
371 
372 			import unecht.core.componentManager;
373 			foreach(c; UEComponentsManager.componentNames)
374 			{
375 				import std..string;
376 				if(c.indexOf(filterString,CaseSensitive.no)==-1)
377 					continue;
378 
379 				if(UEGui.Selectable(c,false))
380 				{
381 					componentToAdd = c;
382 					filterString.length=0;
383 				}
384 			}
385 		}
386 	}
387 }