@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,574 @@
1
+ import {Frame} from './Frame.js';
2
+
3
+ export class GridFrame extends Frame{
4
+ /**
5
+ * Creates a grid-based layout container
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 {string|null} options.id - Component ID
12
+ * @param {p5.Color} options.backgroundColor - Background color
13
+ * @param {p5.Color} options.borderColor - Border color
14
+ * @param {p5.Color} options.highlightedBorderColor - Highlighted border color
15
+ * @param {number} options.borderWidth - Border width
16
+ * @param {number} options.cornerRadius - Corner radius
17
+ * @param {number} options.padx - Horizontal padding
18
+ * @param {number} options.pady - Vertical padding
19
+ * @param {boolean} options.alwaysShowBanner - Always show banner
20
+ * @param {number} options.rows - Number of rows in grid
21
+ * @param {number} options.cols - Number of columns in grid
22
+ * @param {number} options.nearestBorderThreshold - Border detection threshold
23
+ * @param {number} options.bannerHeight - Banner height
24
+ * @param {Component|null} options.parent - Parent component
25
+ * @param {boolean} options.enableReposition - Allow dragging
26
+ * @param {boolean} options.enableOptimisedReposition - Optimized repositioning
27
+ * @param {boolean} options.enableResizing - Allow resizing
28
+ * @param {boolean} options.enableOptimisedResizing - Optimized resizing
29
+ * @param {boolean} options.enableShadow - Enable shadow
30
+ * @param {string} options.shadowColor - Shadow color
31
+ * @param {number} options.shadowIntensity - Shadow opacity
32
+ * @param {number} options.shadowSpread - Shadow spread
33
+ * @param {number} options.shadowDetail - Shadow layers
34
+ */
35
+ constructor(x, y, width, height, {
36
+ id=null,
37
+ backgroundColor = color(255),
38
+ borderColor = color(0),
39
+ highlightedBorderColor = color(0),
40
+ borderWidth = 1,
41
+ cornerRadius = 0,
42
+ padx=0,
43
+ pady=0,
44
+ alwaysShowBanner = false,
45
+ rows=1,
46
+ cols=1,
47
+ nearestBorderThreshold=8,
48
+ bannerHeight=35,
49
+ parent=null,
50
+ enableReposition=false,
51
+ enableOptimisedReposition=false,
52
+ enableResizing=false,
53
+ enableOptimisedResizing=false,
54
+ enableShadow=false,
55
+ shadowColor= 'rgb(0,0,0)',
56
+ shadowIntensity = 0.3,
57
+ shadowSpread = 1,
58
+ shadowDetail = 10,
59
+ }
60
+ ){
61
+ bannerHeight = bannerHeight%height;
62
+ super(x, y, width, height, id, backgroundColor, borderColor, highlightedBorderColor, borderWidth,
63
+ cornerRadius, padx, pady, alwaysShowBanner, bannerHeight, nearestBorderThreshold, parent, "Frame",
64
+ enableReposition, enableOptimisedReposition, enableResizing, enableOptimisedResizing, enableShadow, shadowColor, shadowIntensity, shadowSpread, shadowDetail);
65
+
66
+ //for storing child elements
67
+ this.rows=rows;
68
+ this.cols=cols;
69
+ this.rowWeights = [];
70
+ this.colWeights = [];
71
+ this.grid=null;
72
+ this.totalRowWeight = 0;
73
+ this.totalColWeight = 0;
74
+ }
75
+
76
+ /**
77
+ * Adds an element to the grid at specified position
78
+ * @param {Component} element - The component to add
79
+ * @param {number} row - Grid row index
80
+ * @param {number} col - Grid column index
81
+ * @param {Object} options - Placement options
82
+ * @param {number} options.rowSpan - Number of rows to span
83
+ * @param {number} options.colSpan - Number of columns to span
84
+ * @param {number} options.padL - Left padding
85
+ * @param {number} options.padR - Right padding
86
+ * @param {number} options.padT - Top padding
87
+ * @param {number} options.padB - Bottom padding
88
+ */
89
+ add(element, row, col, {
90
+ rowSpan=1,
91
+ colSpan=1,
92
+ padL=0,
93
+ padR=0,
94
+ padT=0,
95
+ padB=0
96
+ }={})
97
+ {
98
+ //check if grid is configured or not
99
+ if(this.grid===null){
100
+ console.log("element can't be added: gird not configured,");
101
+ console.log("call .gridConfig(rows, cols) to configure grid before adding any elements!");
102
+ return;
103
+ }
104
+
105
+ if(element===null){
106
+ console.log("element to add can't be null");
107
+ return;
108
+ }
109
+
110
+ if(this.findElement(element)){
111
+ console.log(`the component (id: ${element.id}) is already added to the Gridframe (${this.id})`);
112
+ console.log("component: ", element, "\nGridFrame: ", this);
113
+ return;
114
+ }
115
+
116
+ if(row<0 || row>=this.rows || col<0 || col>=this.cols){
117
+ console.log("index out of range; can't add the element at (",row,",",col,")");
118
+ return;
119
+ }
120
+
121
+ if(this.grid[row][col]!=null){
122
+ console.log("Grid cell (",row,",",col,") already taken");
123
+ return;
124
+ }
125
+
126
+ if(this.getElementById(element.id)){
127
+ console.log(`component with duplicate id (${element.id}) found in ${this.constructor.name}; component (${element.constructor.name}) can't be added!`);
128
+ console.log(this);
129
+ console.log("");
130
+ return;
131
+ }
132
+
133
+ element.turnResizingAndRepositionOff();
134
+ element.parent=this;
135
+ this.children.push(element);
136
+ this.grid[row][col] = [element, rowSpan, colSpan, padL, padR, padT, padB];
137
+ this.adjustToGrid(row, col, rowSpan, colSpan);
138
+
139
+ // this.printGrid();
140
+ }
141
+
142
+ /**
143
+ * Finds the grid position of an element
144
+ * @param {Component} element - The element to find
145
+ * @returns {number[]|null} [row, col] or null if not found
146
+ */
147
+ getElementPos(element){
148
+ for(let i=0; i<this.rows; i++){
149
+ for(let j=0; j<this.cols; j++){
150
+ if(this.grid[i][j][0]===element)
151
+ return [i, j];
152
+ }
153
+ }
154
+
155
+ return null;
156
+ }
157
+
158
+ /**
159
+ * Removes an element from the grid
160
+ * @param {Component} element - The element to remove
161
+ */
162
+ remove(element){
163
+ let index = this.findIndexOfElement(element);
164
+ if(index==-1){
165
+ console.log(`element (id: ${element.id}) can't be removed from ScrollFrame (id: ${this.id})
166
+ because it was not found in immediate children!`);
167
+ return;
168
+ }
169
+
170
+ element.parent = null;
171
+ let res = this.getElementPos(element);
172
+ // console.log(res);
173
+ let x = res[0];
174
+ let y = res[1];
175
+ let rowSpan = this.grid[x][y][1];
176
+ let colSpan = this.grid[x][y][2];
177
+ //reclaiming the space taken up by the
178
+ //element in the grid
179
+ if(res !== null){
180
+ for(let i=x; i < x+rowSpan; i++){
181
+ for(let j=y; j< y+colSpan; j++){
182
+ this.grid[i][j] = null;
183
+ }
184
+ }
185
+ }
186
+
187
+ this.removeChild(element);
188
+ this.redraw();
189
+ console.log(`element (id: ${element.id}) successfully removed from ${this.constructor.name} (id: ${this.id})!`);
190
+ // this.printGrid();
191
+ }
192
+
193
+ /**
194
+ * Configures row weight for proportional sizing
195
+ * @param {number} rowNum - Row index to configure
196
+ * @param {number} weight - Weight value for proportional sizing
197
+ */
198
+ rowConfig(rowNum, weight){
199
+ if(rowNum<0 || rowNum>=this.rows){
200
+ return;
201
+ }
202
+ if(this.rowWeights[rowNum]!=null){
203
+ this.totalRowWeight-=this.rowWeights[rowNum];
204
+ }
205
+ this.rowWeights[rowNum] = weight;
206
+ this.totalRowWeight+=weight;
207
+ }
208
+
209
+ /**
210
+ * Configures column weight for proportional sizing
211
+ * @param {number} colNum - Column index to configure
212
+ * @param {number} weight - Weight value for proportional sizing
213
+ */
214
+ colConfig(colNum, weight){
215
+ if(colNum<0 || colNum>=this.cols){
216
+ return;
217
+ }
218
+ if(this.colWeights[colNum]!=null){
219
+ this.totalColWeight-=this.colWeights[colNum];
220
+ }
221
+ this.colWeights[colNum] = weight;
222
+ this.totalColWeight+=weight;
223
+ }
224
+
225
+ /**
226
+ * Initializes the grid structure with specified rows and columns
227
+ * @param {number} rows - Number of rows
228
+ * @param {number} cols - Number of columns
229
+ */
230
+ gridConfig(rows, cols){
231
+ this.rows = rows;
232
+ this.cols = cols;
233
+
234
+ for(let i=0; i<this.rows; i++){
235
+ this.rowConfig(i, 1);
236
+ }
237
+
238
+ for(let i=0; i<this.cols; i++){
239
+ this.colConfig(i, 1);
240
+ }
241
+
242
+ this.grid = new Array(this.rows);
243
+ for(let i=0; i<this.rows; i++){
244
+ this.grid[i] = new Array(this.cols);
245
+ }
246
+
247
+ for(let i=0; i<this.rows; i++){
248
+ for(let j=0; j<this.cols; j++){
249
+ this.grid[i][j]=null;
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Adjusts child component widths based on grid weights
256
+ * @param {number} x - Starting x position
257
+ * @param {number} w - Available width
258
+ */
259
+ adjustWidth(x, w){
260
+ //[element, rowSpan, colSpan, padL, padR, padT, padB];
261
+ for(let i=0; i<this.cols; i++){
262
+ for(let j=0; j<this.rows; j++){
263
+ if(this.grid && this.grid[j][i]!=null && this.grid[j][i]!="taken"){
264
+ let curr = this.grid[j][i];
265
+
266
+ curr[0].x = x + curr[3];
267
+ curr[0].width = 0;
268
+
269
+ for(let k=0; k<curr[2]; k++){
270
+ curr[0].width += (this.colWeights[i+k]/this.totalColWeight) * w;
271
+ }
272
+
273
+ curr[0].width -= curr[3] + curr[4];
274
+
275
+ if(curr[0].type=="Frame"){
276
+ curr[0].adjustWidth(curr[0].x + curr[0].padx, curr[0].width - 2*curr[0].padx);
277
+ } else {
278
+ curr[0].updateWidth();
279
+ }
280
+
281
+ }
282
+ }
283
+
284
+ x += (this.colWeights[i]/this.totalColWeight) * w;
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Adjusts child component heights based on grid weights
290
+ * @param {number} y - Starting y position
291
+ * @param {number} h - Available height
292
+ */
293
+ adjustHeight(y, h){
294
+ //[element, rowSpan, colSpan, padL, padR, padT, padB];
295
+ for(let i=0; i<this.rows; i++){
296
+ for(let j=0; j<this.cols; j++){
297
+ if(this.grid && this.grid[i][j]!=null && this.grid[i][j]!="taken"){
298
+ let curr = this.grid[i][j];
299
+
300
+ curr[0].y = y + curr[5];
301
+ curr[0].height = 0;
302
+
303
+ for(let k=0; k<curr[1]; k++){
304
+ curr[0].height += (this.rowWeights[i+k]/this.totalRowWeight) * h;
305
+ }
306
+
307
+ curr[0].height -= curr[5] + curr[6];
308
+
309
+ //console.log("curr[0].type:"+curr[0].type);
310
+
311
+ if(curr[0].type=="Frame"){
312
+ curr[0].adjustHeight(curr[0].y + curr[0].pady, curr[0].height - 2*curr[0].pady);
313
+ } else {
314
+ curr[0].updateHeight();
315
+ }
316
+ }
317
+ }
318
+
319
+ y += (this.rowWeights[i]/this.totalRowWeight) * h;
320
+ }
321
+ }
322
+
323
+ //only called when a new element is added
324
+ //to make the element fit into the grid cell
325
+ /**
326
+ * Positions a new element within the grid cell
327
+ * @param {number} row - Row index
328
+ * @param {number} col - Column index
329
+ * @param {number} rowSpan - Row span
330
+ * @param {number} colSpan - Column span
331
+ */
332
+ adjustToGrid(row, col, rowSpan, colSpan){
333
+ let x = this.x + this.padx;
334
+ let y = this.y + this.pady;
335
+ if(this.alwaysShowBanner){
336
+ y += this.bannerHeight;
337
+ }
338
+
339
+ let w = this.width - 2*this.padx;
340
+ let h = this.height - 2*this.pady;
341
+ if(this.alwaysShowBanner){
342
+ h -= this.bannerHeight;
343
+ }
344
+
345
+ for(let i=0; i<col; i++){
346
+ x += (this.colWeights[i]/this.totalColWeight) * w;
347
+ }
348
+
349
+ for(let i=0; i<row; i++){
350
+ y += (this.rowWeights[i]/this.totalRowWeight) * h;
351
+ }
352
+
353
+ this.grid[row][col][0].x = x + this.grid[row][col][3];
354
+ this.grid[row][col][0].y = y + this.grid[row][col][5];
355
+
356
+ this.expandElement(row, col, rowSpan, colSpan);
357
+
358
+ //this.storeProportions(row, col);
359
+
360
+ if(this.grid[row][col][0].constructor.name=="GridFrame"){
361
+ this.grid[row][col][0].adjustWidth(this.grid[row][col][0].x + this.grid[row][col][0].padx, this.grid[row][col][0].width - 2*this.grid[row][col][0].padx);
362
+ this.grid[row][col][0].adjustHeight(this.grid[row][col][0].y + this.grid[row][col][0].pady, this.grid[row][col][0].height - 2*this.grid[row][col][0].pady);
363
+ } else if(this.grid[row][col][0].constructor.name=="ScrollFrame"){
364
+ this.grid[row][col][0].redraw();
365
+ } else {
366
+ this.grid[row][col][0].updateWidth();
367
+ if(this.grid[row][col][0].constructor.name!="Label"){
368
+ this.grid[row][col][0].updateHeight();
369
+ }
370
+ }
371
+ }
372
+
373
+ //expand the element to as much grid cells as possible
374
+ //while keeping the desired row and col spans in consideration
375
+ /**
376
+ * Expands element to span multiple grid cells
377
+ * @param {number} row - Starting row
378
+ * @param {number} col - Starting column
379
+ * @param {number} rowSpan - Desired row span
380
+ * @param {number} colSpan - Desired column span
381
+ */
382
+ expandElement(row, col, rowSpan, colSpan){
383
+ let xLimit = 0;
384
+ let yLimit = 0;
385
+ let rBoundary = rowSpan+row-1;
386
+ let cBoundary = colSpan+col-1;
387
+
388
+ if(rBoundary>=this.rows){
389
+ rBoundary = this.rows-1;
390
+ }
391
+
392
+ if(cBoundary>=this.cols){
393
+ cBoundary = this.cols-1;
394
+ }
395
+
396
+ for(let i=row+1; i<=rBoundary; i++){
397
+ if(this.grid[i][col]==null){
398
+ yLimit++;
399
+ } else {
400
+ console.log("can't expand beyond row ",row+yLimit," since there's a non-null element in row ",row+yLimit+1);
401
+ break;
402
+ }
403
+ }
404
+
405
+ let breakFlag=false;
406
+ for(let j=col+1; j<=cBoundary; j++){
407
+ xLimit++;
408
+ for(let i=row; i<=yLimit+row; i++){
409
+ if(this.grid && this.grid[i][j]!=null){
410
+ if(xLimit>0){
411
+ xLimit--;
412
+ console.log("can't expand beyond col ",col+xLimit," since there's a non-null element in col ",col+xLimit+1);
413
+ }
414
+ breakFlag=true;
415
+ break;
416
+ }
417
+ }
418
+ if(breakFlag){
419
+ break;
420
+ }
421
+ }
422
+
423
+ let w=0;
424
+ let h=0;
425
+ for(let i=row; i<=row+yLimit; i++){
426
+ if(this.alwaysShowBanner){
427
+ h += (this.rowWeights[i]/this.totalRowWeight) * (this.height - this.bannerHeight - 2*this.pady);
428
+ } else {
429
+ h += (this.rowWeights[i]/this.totalRowWeight) * (this.height - 2*this.pady);
430
+ }
431
+ }
432
+
433
+ for(let j=col; j<=col+xLimit; j++){
434
+ w += (this.colWeights[j]/this.totalColWeight) * (this.width - 2*this.padx);
435
+ }
436
+
437
+ for(let i=row; i<=row+yLimit; i++){
438
+ for(let j=col; j<=col+xLimit; j++){
439
+ if(this.grid[i][j]==null){
440
+ this.grid[i][j]="taken";
441
+ }
442
+ }
443
+ }
444
+
445
+ this.grid[row][col][0].width = w - this.grid[row][col][3] - this.grid[row][col][4];
446
+ this.grid[row][col][0].height = h - this.grid[row][col][5] - this.grid[row][col][6];
447
+ this.grid[row][col][1] = yLimit+1;
448
+ this.grid[row][col][2] = xLimit+1;
449
+ }
450
+
451
+ /**
452
+ * Shows the frame banner and adjusts layout
453
+ */
454
+ showBanner(){
455
+ this.adjustHeight(this.y + (this.bannerHeight) + this.pady, this.height - (this.bannerHeight) - 2*(this.pady));
456
+ this.isBannerShown=true;
457
+ }
458
+
459
+ /**
460
+ * Hides the frame banner and adjusts layout
461
+ */
462
+ hideBanner(){
463
+ this.adjustHeight(this.y + this.pady, this.height-2*(this.pady));
464
+ this.isBannerShown=false;
465
+ }
466
+
467
+ /**
468
+ * Updates child component positions during frame movement
469
+ * @param {number} xDiff - X position difference
470
+ * @param {number} yDiff - Y position difference
471
+ */
472
+ updatePosUtil(xDiff, yDiff){
473
+ for(let i=0; i<this.rows; i++){
474
+ for(let j=0; j<this.cols; j++){
475
+ if(this.grid && this.grid[i][j]!=null && this.grid[i][j]!="taken"){
476
+ this.grid[i][j][0].x -= xDiff;
477
+ this.grid[i][j][0].y -= yDiff;
478
+ if(this.grid[i][j][0].type=="Frame"){
479
+ this.grid[i][j][0].updatePosUtil(xDiff, yDiff);
480
+ }
481
+ }
482
+ }
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Renders the grid frame and all child components
488
+ */
489
+ show(){
490
+
491
+ //shadow
492
+ if(this.enableShadow){
493
+ this.drawShadow();
494
+ }
495
+
496
+ //applying background color
497
+ if(this.backgroundColor!=null){
498
+ push();
499
+ noStroke();
500
+ fill(this.backgroundColor);
501
+ rect(this.x, this.y, this.width, this.height, this.cornerRadius);
502
+ pop();
503
+ }
504
+
505
+ //applying clipping mask
506
+ push();
507
+ beginClip();
508
+ rect(this.x, this.y, this.width, this.height, this.cornerRadius);
509
+ endClip();
510
+
511
+ //displaying all the child elements
512
+ for(let i=0; i<this.rows; i++){
513
+ for(let j=0; j<this.cols; j++){
514
+ if(this.grid && this.grid[i][j]!=null && this.grid[i][j]!="taken"){
515
+ this.grid[i][j][0].show();
516
+ }
517
+ }
518
+ }
519
+
520
+ //showing the top banner
521
+ if(this.alwaysShowBanner || (this.enableReposition && this.isBannerShown==true)){
522
+ noStroke();
523
+ fill(0);
524
+ rect(this.x, this.y, this.width, this.bannerHeight);
525
+
526
+ fill(255);
527
+ ellipse(this.x+this.width/2,
528
+ this.y+(this.bannerHeight)/2,
529
+ (this.bannerHeight)/4,
530
+ (this.bannerHeight)/4);
531
+ ellipse(this.x+this.width/2 - (this.bannerHeight)/2,
532
+ this.y+(this.bannerHeight)/2, (this.bannerHeight)/4,
533
+ (this.bannerHeight)/4);
534
+ ellipse(this.x+this.width/2 + (this.bannerHeight)/2,
535
+ this.y+(this.bannerHeight)/2,
536
+ (this.bannerHeight)/4,
537
+ (this.bannerHeight)/4);
538
+ }
539
+
540
+ pop();
541
+
542
+ //showing the border
543
+ if(this.borderColor!=null){
544
+ push();
545
+ stroke(this.borderColor);
546
+ strokeWeight(this.borderWidth);
547
+ noFill();
548
+ rect(this.x, this.y, this.width, this.height, this.cornerRadius);
549
+ pop();
550
+ }
551
+
552
+ //highlighting the relevant border if cursor is sufficiently near to it
553
+ // if(this.enableResizing && this.nearestBorder!=null){
554
+ // this.showHighlightedBorder();
555
+ // }
556
+ }
557
+
558
+ /**
559
+ * Prints grid structure to console for debugging
560
+ */
561
+ printGrid(){
562
+ for(let i=0; i<this.rows; i++){
563
+ let row = "";
564
+ for(let j=0; j<this.cols; j++){
565
+ if(this.grid[i][j]){
566
+ row += "* ";
567
+ } else {
568
+ row += "_ ";
569
+ }
570
+ }
571
+ console.log(row);
572
+ }
573
+ }
574
+ }