@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,531 @@
1
+ import { GUIEvent } from '../Core/GUIEvent/GUIEvent.js';
2
+ import { DummyFrame } from './DummyFrame.js';
3
+ import { FrameComponent } from './FrameComponent.js';
4
+
5
+ //frame should allow, closing off -> remove it and all of its childern from the root array
6
+ //frames should also allow docking -> adding frames in frames
7
+
8
+ export class Frame extends FrameComponent{
9
+ /**
10
+ * Creates a resizable and draggable frame container
11
+ * @param {number} x - The x-coordinate
12
+ * @param {number} y - The y-coordinate
13
+ * @param {number} width - The width
14
+ * @param {number} height - The height
15
+ * @param {string|null} id - Component ID
16
+ * @param {p5.Color} backgroundColor - Background color
17
+ * @param {p5.Color} borderColor - Border color
18
+ * @param {p5.Color} highlightedBorderColor - Highlighted border color
19
+ * @param {number} borderWidth - Border width
20
+ * @param {number} cornerRadius - Corner radius
21
+ * @param {number} padx - Horizontal padding
22
+ * @param {number} pady - Vertical padding
23
+ * @param {boolean} alwaysShowBanner - Whether to always show the banner
24
+ * @param {number} bannerHeight - Height of the top banner
25
+ * @param {number} nearestBorderThreshold - Distance threshold for border detection
26
+ * @param {Component|null} parent - Parent component
27
+ * @param {string} type - Component type
28
+ * @param {boolean} enableReposition - Allow dragging
29
+ * @param {boolean} enableOptimisedReposition - Use optimized repositioning
30
+ * @param {boolean} enableResizing - Allow resizing
31
+ * @param {boolean} enableOptimisedResizing - Use optimized resizing
32
+ * @param {boolean} enableShadow - Enable shadow rendering
33
+ * @param {string} shadowColor - Shadow color
34
+ * @param {number} shadowIntensity - Shadow opacity
35
+ * @param {number} shadowSpread - Shadow spread
36
+ * @param {number} shadowDetail - Number of shadow layers
37
+ */
38
+ constructor(x, y, width, height, id, backgroundColor, borderColor, highlightedBorderColor, borderWidth,
39
+ cornerRadius, padx, pady, alwaysShowBanner, bannerHeight, nearestBorderThreshold, parent, type,
40
+ enableReposition, enableOptimisedReposition, enableResizing, enableOptimisedResizing, enableShadow, shadowColor, shadowIntensity, shadowSpread, shadowDetail){
41
+ super(x, y, width, height, {parent: parent, type: type, id: id});
42
+
43
+ this.backgroundColor = backgroundColor;
44
+ this.borderColor = borderColor;
45
+ this.highlightedBorderColor = highlightedBorderColor;
46
+ this.borderWidth = borderWidth;
47
+ this.cornerRadius = cornerRadius;
48
+ this.padx = padx;
49
+ this.pady = pady;
50
+
51
+ this.enableShadow = enableShadow;
52
+ this.enableReposition = enableReposition;
53
+ this.enableOptimisedReposition = enableOptimisedReposition;
54
+ this.enableResizing = enableResizing;
55
+ this.enableOptimisedResizing = enableOptimisedResizing;
56
+ this.alwaysShowBanner = alwaysShowBanner;
57
+
58
+ if(this.enableReposition || this.alwaysShowBanner){
59
+ this.bannerHeight = bannerHeight;
60
+
61
+ if(this.enableReposition){
62
+ this.isBannerShown = false;
63
+ this.xDist = null;
64
+ this.yDist = null;
65
+ this.prevX = null;
66
+ this.prevY = null;
67
+ }
68
+
69
+ if(this.alwaysShowBanner){
70
+ this.isBannerShown = true;
71
+ }
72
+ }
73
+
74
+ if(this.enableResizing){
75
+ this.nearestBorder = null;
76
+ this.nearestBorderThreshold = nearestBorderThreshold;
77
+ }
78
+
79
+ if(this.enableShadow){
80
+ this.shadowColor = shadowColor;//rgb value
81
+ this.shadowIntensity = shadowIntensity;//opacity value between 0 and 1
82
+ this.shadowSpread = shadowSpread;//stroke width of each of those rectangles
83
+ this.shadowDetail = shadowDetail;//number of rectangles that will be drawn around the component
84
+ }
85
+
86
+ this.addEventListener("hover", (event) => this.onMouseHover(event));
87
+ this.addEventListener("mouseLeave", (event)=> this.onMouseLeave(event));
88
+ this.addEventListener("drag", (event)=> this.onMouseDrag(event));
89
+ this.addEventListener("press", (event) => this.onMouseBtnPress(event));
90
+ this.addEventListener("release", (event) => this.onMouseRelease(event));
91
+ this.addEventListener("resize", (event) => this.onResize(event));
92
+ this.addEventListener("reposition", (event) => this.onRepos(event));
93
+ }
94
+
95
+ /**
96
+ * Handles frame resize events
97
+ * @param {GUIEvent} event - The resize event
98
+ */
99
+ onResize(event){
100
+ console.log("resizing...");
101
+ event.stopPropagation();
102
+ }
103
+
104
+ /**
105
+ * Handles frame reposition events
106
+ * @param {GUIEvent} event - The reposition event
107
+ */
108
+ onRepos(event){
109
+ console.log("repositioning...");
110
+ }
111
+
112
+ /**
113
+ * Handles mouse button release events
114
+ * @param {GUIEvent} event - The release event
115
+ */
116
+ onMouseRelease(event){
117
+ if(!this.isOverBannerArea()){
118
+ cursor("");
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Handles mouse leave events
124
+ * @param {GUIEvent} event - The mouse leave event
125
+ */
126
+ onMouseLeave(event){
127
+ // console.log("mouse left...");
128
+ this.clearHoverCache();
129
+ cursor("");
130
+ }
131
+
132
+ /**
133
+ * Checks if cursor is near any border for resizing
134
+ * @returns {boolean} True if cursor is near a border
135
+ */
136
+ isNearBorder(){
137
+ return this.nearestBorder!==null;
138
+ }
139
+
140
+ /**
141
+ * Handles mouse hover events for banner and cursor changes
142
+ * @param {GUIEvent} event - The hover event
143
+ */
144
+ onMouseHover(event){
145
+ // console.log("mouse hovering...");
146
+ if(this.enableResizing) {
147
+ this.checkAndFindNearestBorder();
148
+ if(this.isNearBorder()){
149
+ if(this.isBannerShown){
150
+ this.clearHoverCache({clearResizingCache:false});
151
+ }
152
+ return;
153
+ }
154
+ }
155
+
156
+ if(this.enableReposition && this.isOverBannerArea() && !mouseIsPressed){
157
+ cursor("grab");
158
+ }
159
+
160
+ if(!this.isOverBannerArea()){
161
+ cursor("");
162
+ }
163
+
164
+ if(this.isOverBannerArea() && (this.enableReposition && !this.isBannerShown)) {
165
+ this.showBanner();
166
+ } else {
167
+ if(!this.isOverBannerArea()) {
168
+ if(this.enableReposition && this.isBannerShown){
169
+ this.clearHoverCache({clearResizingCache:false});
170
+ }
171
+
172
+ if(!this.alwaysShowBanner){
173
+ this.hideBanner();
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Handles mouse button press events for dragging and resizing
181
+ * @param {GUIEvent} event - The press event
182
+ */
183
+ onMouseBtnPress(event) {
184
+ if(this.enableResizing && this.isNearBorder()){
185
+ //dummy resize frame
186
+ if(this.enableOptimisedResizing){
187
+ this.createDummyFrame(DummyFrame.RESIZE_DF);
188
+ }
189
+
190
+ return;
191
+ }
192
+
193
+ if(this.isOverBannerArea()){
194
+ if(this.enableReposition) {
195
+ cursor("grabbing");
196
+ this.xDist = mouseX - this.x;
197
+ this.yDist = mouseY - this.y;
198
+
199
+ //dummy reposition frame
200
+ if(this.enableOptimisedReposition){
201
+ this.createDummyFrame(DummyFrame.REPOSITION_DF);
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Checks if the frame is currently being repositioned
209
+ * @returns {boolean} True if repositioning is in progress
210
+ */
211
+ isRepositioning(){
212
+ return (this.xDist!=null && this.yDist!=null);
213
+ }
214
+
215
+ /**
216
+ * Handles mouse drag events for resizing and repositioning
217
+ * @param {GUIEvent} event - The drag event
218
+ */
219
+ onMouseDrag(event){
220
+ if(this.enableResizing){
221
+ if(this.isNearBorder() && !this.isRepositioning()){
222
+ this.updateDimensions();
223
+ this.dispatchTrickleDownEvent(new GUIEvent(event.x, event.y, "resize", this));
224
+ return;
225
+ }
226
+ }
227
+
228
+ if(this.enableReposition && this.isRepositioning()){
229
+ this.updatePosition();
230
+ this.dispatchTrickleDownEvent(new GUIEvent(event.x, event.y, "reposition", this));
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Creates a temporary dummy frame for smooth resizing/repositioning
236
+ * @param {string} type - Type of dummy frame (RESIZE_DF or REPOSITION_DF)
237
+ */
238
+ createDummyFrame(type){
239
+ let DF = new DummyFrame(this.x, this.y, this.width, this.height, type);
240
+ DF.parent = this;
241
+ DF.root = this.root;
242
+
243
+ if(type === DummyFrame.RESIZE_DF){
244
+ DF.nearestBorder = this.nearestBorder;
245
+ } else if(type === DummyFrame.REPOSITION_DF){
246
+ DF.xDist = this.xDist;
247
+ DF.yDist = this.yDist;
248
+ }
249
+
250
+ this.root.add(DF);
251
+ this.root.activeElement = DF;
252
+
253
+ // console.log(DF);
254
+ }
255
+
256
+ /**
257
+ * Checks if mouse is over the banner area
258
+ * @returns {boolean} True if mouse is over the banner
259
+ */
260
+ isOverBannerArea(){
261
+ return (mouseX>this.x && mouseX<this.x+this.width && mouseY>this.y && mouseY<this.y+(this.bannerHeight));
262
+ }
263
+
264
+ /**
265
+ * Utility method for position updates (to be overridden)
266
+ */
267
+ updatePosUtil(){};
268
+
269
+ /**
270
+ * Handles mouse release events for repositioning
271
+ */
272
+ mouseReleasedEventListener(){
273
+ if(this.enableReposition && this.isRepositioning()){
274
+ this.xDist=null;
275
+ this.yDist=null;
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Detects which border is nearest to the cursor for resizing
281
+ */
282
+ checkAndFindNearestBorder(){
283
+ if(mouseX>=this.x && mouseX<=this.x+this.nearestBorderThreshold && mouseY>=this.y+this.cornerRadius && mouseY<=this.y+this.height-this.cornerRadius){
284
+ this.nearestBorder = "left";
285
+ cursor("ew-resize");
286
+ } else if(mouseX>=this.x + this.width - this.nearestBorderThreshold && mouseX<=this.x + this.width && mouseY>=this.y+this.cornerRadius && mouseY<=this.y+this.height-this.cornerRadius){
287
+ this.nearestBorder = "right";
288
+ cursor("ew-resize");
289
+ } else if(mouseY>=this.y && mouseY<=this.y+this.nearestBorderThreshold && mouseX>=this.x+this.cornerRadius && mouseX<=this.x+this.width-this.cornerRadius){
290
+ this.nearestBorder = "top";
291
+ cursor("ns-resize");
292
+ } else if(mouseY>=this.y +this.height - this.nearestBorderThreshold && mouseY<=this.y + this.height && mouseX>=this.x+this.cornerRadius && mouseX<=this.x+this.width-this.cornerRadius){
293
+ this.nearestBorder = "bottom";
294
+ cursor("ns-resize");
295
+ } else if(abs(mouseX-this.x)<=this.nearestBorderThreshold && abs(mouseY-this.y)<=this.nearestBorderThreshold){
296
+ this.nearestBorder = "top-left";
297
+ cursor("nwse-resize");
298
+ } else if(abs(mouseX-(this.x+this.width))<=this.nearestBorderThreshold && abs(mouseY-this.y)<=this.nearestBorderThreshold){
299
+ this.nearestBorder = "top-right";
300
+ cursor("nesw-resize");
301
+ } else if(abs(mouseX-this.x)<=this.nearestBorderThreshold && abs(mouseY - (this.y+this.height))<=this.nearestBorderThreshold){
302
+ this.nearestBorder = "bottom-left";
303
+ cursor("nesw-resize");
304
+ } else if(abs(mouseX-(this.x+this.width))<=this.nearestBorderThreshold && abs(mouseY - (this.y+this.height))<=this.nearestBorderThreshold){
305
+ this.nearestBorder = "bottom-right";
306
+ cursor("nwse-resize");
307
+ } else {
308
+ this.nearestBorder = null;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Draws highlighted borders when cursor is near them
314
+ */
315
+ showHighlightedBorder(){
316
+ push();
317
+ stroke(this.highlightedBorderColor);
318
+ strokeWeight(this.borderWidth+2);
319
+ if(this.nearestBorder=="left"){
320
+ line(this.x, this.y+this.cornerRadius, this.x, this.y+this.height-this.cornerRadius);
321
+ } else if(this.nearestBorder=="right"){
322
+ line(this.x+this.width, this.y+this.cornerRadius, this.x+this.width, this.y+this.height-this.cornerRadius);
323
+ } else if(this.nearestBorder=="top"){
324
+ line(this.x+this.cornerRadius, this.y, this.x+this.width-this.cornerRadius, this.y);
325
+ } else if(this.nearestBorder=="bottom"){
326
+ line(this.x+this.cornerRadius, this.y+this.height, this.x+this.width-this.cornerRadius, this.y+this.height);
327
+ } else {
328
+ noFill();
329
+ strokeWeight(6);
330
+ if(this.nearestBorder=="top-left"){
331
+ arc(this.x+this.cornerRadius, this.y+this.cornerRadius, 2*this.cornerRadius, 2*this.cornerRadius, PI, PI+HALF_PI);
332
+ } else if(this.nearestBorder=="top-right"){
333
+ arc(this.x+this.width-this.cornerRadius, this.y+this.cornerRadius, 2*this.cornerRadius, 2*this.cornerRadius, PI+HALF_PI, TWO_PI);
334
+ } else if(this.nearestBorder=="bottom-left"){
335
+ arc(this.x+this.cornerRadius, this.y+this.height-this.cornerRadius, 2*this.cornerRadius, 2*this.cornerRadius, HALF_PI, PI);
336
+ } else if(this.nearestBorder=="bottom-right"){
337
+ arc(this.x+this.width-this.cornerRadius, this.y+this.height-this.cornerRadius, 2*this.cornerRadius, 2*this.cornerRadius, 0, HALF_PI);
338
+ }
339
+ }
340
+ pop();
341
+ }
342
+
343
+ //corrects position and dimensions of all the child elements so that
344
+ //they fit right in the parent frame
345
+ /**
346
+ * Adjusts child components to fit within the frame's new dimensions
347
+ */
348
+ redraw(){
349
+ if(this.alwaysShowBanner || (this.isOverBannerArea() && this.enableReposition && !this.isBannerShown)){
350
+ this.adjustHeight(this.y + (this.bannerHeight) + this.pady, this.height - (this.bannerHeight) - 2*(this.pady));
351
+ } else {
352
+ this.adjustHeight(this.y+this.pady, this.height-2*this.pady);
353
+ }
354
+
355
+ this.adjustWidth(this.x+this.padx, this.width-2*this.padx);
356
+ }
357
+ /**
358
+ * Updates frame dimensions during resizing
359
+ */
360
+ updateDimensions(){
361
+ if(this.nearestBorder=="left" || this.nearestBorder=="right"){
362
+ if( this.nearestBorder=="left"){
363
+ if(this.x+this.width-mouseX>=this.bannerHeight){
364
+ this.width = this.x + this.width - mouseX;
365
+ this.x = mouseX;
366
+ }
367
+ } else {
368
+ if(mouseX-this.x>=this.bannerHeight){
369
+ this.width = mouseX - this.x;
370
+ }
371
+ }
372
+
373
+ if(this.nearestBorder=="right" && this.xScroll==true){
374
+ return;
375
+ }
376
+
377
+ this.adjustWidth(this.x + this.padx, this.width - 2*(this.padx));
378
+
379
+ } else if(this.nearestBorder=="top"||this.nearestBorder=="bottom"){
380
+ if(this.nearestBorder=="top"){
381
+ if(this.y+this.height-mouseY>=this.bannerHeight){
382
+ this.height =this.y + this.height - mouseY;
383
+ this.y = mouseY;
384
+ }
385
+ } else {
386
+ if(mouseY-this.y>=this.bannerHeight){
387
+ this.height = mouseY - this.y;
388
+ }
389
+ }
390
+
391
+ if(this.yScroll==true && this.nearestBorder=="bottom"){
392
+ return;
393
+ }
394
+
395
+ if(this.alwaysShowBanner){
396
+ this.adjustHeight(this.y + (this.bannerHeight) + this.pady, this.height - (this.bannerHeight) - 2*(this.pady));
397
+ } else {
398
+ this.adjustHeight(this.y+this.pady, this.height-2*this.pady);
399
+ }
400
+
401
+ } else {
402
+ if(this.nearestBorder=="top-left"){
403
+ if(this.x+this.width-mouseX>=50){
404
+ this.width = this.x + this.width - mouseX;
405
+ this.x = mouseX;
406
+ }
407
+
408
+ if(this.y+this.height-mouseY>=50){
409
+ this.height =this.y + this.height - mouseY;
410
+ this.y = mouseY;
411
+ }
412
+ } else if(this.nearestBorder=="top-right"){
413
+ if(mouseX-this.x>=50){
414
+ this.width = mouseX - this.x;
415
+ }
416
+
417
+ if(this.y+this.height-mouseY>=50){
418
+ this.height =this.y + this.height - mouseY;
419
+ this.y = mouseY;
420
+ }
421
+ } else if(this.nearestBorder=="bottom-left"){
422
+ if(this.x+this.width-mouseX>=50){
423
+ this.width = this.x + this.width - mouseX;
424
+ this.x = mouseX;
425
+ }
426
+
427
+ if(mouseY-this.y>=50){
428
+ this.height = mouseY - this.y;
429
+ }
430
+ } else if(this.nearestBorder=="bottom-right"){
431
+ if(mouseX-this.x>=50){
432
+ this.width = mouseX - this.x;
433
+ }
434
+
435
+ if(mouseY-this.y>=50){
436
+ this.height = mouseY - this.y;
437
+ }
438
+ }
439
+
440
+ this.adjustWidth(this.x + this.padx, this.width - 2*(this.padx));
441
+ if(this.alwaysShowBanner){
442
+ this.adjustHeight(this.y + (this.bannerHeight) + this.pady, this.height - (this.bannerHeight) - 2*(this.pady));
443
+ } else {
444
+ this.adjustHeight(this.y+this.pady, this.height-2*this.pady);
445
+ }
446
+ }
447
+ }
448
+ /**
449
+ * Updates frame position during dragging
450
+ */
451
+ updatePosition(){
452
+ this.prevX = this.x;
453
+ this.prevY = this.y;
454
+
455
+ this.x = mouseX - abs(this.xDist);
456
+ this.y = mouseY - abs(this.yDist);
457
+
458
+ if(this.prevX-this.x==0 && this.prevY-this.y==0){
459
+ return;
460
+ }
461
+
462
+ //console.log("(",this.x,",",this.y,")");
463
+
464
+ this.updatePosUtil(this.prevX-this.x, this.prevY-this.y);
465
+ }
466
+ /**
467
+ * Clears hover and interaction cache
468
+ * @param {Object} options - Clear options
469
+ * @param {boolean} options.clearRepositionCache - Clear reposition cache
470
+ * @param {boolean} options.clearResizingCache - Clear resizing cache
471
+ */
472
+ clearHoverCache({clearRepositionCache=true, clearResizingCache=true}={}){
473
+ // console.log("Hover cache cleared...");
474
+ if(clearRepositionCache && this.enableReposition){
475
+ if(!this.alwaysShowBanner){
476
+ this.hideBanner();
477
+ }
478
+
479
+ this.xDist=null;
480
+ this.yDist=null;
481
+ this.prevX=null;
482
+ this.prevY=null;
483
+ // console.log("reposition cache removed...");
484
+ }
485
+
486
+ if(clearResizingCache && this.enableResizing){
487
+ this.nearestBorder=null;
488
+ // console.log("resizing cache removed...");
489
+ }
490
+
491
+ // console.log("");
492
+ }
493
+ /**
494
+ * Converts RGB color string to array
495
+ * @param {string} shadowColor - RGB color string
496
+ * @returns {number[]|null} Array of RGB values or null
497
+ */
498
+ rgbToArray(shadowColor) {
499
+ let match = shadowColor.match(/\d+/g);
500
+ return match ? match.map(Number) : null;
501
+ }
502
+ /**
503
+ * Renders shadow effect around the frame
504
+ * @param {Object} options - Shadow options
505
+ */
506
+ drawShadow({}={}){
507
+ let color = this.rgbToArray(this.shadowColor);
508
+ if(color==null){
509
+ console.log("shadow color value is not in the correct format: rgb(0,0,0)");
510
+ return;
511
+ }
512
+
513
+ if(this.shadowIntensity>1){
514
+ this.shadowIntensity=1;
515
+ console.log("shadow intensity should be between 0 and 1 inclusive.\nAny value given outside of the range will be clipped to the ends.");
516
+ } else if(this.shadowIntensity<0){
517
+ console.log("shadow intensity should be between 0 and 1 inclusive.\nAny value given outside of the range will be clipped to the ends.");
518
+ this.shadowIntensity=0;
519
+ }
520
+
521
+ for(let i=1; i<=this.shadowDetail; i++){
522
+ push();
523
+ noFill();
524
+ let alpha = this.shadowIntensity * pow(1 - i / this.shadowDetail, 2);
525
+ stroke(`rgba(${color[0]}, ${color[1]}, ${color[2]}, ${alpha})`);
526
+ strokeWeight(this.shadowSpread);
527
+ rect(this.x-((i*this.shadowSpread)/2), this.y-((i*this.shadowSpread)/2), this.width+(i*this.shadowSpread), this.height+(i*this.shadowSpread), this.cornerRadius);
528
+ pop();
529
+ }
530
+ }
531
+ }
@@ -0,0 +1,54 @@
1
+ import { Component } from "../Core/Component.js";
2
+
3
+ export class FrameComponent extends Component{
4
+ /**
5
+ * Creates a base frame component for container elements
6
+ * @param {number} x - The x-coordinate
7
+ * @param {number} y - The y-coordinate
8
+ * @param {number} width - The width
9
+ * @param {number} height - The height
10
+ * @param {Object} options - Configuration options
11
+ * @param {Component|null} options.parent - Parent component
12
+ * @param {string} options.type - Component type
13
+ * @param {string|null} options.id - Component ID
14
+ */
15
+ constructor(x, y, width, height, {parent=null, type="", id=null} = {}){
16
+ super(x, y, width, height, {parent: parent, type: type, id: id,});
17
+ }
18
+
19
+ /**
20
+ * Adjusts the height of child components (abstract method)
21
+ * @abstract
22
+ */
23
+ adjustHeight(){};
24
+
25
+ /**
26
+ * Adjusts the width of child components (abstract method)
27
+ * @abstract
28
+ */
29
+ adjustWidth(){};
30
+
31
+ /**
32
+ * Shows the frame banner (abstract method)
33
+ * @abstract
34
+ */
35
+ showBanner(){};
36
+
37
+ /**
38
+ * Hides the frame banner (abstract method)
39
+ * @abstract
40
+ */
41
+ hideBanner(){};
42
+
43
+ /**
44
+ * Updates the position during dragging (abstract method)
45
+ * @abstract
46
+ */
47
+ updatePosition(){};//for dragging
48
+
49
+ /**
50
+ * Adds a child component (abstract method)
51
+ * @abstract
52
+ */
53
+ add(){};
54
+ }