@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.
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,426 @@
1
+ import { GUIEvent } from "./GUIEvent/GUIEvent.js";
2
+
3
+ export class Component{
4
+ /**
5
+ * Creates a new Component instance
6
+ * @param {number} x - The x-coordinate of the component
7
+ * @param {number} y - The y-coordinate of the component
8
+ * @param {number} width - The width of the component
9
+ * @param {number} height - The height of the component
10
+ * @param {Object} options - Configuration options
11
+ * @param {string} options.type - The type identifier of the component
12
+ * @param {string|null} options.id - Unique identifier for the component
13
+ */
14
+ constructor(x, y, width, height, {type="", id=null} = {}){
15
+ this.x = x;
16
+ this.y = y;
17
+ this.id = id;
18
+ this.height = height;
19
+ this.width = width;
20
+ this.parent = null;
21
+ this.type = type;
22
+ this.root = null;
23
+ this.eventListeners = {};
24
+ this.children = [];
25
+ }
26
+
27
+ //to show itself
28
+ /**
29
+ * Renders the component on the canvas
30
+ * @abstract
31
+ */
32
+ show(){};
33
+
34
+ //you can add multiple callback functions to one eventType
35
+ //for each component
36
+ /**
37
+ * Registers an event listener for the component
38
+ *
39
+ * @param {EventType} eventType - The type of event to listen for. Available options:
40
+ *
41
+ * ## Mouse Events
42
+ * - `"hover"` - Fires continuously while mouse is over the component (use in draw loop)
43
+ * - `"mouseEnter"` - Fires once when mouse enters component boundaries
44
+ * - `"mouseLeave"` - Fires once when mouse leaves component boundaries
45
+ * - `"click"` - Fires on mouse click (press + release on same component)
46
+ * - `"press"` - Fires when mouse button is pressed down
47
+ * - `"release"` - Fires when mouse button is released
48
+ * - `"drag"` - Fires continuously while mouse is dragged with button pressed
49
+ * - `"doubleClick"` - Fires on double click
50
+ * - `"scroll"` - Fires on mouse wheel scroll
51
+ *
52
+ * ## Keyboard Events
53
+ * - `"keyPress"` - Fires once when a key is pressed
54
+ * - `"keyDown"` - Fires continuously while a key is held down (use in draw loop)
55
+ *
56
+ * ## Frame Events (Frame, GridFrame, ScrollFrame only)
57
+ * - `"resize"` - Fires when frame is being resized by user
58
+ * - `"reposition"` - Fires when frame is being dragged/moved by user
59
+ *
60
+ * ## Input Events (TextField, Input only)
61
+ * - `"focus"` - Fires when input field gains focus
62
+ * - `"blur"` - Fires when input field loses focus
63
+ *
64
+ * @param {function(GUIEvent): void} callback - The function to call when event occurs.
65
+ * The callback receives a GUIEvent object with the following properties:
66
+ * - `x` {number} - The x-coordinate where event occurred
67
+ * - `y` {number} - The y-coordinate where event occurred
68
+ * - `type` {string} - The event type that triggered
69
+ * - `target` {Component} - The component that received the event
70
+ * - `propagationStopped` {boolean} - Whether event propagation was stopped
71
+ *
72
+ * For specific event types:
73
+ * - **MouseEvent** (extends GUIEvent): All mouse-related events
74
+ * - **KeyboardEvent** (extends GUIEvent): `keyPress`, `keyDown`
75
+ * - **Scroll events**: Includes `delta` property for scroll direction
76
+ *
77
+ * @example
78
+ * // Basic click handler
79
+ * component.addEventListener("click", (event) => {
80
+ * console.log(`Clicked at ${event.x}, ${event.y}`);
81
+ * });
82
+ *
83
+ * @example
84
+ * // Frame resize handler
85
+ * frame.addEventListener("resize", (event) => {
86
+ * console.log(`Frame resized to ${event.target.width}x${event.target.height}`);
87
+ * });
88
+ *
89
+ * @example
90
+ * // Input focus handler
91
+ * textField.addEventListener("focus", (event) => {
92
+ * event.target.borderWidth += 2; // Visual feedback
93
+ * });
94
+ *
95
+ * @example
96
+ * // Stop event propagation
97
+ * component.addEventListener("click", (event) => {
98
+ * event.stopPropagation(); // Prevents parent components from receiving event
99
+ * console.log("Event handled here only");
100
+ * });
101
+ *
102
+ * @throws {Error} If eventType is not a string or callback is not a function
103
+ *
104
+ */
105
+
106
+ addEventListener(eventType, callback){
107
+ if(!this.eventListeners[eventType]){
108
+ this.eventListeners[eventType] = [];
109
+ }
110
+ this.eventListeners[eventType].push(callback);
111
+ }
112
+ /**
113
+ * Dispatches a mouse enter event to the component
114
+ * @param {MouseEvent} event - The mouse enter event
115
+ */
116
+ dispatchMouseEnterEvent(event){
117
+ this.dispatchMouseEnterLeaveEventUtil(event);
118
+ }
119
+ /**
120
+ * Dispatches a mouse leave event to the component
121
+ * @param {MouseEvent} event - The mouse leave event
122
+ */
123
+ dispatchMouseLeaveEvent(event){
124
+ this.dispatchMouseEnterLeaveEventUtil(event);
125
+ }
126
+ /**
127
+ * Internal utility for mouse enter/leave event handling
128
+ * @param {MouseEvent} event - The mouse event
129
+ * @private
130
+ */
131
+ dispatchMouseEnterLeaveEventUtil(event){
132
+ if(this.eventListeners[event.type]){
133
+ for(let callback of this.eventListeners[event.type]){
134
+ callback(event);
135
+ }
136
+ }
137
+ }
138
+
139
+ //to take action upon event occurence
140
+ /**
141
+ * Dispatches an event to the component and propagates to parents
142
+ * @param {GUIEvent} event - The event to dispatch
143
+ */
144
+ dispatchEvent(event){
145
+ // console.log("Dispatching to:", event.target, "Event type:", event.type);
146
+ if(this.eventListeners[event.type]){
147
+ for(let callback of this.eventListeners[event.type]){
148
+ callback(event);
149
+ if(event.propagationStopped){
150
+ return;
151
+ }
152
+ }
153
+ }
154
+ // Bubble up to parent
155
+ if(this.parent){
156
+ this.parent.dispatchEvent(event);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Dispatches an event only to this component (no propagation)
162
+ * @param {GUIEvent} event - The event to dispatch
163
+ */
164
+ dispatchEventOnlyOnSelf(event){
165
+ if(this.eventListeners[event.type]){
166
+ for(let callback of this.eventListeners[event.type]){
167
+ callback(event);
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Dispatches an event to this component and all children
174
+ * @param {GUIEvent} event - The event to dispatch
175
+ */
176
+ dispatchTrickleDownEvent(event){
177
+ if(this.eventListeners[event.type]){
178
+ for(let callback of this.eventListeners[event.type]){
179
+ callback(event);
180
+ }
181
+ }
182
+
183
+ for(let i=0; i<this.children.length; i++){
184
+ this.children[i].dispatchTrickleDownEvent(event);
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Disables resizing and repositioning for this component (if Frame) and all of its children
190
+ */
191
+ turnResizingAndRepositionOff(){
192
+ if(this.type==="Frame"){
193
+ this.enableResizing=false;
194
+ this.enableReposition=false;
195
+
196
+ for(let i=0; i<this.children.length; i++){
197
+ this.children[i].turnResizingAndRepositionOff();
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Checks if the mouse is inside the component boundaries
204
+ * @returns {boolean} True if mouse is inside the component
205
+ */
206
+ isInside() {
207
+ let insideRect = mouseX > this.x && mouseX < this.x + this.width &&
208
+ mouseY > this.y && mouseY < this.y + this.height;
209
+
210
+ if (!insideRect) return false;
211
+
212
+ let r = this.cornerRadius || 0;
213
+
214
+ // If corner radius is 0, we don't need further checks
215
+ if (r <= 0) return this.checkParent();
216
+
217
+ // Define the 4 corner centers
218
+ let corners = [
219
+ { x: this.x + r, y: this.y + r }, // top-left
220
+ { x: this.x + this.width - r, y: this.y + r }, // top-right
221
+ { x: this.x + r, y: this.y + this.height - r }, // bottom-left
222
+ { x: this.x + this.width - r, y: this.y + this.height - r }, // bottom-right
223
+ ];
224
+
225
+ // Check if mouse is within the quarter circle of any corner
226
+ if (mouseX < this.x + r && mouseY < this.y + r && dist(mouseX, mouseY, corners[0].x, corners[0].y) > r)
227
+ return false;
228
+ if (mouseX > this.x + this.width - r && mouseY < this.y + r && dist(mouseX, mouseY, corners[1].x, corners[1].y) > r)
229
+ return false;
230
+ if (mouseX < this.x + r && mouseY > this.y + this.height - r && dist(mouseX, mouseY, corners[2].x, corners[2].y) > r)
231
+ return false;
232
+ if (mouseX > this.x + this.width - r && mouseY > this.y + this.height - r && dist(mouseX, mouseY, corners[3].x, corners[3].y) > r)
233
+ return false;
234
+
235
+ return this.checkParent();
236
+ }
237
+
238
+ //removes child from children array
239
+ //only from the immediate children
240
+ /**
241
+ * Removes a child component from immediate children
242
+ * @param {Component} element - The child component to remove
243
+ * @returns {boolean} True if component was found and removed
244
+ */
245
+ removeChild(element){
246
+ if(this.children.includes(element)){
247
+ this.children = this.children.filter((elem)=>elem!==element);
248
+ // console.log(`element (id: ${element.id}) removed successfully from ${this.constructor.name} (id: ${this.id})!`);
249
+ return true;
250
+ }
251
+
252
+ return false;
253
+ }
254
+
255
+ // helper to deal with scrollable parent
256
+ /**
257
+ * Helper method to check parent boundaries for scrollable containers
258
+ * @returns {boolean} True if parent boundaries allow interaction
259
+ * @private
260
+ */
261
+ checkParent() {
262
+ if (this.parent && this.parent.constructor.name === "ScrollFrame" &&
263
+ (this.parent.enableVScroll || this.parent.enableHScroll)) {
264
+ return this.parent.isInside();
265
+ }
266
+ return true;
267
+ }
268
+
269
+
270
+ //finds element recursively among children
271
+ /**
272
+ * Recursively searches for an element in the children tree
273
+ * @param {Component} element - The element to find
274
+ * @returns {boolean} True if element is found in the hierarchy or matches to self
275
+ */
276
+ findElement(element){
277
+ if(element==this){
278
+ return true;
279
+ }
280
+
281
+ for(let i = this.children.length - 1; i >= 0; i--){
282
+ let child = this.children[i];
283
+ if(child.findElement(element)){
284
+ return true;
285
+ }
286
+ }
287
+
288
+ return false;
289
+ }
290
+
291
+ /**
292
+ * Finds the index of a child element
293
+ * @param {Component} element - The child element to find
294
+ * @returns {number} Index of the element or -1 if not found
295
+ */
296
+ findIndexOfElement(element){
297
+ return this.children.findIndex((elem)=> elem===element);
298
+ }
299
+
300
+ // cheking if it or any of its children have ids
301
+ // already present in the map
302
+ // if there is, then the duplicate id is returned
303
+ /**
304
+ * Checks if this component can be safely added to the elements map
305
+ * @param {Map} map - The elements map to check against
306
+ * @returns {string} "<<no_match_found>>" if safe, duplicate ID otherwise
307
+ */
308
+ safeToFillElementsMap(map){
309
+ if(map.has(this.id)){
310
+ return this.id;//false
311
+ }
312
+
313
+ for(let i=0; i<this.children.length; i++){
314
+ let res = this.children[i].safeToFillElementsMap(map);
315
+ if(res!=="<<no_match_found>>"){
316
+ return res;//return the duplicate id
317
+ }
318
+ }
319
+
320
+ return "<<no_match_found>>";//true
321
+ }
322
+ /**
323
+ * Fills the elements map with this component and all children
324
+ * @param {Map} map - The elements map to populate
325
+ * @returns {string} "<<map_filled_successfully>>" or duplicate ID
326
+ */
327
+ fillElementsMap(map){
328
+ let res = this.safeToFillElementsMap(map);
329
+ if(res!=="<<no_match_found>>"){
330
+ return res;//return duplicate id
331
+ }
332
+
333
+ this.fillElementsMapUtil(map);
334
+ return "<<map_filled_successfully>>";
335
+ }
336
+ /**
337
+ * Internal recursive method to populate elements map
338
+ * @param {Map} map - The elements map to populate
339
+ * @private
340
+ */
341
+ fillElementsMapUtil(map){
342
+ if(this.id!==null){
343
+ map.set(this.id, this);
344
+ }
345
+
346
+ for(let i=0; i<this.children.length; i++){
347
+ this.children[i].fillElementsMapUtil(map);
348
+ }
349
+ }
350
+
351
+ //recursively finds element by ID
352
+ /**
353
+ * Recursively finds a component by its ID
354
+ * @param {string} id - The ID to search for
355
+ * @returns {Component|null} The found component or null
356
+ */
357
+ getElementById(id){
358
+ if(id===null){
359
+ return null;
360
+ }
361
+
362
+ if(this.id===id){
363
+ return this;
364
+ }
365
+
366
+ for(let i=0; i<this.children.length; i++){
367
+ let result = this.children[i].getElementById(id);
368
+ if(result){
369
+ return result;
370
+ }
371
+ }
372
+
373
+ return null;
374
+ }
375
+
376
+ //recursively finds target component
377
+ /**
378
+ * Recursively finds the target component for mouse events
379
+ * @returns {Component|null} The deepest component under the mouse
380
+ */
381
+ findTarget(){
382
+ for(let i = this.children.length - 1; i >= 0; i--){
383
+ let child = this.children[i];
384
+ if(child.isInside()){
385
+ return child.findTarget();
386
+ }
387
+ }
388
+
389
+ if(this.isInside()){
390
+ return this;
391
+ }
392
+
393
+ return null;
394
+ }
395
+ /**
396
+ * Checks if this component is a child of the specified element
397
+ * @param {Component} elem - The potential parent element
398
+ * @returns {boolean} True if this is a child of the element
399
+ */
400
+ isChildOf(elem){
401
+ if(!this.parent || !elem){
402
+ return false;
403
+ }
404
+
405
+ if(this.parent === elem){
406
+ return true;
407
+ }
408
+
409
+ return this.parent.isChildOf(elem);
410
+ }
411
+
412
+ //recursively sets root of itself and of its children
413
+ /**
414
+ * Sets the root reference for this component and all children
415
+ * @param {Root} root - The root manager instance
416
+ */
417
+ setRoot(root){
418
+ this.root = root;
419
+
420
+ for(let i=0; i<this.children.length; i++){
421
+ let child = this.children[i];
422
+ child.setRoot(root);
423
+ }
424
+ }
425
+
426
+ }
@@ -0,0 +1,23 @@
1
+ export class GUIEvent {
2
+ /**
3
+ * Creates a base GUI event
4
+ * @param {number} x - The x-coordinate where the event occurred
5
+ * @param {number} y - The y-coordinate where the event occurred
6
+ * @param {string} type - The type of event (e.g., "click", "hover")
7
+ * @param {Component} target - The component that is the target of the event
8
+ */
9
+ constructor(x, y, type, target) {
10
+ this.x = x;
11
+ this.y = y;
12
+ this.type = type;
13
+ this.target = target;
14
+ this.propagationStopped = false;
15
+ }
16
+
17
+ /**
18
+ * Stops the event from propagating to parent components
19
+ */
20
+ stopPropagation() {
21
+ this.propagationStopped = true;
22
+ }
23
+ }
@@ -0,0 +1,14 @@
1
+ import { GUIEvent } from "./GUIEvent.js";
2
+
3
+ export class KeyboardEvent extends GUIEvent{
4
+ /**
5
+ * Creates a keyboard-specific event
6
+ * @param {number} x - The current mouse x-coordinate
7
+ * @param {number} y - The current mouse y-coordinate
8
+ * @param {string} type - The type of keyboard event
9
+ * @param {Component} target - The target component
10
+ */
11
+ constructor(x, y, type, target){
12
+ super(x, y, type, target);
13
+ }
14
+ }
@@ -0,0 +1,22 @@
1
+ import { GUIEvent } from "./GUIEvent.js";
2
+
3
+ export class MouseEvent extends GUIEvent{
4
+ /**
5
+ * Creates a mouse-specific event
6
+ * @param {number} x - The x-coordinate of the mouse event
7
+ * @param {number} y - The y-coordinate of the mouse event
8
+ * @param {string} type - The type of mouse event
9
+ * @param {Component} target - The target component
10
+ * @param {Object} options - Additional options
11
+ * @param {WheelEvent|null} options.event - The original wheel event for scroll events
12
+ */
13
+ constructor(x, y, type, target, {event=null}={}){
14
+ super(x, y, type, target);
15
+ //storing scroll info
16
+ if(event){
17
+ //positive delta value denotes scroll up
18
+ //non-positive denotes scroll down
19
+ this.delta = event.delta;
20
+ }
21
+ }
22
+ }