1 module hoekjed.kern.venster;
2 import bindbc.glfw;
3 import bindbc.opengl;
4 import hoekjed.kern;
5 import std.container.rbtree;
6 import std.conv : to;
7 
8 struct ToetsInvoer {
9 	int toets, toets_verwijzing, gebeurtenis, toevoeging;
10 }
11 
12 struct MuisknopInvoer {
13 	int knop, gebeurtenis, toevoeging;
14 }
15 
16 struct MuisplekInvoer {
17 	double x, y;
18 }
19 
20 struct MuiswielInvoer {
21 	double x, y;
22 }
23 
24 alias ToetsTerugroeper = void delegate(ToetsInvoer invoer) nothrow;
25 alias MuisknopTerugroeper = void delegate(MuisknopInvoer invoer) nothrow;
26 alias MuisplekTerugroeper = void delegate(MuisplekInvoer invoer) nothrow;
27 alias MuiswielTerugroeper = void delegate(MuiswielInvoer invoer) nothrow;
28 
29 enum Muissoort {
30 	NORMAAL = GLFW_CURSOR_NORMAL,
31 	GEVANGEN = GLFW_CURSOR_DISABLED,
32 	ONZICHTBAAR = GLFW_CURSOR_HIDDEN
33 }
34 
35 class Venster {
36 	static public Venster[GLFWwindow* ] vensters;
37 	package GLFWwindow* glfw_venster;
38 
39 	// Eigenschappen
40 	string naam;
41 	int breedte, hoogte;
42 	Scherm scherm;
43 
44 	// Invoer
45 	ToetsTerugroeper[] toetsTerugroepers = [];
46 	MuisknopTerugroeper[] muisknopTerugroepers = [];
47 	MuisplekTerugroeper[] muisplekTerugroepers = [];
48 	MuiswielTerugroeper[] muiswielTerugroepers = [];
49 	ToetsInvoer[] toetsInvoer = [];
50 	MuisplekInvoer[] muisplekInvoer = [];
51 	MuisknopInvoer[] muisknopInvoer = [];
52 	MuiswielInvoer[] muiswielInvoer = [];
53 
54 	alias scherm this;
55 
56 	static void zetStandaardZichtbaar(bool zichtbaar) {
57 		glfwWindowHint(GLFW_VISIBLE, zichtbaar);
58 	}
59 
60 	static void zetStandaardRand(bool rand) {
61 		glfwWindowHint(GLFW_DECORATED, rand);
62 	}
63 
64 	static void zetStandaardDoorzichtig(bool doorzichtig) {
65 		glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, doorzichtig);
66 	}
67 
68 	void zetAchtergrondKleur(Vec!(4, float) kleur) {
69 		glClearColor(kleur.x, kleur.y, kleur.z, kleur.w);
70 	}
71 
72 	void zetMuissoort(Muissoort soort) {
73 		glfwSetInputMode(glfw_venster, GLFW_CURSOR, soort);
74 	}
75 
76 	this(string naam = "HoekjeD", int glfw_breedte = 1920 / 2, int glfw_hoogte = 1080 / 2) {
77 		debug glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true);
78 
79 		this.glfw_venster = glfwCreateWindow(glfw_breedte, glfw_hoogte, naam.ptr, null, null);
80 		assert(glfw_venster !is null, "GLFW kon geen scherm aanmaken.");
81 
82 		Venster.vensters[glfw_venster] = this;
83 		glfwMakeContextCurrent(glfw_venster); // VOEG TOE: Moet voor multithreading & meerdere vensters nog een oplossing vinden.
84 		//glfwSwapInterval(0); Kan met 1 vsynch gebruiken.
85 
86 		glfwSetKeyCallback(glfw_venster, &venster_toets_terugroeper);
87 		glfwSetMouseButtonCallback(glfw_venster, &venster_muisknop_terugroeper);
88 		glfwSetCursorPosCallback(glfw_venster, &venster_muisplek_terugroeper);
89 		glfwSetScrollCallback(glfw_venster, &venster_muiswiel_terugroeper);
90 		// glfwSetWindowSizeCallback(glfw_venster, &venster_grootte_terugroeper);
91 		glfwSetFramebufferSizeCallback(glfw_venster, &venster_grootte_terugroeper);
92 
93 		this.naam = naam;
94 		glfwGetFramebufferSize(glfw_venster, &breedte, &hoogte);
95 		this.scherm = Scherm();
96 		this.scherm.hervorm(Vec!(2, int)([0, 0]), Vec!(2, int)([breedte, hoogte]));
97 
98 		GLSupport gl_versie = loadOpenGL();
99 		assert(gl_versie == GLSupport.gl46, "GL laadt niet: " ~ gl_versie.to!string);
100 
101 		debug {
102 			glEnable(GL_DEBUG_OUTPUT);
103 			glDebugMessageCallback(&gl_fout_terugroeper, null);
104 			glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE,
105 				GL_DEBUG_SEVERITY_NOTIFICATION, 0, null, false);
106 		}
107 
108 		glfwSetCursorPos(glfw_venster, 0, 0);
109 		glEnable(GL_DEPTH_TEST);
110 	}
111 
112 	void bekijk() {
113 		glfwFocusWindow(glfw_venster);
114 	}
115 
116 	void toon() {
117 		glfwShowWindow(glfw_venster);
118 		glClear(GL_COLOR_BUFFER_BIT);
119 		glfwSwapBuffers(glfw_venster);
120 	}
121 
122 	void verstop() {
123 		glfwHideWindow(glfw_venster);
124 	}
125 
126 	void verwerkInvoer() {
127 		// Behoudt volgorde van invoer over alle terugroepers.
128 		foreach (ToetsInvoer invoer; toetsInvoer)
129 			foreach (ToetsTerugroeper terugroeper; toetsTerugroepers)
130 				terugroeper(invoer);
131 
132 		foreach (MuisknopInvoer invoer; muisknopInvoer)
133 			foreach (MuisknopTerugroeper terugroeper; muisknopTerugroepers)
134 				terugroeper(invoer);
135 
136 		foreach (MuisplekInvoer invoer; muisplekInvoer)
137 			foreach (MuisplekTerugroeper terugroeper; muisplekTerugroepers)
138 				terugroeper(invoer);
139 
140 		foreach (MuiswielInvoer invoer; muiswielInvoer)
141 			foreach (MuiswielTerugroeper terugroeper; muiswielTerugroepers)
142 				terugroeper(invoer);
143 
144 		//PAS OP: neemt onafhankelijkheid van muis & toets volgorde aan op korte tijdsverschillen.
145 	}
146 
147 	// PAS OP: Moet mogelijk testen wat de toevoeging is bij gebrek aan toevoeging of dubbele
148 	// toevoegingen. Hier is de documentatie niet duidelijk.
149 	public bool krijgToets(int toets) {
150 		foreach (ToetsInvoer t; this.toetsInvoer)
151 			if (t.toets == toets && (t.gebeurtenis == GLFW_PRESS || t.gebeurtenis == GLFW_REPEAT))
152 				return true;
153 		return false;
154 	}
155 
156 	void leegInvoer() {
157 		toetsInvoer = [];
158 		muisknopInvoer = [];
159 		muisplekInvoer = [];
160 		muiswielInvoer = [];
161 	}
162 
163 	void teken() {
164 		glViewport(0, 0, breedte, hoogte); // Zet het tekengebied.
165 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Verschoont het scherm.
166 		scherm.teken();
167 
168 		// VOEG TOE:
169 		// Hebben een beter denksysteem nodig. Per ding of/en ook per scherm.
170 		// VOEG TOE: alle denk functies in een globale lijst, voorkomt herhaalde instructies & maakt
171 		// het mogelijk de "bijgewerkt" te verwijderen en "werkBij" sneller te maken door veranderde
172 		// dingen in een globale set te plaatsen & hun en hun kinderen specifiek bij te werken.
173 
174 		// Mogelijk is het beter dit niet blobaal te doen maar per wereld, met werelden in een
175 		// globale lijst, omdat het zo eenvoudiger is een wereld te pauzeren.
176 
177 		// Kan net als veel motoren dingen eigenschappen geven opdat het uitschakelen van een
178 		// ding bijvoorbeeld eenvoudig door te voeren is naar het verwijderen van zijn denk opdracht(en).
179 		glfwSwapBuffers(glfw_venster);
180 	}
181 
182 	unittest {
183 		import hoekjed.kern;
184 
185 		hdZetOp();
186 		Venster.zetStandaardDoorzichtig(true);
187 
188 		bool called = false;
189 		ToetsTerugroeper foo = (ToetsInvoer invoer) { called = true; };
190 		Venster venster = new Venster();
191 		venster.toetsTerugroepers ~= foo;
192 
193 		venster_toets_terugroeper(venster.glfw_venster, 0, 0, 0, 0);
194 		venster.verwerkInvoer();
195 
196 		assert(called);
197 	}
198 
199 	// VOEG TOE: glfw mogelijkheden, zoals openen/sluiten/focus/muis/toetsen (toetsen per scherm of venster?)
200 
201 	protected void hervorm() nothrow {
202 		Vec!(2, int) lb = {[0, 0]};
203 		Vec!(2, int) grootte = {[breedte, hoogte]};
204 		scherm.hervorm(lb, grootte);
205 	}
206 
207 	unittest {
208 		import hoekjed.kern;
209 
210 		// TODO:
211 		// hdZetOp();
212 		// Venster.zetStandaardZichtbaar(false);
213 		// Venster venster = new Venster();
214 		// Vec!(2, int) ro = venster.scherm.rechtsonder;
215 		// venster.breedte *= 2;
216 		// venster.hervorm();
217 		// Vec!(2, int) ro2 = venster.scherm.rechtsonder;
218 		// assert(ro2.x == 2 * ro.x && ro2.y == ro.y,
219 		// 		"[2 * " ~ ro.x.to!string ~ ", " ~ ro.y.to!string ~ "] != " ~ ro2.to!string);
220 	}
221 }
222 
223 import hoekjed.dingen.zicht;
224 
225 // VERBETER: Vensters, Schremen & Zichten volledig herwerken.
226 
227 struct Scherm {
228 	Vec!2 linksboven_f = {[0, 0]};
229 	Vec!2 rechtsonder_f = {[1, 1]};
230 	Vec!(2, int) linksboven;
231 	Vec!(2, int) rechtsonder;
232 
233 	Scherm[] deelschermen;
234 	Wereld wereld;
235 	Zicht zicht; // In principe kan deze uit een andere wereld komen. . . Parallele werelden?
236 
237 	void teken() {
238 		foreach (Scherm scherm; deelschermen)
239 			scherm.teken();
240 		if (zicht is null)
241 			return;
242 		glViewport(linksboven.x, linksboven.y, rechtsonder.x, rechtsonder.y); // Zet het tekengebied.
243 		if (deelschermen.length != 0)
244 			glClear(GL_DEPTH_BUFFER_BIT); // Over deelscherm heen tekenen.
245 
246 		wereld.tekenWereld(zicht);
247 	}
248 
249 	protected void hervorm(Vec!(2, int) lb, Vec!(2, int) grootte) nothrow {
250 		linksboven = lb + cast(Vec!(2, int))(linksboven_f * grootte);
251 		rechtsonder = lb + cast(Vec!(2, int))(rechtsonder_f * grootte);
252 		if (deelschermen.length != 0) {
253 			Vec!(2, int) eigen_grootte = rechtsonder - linksboven;
254 			foreach (Scherm scherm; deelschermen) {
255 				scherm.hervorm(linksboven, eigen_grootte);
256 			}
257 		}
258 	}
259 
260 	unittest {
261 		// TODO:
262 		// Scherm s = {rechtsonder_f: {[0.25, 0.5]}};
263 		// Vec!(2, int) a = {[5, 5]};
264 		// Vec!(2, int) b = {[1, 2]};
265 		// s.hervorm(a, b);
266 		// assert(s.rechtsonder.x == 5 && s.rechtsonder.y == 6);
267 	}
268 }
269 
270 extern (C) void venster_grootte_terugroeper(GLFWwindow* glfw_venster, int breedte, int hoogte) nothrow {
271 	Venster venster = Venster.vensters[glfw_venster];
272 	venster.breedte = breedte;
273 	venster.hoogte = hoogte;
274 	venster.hervorm();
275 }
276 
277 extern (C) void venster_toets_terugroeper(GLFWwindow* glfw_venster, int toets,
278 	int toets_sleutel, int gebeurtenis, int toevoeging) nothrow {
279 	debug {
280 		import core.sys.windows.windows;
281 
282 		if (toets == GLFW_KEY_GRAVE_ACCENT) {
283 			ShowWindow(console, _console_zichtbaar ? SW_HIDE : SW_RESTORE);
284 			glfwFocusWindow(glfw_venster);
285 			_console_zichtbaar = !_console_zichtbaar;
286 		}
287 	}
288 	if (toets == GLFW_KEY_ESCAPE)
289 		glfwSetWindowShouldClose(glfw_venster, true);
290 
291 	Venster venster = Venster.vensters[glfw_venster];
292 	ToetsInvoer invoer = ToetsInvoer(toets, toets_sleutel, gebeurtenis, toevoeging);
293 	venster.toetsInvoer ~= invoer;
294 }
295 
296 extern (C) void venster_muisknop_terugroeper(GLFWwindow* glfw_venster, int knop,
297 	int gebeurtenis, int toevoeging) nothrow {
298 	Venster venster = Venster.vensters[glfw_venster];
299 	MuisknopInvoer invoer = MuisknopInvoer(knop, gebeurtenis, toevoeging);
300 	venster.muisknopInvoer ~= invoer;
301 }
302 
303 extern (C) void venster_muisplek_terugroeper(GLFWwindow* glfw_venster, double x, double y) nothrow {
304 	Venster venster = Venster.vensters[glfw_venster];
305 	MuisplekInvoer invoer = MuisplekInvoer(x, y);
306 	venster.muisplekInvoer ~= invoer;
307 }
308 
309 extern (C) void venster_muiswiel_terugroeper(GLFWwindow* glfw_venster, double x, double y) nothrow {
310 	Venster venster = Venster.vensters[glfw_venster];
311 	MuiswielInvoer invoer = MuiswielInvoer(x, y);
312 	venster.muiswielInvoer ~= invoer;
313 }
314 
315 debug {
316 	extern (System) void gl_fout_terugroeper(GLenum bron, GLenum soort, GLuint id,
317 		GLenum ernstigheid, GLsizei length, const GLchar* message, const void* userParam) nothrow {
318 		import std.stdio : write, writeln;
319 		import std.conv : to;
320 		import bindbc.opengl.bind.types;
321 
322 		try {
323 			writeln("OpenGL Fout #" ~ id.to!string);
324 			write("\tBron: ");
325 			switch (bron) {
326 			case GL_DEBUG_SOURCE_API:
327 				writeln("OpenGL API");
328 				break;
329 			case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
330 				writeln("Venster Systeem API");
331 				break;
332 			case GL_DEBUG_SOURCE_SHADER_COMPILER:
333 				writeln("Shader Compiler");
334 				break;
335 			case GL_DEBUG_SOURCE_THIRD_PARTY:
336 				writeln("Derde Partij");
337 				break;
338 			case GL_DEBUG_SOURCE_APPLICATION:
339 				writeln("Gebruikerscode");
340 				break;
341 			case GL_DEBUG_SOURCE_OTHER:
342 				writeln("Overig");
343 				break;
344 			default:
345 				assert(false);
346 			}
347 
348 			write("\tSoort: ");
349 			switch (soort) {
350 			case GL_DEBUG_TYPE_ERROR:
351 				writeln("Fout ╮(. ❛ ᴗ ❛.)╭");
352 				break;
353 			case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
354 				writeln("Verouderd gebruik");
355 				break;
356 			case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
357 				writeln("Ongedefiniëerd gedrag");
358 				break;
359 			case GL_DEBUG_TYPE_PORTABILITY:
360 				writeln("Systeem overzetbaarheid");
361 				break;
362 			case GL_DEBUG_TYPE_PERFORMANCE:
363 				writeln("Uitvoeringsproblemen");
364 				break;
365 			case GL_DEBUG_TYPE_MARKER:
366 				writeln("\"Command stream annotation\"");
367 				break;
368 			case GL_DEBUG_TYPE_PUSH_GROUP:
369 				writeln("\"Group pushing\"");
370 				break;
371 			case GL_DEBUG_TYPE_POP_GROUP:
372 				writeln("\"foo\"");
373 				break;
374 			case GL_DEBUG_TYPE_OTHER:
375 				writeln("Overig");
376 				break;
377 			default:
378 				assert(false);
379 			}
380 
381 			write("\tErnstigheid: ");
382 			switch (ernstigheid) {
383 			case GL_DEBUG_SEVERITY_HIGH:
384 				writeln("Hoog");
385 				break;
386 			case GL_DEBUG_SEVERITY_MEDIUM:
387 				writeln("Middelmatig");
388 				break;
389 			case GL_DEBUG_SEVERITY_LOW:
390 				writeln("Laag");
391 				break;
392 			case GL_DEBUG_SEVERITY_NOTIFICATION:
393 				writeln("Melding (Overig)");
394 				break;
395 			default:
396 				assert(false);
397 			}
398 
399 			writeln("\tBericht: " ~ message.to!string);
400 		} catch (Exception e) {
401 		}
402 	}
403 }