@usman404/crowjs 1.1.2 β 1.2.0
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/CONTRIBUTING.md +66 -0
- package/Frames/GridFrame.js +29 -4
- package/Frames/ScrollFrame.js +75 -2
- package/README.md +84 -82
- package/UIComponents/Checkbox.js +296 -0
- package/UIComponents/RadioButton.js +360 -0
- package/compile_instructions.txt +3 -0
- package/index.js +4 -0
- package/package.json +9 -5
- package/problems.txt +4 -1
- package/sketch.js +17 -13
package/CONTRIBUTING.md
CHANGED
|
@@ -1 +1,67 @@
|
|
|
1
|
+
# Contributing to CrowJS π¦ββ¬
|
|
1
2
|
|
|
3
|
+
First off, thank you for considering contributing to CrowJS! Itβs people like you who make the p5.js ecosystem better for everyone.
|
|
4
|
+
|
|
5
|
+
To maintain code quality and stability, we follow a strict contribution workflow. Please read the guidelines below before getting started.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ποΈ Getting Started
|
|
10
|
+
|
|
11
|
+
### 1. Understand the Structure
|
|
12
|
+
CrowJS is a canvas-native library. Before writing code, please:
|
|
13
|
+
* **Read the Documentation:** Visit [crow-js.vercel.app](https://crow-js.vercel.app/) to understand the component lifecycle and event system.
|
|
14
|
+
* **Explore the Source:** Look at `Core/Root.js` to see how the main engine works and `UIComponents/` to see how existing components (like Buttons or Sliders) are implemented.
|
|
15
|
+
|
|
16
|
+
### 2. Local Setup
|
|
17
|
+
1. Fork the repository.
|
|
18
|
+
2. Clone your fork locally:
|
|
19
|
+
```bash
|
|
20
|
+
git clone [https://github.com/YOUR_USERNAME/CrowJS.git](https://github.com/YOUR_USERNAME/CrowJS.git)
|
|
21
|
+
```
|
|
22
|
+
3. Run `npm install` to set up the development environment (including VitePress for docs).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## π¦ Contribution Workflow
|
|
27
|
+
|
|
28
|
+
**Note:** Direct pushes to the `main` branch are disabled. All changes must come through a Pull Request (PR).
|
|
29
|
+
|
|
30
|
+
### Step 1: Create a Branch
|
|
31
|
+
Always create a descriptive branch for your work:
|
|
32
|
+
```bash
|
|
33
|
+
git checkout -b feature/your-feature-name
|
|
34
|
+
# OR
|
|
35
|
+
git checkout -b fix/issue-description
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Step 2: Make Your Changes
|
|
39
|
+
* Follow the existing code style (clean, documented, and modular).
|
|
40
|
+
* If you add a new component, ensure it inherits from the appropriate base class.
|
|
41
|
+
* Update the documentation in the `docs/` folder if your changes affect the API.
|
|
42
|
+
|
|
43
|
+
### Step 3: Open a Pull Request
|
|
44
|
+
1. Push your branch to your fork.
|
|
45
|
+
2. Open a Pull Request against the CrowJS `main` branch.
|
|
46
|
+
3. **Requirement:** Per our repository settings, at least **one approving review** is required before a PR can be merged.
|
|
47
|
+
4. Ensure all conversations and feedback in the PR are resolved.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## π Coding Guidelines
|
|
52
|
+
* **No DOM Interference:** Remember, CrowJS is canvas-native. Avoid using `document.createElement` or CSS overlays unless absolutely necessary.
|
|
53
|
+
* **Performance:** p5.js sketches run at 60fps. Ensure your component's `draw` (or `show`) methods are highly optimized.
|
|
54
|
+
* **Compatibility:** Ensure your code works with the latest version of p5.js.
|
|
55
|
+
|
|
56
|
+
## π Reporting Bugs
|
|
57
|
+
If you find a bug, please open an **Issue** with:
|
|
58
|
+
* A clear description of the bug.
|
|
59
|
+
* A minimal `sketch.js` example demonstrating the issue.
|
|
60
|
+
* Your browser and OS version.
|
|
61
|
+
|
|
62
|
+
## π‘ Feature Requests
|
|
63
|
+
Have an idea for a new component (like a Color Picker or Graph)? Open an issue with the "Feature Request" label to discuss it before starting work!
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
Created with β€οΈ by [Usman Ali](https://github.com/UsmanAli404)
|
package/Frames/GridFrame.js
CHANGED
|
@@ -134,12 +134,25 @@ export class GridFrame extends Frame{
|
|
|
134
134
|
{
|
|
135
135
|
//check if grid is configured or not
|
|
136
136
|
if(this.grid===null){
|
|
137
|
+
//preserve any user-set weights before auto-configuring
|
|
138
|
+
let savedRowWeights = [...this.rowWeights];
|
|
139
|
+
let savedColWeights = [...this.colWeights];
|
|
140
|
+
|
|
137
141
|
this.gridConfig(this.rows, this.cols);
|
|
138
|
-
console.log(`grid configured automatically [${this.rows}x${this.cols}]`);
|
|
139
142
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
//restore user-set weights
|
|
144
|
+
for(let i=0; i<savedRowWeights.length && i<this.rows; i++){
|
|
145
|
+
if(savedRowWeights[i] != null){
|
|
146
|
+
this.rowConfig(i, savedRowWeights[i]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for(let i=0; i<savedColWeights.length && i<this.cols; i++){
|
|
150
|
+
if(savedColWeights[i] != null){
|
|
151
|
+
this.colConfig(i, savedColWeights[i]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`grid configured automatically [${this.rows}x${this.cols}]`);
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
if(element===null){
|
|
@@ -326,6 +339,18 @@ export class GridFrame extends Frame{
|
|
|
326
339
|
gridConfig(rows, cols){
|
|
327
340
|
this.rows = rows;
|
|
328
341
|
this.cols = cols;
|
|
342
|
+
|
|
343
|
+
//clear stale children
|
|
344
|
+
for(let i=0; i<this.children.length; i++){
|
|
345
|
+
this.children[i].parent = null;
|
|
346
|
+
}
|
|
347
|
+
this.children = [];
|
|
348
|
+
|
|
349
|
+
//reset weights before reconfiguring
|
|
350
|
+
this.rowWeights = [];
|
|
351
|
+
this.colWeights = [];
|
|
352
|
+
this.totalRowWeight = 0;
|
|
353
|
+
this.totalColWeight = 0;
|
|
329
354
|
|
|
330
355
|
for(let i=0; i<this.rows; i++){
|
|
331
356
|
this.rowConfig(i, 1);
|
package/Frames/ScrollFrame.js
CHANGED
|
@@ -299,6 +299,17 @@ export class ScrollFrame extends Frame{
|
|
|
299
299
|
this.totalWeight -= this.preferences[index][0];
|
|
300
300
|
this.preferences = this.preferences.filter((_, i) => i!==index);
|
|
301
301
|
this.removeChild(element);
|
|
302
|
+
|
|
303
|
+
//recalculate scroll tracking indices
|
|
304
|
+
if(this.enableVScroll){
|
|
305
|
+
this.recalcTopper();
|
|
306
|
+
this.recalcDeepest();
|
|
307
|
+
}
|
|
308
|
+
if(this.enableHScroll){
|
|
309
|
+
this.recalcLeftist();
|
|
310
|
+
this.recalcRightist();
|
|
311
|
+
}
|
|
312
|
+
|
|
302
313
|
this.redraw();
|
|
303
314
|
|
|
304
315
|
console.log(`element (id: ${element.id}) successfully removed from ${this.constructor.name} (id: ${this.id})!`);
|
|
@@ -315,14 +326,15 @@ export class ScrollFrame extends Frame{
|
|
|
315
326
|
return;
|
|
316
327
|
}
|
|
317
328
|
|
|
318
|
-
if(weight
|
|
319
|
-
console.log("weight
|
|
329
|
+
if(weight<=0){
|
|
330
|
+
console.log("weight can't be non-positive");
|
|
320
331
|
return;
|
|
321
332
|
}
|
|
322
333
|
|
|
323
334
|
this.totalWeight -= this.preferences[index][0];
|
|
324
335
|
this.totalWeight += weight;
|
|
325
336
|
this.preferences[index][0] = weight;
|
|
337
|
+
this.redraw();
|
|
326
338
|
}
|
|
327
339
|
|
|
328
340
|
/**
|
|
@@ -356,6 +368,7 @@ export class ScrollFrame extends Frame{
|
|
|
356
368
|
}
|
|
357
369
|
|
|
358
370
|
this.preferences[index][1] = padL;
|
|
371
|
+
this.redraw();
|
|
359
372
|
}
|
|
360
373
|
|
|
361
374
|
/**
|
|
@@ -389,6 +402,7 @@ export class ScrollFrame extends Frame{
|
|
|
389
402
|
}
|
|
390
403
|
|
|
391
404
|
this.preferences[index][2] = padR;
|
|
405
|
+
this.redraw();
|
|
392
406
|
}
|
|
393
407
|
|
|
394
408
|
/**
|
|
@@ -422,6 +436,7 @@ export class ScrollFrame extends Frame{
|
|
|
422
436
|
}
|
|
423
437
|
|
|
424
438
|
this.preferences[index][3] = padT;
|
|
439
|
+
this.redraw();
|
|
425
440
|
}
|
|
426
441
|
|
|
427
442
|
/**
|
|
@@ -455,6 +470,7 @@ export class ScrollFrame extends Frame{
|
|
|
455
470
|
}
|
|
456
471
|
|
|
457
472
|
this.preferences[index][4] = padB;
|
|
473
|
+
this.redraw();
|
|
458
474
|
}
|
|
459
475
|
|
|
460
476
|
/**
|
|
@@ -556,6 +572,63 @@ export class ScrollFrame extends Frame{
|
|
|
556
572
|
this.rightist = i;
|
|
557
573
|
}
|
|
558
574
|
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Recalculates the topmost child index by scanning all children
|
|
578
|
+
*/
|
|
579
|
+
recalcTopper(){
|
|
580
|
+
if(this.children.length===0){ this.topper=-1; return; }
|
|
581
|
+
this.topper=0;
|
|
582
|
+
for(let i=1; i<this.children.length; i++){
|
|
583
|
+
if(this.children[i].y + this.preferences[i][3] <
|
|
584
|
+
this.children[this.topper].y + this.preferences[this.topper][3]){
|
|
585
|
+
this.topper=i;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Recalculates the bottommost child index by scanning all children
|
|
592
|
+
*/
|
|
593
|
+
recalcDeepest(){
|
|
594
|
+
if(this.children.length===0){ this.deepest=-1; return; }
|
|
595
|
+
this.deepest=0;
|
|
596
|
+
for(let i=1; i<this.children.length; i++){
|
|
597
|
+
if(this.children[i].y + this.children[i].height - this.preferences[i][4] >
|
|
598
|
+
this.children[this.deepest].y + this.children[this.deepest].height - this.preferences[this.deepest][4]){
|
|
599
|
+
this.deepest=i;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Recalculates the leftmost child index by scanning all children
|
|
606
|
+
*/
|
|
607
|
+
recalcLeftist(){
|
|
608
|
+
if(this.children.length===0){ this.leftist=-1; return; }
|
|
609
|
+
this.leftist=0;
|
|
610
|
+
for(let i=1; i<this.children.length; i++){
|
|
611
|
+
if(this.children[i].x + this.preferences[i][1] <
|
|
612
|
+
this.children[this.leftist].x + this.preferences[this.leftist][1]){
|
|
613
|
+
this.leftist=i;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Recalculates the rightmost child index by scanning all children
|
|
620
|
+
*/
|
|
621
|
+
recalcRightist(){
|
|
622
|
+
if(this.children.length===0){ this.rightist=-1; return; }
|
|
623
|
+
this.rightist=0;
|
|
624
|
+
for(let i=1; i<this.children.length; i++){
|
|
625
|
+
if(this.children[i].x + this.children[i].width - this.preferences[i][2] >
|
|
626
|
+
this.children[this.rightist].x + this.children[this.rightist].width - this.preferences[this.rightist][2]){
|
|
627
|
+
this.rightist=i;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
559
632
|
/**
|
|
560
633
|
* Scrolls content downward
|
|
561
634
|
*/
|
package/README.md
CHANGED
|
@@ -1,130 +1,132 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="/docs/public/crowjs_favicon.png" alt="CrowJS Logo" width="270" />
|
|
3
|
+
|
|
4
|
+
# CrowJS
|
|
5
|
+
|
|
6
|
+
**A lightweight, canvas-native GUI library built for p5.js.**
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/@usman404/crowjs)
|
|
9
|
+
[](https://crow-js.vercel.app/)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
|
|
12
|
+
<br/>
|
|
13
|
+
|
|
14
|
+
*Designed for creative coders and simulation builders who need intuitive UI components like buttons, sliders, and input fields rendered directly inside the p5.js canvas.*
|
|
15
|
+
</div>
|
|
3
16
|
|
|
4
|
-
|
|
17
|
+
---
|
|
5
18
|
|
|
6
|
-
##
|
|
7
|
-
|
|
8
|
-
- Frames, nested layouts, scrollable & grid containers
|
|
9
|
-
- Customizable UI components: buttons, sliders, toggles, input fields, etc.
|
|
10
|
-
- Event system inspired by the web (click, hover, focus, blur)
|
|
11
|
-
- Modular design with support for custom components
|
|
12
|
-
- Fully drawn inside p5.js (no HTML overlays or DOM interference)
|
|
13
|
-
- Ideal for creative coding, simulations, visual experiments, and tools
|
|
19
|
+
## π Why CrowJS?
|
|
20
|
+
While p5.js is amazing for generative art and simulations, there's a noticeable gap when it comes to in-sketch UI. CrowJS fills that gap by giving you a **canvas-first GUI framework** that blends seamlessly with your visuals without relying on clunky HTML overlays or DOM interference.
|
|
14
21
|
|
|
15
|
-
##
|
|
22
|
+
## β¨ Features
|
|
23
|
+
- **Minimal & Canvas-Native:** UI drawn directly inside your p5.js sketches.
|
|
24
|
+
- **Layout Systems:** Frames, nested layouts, scrollable areas, and grid containers.
|
|
25
|
+
- **Customizable Components:** Buttons, sliders, toggles, input fields, and more.
|
|
26
|
+
- **Web-Style Event System:** Supports `click`, `hover`, `focus`, and `blur`.
|
|
27
|
+
- **Modular Design:** Easily create and integrate your own custom components.
|
|
28
|
+
- **Perfect For:** Creative coding, visual experiments, simulations, and internal tools.
|
|
16
29
|
|
|
17
|
-
|
|
30
|
+
---
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
Once included, open the `index.html` file in your browser; it already contains the required setup for p5.js and for loading CrowJS modules.
|
|
32
|
+
## π Getting Started
|
|
21
33
|
|
|
22
|
-
|
|
34
|
+
### 1. Installation
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
**Via NPM (Recommended)**
|
|
37
|
+
```bash
|
|
38
|
+
npm install @usman404/crowjs
|
|
39
|
+
```
|
|
25
40
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **Do not alter** the structure or script tags inside `index.html`. Modifying them may prevent CrowJS from running correctly.
|
|
29
|
-
- A **CDN-based installation** method will be added in the future to make setup even easier.
|
|
41
|
+
**Via Manual Download**
|
|
42
|
+
Download this repository and place the `CrowJS` folder inside your project's source code. Open the `index.html` file in your browser (it already contains the required setup for p5.js and module loading).
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
> [!WARNING]
|
|
45
|
+
> - **Do not rename** the `sketch.js` file. CrowJS depends on this filename for consistent module loading.
|
|
46
|
+
> - **Do not alter** the structure or script tags inside `index.html`. Modifying them may prevent CrowJS from running correctly.
|
|
32
47
|
|
|
33
48
|
### 2. Basic Example
|
|
49
|
+
All GUI development should be done inside **`sketch.js`**, which serves as the main entry point for both your p5.js sketch and your CrowJS components.
|
|
50
|
+
|
|
34
51
|
```javascript
|
|
35
52
|
import { Root } from "./Core/Root.js";
|
|
36
|
-
import {
|
|
37
|
-
|
|
53
|
+
import { Button } from "./UIComponents/Button.js";
|
|
38
54
|
|
|
39
55
|
/** @type {Root} */
|
|
40
56
|
let root;
|
|
41
|
-
let clickTimes=
|
|
42
|
-
|
|
43
|
-
window.setup = function(){
|
|
44
|
-
createCanvas(windowWidth, windowHeight);
|
|
45
|
-
root = new Root();
|
|
57
|
+
let clickTimes = 0;
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
function setup() {
|
|
60
|
+
root = new Root();
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
textColor: "rgba(255, 255, 255, 1)",
|
|
54
|
-
});
|
|
62
|
+
const button = new Button(0, 0, 200, 100, "Click Me! π¦ββ¬", {
|
|
63
|
+
cornerRadius: 10,
|
|
64
|
+
});
|
|
55
65
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
button.addEventListener('click', (event) => {
|
|
67
|
+
clickTimes += 1;
|
|
68
|
+
event.target.setText(`You clicked ${clickTimes} times!`);
|
|
69
|
+
});
|
|
60
70
|
|
|
61
|
-
|
|
71
|
+
root.add(button);
|
|
62
72
|
}
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
background('rgba(255,255,255,255)');
|
|
74
|
+
function draw() {
|
|
66
75
|
root.show();
|
|
76
|
+
|
|
77
|
+
// Event Listeners
|
|
67
78
|
root.mouseEnterEventListeners(mouseX, mouseY);
|
|
68
79
|
root.hoverEventListeners(mouseX, mouseY);
|
|
69
80
|
root.mouseLeaveEventListeners(mouseX, mouseY);
|
|
70
|
-
root.keyDownEventListeners(mouseX, mouseY);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
window.mouseDragged = function (){
|
|
74
|
-
root.mouseDraggedEventListeners(mouseX, mouseY);
|
|
75
81
|
}
|
|
76
82
|
|
|
77
|
-
window.mouseClicked = function(){
|
|
83
|
+
window.mouseClicked = function() {
|
|
78
84
|
root.mouseClickedEventListeners(mouseX, mouseY);
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
window.mousePressed = function(){
|
|
87
|
+
window.mousePressed = function() {
|
|
82
88
|
root.mousePressedEventListeners(mouseX, mouseY);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
window.mouseReleased = function(){
|
|
91
|
+
window.mouseReleased = function() {
|
|
86
92
|
root.mouseReleasedEventListeners(mouseX, mouseY);
|
|
87
93
|
}
|
|
94
|
+
```
|
|
88
95
|
|
|
89
|
-
|
|
90
|
-
root.mouseWheelEventListeners(mouseX, mouseY, event);
|
|
91
|
-
}
|
|
96
|
+
---
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
## π§© Components Overview
|
|
99
|
+
|
|
100
|
+
- Label(x, y, width, height, text, {options})
|
|
101
|
+
- Button
|
|
102
|
+
- ScrollFrame
|
|
103
|
+
- GridFrame
|
|
104
|
+
|
|
105
|
+
*More components coming soon!*
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Components Overview
|
|
103
|
-
- `Label(x, y, width, height, text, {options})`
|
|
104
|
-
- `ScrollFrame`, `GridFrame`, `Label`, etc.
|
|
107
|
+
---
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
## π Documentation & Tutorials
|
|
110
|
+
A full **video series** and our documentation site are actively being updated!
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
A full **video series** and documentation site are in the works!
|
|
110
|
-
Stay tuned for:
|
|
112
|
+
Check out the official docs at **[crow-js.vercel.app](https://crow-js.vercel.app/)** for:
|
|
111
113
|
- Getting started tutorials
|
|
112
|
-
- Custom component creation
|
|
114
|
+
- Custom component creation guides
|
|
113
115
|
- Internals of CrowJS for contributors
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
While p5.js is amazing for generative art and simulations, there's a noticeable gap when it comes to in-sketch UI. CrowJS fills that gap by giving you a canvas-first GUI framework that blends seamlessly with your visuals.
|
|
117
|
+
---
|
|
117
118
|
|
|
118
|
-
## Contributing
|
|
119
|
-
CrowJS is open-source! Contributions, bug reports, and ideas are welcome.
|
|
120
|
-
|
|
119
|
+
## π€ Contributing
|
|
120
|
+
CrowJS is open-source! Contributions, bug reports, and ideas are highly welcome.
|
|
121
|
+
Please see the `CONTRIBUTING.md` file for details on how to get started.
|
|
121
122
|
|
|
122
|
-
##
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
## π Links
|
|
124
|
+
- **Creator's Sketches:** [Usman's p5.js Editor](https://editor.p5js.org/Usman_Ali/sketches/)
|
|
125
|
+
- **Powered By:** [p5.js Official Site](https://p5js.org)
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
- My sketches: [https://editor.p5js.org/Usman_Ali/sketches/](p5js.org/Usman_Ali/sketches/)
|
|
128
|
-
- p5.js: [https://p5js.org](https://p5js.org)
|
|
127
|
+
---
|
|
129
128
|
|
|
130
|
-
|
|
129
|
+
<div align="center">
|
|
130
|
+
Created with β€οΈ by <b>Usman</b> <br/>
|
|
131
|
+
<i>Licensed under the MIT License.</i>
|
|
132
|
+
</div>
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { TextComponent } from './TextComponent.js';
|
|
2
|
+
import { Component } from '../Core/Component.js';
|
|
3
|
+
|
|
4
|
+
export class Checkbox extends TextComponent {
|
|
5
|
+
/**
|
|
6
|
+
* Creates a checkbox component with a toggleable checked state and label text.
|
|
7
|
+
* @param {number} x - The x-coordinate
|
|
8
|
+
* @param {number} y - The y-coordinate
|
|
9
|
+
* @param {number} width - The width
|
|
10
|
+
* @param {number} height - The height
|
|
11
|
+
* @param {string} label - The text to display next to the checkbox
|
|
12
|
+
* @param {Object} options - Configuration options
|
|
13
|
+
* @param {string|null} options.id - Component ID
|
|
14
|
+
* @param {Component|null} options.parent - Parent component
|
|
15
|
+
* @param {boolean} options.checked - Initial checked state
|
|
16
|
+
* @param {p5.Color} options.backgroundColor - Background color
|
|
17
|
+
* @param {p5.Color} options.textColor - Text color
|
|
18
|
+
* @param {p5.Color} options.boxColor - Checkbox box background color (unchecked)
|
|
19
|
+
* @param {p5.Color} options.boxBorderColor - Checkbox box border color
|
|
20
|
+
* @param {number} options.boxBorderWidth - Checkbox box border width
|
|
21
|
+
* @param {p5.Color} options.checkedColor - Background color of the box when checked
|
|
22
|
+
* @param {p5.Color} options.checkmarkColor - Checkmark color
|
|
23
|
+
* @param {number} options.boxSize - Size of the checkbox box in pixels
|
|
24
|
+
* @param {number} options.boxGap - Gap between checkbox box and label text
|
|
25
|
+
* @param {number} options.boxCornerRadius - Corner radius of the checkbox box
|
|
26
|
+
* @param {p5.Color} options.hoverBoxBorderColor - Box border color on hover
|
|
27
|
+
* @param {boolean} options.enabled - Whether the checkbox is interactive
|
|
28
|
+
* @param {boolean} options.borderFlag - Whether to show component border
|
|
29
|
+
* @param {p5.Color} options.borderColor - Component border color
|
|
30
|
+
* @param {number} options.borderWidth - Component border width
|
|
31
|
+
* @param {number} options.cornerRadius - Component corner radius
|
|
32
|
+
* @param {boolean} options.enableShadow - Enable shadow rendering
|
|
33
|
+
* @param {string} options.shadowColor - Shadow color (CSS color string)
|
|
34
|
+
* @param {number} options.shadowBlur - Shadow blur radius
|
|
35
|
+
* @param {number} options.shadowOffsetX - Shadow offset on X axis
|
|
36
|
+
* @param {number} options.shadowOffsetY - Shadow offset on Y axis
|
|
37
|
+
* @param {string} options.HTextAlign - Horizontal text alignment
|
|
38
|
+
* @param {string} options.VTextAlign - Vertical text alignment
|
|
39
|
+
* @param {number} options.pad - General padding
|
|
40
|
+
* @param {number} options.padx - Horizontal padding
|
|
41
|
+
* @param {number} options.pady - Vertical padding
|
|
42
|
+
* @param {number} options.padl - Left padding
|
|
43
|
+
* @param {number} options.padr - Right padding
|
|
44
|
+
* @param {number} options.padt - Top padding
|
|
45
|
+
* @param {number} options.padb - Bottom padding
|
|
46
|
+
* @param {boolean} options.wrap - Whether to wrap text
|
|
47
|
+
* @param {string} options.wrapMode - Wrap mode: "word" or "char"
|
|
48
|
+
* @param {string} options.noWrapMode - No-wrap mode: "ellipsis" or "font-size"
|
|
49
|
+
* @param {string} options.ellipsisMode - Ellipsis mode: "leading", "center", or "trailing"
|
|
50
|
+
* @param {number} options.margin - General margin for all sides
|
|
51
|
+
* @param {number} options.marginx - Horizontal margin (left and right)
|
|
52
|
+
* @param {number} options.marginy - Vertical margin (top and bottom)
|
|
53
|
+
* @param {number} options.marginl - Left margin
|
|
54
|
+
* @param {number} options.marginr - Right margin
|
|
55
|
+
* @param {number} options.margint - Top margin
|
|
56
|
+
* @param {number} options.marginb - Bottom margin
|
|
57
|
+
*/
|
|
58
|
+
constructor(x, y, width, height, label, {
|
|
59
|
+
id = null,
|
|
60
|
+
parent = null,
|
|
61
|
+
checked = false,
|
|
62
|
+
backgroundColor = color('#1e1e2e'),
|
|
63
|
+
textColor = color('#e0e0e0'),
|
|
64
|
+
boxColor = color('#2a2a3d'),
|
|
65
|
+
boxBorderColor = color('#3a3a4d'),
|
|
66
|
+
boxBorderWidth = 2,
|
|
67
|
+
checkedColor = color('#4a9eff'),
|
|
68
|
+
checkmarkColor = color('#ffffff'),
|
|
69
|
+
boxSize = 18,
|
|
70
|
+
boxGap = 8,
|
|
71
|
+
boxCornerRadius = 4,
|
|
72
|
+
hoverBoxBorderColor = color('#5a5a7a'),
|
|
73
|
+
enabled = true,
|
|
74
|
+
borderFlag = false,
|
|
75
|
+
borderColor = color('#3a3a4d'),
|
|
76
|
+
borderWidth = 1,
|
|
77
|
+
cornerRadius = 8,
|
|
78
|
+
enableShadow = false,
|
|
79
|
+
shadowColor = 'rgba(0,0,0,0.5)',
|
|
80
|
+
shadowBlur = 12,
|
|
81
|
+
shadowOffsetX = 0,
|
|
82
|
+
shadowOffsetY = 4,
|
|
83
|
+
HTextAlign = 'left',
|
|
84
|
+
VTextAlign = 'center',
|
|
85
|
+
pad = 5,
|
|
86
|
+
padx = null,
|
|
87
|
+
pady = null,
|
|
88
|
+
padl = null,
|
|
89
|
+
padr = null,
|
|
90
|
+
padt = null,
|
|
91
|
+
padb = null,
|
|
92
|
+
wrap = false,
|
|
93
|
+
wrapMode = 'word',
|
|
94
|
+
noWrapMode = 'font-size',
|
|
95
|
+
ellipsisMode = 'trailing',
|
|
96
|
+
margin = 0,
|
|
97
|
+
marginx = null,
|
|
98
|
+
marginy = null,
|
|
99
|
+
marginl = null,
|
|
100
|
+
marginr = null,
|
|
101
|
+
margint = null,
|
|
102
|
+
marginb = null,
|
|
103
|
+
minWidth = 0,
|
|
104
|
+
minHeight = 0,
|
|
105
|
+
showDebugOverlay = false,
|
|
106
|
+
} = {}) {
|
|
107
|
+
super(x, y, width, height, label, {
|
|
108
|
+
id,
|
|
109
|
+
parent,
|
|
110
|
+
backgroundColor,
|
|
111
|
+
textColor,
|
|
112
|
+
borderFlag,
|
|
113
|
+
borderColor,
|
|
114
|
+
borderWidth,
|
|
115
|
+
cornerRadius,
|
|
116
|
+
enableShadow,
|
|
117
|
+
shadowColor,
|
|
118
|
+
shadowBlur,
|
|
119
|
+
shadowOffsetX,
|
|
120
|
+
shadowOffsetY,
|
|
121
|
+
HTextAlign,
|
|
122
|
+
VTextAlign,
|
|
123
|
+
pad,
|
|
124
|
+
padx,
|
|
125
|
+
pady,
|
|
126
|
+
padl,
|
|
127
|
+
padr,
|
|
128
|
+
padt,
|
|
129
|
+
padb,
|
|
130
|
+
wrap,
|
|
131
|
+
wrapMode,
|
|
132
|
+
noWrapMode,
|
|
133
|
+
ellipsisMode,
|
|
134
|
+
icon: null,
|
|
135
|
+
iconSize: 20,
|
|
136
|
+
iconPosition: 'left',
|
|
137
|
+
iconGap: 6,
|
|
138
|
+
iconTintColor: null,
|
|
139
|
+
iconOpacity: 255,
|
|
140
|
+
margin,
|
|
141
|
+
marginx,
|
|
142
|
+
marginy,
|
|
143
|
+
marginl,
|
|
144
|
+
marginr,
|
|
145
|
+
margint,
|
|
146
|
+
marginb,
|
|
147
|
+
minWidth,
|
|
148
|
+
minHeight,
|
|
149
|
+
showDebugOverlay,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
this.checked = checked;
|
|
153
|
+
this.boxColor = boxColor;
|
|
154
|
+
this.boxBorderColor = boxBorderColor;
|
|
155
|
+
this.boxBorderWidth = boxBorderWidth;
|
|
156
|
+
this.checkedColor = checkedColor;
|
|
157
|
+
this.checkmarkColor = checkmarkColor;
|
|
158
|
+
this.boxSize = boxSize;
|
|
159
|
+
this.boxGap = boxGap;
|
|
160
|
+
this.boxCornerRadius = boxCornerRadius;
|
|
161
|
+
this.hoverBoxBorderColor = hoverBoxBorderColor;
|
|
162
|
+
this.enabled = enabled;
|
|
163
|
+
this.isHovered = false;
|
|
164
|
+
|
|
165
|
+
// Absorb box space into left padding so that all TextComponent layout
|
|
166
|
+
// machinery (renderText, updateLabelSize, getContentWidth, debug overlay)
|
|
167
|
+
// works correctly without any overrides.
|
|
168
|
+
this.padl += this.boxSize + this.boxGap;
|
|
169
|
+
|
|
170
|
+
// Recalculate label size with the updated padl
|
|
171
|
+
if (!this.wrap && this.noWrapMode === 'font-size') {
|
|
172
|
+
this.updateLabelSize();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.addEventListener('mouseEnter', () => {
|
|
176
|
+
if (!this.enabled) return;
|
|
177
|
+
this.isHovered = true;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
this.addEventListener('hover', (event) => {
|
|
181
|
+
if (!this.enabled) return;
|
|
182
|
+
cursor('pointer');
|
|
183
|
+
event.stopPropagation();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.addEventListener('mouseLeave', () => {
|
|
187
|
+
this.isHovered = false;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
this.addEventListener('click', () => {
|
|
191
|
+
if (!this.enabled) return;
|
|
192
|
+
this.checked = !this.checked;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Returns whether the checkbox is currently checked
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
isChecked() {
|
|
201
|
+
return this.checked;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Sets the checked state of the checkbox
|
|
206
|
+
* @param {boolean} checked - Checked state
|
|
207
|
+
*/
|
|
208
|
+
setChecked(checked) {
|
|
209
|
+
this.checked = checked;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Toggles the checked state
|
|
214
|
+
*/
|
|
215
|
+
toggle() {
|
|
216
|
+
this.checked = !this.checked;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Enables or disables the checkbox
|
|
221
|
+
* @param {boolean} enabled - True to enable, false to disable
|
|
222
|
+
*/
|
|
223
|
+
setEnabled(enabled) {
|
|
224
|
+
this.enabled = enabled;
|
|
225
|
+
if (!enabled) {
|
|
226
|
+
this.isHovered = false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Renders the checkbox with label
|
|
232
|
+
*/
|
|
233
|
+
show() {
|
|
234
|
+
if (this.enableShadow) {
|
|
235
|
+
this.drawShadow();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
push();
|
|
239
|
+
beginClip();
|
|
240
|
+
rect(this.x, this.y, this.width, this.height, this.cornerRadius);
|
|
241
|
+
endClip();
|
|
242
|
+
translate(this.x, this.y);
|
|
243
|
+
|
|
244
|
+
// Background
|
|
245
|
+
fill(this.backgroundColor);
|
|
246
|
+
rect(0, 0, this.width, this.height, this.cornerRadius);
|
|
247
|
+
|
|
248
|
+
// Checkbox box position: sits in the gap between original padl and expanded padl
|
|
249
|
+
const originalPadl = this.padl - this.boxSize - this.boxGap;
|
|
250
|
+
const boxX = originalPadl;
|
|
251
|
+
const boxY = (this.height - this.boxSize) / 2;
|
|
252
|
+
|
|
253
|
+
// Draw checkbox box
|
|
254
|
+
if (this.checked) {
|
|
255
|
+
fill(this.checkedColor);
|
|
256
|
+
} else {
|
|
257
|
+
fill(this.boxColor);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (this.isHovered && this.enabled) {
|
|
261
|
+
stroke(this.hoverBoxBorderColor);
|
|
262
|
+
} else {
|
|
263
|
+
stroke(this.boxBorderColor);
|
|
264
|
+
}
|
|
265
|
+
strokeWeight(this.boxBorderWidth);
|
|
266
|
+
rect(boxX, boxY, this.boxSize, this.boxSize, this.boxCornerRadius);
|
|
267
|
+
|
|
268
|
+
// Draw checkmark when checked
|
|
269
|
+
if (this.checked) {
|
|
270
|
+
stroke(this.checkmarkColor);
|
|
271
|
+
strokeWeight(2.5);
|
|
272
|
+
noFill();
|
|
273
|
+
const cx = boxX + this.boxSize * 0.5;
|
|
274
|
+
const cy = boxY + this.boxSize * 0.5;
|
|
275
|
+
const s = this.boxSize * 0.3;
|
|
276
|
+
// Checkmark path: two connected lines
|
|
277
|
+
line(cx - s * 0.6, cy, cx - s * 0.1, cy + s * 0.5);
|
|
278
|
+
line(cx - s * 0.1, cy + s * 0.5, cx + s * 0.7, cy - s * 0.5);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Draw label text β renderText uses this.padl which already includes box space
|
|
282
|
+
noStroke();
|
|
283
|
+
fill(this.enabled ? this.textColor : color(120, 120, 140));
|
|
284
|
+
this.renderText();
|
|
285
|
+
|
|
286
|
+
// Border
|
|
287
|
+
if (this.borderFlag) {
|
|
288
|
+
noFill();
|
|
289
|
+
stroke(this.borderColor);
|
|
290
|
+
strokeWeight(this.borderWidth);
|
|
291
|
+
rect(0, 0, this.width, this.height, this.cornerRadius);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
pop();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { TextComponent } from './TextComponent.js';
|
|
2
|
+
import { Component } from '../Core/Component.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Static registry of radio button groups.
|
|
6
|
+
* Maps group name β Set of RadioButton instances.
|
|
7
|
+
* @type {Map<string, Set<RadioButton>>}
|
|
8
|
+
*/
|
|
9
|
+
const _radioGroups = new Map();
|
|
10
|
+
|
|
11
|
+
export class RadioButton extends TextComponent {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a radio button component with group-exclusive selection and label text.
|
|
14
|
+
* Only one radio button per `group` can be selected at a time.
|
|
15
|
+
*
|
|
16
|
+
* @param {number} x - The x-coordinate
|
|
17
|
+
* @param {number} y - The y-coordinate
|
|
18
|
+
* @param {number} width - The width
|
|
19
|
+
* @param {number} height - The height
|
|
20
|
+
* @param {string} label - The text to display next to the radio button
|
|
21
|
+
* @param {Object} options - Configuration options
|
|
22
|
+
* @param {string|null} options.id - Component ID
|
|
23
|
+
* @param {Component|null} options.parent - Parent component
|
|
24
|
+
* @param {string} options.group - The group name this radio button belongs to
|
|
25
|
+
* @param {boolean} options.selected - Initial selected state
|
|
26
|
+
* @param {string|*} options.value - The value this radio button represents
|
|
27
|
+
* @param {p5.Color} options.backgroundColor - Background color
|
|
28
|
+
* @param {p5.Color} options.textColor - Text color
|
|
29
|
+
* @param {p5.Color} options.circleColor - Outer circle background color (unselected)
|
|
30
|
+
* @param {p5.Color} options.circleBorderColor - Outer circle border color
|
|
31
|
+
* @param {number} options.circleBorderWidth - Outer circle border width
|
|
32
|
+
* @param {p5.Color} options.selectedColor - Inner dot color when selected
|
|
33
|
+
* @param {p5.Color} options.selectedCircleBorderColor - Outer circle border color when selected
|
|
34
|
+
* @param {number} options.circleSize - Diameter of the outer circle in pixels
|
|
35
|
+
* @param {number} options.dotSize - Diameter of the inner dot in pixels
|
|
36
|
+
* @param {number} options.circleGap - Gap between circle and label text
|
|
37
|
+
* @param {p5.Color} options.hoverCircleBorderColor - Circle border color on hover
|
|
38
|
+
* @param {boolean} options.enabled - Whether the radio button is interactive
|
|
39
|
+
* @param {boolean} options.borderFlag - Whether to show component border
|
|
40
|
+
* @param {p5.Color} options.borderColor - Component border color
|
|
41
|
+
* @param {number} options.borderWidth - Component border width
|
|
42
|
+
* @param {number} options.cornerRadius - Component corner radius
|
|
43
|
+
* @param {boolean} options.enableShadow - Enable shadow rendering
|
|
44
|
+
* @param {string} options.shadowColor - Shadow color (CSS color string)
|
|
45
|
+
* @param {number} options.shadowBlur - Shadow blur radius
|
|
46
|
+
* @param {number} options.shadowOffsetX - Shadow offset on X axis
|
|
47
|
+
* @param {number} options.shadowOffsetY - Shadow offset on Y axis
|
|
48
|
+
* @param {string} options.HTextAlign - Horizontal text alignment
|
|
49
|
+
* @param {string} options.VTextAlign - Vertical text alignment
|
|
50
|
+
* @param {number} options.pad - General padding
|
|
51
|
+
* @param {number} options.padx - Horizontal padding
|
|
52
|
+
* @param {number} options.pady - Vertical padding
|
|
53
|
+
* @param {number} options.padl - Left padding
|
|
54
|
+
* @param {number} options.padr - Right padding
|
|
55
|
+
* @param {number} options.padt - Top padding
|
|
56
|
+
* @param {number} options.padb - Bottom padding
|
|
57
|
+
* @param {boolean} options.wrap - Whether to wrap text
|
|
58
|
+
* @param {string} options.wrapMode - Wrap mode: "word" or "char"
|
|
59
|
+
* @param {string} options.noWrapMode - No-wrap mode: "ellipsis" or "font-size"
|
|
60
|
+
* @param {string} options.ellipsisMode - Ellipsis mode: "leading", "center", or "trailing"
|
|
61
|
+
* @param {number} options.margin - General margin for all sides
|
|
62
|
+
* @param {number} options.marginx - Horizontal margin (left and right)
|
|
63
|
+
* @param {number} options.marginy - Vertical margin (top and bottom)
|
|
64
|
+
* @param {number} options.marginl - Left margin
|
|
65
|
+
* @param {number} options.marginr - Right margin
|
|
66
|
+
* @param {number} options.margint - Top margin
|
|
67
|
+
* @param {number} options.marginb - Bottom margin
|
|
68
|
+
*/
|
|
69
|
+
constructor(x, y, width, height, label, {
|
|
70
|
+
id = null,
|
|
71
|
+
parent = null,
|
|
72
|
+
group = 'default',
|
|
73
|
+
selected = false,
|
|
74
|
+
value = null,
|
|
75
|
+
backgroundColor = color('#1e1e2e'),
|
|
76
|
+
textColor = color('#e0e0e0'),
|
|
77
|
+
circleColor = color('#2a2a3d'),
|
|
78
|
+
circleBorderColor = color('#3a3a4d'),
|
|
79
|
+
circleBorderWidth = 2,
|
|
80
|
+
selectedColor = color('#4a9eff'),
|
|
81
|
+
selectedCircleBorderColor = color('#4a9eff'),
|
|
82
|
+
circleSize = 18,
|
|
83
|
+
dotSize = 10,
|
|
84
|
+
circleGap = 8,
|
|
85
|
+
hoverCircleBorderColor = color('#5a5a7a'),
|
|
86
|
+
enabled = true,
|
|
87
|
+
borderFlag = false,
|
|
88
|
+
borderColor = color('#3a3a4d'),
|
|
89
|
+
borderWidth = 1,
|
|
90
|
+
cornerRadius = 8,
|
|
91
|
+
enableShadow = false,
|
|
92
|
+
shadowColor = 'rgba(0,0,0,0.5)',
|
|
93
|
+
shadowBlur = 12,
|
|
94
|
+
shadowOffsetX = 0,
|
|
95
|
+
shadowOffsetY = 4,
|
|
96
|
+
HTextAlign = 'left',
|
|
97
|
+
VTextAlign = 'center',
|
|
98
|
+
pad = 5,
|
|
99
|
+
padx = null,
|
|
100
|
+
pady = null,
|
|
101
|
+
padl = null,
|
|
102
|
+
padr = null,
|
|
103
|
+
padt = null,
|
|
104
|
+
padb = null,
|
|
105
|
+
wrap = false,
|
|
106
|
+
wrapMode = 'word',
|
|
107
|
+
noWrapMode = 'font-size',
|
|
108
|
+
ellipsisMode = 'trailing',
|
|
109
|
+
margin = 0,
|
|
110
|
+
marginx = null,
|
|
111
|
+
marginy = null,
|
|
112
|
+
marginl = null,
|
|
113
|
+
marginr = null,
|
|
114
|
+
margint = null,
|
|
115
|
+
marginb = null,
|
|
116
|
+
minWidth = 0,
|
|
117
|
+
minHeight = 0,
|
|
118
|
+
showDebugOverlay = false,
|
|
119
|
+
} = {}) {
|
|
120
|
+
super(x, y, width, height, label, {
|
|
121
|
+
id,
|
|
122
|
+
parent,
|
|
123
|
+
backgroundColor,
|
|
124
|
+
textColor,
|
|
125
|
+
borderFlag,
|
|
126
|
+
borderColor,
|
|
127
|
+
borderWidth,
|
|
128
|
+
cornerRadius,
|
|
129
|
+
enableShadow,
|
|
130
|
+
shadowColor,
|
|
131
|
+
shadowBlur,
|
|
132
|
+
shadowOffsetX,
|
|
133
|
+
shadowOffsetY,
|
|
134
|
+
HTextAlign,
|
|
135
|
+
VTextAlign,
|
|
136
|
+
pad,
|
|
137
|
+
padx,
|
|
138
|
+
pady,
|
|
139
|
+
padl,
|
|
140
|
+
padr,
|
|
141
|
+
padt,
|
|
142
|
+
padb,
|
|
143
|
+
wrap,
|
|
144
|
+
wrapMode,
|
|
145
|
+
noWrapMode,
|
|
146
|
+
ellipsisMode,
|
|
147
|
+
icon: null,
|
|
148
|
+
iconSize: 20,
|
|
149
|
+
iconPosition: 'left',
|
|
150
|
+
iconGap: 6,
|
|
151
|
+
iconTintColor: null,
|
|
152
|
+
iconOpacity: 255,
|
|
153
|
+
margin,
|
|
154
|
+
marginx,
|
|
155
|
+
marginy,
|
|
156
|
+
marginl,
|
|
157
|
+
marginr,
|
|
158
|
+
margint,
|
|
159
|
+
marginb,
|
|
160
|
+
minWidth,
|
|
161
|
+
minHeight,
|
|
162
|
+
showDebugOverlay,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
this.group = group;
|
|
166
|
+
this.selected = false;
|
|
167
|
+
this.value = value ?? label;
|
|
168
|
+
this.circleColor = circleColor;
|
|
169
|
+
this.circleBorderColor = circleBorderColor;
|
|
170
|
+
this.circleBorderWidth = circleBorderWidth;
|
|
171
|
+
this.selectedColor = selectedColor;
|
|
172
|
+
this.selectedCircleBorderColor = selectedCircleBorderColor;
|
|
173
|
+
this.circleSize = circleSize;
|
|
174
|
+
this.dotSize = dotSize;
|
|
175
|
+
this.circleGap = circleGap;
|
|
176
|
+
this.hoverCircleBorderColor = hoverCircleBorderColor;
|
|
177
|
+
this.enabled = enabled;
|
|
178
|
+
this.isHovered = false;
|
|
179
|
+
|
|
180
|
+
// Absorb circle space into left padding so that all TextComponent layout
|
|
181
|
+
// machinery (renderText, updateLabelSize, getContentWidth, debug overlay)
|
|
182
|
+
// works correctly without any overrides.
|
|
183
|
+
this.padl += this.circleSize + this.circleGap;
|
|
184
|
+
|
|
185
|
+
// Recalculate label size with the updated padl
|
|
186
|
+
if (!this.wrap && this.noWrapMode === 'font-size') {
|
|
187
|
+
this.updateLabelSize();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Register in group
|
|
191
|
+
if (!_radioGroups.has(this.group)) {
|
|
192
|
+
_radioGroups.set(this.group, new Set());
|
|
193
|
+
}
|
|
194
|
+
_radioGroups.get(this.group).add(this);
|
|
195
|
+
|
|
196
|
+
// If initially selected, deselect others in the group
|
|
197
|
+
if (selected) {
|
|
198
|
+
this.select();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.addEventListener('mouseEnter', () => {
|
|
202
|
+
if (!this.enabled) return;
|
|
203
|
+
this.isHovered = true;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
this.addEventListener('hover', (event) => {
|
|
207
|
+
if (!this.enabled) return;
|
|
208
|
+
cursor('pointer');
|
|
209
|
+
event.stopPropagation();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
this.addEventListener('mouseLeave', () => {
|
|
213
|
+
this.isHovered = false;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
this.addEventListener('click', () => {
|
|
217
|
+
if (!this.enabled) return;
|
|
218
|
+
this.select();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Selects this radio button and deselects all others in the same group.
|
|
224
|
+
*/
|
|
225
|
+
select() {
|
|
226
|
+
const group = _radioGroups.get(this.group);
|
|
227
|
+
if (group) {
|
|
228
|
+
for (const rb of group) {
|
|
229
|
+
if (rb !== this) {
|
|
230
|
+
rb.selected = false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
this.selected = true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Deselects this radio button.
|
|
239
|
+
*/
|
|
240
|
+
deselect() {
|
|
241
|
+
this.selected = false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Returns whether this radio button is currently selected.
|
|
246
|
+
* @returns {boolean}
|
|
247
|
+
*/
|
|
248
|
+
isSelected() {
|
|
249
|
+
return this.selected;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Returns the value of the currently selected radio button in this group.
|
|
254
|
+
* @returns {*|null} The value, or null if none selected.
|
|
255
|
+
*/
|
|
256
|
+
getSelectedValue() {
|
|
257
|
+
const group = _radioGroups.get(this.group);
|
|
258
|
+
if (group) {
|
|
259
|
+
for (const rb of group) {
|
|
260
|
+
if (rb.selected) return rb.value;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Returns all RadioButton instances in the same group.
|
|
268
|
+
* @returns {RadioButton[]}
|
|
269
|
+
*/
|
|
270
|
+
getGroupMembers() {
|
|
271
|
+
const group = _radioGroups.get(this.group);
|
|
272
|
+
return group ? [...group] : [];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Enables or disables the radio button.
|
|
277
|
+
* @param {boolean} enabled - True to enable, false to disable
|
|
278
|
+
*/
|
|
279
|
+
setEnabled(enabled) {
|
|
280
|
+
this.enabled = enabled;
|
|
281
|
+
if (!enabled) {
|
|
282
|
+
this.isHovered = false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Removes this radio button from its group registry.
|
|
288
|
+
* Call this when the radio button is permanently removed from the UI.
|
|
289
|
+
*/
|
|
290
|
+
destroy() {
|
|
291
|
+
const group = _radioGroups.get(this.group);
|
|
292
|
+
if (group) {
|
|
293
|
+
group.delete(this);
|
|
294
|
+
if (group.size === 0) {
|
|
295
|
+
_radioGroups.delete(this.group);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Renders the radio button with label.
|
|
302
|
+
*/
|
|
303
|
+
show() {
|
|
304
|
+
if (this.enableShadow) {
|
|
305
|
+
this.drawShadow();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
push();
|
|
309
|
+
beginClip();
|
|
310
|
+
rect(this.x, this.y, this.width, this.height, this.cornerRadius);
|
|
311
|
+
endClip();
|
|
312
|
+
translate(this.x, this.y);
|
|
313
|
+
|
|
314
|
+
// Background
|
|
315
|
+
fill(this.backgroundColor);
|
|
316
|
+
rect(0, 0, this.width, this.height, this.cornerRadius);
|
|
317
|
+
|
|
318
|
+
// Radio circle position: sits in the gap between original padl and expanded padl
|
|
319
|
+
const originalPadl = this.padl - this.circleSize - this.circleGap;
|
|
320
|
+
const cx = originalPadl + this.circleSize / 2;
|
|
321
|
+
const cy = this.height / 2;
|
|
322
|
+
|
|
323
|
+
// Draw outer circle
|
|
324
|
+
if (this.selected) {
|
|
325
|
+
fill(this.circleColor);
|
|
326
|
+
stroke(this.selectedCircleBorderColor);
|
|
327
|
+
} else {
|
|
328
|
+
fill(this.circleColor);
|
|
329
|
+
if (this.isHovered && this.enabled) {
|
|
330
|
+
stroke(this.hoverCircleBorderColor);
|
|
331
|
+
} else {
|
|
332
|
+
stroke(this.circleBorderColor);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
strokeWeight(this.circleBorderWidth);
|
|
336
|
+
ellipse(cx, cy, this.circleSize, this.circleSize);
|
|
337
|
+
|
|
338
|
+
// Draw inner dot when selected
|
|
339
|
+
if (this.selected) {
|
|
340
|
+
noStroke();
|
|
341
|
+
fill(this.selectedColor);
|
|
342
|
+
ellipse(cx, cy, this.dotSize, this.dotSize);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Draw label text β renderText uses this.padl which already includes circle space
|
|
346
|
+
noStroke();
|
|
347
|
+
fill(this.enabled ? this.textColor : color(120, 120, 140));
|
|
348
|
+
this.renderText();
|
|
349
|
+
|
|
350
|
+
// Border
|
|
351
|
+
if (this.borderFlag) {
|
|
352
|
+
noFill();
|
|
353
|
+
stroke(this.borderColor);
|
|
354
|
+
strokeWeight(this.borderWidth);
|
|
355
|
+
rect(0, 0, this.width, this.height, this.cornerRadius);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
pop();
|
|
359
|
+
}
|
|
360
|
+
}
|
package/index.js
CHANGED
|
@@ -16,6 +16,8 @@ const ScrollFrame = require('./Frames/ScrollFrame');
|
|
|
16
16
|
const Input = require('./UIComponents/Input');
|
|
17
17
|
const Label = require('./UIComponents/Label');
|
|
18
18
|
const Button = require('./UIComponents/Button');
|
|
19
|
+
const Checkbox = require('./UIComponents/Checkbox');
|
|
20
|
+
const RadioButton = require('./UIComponents/RadioButton');
|
|
19
21
|
const TextField = require('./UIComponents/TextField');
|
|
20
22
|
const UIComponent = require('./UIComponents/UIComponent');
|
|
21
23
|
const Icon = require('./UIComponents/Icon');
|
|
@@ -39,6 +41,8 @@ module.exports = {
|
|
|
39
41
|
Input,
|
|
40
42
|
Label,
|
|
41
43
|
Button,
|
|
44
|
+
Checkbox,
|
|
45
|
+
RadioButton,
|
|
42
46
|
TextField,
|
|
43
47
|
UIComponent,
|
|
44
48
|
Icon
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usman404/crowjs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "lightweight and extensible GUI library built on top of p5.js",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
6
|
+
"p5js",
|
|
7
|
+
"gui",
|
|
8
|
+
"canvas",
|
|
9
|
+
"creative-coding",
|
|
10
|
+
"crowjs"
|
|
7
11
|
],
|
|
8
12
|
"homepage": "https://crow-js.vercel.app/",
|
|
9
13
|
"bugs": {
|
|
@@ -11,11 +15,11 @@
|
|
|
11
15
|
},
|
|
12
16
|
"repository": {
|
|
13
17
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/UsmanAli404/CrowJS"
|
|
18
|
+
"url": "git+https://github.com/UsmanAli404/CrowJS.git"
|
|
15
19
|
},
|
|
16
20
|
"license": "MIT",
|
|
17
21
|
"author": "Usman Ali",
|
|
18
|
-
"type": "
|
|
22
|
+
"type": "module",
|
|
19
23
|
"main": "index.js",
|
|
20
24
|
"scripts": {
|
|
21
25
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -27,4 +31,4 @@
|
|
|
27
31
|
"@iconify/vue": "^4.1.2",
|
|
28
32
|
"vitepress": "^1.6.4"
|
|
29
33
|
}
|
|
30
|
-
}
|
|
34
|
+
}
|
package/problems.txt
CHANGED
package/sketch.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Root } from "./Core/Root.js";
|
|
2
2
|
import { Label } from "./UIComponents/Label.js";
|
|
3
|
-
|
|
3
|
+
import { Checkbox } from "./UIComponents/Checkbox.js";
|
|
4
|
+
import { RadioButton } from "./UIComponents/RadioButton.js";
|
|
5
|
+
import { GridFrame } from "./Frames/GridFrame.js";
|
|
4
6
|
|
|
5
7
|
/** @type {Root} */
|
|
6
8
|
let root;
|
|
@@ -10,21 +12,23 @@ window.setup = function(){
|
|
|
10
12
|
createCanvas(windowWidth, windowHeight);
|
|
11
13
|
root = new Root();
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
const frame = new GridFrame(20, 20, 500, 50, {
|
|
16
|
+
rows: 1,
|
|
17
|
+
cols: 3,
|
|
18
|
+
backgroundColor: "#1e1e2e",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const rb1 = new RadioButton(0, 0, 0, 0, "Low", { group: "quality", value: "low", selected: true });
|
|
22
|
+
const rb2 = new RadioButton(0, 0, 0, 0, "Medium", { group: "quality", value: "medium" });
|
|
23
|
+
const rb3 = new RadioButton(0, 0, 0, 0, "High", { group: "quality", value: "high" });
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
{cornerRadius: 20,
|
|
18
|
-
backgroundColor: "rgba(0, 0, 0, 1)",
|
|
19
|
-
textColor: "rgba(255, 255, 255, 1)",
|
|
20
|
-
});
|
|
25
|
+
frame.colConfig(1, 2);
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
27
|
+
frame.add(rb1, 0, 0);
|
|
28
|
+
frame.add(rb2, 0, 1);
|
|
29
|
+
frame.add(rb3, 0, 2);
|
|
26
30
|
|
|
27
|
-
root.add(
|
|
31
|
+
root.add(frame);
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
window.draw = function () {
|