@usman404/crowjs 1.0.4 → 1.1.1

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/Component.js CHANGED
@@ -10,8 +10,15 @@ export class Component{
10
10
  * @param {Object} options - Configuration options
11
11
  * @param {string} options.type - The type identifier of the component
12
12
  * @param {string|null} options.id - Unique identifier for the component
13
+ * @param {number} options.margin - General margin for all sides
14
+ * @param {number} options.marginx - Horizontal margin (left and right)
15
+ * @param {number} options.marginy - Vertical margin (top and bottom)
16
+ * @param {number} options.marginl - Left margin
17
+ * @param {number} options.marginr - Right margin
18
+ * @param {number} options.margint - Top margin
19
+ * @param {number} options.marginb - Bottom margin
13
20
  */
14
- constructor(x, y, width, height, {type="", id=null} = {}){
21
+ constructor(x, y, width, height, {type="", id=null, margin=0, marginx=null, marginy=null, marginl=null, marginr=null, margint=null, marginb=null, minWidth=0, minHeight=0, showDebugOverlay=false} = {}){
15
22
  this.x = x;
16
23
  this.y = y;
17
24
  this.id = id;
@@ -22,6 +29,74 @@ export class Component{
22
29
  this.root = null;
23
30
  this.eventListeners = {};
24
31
  this.children = [];
32
+ this.showDebugOverlay = showDebugOverlay;
33
+
34
+ // Margin resolution
35
+ const resolvedMarginx = (marginx ?? margin ?? 0);
36
+ const resolvedMarginy = (marginy ?? margin ?? 0);
37
+ this.margin = margin;
38
+ this.marginx = resolvedMarginx;
39
+ this.marginy = resolvedMarginy;
40
+ this.marginl = marginl ?? resolvedMarginx;
41
+ this.marginr = marginr ?? resolvedMarginx;
42
+ this.margint = margint ?? resolvedMarginy;
43
+ this.marginb = marginb ?? resolvedMarginy;
44
+
45
+ // Minimum dimensions
46
+ this.minWidth = minWidth;
47
+ this.minHeight = minHeight;
48
+ }
49
+
50
+ /**
51
+ * Returns the x-coordinate including left margin (outer bound)
52
+ * @returns {number}
53
+ */
54
+ get outerX() {
55
+ return this.x - this.marginl;
56
+ }
57
+
58
+ /**
59
+ * Returns the y-coordinate including top margin (outer bound)
60
+ * @returns {number}
61
+ */
62
+ get outerY() {
63
+ return this.y - this.margint;
64
+ }
65
+
66
+ /**
67
+ * Returns the total width including left and right margins
68
+ * @returns {number}
69
+ */
70
+ get outerWidth() {
71
+ return this.width + this.marginl + this.marginr;
72
+ }
73
+
74
+ /**
75
+ * Returns the total height including top and bottom margins
76
+ * @returns {number}
77
+ */
78
+ get outerHeight() {
79
+ return this.height + this.margint + this.marginb;
80
+ }
81
+
82
+ /**
83
+ * Returns the effective minimum width, accounting for children's constraints.
84
+ * Subclasses (e.g. GridFrame, ScrollFrame) override this to compute
85
+ * the minimum from their children's requirements.
86
+ * @returns {number}
87
+ */
88
+ getEffectiveMinWidth() {
89
+ return this.minWidth;
90
+ }
91
+
92
+ /**
93
+ * Returns the effective minimum height, accounting for children's constraints.
94
+ * Subclasses (e.g. GridFrame, ScrollFrame) override this to compute
95
+ * the minimum from their children's requirements.
96
+ * @returns {number}
97
+ */
98
+ getEffectiveMinHeight() {
99
+ return this.minHeight;
25
100
  }
26
101
 
27
102
  //to show itself
@@ -423,4 +498,135 @@ export class Component{
423
498
  }
424
499
  }
425
500
 
501
+ /**
502
+ * Draws debug overlay for margin and padding visualization.
503
+ * Margin is shown in orange, padding in blue, both with low opacity.
504
+ * Small numbers are drawn when there is enough room.
505
+ */
506
+ drawDebugOverlay(){
507
+ const marginColor = [255, 140, 0, 80]; // orange, low opacity
508
+ const marginBorder = [255, 100, 0]; // orange, high contrast border
509
+ const paddingColor = [0, 120, 255, 80]; // blue, low opacity
510
+ const paddingBorder = [0, 80, 220]; // blue, high contrast border
511
+ const borderWeight = 1.5;
512
+ const labelColor = [0, 0, 0, 200];
513
+ const minLabelSize = 8;
514
+ const labelFontSize = 9;
515
+
516
+ push();
517
+
518
+ // --- Margin areas (orange) ---
519
+ // Top margin
520
+ if(this.margint > 0){
521
+ fill(...marginColor);
522
+ stroke(...marginBorder);
523
+ strokeWeight(borderWeight);
524
+ rect(this.x, this.y - this.margint, this.width, this.margint);
525
+ this._drawDebugLabel(this.margint, this.x, this.y - this.margint, this.width, this.margint, labelColor, labelFontSize, minLabelSize);
526
+ }
527
+ // Bottom margin
528
+ if(this.marginb > 0){
529
+ fill(...marginColor);
530
+ stroke(...marginBorder);
531
+ strokeWeight(borderWeight);
532
+ rect(this.x, this.y + this.height, this.width, this.marginb);
533
+ this._drawDebugLabel(this.marginb, this.x, this.y + this.height, this.width, this.marginb, labelColor, labelFontSize, minLabelSize);
534
+ }
535
+ // Left margin (full outer height including top/bottom margins)
536
+ if(this.marginl > 0){
537
+ fill(...marginColor);
538
+ stroke(...marginBorder);
539
+ strokeWeight(borderWeight);
540
+ rect(this.x - this.marginl, this.y - this.margint, this.marginl, this.height + this.margint + this.marginb);
541
+ this._drawDebugLabel(this.marginl, this.x - this.marginl, this.y - this.margint, this.marginl, this.height + this.margint + this.marginb, labelColor, labelFontSize, minLabelSize);
542
+ }
543
+ // Right margin (full outer height including top/bottom margins)
544
+ if(this.marginr > 0){
545
+ fill(...marginColor);
546
+ stroke(...marginBorder);
547
+ strokeWeight(borderWeight);
548
+ rect(this.x + this.width, this.y - this.margint, this.marginr, this.height + this.margint + this.marginb);
549
+ this._drawDebugLabel(this.marginr, this.x + this.width, this.y - this.margint, this.marginr, this.height + this.margint + this.marginb, labelColor, labelFontSize, minLabelSize);
550
+ }
551
+
552
+ // --- Padding areas (blue) ---
553
+ // Resolve padding values: TextComponent uses padl/padr/padt/padb, Frame uses padx/pady
554
+ let pl = this.padl ?? this.padx ?? 0;
555
+ let pr = this.padr ?? this.padx ?? 0;
556
+ let pt = this.padt ?? this.pady ?? 0;
557
+ let pb = this.padb ?? this.pady ?? 0;
558
+
559
+ // Top padding
560
+ if(pt > 0){
561
+ fill(...paddingColor);
562
+ stroke(...paddingBorder);
563
+ strokeWeight(borderWeight);
564
+ rect(this.x, this.y, this.width, pt);
565
+ this._drawDebugLabel(pt, this.x, this.y, this.width, pt, labelColor, labelFontSize, minLabelSize);
566
+ }
567
+ // Bottom padding
568
+ if(pb > 0){
569
+ fill(...paddingColor);
570
+ stroke(...paddingBorder);
571
+ strokeWeight(borderWeight);
572
+ rect(this.x, this.y + this.height - pb, this.width, pb);
573
+ this._drawDebugLabel(pb, this.x, this.y + this.height - pb, this.width, pb, labelColor, labelFontSize, minLabelSize);
574
+ }
575
+ // Left padding (between top and bottom padding)
576
+ if(pl > 0){
577
+ fill(...paddingColor);
578
+ stroke(...paddingBorder);
579
+ strokeWeight(borderWeight);
580
+ rect(this.x, this.y + pt, pl, this.height - pt - pb);
581
+ this._drawDebugLabel(pl, this.x, this.y + pt, pl, this.height - pt - pb, labelColor, labelFontSize, minLabelSize);
582
+ }
583
+ // Right padding (between top and bottom padding)
584
+ if(pr > 0){
585
+ fill(...paddingColor);
586
+ stroke(...paddingBorder);
587
+ strokeWeight(borderWeight);
588
+ rect(this.x + this.width - pr, this.y + pt, pr, this.height - pt - pb);
589
+ this._drawDebugLabel(pr, this.x + this.width - pr, this.y + pt, pr, this.height - pt - pb, labelColor, labelFontSize, minLabelSize);
590
+ }
591
+
592
+ pop();
593
+ }
594
+
595
+ /**
596
+ * Draws a small numeric label centered in a debug overlay region if there's enough room
597
+ * @param {number} value - The numeric value to display
598
+ * @param {number} rx - Region x
599
+ * @param {number} ry - Region y
600
+ * @param {number} rw - Region width
601
+ * @param {number} rh - Region height
602
+ * @param {number[]} labelColor - RGBA color array for text
603
+ * @param {number} fontSize - Font size for the label
604
+ * @param {number} minSize - Minimum region dimension to show label
605
+ * @private
606
+ */
607
+ _drawDebugLabel(value, rx, ry, rw, rh, labelColor, fontSize, minSize){
608
+ if(rw < minSize || rh < minSize) return;
609
+ push();
610
+ fill(...labelColor);
611
+ noStroke();
612
+ textSize(fontSize);
613
+ textAlign(CENTER, CENTER);
614
+ text(Math.round(value), rx + rw / 2, ry + rh / 2);
615
+ pop();
616
+ }
617
+
618
+ /**
619
+ * Recursively draws debug overlays for this component and all children.
620
+ * Draws if the component's own showDebugOverlay is true, or if the root's showDebugOverlay is true.
621
+ */
622
+ drawDebugOverlayRecursive(){
623
+ const rootDebug = this.root && this.root.showDebugOverlay;
624
+ if(this.showDebugOverlay || rootDebug){
625
+ this.drawDebugOverlay();
626
+ }
627
+ for(let i = 0; i < this.children.length; i++){
628
+ this.children[i].drawDebugOverlayRecursive();
629
+ }
630
+ }
631
+
426
632
  }
@@ -8,7 +8,8 @@ export class KeyboardEvent extends GUIEvent{
8
8
  * @param {string} type - The type of keyboard event
9
9
  * @param {Component} target - The target component
10
10
  */
11
- constructor(x, y, type, target){
11
+ constructor(x, y, type, target, nativeEvent){
12
12
  super(x, y, type, target);
13
+ this.nativeEvent = nativeEvent || null;
13
14
  }
14
15
  }
package/Core/Root.js CHANGED
@@ -5,8 +5,10 @@ import { Component } from "./Component.js";
5
5
  export class Root{
6
6
  /**
7
7
  * Creates the root manager that handles all GUI components and events
8
+ * @param {Object} options - Configuration options
9
+ * @param {boolean} options.showDebugOverlay - When true, visualizes margin (orange) and padding (blue) for all components
8
10
  */
9
- constructor(){
11
+ constructor({showDebugOverlay = false} = {}){
10
12
  /** @type {Array<Component>} */
11
13
  this.layers = [];//determines order of display
12
14
  this.lastActiveElement = -2;
@@ -17,6 +19,8 @@ export class Root{
17
19
  this.elementsMap = new Map();//for storing ids
18
20
  // this.preferences = {};
19
21
  this.focusedField = null;//for focusable input fields
22
+ this.showDebugOverlay = showDebugOverlay;//for visualizing padding and margin
23
+ this._pressedInsideFocusedField = false;
20
24
  }
21
25
 
22
26
  /**
@@ -38,12 +42,18 @@ export class Root{
38
42
  }
39
43
 
40
44
  /**
41
- * Renders all components in the correct z-order
45
+ * Renders all components in the correct z-order.
46
+ * When showDebugOverlay is true, also draws margin/padding visualization.
42
47
  */
43
48
  show(){
44
49
  for(let i=0; i<this.layers.length; i++){
45
50
  this.layers[i].show();
46
51
  }
52
+
53
+ // Always run the recursive check; each component decides based on its own flag or root's flag
54
+ for(let i=0; i<this.layers.length; i++){
55
+ this.layers[i].drawDebugOverlayRecursive();
56
+ }
47
57
  }
48
58
 
49
59
  /**
@@ -228,8 +238,8 @@ export class Root{
228
238
  * @param {Event|null} options.e - The original browser event
229
239
  */
230
240
  handleMouseEvent(type, x, y, {e=null}={}){
231
- //currently engaged element
232
- if(mouseIsPressed && !this.isNum(this.activeElement)){
241
+ //currently engaged element (only for continuous events like drag, not for new press/click)
242
+ if(type !== "press" && type !== "click" && mouseIsPressed && !this.isNum(this.activeElement)){
233
243
  this.sendToFront(this.activeElement);
234
244
 
235
245
  let target = this.activeElement;
@@ -256,7 +266,15 @@ export class Root{
256
266
  //clicked somewhere outside
257
267
  if(target==null){
258
268
  if(type=="click"||type=="press"){
269
+ // If the press started inside the focused field (drag-select that ended outside), don't blur
270
+ if(type=="click" && this._pressedInsideFocusedField){
271
+ this._pressedInsideFocusedField = false;
272
+ this.activeElement = -1;
273
+ return;
274
+ }
275
+
259
276
  if(!this.focusedField){
277
+ this.activeElement = -1;
260
278
  return;
261
279
  }
262
280
 
@@ -280,7 +298,19 @@ export class Root{
280
298
  if(!this.focusedField.isFocused){
281
299
  this.focusedField.focus();
282
300
  }
301
+
302
+ // Track that this press originated inside the focused field
303
+ if(type=="press"){
304
+ this._pressedInsideFocusedField = true;
305
+ }
283
306
 
307
+ } else {
308
+ // Pressed/clicked on a non-input component: blur focused field
309
+ if(type=="press" && this.focusedField){
310
+ this.focusedField.blur();
311
+ this.focusedField = null;
312
+ }
313
+ this._pressedInsideFocusedField = false;
284
314
  }
285
315
  }
286
316
  }
@@ -291,33 +321,13 @@ export class Root{
291
321
  * @param {number} x - The x-coordinate
292
322
  * @param {number} y - The y-coordinate
293
323
  */
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;
324
+ handleKeyboardEvent(type, x, y, nativeEvent){
325
+ if(!this.focusedField){
317
326
  return;
318
327
  }
319
328
 
320
- let event = new KeyboardEvent(x, y, type, target);
329
+ let target = this.focusedField;
330
+ let event = new KeyboardEvent(x, y, type, target, nativeEvent);
321
331
  target.dispatchEvent(event);
322
332
  }
323
333
 
@@ -533,8 +543,8 @@ export class Root{
533
543
  * @param {number} x - The current mouse x-coordinate
534
544
  * @param {number} y - The current mouse y-coordinate
535
545
  */
536
- keyPressedEventListeners(x, y){
537
- this.handleKeyboardEvent("keyPress", x, y);
546
+ keyPressedEventListeners(x, y, nativeEvent){
547
+ this.handleKeyboardEvent("keyPress", x, y, nativeEvent);
538
548
  }
539
549
 
540
550
  //runs continously when a key is pressed
@@ -71,74 +71,76 @@ export class DummyFrame extends Component{
71
71
  */
72
72
  updateDimensions(){
73
73
  // console.log("updating dimensions...");
74
+ const effMinW = this.parent.getEffectiveMinWidth();
75
+ const effMinH = this.parent.getEffectiveMinHeight();
74
76
 
75
77
  switch(this.nearestBorder){
76
78
  case "left":
77
- if(this.x+this.width-mouseX>=this.parent.bannerHeight){
79
+ if(this.x+this.width-mouseX>=effMinW){
78
80
  this.width = this.x + this.width - mouseX;
79
81
  this.x = mouseX;
80
82
  }
81
83
 
82
84
  break;
83
85
  case "right":
84
- if(mouseX-this.x>=this.parent.bannerHeight){
86
+ if(mouseX-this.x>=effMinW){
85
87
  this.width = mouseX - this.x;
86
88
  }
87
89
 
88
90
  break;
89
91
  case "top":
90
- if(this.y+this.height-mouseY>=this.parent.bannerHeight){
92
+ if(this.y+this.height-mouseY>=effMinH){
91
93
  this.height =this.y + this.height - mouseY;
92
94
  this.y = mouseY;
93
95
  }
94
96
 
95
97
  break;
96
98
  case "bottom":
97
- if(mouseY-this.y>=this.parent.bannerHeight){
99
+ if(mouseY-this.y>=effMinH){
98
100
  this.height = mouseY - this.y;
99
101
  }
100
102
 
101
103
  break;
102
104
  case "top-left":
103
- if(this.x+this.width-mouseX>=this.parent.bannerHeight){
105
+ if(this.x+this.width-mouseX>=effMinW){
104
106
  this.width = this.x + this.width - mouseX;
105
107
  this.x = mouseX;
106
108
  }
107
109
 
108
- if(this.y+this.height-mouseY>=this.parent.bannerHeight){
110
+ if(this.y+this.height-mouseY>=effMinH){
109
111
  this.height =this.y + this.height - mouseY;
110
112
  this.y = mouseY;
111
113
  }
112
114
 
113
115
  break;
114
116
  case "top-right":
115
- if(mouseX-this.x>=this.parent.bannerHeight){
117
+ if(mouseX-this.x>=effMinW){
116
118
  this.width = mouseX - this.x;
117
119
  }
118
120
 
119
- if(this.y+this.height-mouseY>=this.parent.bannerHeight){
121
+ if(this.y+this.height-mouseY>=effMinH){
120
122
  this.height =this.y + this.height - mouseY;
121
123
  this.y = mouseY;
122
124
  }
123
125
 
124
126
  break;
125
127
  case "bottom-left":
126
- if(this.x+this.width-mouseX>=this.parent.bannerHeight){
128
+ if(this.x+this.width-mouseX>=effMinW){
127
129
  this.width = this.x + this.width - mouseX;
128
130
  this.x = mouseX;
129
131
  }
130
132
 
131
- if(mouseY-this.y>=this.parent.bannerHeight){
133
+ if(mouseY-this.y>=effMinH){
132
134
  this.height = mouseY - this.y;
133
135
  }
134
136
 
135
137
  break;
136
138
  case "bottom-right":
137
- if(mouseX-this.x>=this.parent.bannerHeight){
139
+ if(mouseX-this.x>=effMinW){
138
140
  this.width = mouseX - this.x;
139
141
  }
140
142
 
141
- if(mouseY-this.y>=this.parent.bannerHeight){
143
+ if(mouseY-this.y>=effMinH){
142
144
  this.height = mouseY - this.y;
143
145
  }
144
146