@usman404/crowjs 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Core/Root.js ADDED
@@ -0,0 +1,558 @@
1
+ import { KeyboardEvent } from "./GUIEvent/KeyboardEvent.js";
2
+ import { MouseEvent } from "./GUIEvent/MouseEvent.js";
3
+ import { Component } from "./Component.js";
4
+
5
+ export class Root{
6
+ /**
7
+ * Creates the root manager that handles all GUI components and events
8
+ */
9
+ constructor(){
10
+ /** @type {Array<Component>} */
11
+ this.layers = [];//determines order of display
12
+ this.lastActiveElement = -2;
13
+ this.activeElement = -1;
14
+ //for keeping track of entry order,
15
+ //useful for mouseEnter and mouseLeave events
16
+ this.entryOrderStack = [];
17
+ this.elementsMap = new Map();//for storing ids
18
+ // this.preferences = {};
19
+ this.focusedField = null;//for focusable input fields
20
+ }
21
+
22
+ /**
23
+ * Prints the current mouse enter stack for debugging
24
+ */
25
+ printEnterStack(){
26
+ for(let i=this.entryOrderStack.length-1; i>=0; i--){
27
+ console.log(this.entryOrderStack[i]);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Finds a component by its ID from all registered components
33
+ * @param {string} id - The ID to search for
34
+ * @returns {Component|null} The found component or null
35
+ */
36
+ getElementById(id){
37
+ return this.elementsMap.get(id);
38
+ }
39
+
40
+ /**
41
+ * Renders all components in the correct z-order
42
+ */
43
+ show(){
44
+ for(let i=0; i<this.layers.length; i++){
45
+ this.layers[i].show();
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Adds a component to the root manager and registers it in the elements map
51
+ * @param {Component} element - The component to add
52
+ */
53
+ add(element){
54
+ let res = element.fillElementsMap(this.elementsMap);
55
+ if(res!=="<<map_filled_successfully>>"){
56
+ console.log(`duplicate id (${res}) found across this element and previously added elements;
57
+ this element (${element.constructor.name}) can't be added!`);
58
+ console.log(element);
59
+ console.log("");
60
+ return;
61
+ }
62
+
63
+ this.layers.push(element);
64
+ element.setRoot(this);
65
+ }
66
+
67
+ /**
68
+ * Checks if a value is a number
69
+ * @param {*} val - The value to check
70
+ * @returns {boolean} True if the value is a number
71
+ * @private
72
+ */
73
+ isNum(val){
74
+ return Number.isInteger(val);
75
+ }
76
+
77
+ /**
78
+ * Removes a component from the root manager and cleans up references
79
+ * @param {Component} element - The component to remove
80
+ */
81
+ remove(element){
82
+ let index = this.getElementIndex(element);
83
+ if(index===-1){
84
+ return;
85
+ }
86
+
87
+ this.layers.splice(index, 1);
88
+
89
+ if(this.lastActiveElement === element ||
90
+ (!this.isNum(this.lastActiveElement) &&
91
+ this.lastActiveElement.isChildOf(element))
92
+ ){
93
+ this.lastActiveElement = -2;
94
+ }
95
+
96
+ if(this.activeElement === element ||
97
+ (!this.isNum(this.activeElement) &&
98
+ this.activeElement.isChildOf(element))
99
+ ){
100
+ this.activeElement = -1;
101
+ }
102
+
103
+ this.entryOrderStack.splice(index, 1);
104
+ if(element.id!==null){
105
+ this.elementsMap.delete(element.id);
106
+ }
107
+
108
+ // console.log(`Element ${element.type} removed successfully from root!`);
109
+ // console.log(element);
110
+ // console.log("");
111
+ }
112
+
113
+ //finds element only in layers
114
+ /**
115
+ * Finds the index of an element in the layers array
116
+ * @param {Component} element - The element to find
117
+ * @returns {number} Index of the element or -1 if not found
118
+ */
119
+ getElementIndex(element){
120
+ for(let i=0; i<this.layers.length; i++){
121
+ if(this.layers[i]===element){
122
+ return i;
123
+ }
124
+ }
125
+
126
+ console.log("Element not found!\n");
127
+ return -1;
128
+ }
129
+
130
+ // if it's a child of an element in layers,
131
+ // return parent's index
132
+ // else if is an element in layers,
133
+ // return the element's index
134
+ /**
135
+ * Recursively finds the index of an element or its parent in layers
136
+ * @param {Component} element - The element to find
137
+ * @returns {number} Index of the element or its parent, or -1 if not found
138
+ */
139
+ getElementIndexRec(element){
140
+ for(let i=0; i<this.layers.length; i++){
141
+ if(this.layers[i].findElement(element)){
142
+ return i;
143
+ }
144
+ }
145
+
146
+ console.log("Element not found!\n");
147
+ return -1;
148
+ }
149
+
150
+ /**
151
+ * Moves an element one position backward in the z-order
152
+ * @param {Component} element - The element to move backward
153
+ */
154
+ sendBackwards(element){
155
+ let i = this.getElementIndexRec(element);
156
+ if(i!=1){
157
+ if(i-1>=0){
158
+ let temp = this.layers[i-1];
159
+ this.layers[i-1]=element;
160
+ this.layers[i]=temp;
161
+ }
162
+ }
163
+ }
164
+ /**
165
+ * Moves an element to the very back of the z-order
166
+ * @param {Component} element - The element to send to back
167
+ */
168
+ sendToBack(element){
169
+ if(this.layers.length!==0){
170
+ if(this.layers[0]==element){
171
+ return;
172
+ }
173
+ }
174
+
175
+ let i = this.getElementIndexRec(element);
176
+ if(i!=-1){
177
+ let temp = this.layers[i];
178
+ for(let j=i; j>0; j--){
179
+ this.layers[j]=this.layers[j-1];
180
+ }
181
+ this.layers[0]=temp;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Moves an element one position forward in the z-order
187
+ * @param {Component} element - The element to move forward
188
+ */
189
+ sendForwards(element){
190
+ let i = this.getElementIndexRec(element);
191
+ if(i!=-1){
192
+ if(i+1<this.layers.length){
193
+ let temp = this.layers[i+1];
194
+ this.layers[i+1]=element;
195
+ this.layers[i]=temp;
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Moves an element to the very front of the z-order
202
+ * @param {Component} element - The element to send to front
203
+ */
204
+ sendToFront(element){
205
+ if(this.layers.length!=0){
206
+ //already in front
207
+ if(this.layers[this.layers.length-1]==element){
208
+ return;
209
+ }
210
+ }
211
+
212
+ let i=this.getElementIndexRec(element);
213
+ if(i!=-1){
214
+ let temp = this.layers[i];
215
+ for(let j=i; j<this.layers.length-1; j++){
216
+ this.layers[j]=this.layers[j+1];
217
+ }
218
+ this.layers[this.layers.length-1] = temp;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Handles mouse events and routes them to the appropriate component
224
+ * @param {string} type - The type of mouse event
225
+ * @param {number} x - The x-coordinate of the event
226
+ * @param {number} y - The y-coordinate of the event
227
+ * @param {Object} options - Additional options
228
+ * @param {Event|null} options.e - The original browser event
229
+ */
230
+ handleMouseEvent(type, x, y, {e=null}={}){
231
+ //currently engaged element
232
+ if(mouseIsPressed && !this.isNum(this.activeElement)){
233
+ this.sendToFront(this.activeElement);
234
+
235
+ let target = this.activeElement;
236
+ let event = new MouseEvent(x, y, type, target, {event: e});
237
+ target.dispatchEvent(event);
238
+ return;
239
+ }
240
+
241
+
242
+ let target = null;
243
+ for(let i=this.layers.length-1; i>=0; i--){
244
+ target = this.layers[i].findTarget();
245
+ if(target!=null){
246
+ if(this.activeElement!=this.lastActiveElement){
247
+ this.mouseLeaveEventHandler(x, y);
248
+ }
249
+ this.lastActiveElement = this.activeElement;
250
+ this.activeElement = target;
251
+ if(target)
252
+ break;
253
+ }
254
+ }
255
+
256
+ //clicked somewhere outside
257
+ if(target==null){
258
+ if(type=="click"||type=="press"){
259
+ if(!this.focusedField){
260
+ return;
261
+ }
262
+
263
+ this.focusedField.blur();
264
+ this.focusedField = null;
265
+ }
266
+ this.activeElement = -1;
267
+ return;
268
+ }
269
+
270
+ let event = new MouseEvent(x, y, type, target, {event: e});
271
+ target.dispatchEvent(event);
272
+
273
+ if(event.type=="click" || event.type=="press"){
274
+ if(target.type=="Input"){
275
+ if(this.focusedField && this.focusedField!=target){
276
+ this.focusedField.blur();
277
+ }
278
+
279
+ this.focusedField = target;
280
+ if(!this.focusedField.isFocused){
281
+ this.focusedField.focus();
282
+ }
283
+
284
+ }
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Handles keyboard events and routes them to focused components
290
+ * @param {string} type - The type of keyboard event
291
+ * @param {number} x - The x-coordinate
292
+ * @param {number} y - The y-coordinate
293
+ */
294
+ handleKeyboardEvent(type, x, y){
295
+ if(this.focusedField){
296
+ // console.log(this.focusedField);
297
+ let target = this.focusedField;
298
+ let event = new KeyboardEvent(x, y, type, target);
299
+ // console.log(event);
300
+ target.dispatchEvent(event);
301
+ return;
302
+ }
303
+
304
+ let target = null;
305
+ for(let i=this.layers.length-1; i>=0; i--){
306
+ target = this.layers[i].findTarget();
307
+ if(target!=null){
308
+ // console.log(target.constructor.name);
309
+ this.lastActiveElement = this.activeElement;
310
+ this.activeElement = target;
311
+ break;
312
+ }
313
+ }
314
+
315
+ if(target==null){
316
+ this.activeElement = -1;
317
+ return;
318
+ }
319
+
320
+ let event = new KeyboardEvent(x, y, type, target);
321
+ target.dispatchEvent(event);
322
+ }
323
+
324
+ //runs continously on hover
325
+ //put in draw loop
326
+ /**
327
+ * Continuously handles mouse hover events (call in draw loop)
328
+ * @param {number} x - The current mouse x-coordinate
329
+ * @param {number} y - The current mouse y-coordinate
330
+ */
331
+ hoverEventListeners(x, y){
332
+ this.handleMouseEvent("hover", x, y);
333
+ }
334
+
335
+ //runs immediately after a mouse button
336
+ //is pressed
337
+ //separate function
338
+ /**
339
+ * Handles mouse click events (call when mouse is clicked)
340
+ * @param {number} x - The click x-coordinate
341
+ * @param {number} y - The click y-coordinate
342
+ */
343
+ mouseClickedEventListeners(x, y){
344
+ if(this.activeElement==-1){
345
+ return;
346
+ }
347
+
348
+ // if(this.activeElement==this.lastActiveElement){
349
+ // return;
350
+ // }
351
+
352
+ this.handleMouseEvent("click", x, y);
353
+ }
354
+
355
+ //similar to click, but runs as soon as
356
+ //any mouse button is clicked
357
+ //separate function
358
+ /**
359
+ * Handles mouse press events (call when mouse button is pressed)
360
+ * @param {number} x - The press x-coordinate
361
+ * @param {number} y - The press y-coordinate
362
+ */
363
+ mousePressedEventListeners(x, y){
364
+ this.handleMouseEvent("press", x, y);
365
+ }
366
+
367
+ //runs once after double click
368
+ //separate function
369
+ /**
370
+ * Handles mouse double-click events
371
+ * @param {number} x - The double-click x-coordinate
372
+ * @param {number} y - The double-click y-coordinate
373
+ */
374
+ mouseDoubleClickedEventListeners(x, y){
375
+ this.handleMouseEvent("doubleClick", x, y);
376
+ }
377
+
378
+ //runs once after a mouse button is released
379
+ //separate function
380
+ /**
381
+ * Handles mouse release events (call when mouse button is released)
382
+ * @param {number} x - The release x-coordinate
383
+ * @param {number} y - The release y-coordinate
384
+ */
385
+ mouseReleasedEventListeners(x, y){
386
+ if(!this.isNum(this.activeElement)){
387
+ // this.sendToFront(this.activeElement);
388
+
389
+ let target = this.activeElement;
390
+ let event = new MouseEvent(x, y, "release", target);
391
+ target.dispatchEvent(event);
392
+ return;
393
+ }
394
+
395
+ this.handleMouseEvent("release", x, y);
396
+ }
397
+
398
+ //separate function
399
+ /**
400
+ * Handles mouse drag events (call when mouse is dragged)
401
+ * @param {number} x - The current drag x-coordinate
402
+ * @param {number} y - The current drag y-coordinate
403
+ */
404
+ mouseDraggedEventListeners(x, y){
405
+ this.handleMouseEvent("drag", x, y);
406
+ }
407
+
408
+ //(enters one component and leaves another)
409
+ //in draw function
410
+ /**
411
+ * Handles mouse enter events for components (call in draw function)
412
+ * @param {number} x - The current mouse x-coordinate
413
+ * @param {number} y - The current mouse y-coordinate
414
+ */
415
+ mouseEnterEventListeners(x, y){
416
+ if(this.activeElement==-1){
417
+ return;
418
+ }
419
+
420
+ if(this.entryOrderStack.includes(this.activeElement)){
421
+ return;
422
+ }
423
+
424
+ let target = this.activeElement;
425
+ this.pushParent(target);
426
+
427
+ let event = new MouseEvent(x, y, "mouseEnter", target);
428
+ target.dispatchMouseEnterEvent(event);
429
+ this.entryOrderStack.push(target);
430
+ // console.log(target);
431
+ // console.log(this.entryOrderStack);
432
+ }
433
+ /**
434
+ * Recursively pushes parent components to the entry order stack
435
+ * @param {Component} element - The element whose parents should be pushed
436
+ */
437
+ pushParent(element){
438
+ if(element.parent==null){
439
+ return;
440
+ }
441
+
442
+ if(this.entryOrderStack.includes(element.parent)){
443
+ return;
444
+ }
445
+
446
+ this.pushParent(element.parent);
447
+ this.entryOrderStack.push(element.parent);
448
+ }
449
+
450
+ //in draw function
451
+ /**
452
+ * Handles mouse leave events for components (call in draw function)
453
+ * @param {number} x - The current mouse x-coordinate
454
+ * @param {number} y - The current mouse y-coordinate
455
+ */
456
+ mouseLeaveEventListeners(x, y){
457
+ if(mouseIsPressed && !this.isNum(this.activeElement)){
458
+ return;
459
+ }
460
+
461
+ if(this.entryOrderStack.length==0){
462
+ return;
463
+ }
464
+
465
+ // console.log("mouseLeaveEventListeners...");
466
+
467
+ while(true && this.entryOrderStack.length>0){
468
+ let top = this.entryOrderStack.pop();
469
+ if(!top.isInside()){
470
+ let event = new MouseEvent(x, y, "mouseLeave", top);
471
+ top.dispatchMouseLeaveEvent(event);
472
+ } else {
473
+ this.entryOrderStack.push(top);
474
+ break;
475
+ }
476
+ }
477
+
478
+ // console.log(this.entryOrderStack);
479
+ }
480
+
481
+ //used for component to component cursor transition
482
+ //triggered on the component the cursor left from
483
+ /**
484
+ * Handles component-to-component cursor transitions
485
+ * @param {number} x - The current mouse x-coordinate
486
+ * @param {number} y - The current mouse y-coordinate
487
+ */
488
+ mouseLeaveEventHandler(x, y){
489
+ // console.log("mouseLeaveEventHandler ...");
490
+ if(this.activeElement==-1 || this.isNum(this.lastActiveElement)){
491
+ return;
492
+ }
493
+
494
+ let index = -1;
495
+ for(let i=this.entryOrderStack.length-1; i>=0; i--){
496
+ if(this.layers.includes(this.entryOrderStack[i])){
497
+ index = i;
498
+ // console.log("index:", index);
499
+ break;
500
+ }
501
+ }
502
+
503
+ if(index==-1){
504
+ return;
505
+ }
506
+
507
+ let temp = this.entryOrderStack.slice(0, index);
508
+ // console.log(temp);
509
+ while(temp.length>0){
510
+ let top = temp.pop();
511
+ let event = new MouseEvent(x, y, "mouseLeave", top);
512
+ top.dispatchMouseLeaveEvent(event);
513
+ }
514
+
515
+ this.entryOrderStack.splice(0, index);
516
+ }
517
+
518
+ //separate function
519
+ /**
520
+ * Handles mouse wheel scroll events
521
+ * @param {number} x - The current mouse x-coordinate
522
+ * @param {number} y - The current mouse y-coordinate
523
+ * @param {WheelEvent} event - The wheel event object
524
+ */
525
+ mouseWheelEventListeners(x, y, event){
526
+ this.handleMouseEvent("scroll", x, y, {e:event});
527
+ }
528
+
529
+ //runs once
530
+ //separate function
531
+ /**
532
+ * Handles key press events (call once when key is pressed)
533
+ * @param {number} x - The current mouse x-coordinate
534
+ * @param {number} y - The current mouse y-coordinate
535
+ */
536
+ keyPressedEventListeners(x, y){
537
+ this.handleKeyboardEvent("keyPress", x, y);
538
+ }
539
+
540
+ //runs continously when a key is pressed
541
+ //to check for key continously pressed
542
+ //keyIsPressed
543
+ //key === 'A'
544
+ //keyCode === ENTER
545
+ //draw function
546
+ /**
547
+ * Handles continuous key down events (call in draw loop when keyIsPressed)
548
+ * @param {number} x - The current mouse x-coordinate
549
+ * @param {number} y - The current mouse y-coordinate
550
+ */
551
+ keyDownEventListeners(x, y){
552
+ if(!keyIsPressed){
553
+ return;
554
+ }
555
+
556
+ this.handleKeyboardEvent("keyDown", x, y);
557
+ }
558
+ }