@usman404/crowjs 1.0.4 → 1.1.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.
@@ -14,6 +14,7 @@ export class ScrollFrame extends Frame{
14
14
  * @param {p5.Color} options.highlightedBorderColor - Highlighted border color
15
15
  * @param {number} options.borderWidth - Border width
16
16
  * @param {number} options.cornerRadius - Corner radius
17
+ * @param {number} options.pad - Unified padding for both axes
17
18
  * @param {number} options.padx - Horizontal padding
18
19
  * @param {number} options.pady - Vertical padding
19
20
  * @param {boolean} options.alwaysShowBanner - Always show banner
@@ -21,6 +22,8 @@ export class ScrollFrame extends Frame{
21
22
  * @param {boolean} options.enableHScroll - Enable horizontal scrolling
22
23
  * @param {number} options.scrollSensitivity - Scroll speed
23
24
  * @param {number} options.bannerHeight - Banner height
25
+ * @param {p5.Color|string} options.bannerColor - Banner background color
26
+ * @param {p5.Color|string} options.bannerDotColor - Banner dot indicator color
24
27
  * @param {string} options.alignment - Layout alignment ("v" or "h")
25
28
  * @param {number} options.nearestBorderThreshold - Border detection threshold
26
29
  * @param {Component|null} options.parent - Parent component
@@ -29,25 +32,35 @@ export class ScrollFrame extends Frame{
29
32
  * @param {boolean} options.enableResizing - Allow resizing
30
33
  * @param {boolean} options.enableOptimisedResizing - Optimized resizing
31
34
  * @param {boolean} options.enableShadow - Enable shadow
32
- * @param {string} options.shadowColor - Shadow color
33
- * @param {number} options.shadowIntensity - Shadow opacity
34
- * @param {number} options.shadowSpread - Shadow spread
35
- * @param {number} options.shadowDetail - Shadow layers
35
+ * @param {string} options.shadowColor - Shadow color (CSS color string)
36
+ * @param {number} options.shadowBlur - Shadow blur radius
37
+ * @param {number} options.shadowOffsetX - Shadow offset on X axis
38
+ * @param {number} options.shadowOffsetY - Shadow offset on Y axis
39
+ * @param {number} options.margin - General margin for all sides
40
+ * @param {number} options.marginx - Horizontal margin (left and right)
41
+ * @param {number} options.marginy - Vertical margin (top and bottom)
42
+ * @param {number} options.marginl - Left margin
43
+ * @param {number} options.marginr - Right margin
44
+ * @param {number} options.margint - Top margin
45
+ * @param {number} options.marginb - Bottom margin
36
46
  */
37
47
  constructor(x, y, width, height, {
38
48
  id=null,
39
- backgroundColor = color(255),
40
- borderColor = color(0),
41
- highlightedBorderColor = color(0),
49
+ backgroundColor = color('#1e1e2e'),
50
+ borderColor = color('#3a3a4d'),
51
+ highlightedBorderColor = color('#5a5a7a'),
42
52
  borderWidth = 1,
43
- cornerRadius = 0,
44
- padx=0,
45
- pady=0,
53
+ cornerRadius = 8,
54
+ pad=null,
55
+ padx=null,
56
+ pady=null,
46
57
  alwaysShowBanner = false,
47
58
  enableVScroll=false,
48
59
  enableHScroll=false,
49
60
  scrollSensitivity=20,
50
61
  bannerHeight=35,
62
+ bannerColor='#2a2a3d',
63
+ bannerDotColor='#6a6a8a',
51
64
  alignment="v", //v for vertical, h for horizontal
52
65
  nearestBorderThreshold=8,
53
66
  parent=null,
@@ -56,15 +69,39 @@ export class ScrollFrame extends Frame{
56
69
  enableResizing=false,
57
70
  enableOptimisedResizing=false,
58
71
  enableShadow=false,
59
- shadowColor= 'rgb(0,0,0)',
60
- shadowIntensity= 0.3,
61
- shadowSpread= 1,
62
- shadowDetail=10,
72
+ shadowColor= 'rgba(0,0,0,0.5)',
73
+ shadowBlur= 12,
74
+ shadowOffsetX= 0,
75
+ shadowOffsetY= 4,
76
+ margin = 0,
77
+ marginx = null,
78
+ marginy = null,
79
+ marginl = null,
80
+ marginr = null,
81
+ margint = null,
82
+ marginb = null,
83
+ minWidth = 0,
84
+ minHeight = 0,
85
+ showDebugOverlay = false,
63
86
  } = {}) {
87
+ if (pad !== null && pad !== undefined) {
88
+ padx = pad;
89
+ pady = pad;
90
+ }
91
+
92
+ if (padx === null || padx === undefined) {
93
+ padx = 0;
94
+ }
95
+
96
+ if (pady === null || pady === undefined) {
97
+ pady = 0;
98
+ }
99
+
64
100
  bannerHeight = bannerHeight%height;
65
101
  super(x, y, width, height, id, backgroundColor, borderColor, highlightedBorderColor, borderWidth,
66
- cornerRadius, padx, pady, alwaysShowBanner, bannerHeight, nearestBorderThreshold, parent, "Frame",
67
- enableReposition, enableOptimisedReposition, enableResizing, enableOptimisedResizing, enableShadow, shadowColor, shadowIntensity, shadowSpread, shadowDetail);
102
+ cornerRadius, padx, pady, alwaysShowBanner, bannerHeight, bannerColor, bannerDotColor, nearestBorderThreshold, parent, "Frame",
103
+ enableReposition, enableOptimisedReposition, enableResizing, enableOptimisedResizing, enableShadow, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY,
104
+ {margin, marginx, marginy, marginl, marginr, margint, marginb, minWidth, minHeight, showDebugOverlay});
68
105
 
69
106
  this.preferences = [];
70
107
  //used for calculating weighted dimensions of child elements
@@ -155,10 +192,10 @@ export class ScrollFrame extends Frame{
155
192
  //show the top banner
156
193
  if(this.alwaysShowBanner || (this.enableReposition && this.isBannerShown)){
157
194
  noStroke();
158
- fill(0);
159
- rect(this.x, this.y, this.width, this.bannerHeight);
195
+ fill(this.bannerColor);
196
+ rect(this.x, this.y, this.width, this.bannerHeight, this.cornerRadius, this.cornerRadius, 0, 0);
160
197
 
161
- fill(255);
198
+ fill(this.bannerDotColor);
162
199
  ellipse(this.x+this.width/2, this.y+(this.bannerHeight)/2, (this.bannerHeight)/4, (this.bannerHeight)/4);
163
200
  ellipse(this.x+this.width/2 - (this.bannerHeight)/2, this.y+(this.bannerHeight)/2, (this.bannerHeight)/4, (this.bannerHeight)/4);
164
201
  ellipse(this.x+this.width/2 + (this.bannerHeight)/2, this.y+(this.bannerHeight)/2, (this.bannerHeight)/4, (this.bannerHeight)/4);
@@ -669,34 +706,104 @@ export class ScrollFrame extends Frame{
669
706
  }
670
707
  }
671
708
  }
709
+
710
+ /**
711
+ * Computes the effective minimum width from children's constraints.
712
+ * @returns {number}
713
+ */
714
+ getEffectiveMinWidth() {
715
+ let minW = this.minWidth;
716
+ let len = this.children.length;
717
+ if (len === 0) return minW;
718
+
719
+ let maxNeeded = 0;
720
+ let invWeight = this.totalWeight > 0 ? 1 / this.totalWeight : 0;
721
+ for (let i = 0; i < len; i++) {
722
+ let curr = this.children[i];
723
+ let pref = this.preferences[i];
724
+ let childMin = curr.getEffectiveMinWidth();
725
+ if (childMin <= 0) continue;
726
+ let childNeeded = childMin + pref[1] + pref[2] + curr.marginl + curr.marginr;
727
+ if (this.alignment === "v") {
728
+ // All children share full width
729
+ maxNeeded = Math.max(maxNeeded, childNeeded + 2 * this.padx);
730
+ } else {
731
+ // Horizontal: proportional distribution
732
+ let weight = pref[0];
733
+ let needed = childNeeded / (weight * invWeight) + 2 * this.padx;
734
+ maxNeeded = Math.max(maxNeeded, needed);
735
+ }
736
+ }
737
+
738
+ return Math.max(minW, maxNeeded);
739
+ }
740
+
741
+ /**
742
+ * Computes the effective minimum height from children's constraints.
743
+ * @returns {number}
744
+ */
745
+ getEffectiveMinHeight() {
746
+ let minH = this.minHeight;
747
+ let len = this.children.length;
748
+ if (len === 0) return minH;
749
+
750
+ let bannerH = (this.alwaysShowBanner || this.enableReposition) ? (this.bannerHeight || 0) : 0;
751
+
752
+ let maxNeeded = 0;
753
+ let invWeight = this.totalWeight > 0 ? 1 / this.totalWeight : 0;
754
+ for (let i = 0; i < len; i++) {
755
+ let curr = this.children[i];
756
+ let pref = this.preferences[i];
757
+ let childMin = curr.getEffectiveMinHeight();
758
+ if (childMin <= 0) continue;
759
+ let childNeeded = childMin + pref[3] + pref[4] + curr.margint + curr.marginb;
760
+ if (this.alignment === "v") {
761
+ // Vertical: proportional distribution
762
+ let weight = pref[0];
763
+ let needed = childNeeded / (weight * invWeight) + 2 * this.pady + bannerH;
764
+ maxNeeded = Math.max(maxNeeded, needed);
765
+ } else {
766
+ // All children share full height
767
+ maxNeeded = Math.max(maxNeeded, childNeeded + 2 * this.pady + bannerH);
768
+ }
769
+ }
770
+
771
+ return Math.max(minH, maxNeeded);
772
+ }
773
+
672
774
  /**
673
775
  * Adjusts child component heights based on weights and alignment
674
776
  * @param {number} y - Starting y position
675
777
  * @param {number} h - Available height
676
778
  */
677
779
  adjustHeight(y, h){
678
- //[element, weight, padL, padR, padT, padB]
679
- for(let i=0; i<this.children.length; i++){
780
+ let len = this.children.length;
781
+ let invWeight = this.totalWeight > 0 ? 1 / this.totalWeight : 0;
782
+ for(let i=0; i<len; i++){
680
783
 
681
784
  let curr = this.children[i];
785
+ let pref = this.preferences[i];
682
786
 
683
- if(i-1>=0){
787
+ if(i > 0){
684
788
  let prev = this.children[i-1];
685
789
  if(this.alignment=="v"){
686
- curr.y = prev.y + prev.height + this.preferences[i-1][4] + this.preferences[i][3];
790
+ curr.y = prev.y + prev.height + prev.marginb + this.preferences[i-1][4] + curr.margint + pref[3];
687
791
  } else {
688
- curr.y = y + this.preferences[i][3];
792
+ curr.y = y + curr.margint + pref[3];
689
793
  }
690
794
  } else {
691
- curr.y = y + this.preferences[i][3];
795
+ curr.y = y + curr.margint + pref[3];
692
796
  }
693
797
 
694
798
  if(this.enableVScroll==false){
695
799
  if(this.alignment=="v"){
696
- curr.height = (this.preferences[i][0]/(this.totalWeight))*(h) - this.preferences[i][3] - this.preferences[i][4];
800
+ curr.height = (pref[0] * invWeight) * h - pref[3] - pref[4] - curr.margint - curr.marginb;
697
801
  } else {
698
- curr.height = h - this.preferences[i][3] - this.preferences[i][4];
802
+ curr.height = h - pref[3] - pref[4] - curr.margint - curr.marginb;
699
803
  }
804
+
805
+ // Clamp to child's effective minimum height
806
+ curr.height = Math.max(curr.height, curr.getEffectiveMinHeight());
700
807
  }
701
808
 
702
809
  if(curr.type=="Frame"){
@@ -715,26 +822,31 @@ export class ScrollFrame extends Frame{
715
822
  * @param {number} w - Available width
716
823
  */
717
824
  adjustWidth(x, w){
718
- //[element, weight, padL, padR, padT, padB]
719
- for(let i=0; i<this.children.length; i++){
825
+ let len = this.children.length;
826
+ let invWeight = this.totalWeight > 0 ? 1 / this.totalWeight : 0;
827
+ for(let i=0; i<len; i++){
720
828
  let curr = this.children[i];
721
- if(i-1>=0){
829
+ let pref = this.preferences[i];
830
+ if(i > 0){
722
831
  let prev = this.children[i-1];
723
832
  if(this.alignment!="v"){
724
- curr.x = prev.x + prev.width + this.preferences[i-1][2] + this.preferences[i][1];
833
+ curr.x = prev.x + prev.width + prev.marginr + this.preferences[i-1][2] + curr.marginl + pref[1];
725
834
  } else {
726
- curr.x = x + this.preferences[i][1];
835
+ curr.x = x + curr.marginl + pref[1];
727
836
  }
728
837
  } else {
729
- curr.x = x + this.preferences[i][1];
838
+ curr.x = x + curr.marginl + pref[1];
730
839
  }
731
840
 
732
841
  if(this.enableHScroll==false){
733
842
  if(this.alignment=="v"){
734
- curr.width = w - this.preferences[i][1] - this.preferences[i][2];
843
+ curr.width = w - pref[1] - pref[2] - curr.marginl - curr.marginr;
735
844
  } else {
736
- curr.width = (this.preferences[i][0]/(this.totalWeight))*(w) - this.preferences[i][1] - this.preferences[i][2];
845
+ curr.width = (pref[0] * invWeight) * w - pref[1] - pref[2] - curr.marginl - curr.marginr;
737
846
  }
847
+
848
+ // Clamp to child's effective minimum width
849
+ curr.width = Math.max(curr.width, curr.getEffectiveMinWidth());
738
850
  }
739
851
 
740
852
  if(curr.type=="Frame"){
@@ -753,11 +865,13 @@ export class ScrollFrame extends Frame{
753
865
  * @param {number} yDiff - Y position difference
754
866
  */
755
867
  updatePosUtil(xDiff, yDiff){
756
- for(let i=0; i<this.children.length; i++){
757
- this.children[i].x -= xDiff;
758
- this.children[i].y -= yDiff;
759
- if(this.children[i].type=="Frame"){
760
- this.children[i].updatePosUtil(xDiff, yDiff);
868
+ let len = this.children.length;
869
+ for(let i=0; i<len; i++){
870
+ let child = this.children[i];
871
+ child.x -= xDiff;
872
+ child.y -= yDiff;
873
+ if(child.type==="Frame"){
874
+ child.updatePosUtil(xDiff, yDiff);
761
875
  }
762
876
  }
763
877
  }
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  ![Logo](/crowjs-01-01.png)
5
5
 
6
- ## Features
6
+ ## Features
7
7
  - Minimal and canvas-native UI for p5.js sketches
8
8
  - Frames, nested layouts, scrollable & grid containers
9
9
  - Customizable UI components: buttons, sliders, toggles, input fields, etc.
@@ -12,7 +12,7 @@
12
12
  - Fully drawn inside p5.js (no HTML overlays or DOM interference)
13
13
  - Ideal for creative coding, simulations, visual experiments, and tools
14
14
 
15
- ## 🚀 Getting Started
15
+ ## Getting Started
16
16
 
17
17
  ### 1. Install
18
18
 
@@ -99,31 +99,31 @@ window.keyPressed = function(event){
99
99
  }
100
100
  ```
101
101
 
102
- ## 🧰 Components Overview
102
+ ## Components Overview
103
103
  - `Label(x, y, width, height, text, {options})`
104
104
  - `ScrollFrame`, `GridFrame`, `Label`, etc.
105
105
 
106
106
  More components and documentation coming soon.
107
107
 
108
- ## 📚 Documentation & Tutorials
108
+ ## Documentation & Tutorials
109
109
  A full **video series** and documentation site are in the works!
110
110
  Stay tuned for:
111
111
  - Getting started tutorials
112
112
  - Custom component creation
113
113
  - Internals of CrowJS for contributors
114
114
 
115
- ## 💡 Why CrowJS?
115
+ ## Why CrowJS?
116
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
- ## 🤝 Contributing
118
+ ## Contributing
119
119
  CrowJS is open-source! Contributions, bug reports, and ideas are welcome.
120
120
  See the `CONTRIBUTING.md` file for details.
121
121
 
122
- ## 📄 License
122
+ ## License
123
123
  This project is licensed under the MIT License.
124
124
  See the `LICENSE` file for details.
125
125
 
126
- ## 🌐 Links
126
+ ## Links
127
127
  - My sketches: [https://editor.p5js.org/Usman_Ali/sketches/](p5js.org/Usman_Ali/sketches/)
128
128
  - p5.js: [https://p5js.org](https://p5js.org)
129
129
 
@@ -0,0 +1,281 @@
1
+ import { TextComponent } from './TextComponent.js';
2
+ import { Component } from '../Core/Component.js';
3
+
4
+ export class Button extends TextComponent {
5
+ /**
6
+ * Creates a button component with hover/press visuals and click behavior
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
12
+ * @param {Object} options - Configuration options
13
+ * @param {string|null} options.id - Component ID
14
+ * @param {Component|null} options.parent - Parent component
15
+ * @param {p5.Color} options.backgroundColor - Background color
16
+ * @param {p5.Color} options.textColor - Text color
17
+ * @param {p5.Color} options.hoverBackgroundColor - Background on hover
18
+ * @param {p5.Color} options.hoverTextColor - Text color on hover
19
+ * @param {p5.Color} options.pressedBackgroundColor - Background on press
20
+ * @param {p5.Color} options.pressedTextColor - Text color on press
21
+ * @param {boolean} options.borderFlag - Whether to show border
22
+ * @param {p5.Color} options.borderColor - Border color
23
+ * @param {number} options.borderWidth - Border width
24
+ * @param {number} options.cornerRadius - Corner radius
25
+ * @param {boolean} options.enableShadow - Enable shadow rendering
26
+ * @param {string} options.shadowColor - Shadow color (CSS color string)
27
+ * @param {number} options.shadowBlur - Shadow blur radius
28
+ * @param {number} options.shadowOffsetX - Shadow offset on X axis
29
+ * @param {number} options.shadowOffsetY - Shadow offset on Y axis
30
+ * @param {string} options.HTextAlign - Horizontal text alignment
31
+ * @param {string} options.VTextAlign - Vertical text alignment
32
+ * @param {number} options.pad - General padding
33
+ * @param {number} options.padx - Horizontal padding
34
+ * @param {number} options.pady - Vertical padding
35
+ * @param {number} options.padl - Left padding
36
+ * @param {number} options.padr - Right padding
37
+ * @param {number} options.padt - Top padding
38
+ * @param {number} options.padb - Bottom padding
39
+ * @param {boolean} options.wrap - Whether to wrap text
40
+ * @param {string} options.wrapMode - Wrap mode: "word" or "char"
41
+ * @param {string} options.noWrapMode - No-wrap mode: "ellipsis" or "font-size"
42
+ * @param {string} options.ellipsisMode - Ellipsis mode: "leading", "center", or "trailing"
43
+ * @param {p5.Image|null} options.icon - Icon image (null = text only)
44
+ * @param {number} options.iconSize - Icon display size in pixels
45
+ * @param {string} options.iconPosition - "left", "right", "top", or "bottom"
46
+ * @param {number} options.iconGap - Gap between icon and text
47
+ * @param {p5.Color|null} options.iconTintColor - Optional icon tint
48
+ * @param {number} options.iconOpacity - Icon opacity 0-255
49
+ * @param {number} options.margin - General margin for all sides
50
+ * @param {number} options.marginx - Horizontal margin (left and right)
51
+ * @param {number} options.marginy - Vertical margin (top and bottom)
52
+ * @param {number} options.marginl - Left margin
53
+ * @param {number} options.marginr - Right margin
54
+ * @param {number} options.margint - Top margin
55
+ * @param {number} options.marginb - Bottom margin
56
+ * @param {boolean} options.enabled - Whether the button is enabled
57
+ */
58
+ constructor(x, y, width, height, label,
59
+ {
60
+ id = null,
61
+ parent = null,
62
+ backgroundColor = color('#2a2a3d'),
63
+ textColor = color('#e0e0e0'),
64
+ hoverBackgroundColor = color('#3a3a5a'),
65
+ hoverTextColor = color('#ffffff'),
66
+ pressedBackgroundColor = color('#4a4a6a'),
67
+ pressedTextColor = color('#ffffff'),
68
+ borderFlag = true,
69
+ borderColor = color('#3a3a4d'),
70
+ borderWidth = 1,
71
+ cornerRadius = 8,
72
+ enableShadow = false,
73
+ shadowColor = 'rgba(0,0,0,0.5)',
74
+ shadowBlur = 12,
75
+ shadowOffsetX = 0,
76
+ shadowOffsetY = 4,
77
+ HTextAlign = 'center',
78
+ VTextAlign = 'center',
79
+ pad = 5,
80
+ padx = null,
81
+ pady = null,
82
+ padl = null,
83
+ padr = null,
84
+ padt = null,
85
+ padb = null,
86
+ wrap = true,
87
+ wrapMode = 'word',
88
+ noWrapMode = 'font-size',
89
+ ellipsisMode = 'trailing',
90
+ icon = null,
91
+ iconSize = 20,
92
+ iconPosition = 'left',
93
+ iconGap = 6,
94
+ iconTintColor = null,
95
+ iconOpacity = 255,
96
+ margin = 0,
97
+ marginx = null,
98
+ marginy = null,
99
+ marginl = null,
100
+ marginr = null,
101
+ margint = null,
102
+ marginb = null,
103
+ enabled = true,
104
+ minWidth = 0,
105
+ minHeight = 0,
106
+ showDebugOverlay = false,
107
+ } = {}) {
108
+ super(x, y, width, height, label, {
109
+ id,
110
+ parent,
111
+ backgroundColor,
112
+ textColor,
113
+ borderFlag,
114
+ borderColor,
115
+ borderWidth,
116
+ cornerRadius,
117
+ enableShadow,
118
+ shadowColor,
119
+ shadowBlur,
120
+ shadowOffsetX,
121
+ shadowOffsetY,
122
+ HTextAlign,
123
+ VTextAlign,
124
+ pad,
125
+ padx,
126
+ pady,
127
+ padl,
128
+ padr,
129
+ padt,
130
+ padb,
131
+ wrap,
132
+ wrapMode,
133
+ noWrapMode,
134
+ ellipsisMode,
135
+ icon,
136
+ iconSize,
137
+ iconPosition,
138
+ iconGap,
139
+ iconTintColor,
140
+ iconOpacity,
141
+ margin,
142
+ marginx,
143
+ marginy,
144
+ marginl,
145
+ marginr,
146
+ margint,
147
+ marginb,
148
+ minWidth,
149
+ minHeight,
150
+ showDebugOverlay,
151
+ });
152
+
153
+ this.hoverBackgroundColor = hoverBackgroundColor;
154
+ this.hoverTextColor = hoverTextColor;
155
+ this.pressedBackgroundColor = pressedBackgroundColor;
156
+ this.pressedTextColor = pressedTextColor;
157
+
158
+ this.enabled = enabled;
159
+ this.isHovered = false;
160
+ this.isPressed = false;
161
+
162
+ this.addEventListener('mouseEnter', () => {
163
+ if (!this.enabled) return;
164
+ this.isHovered = true;
165
+ });
166
+
167
+ this.addEventListener('hover', (event) => {
168
+ if (!this.enabled) return;
169
+ cursor('pointer');
170
+ event.stopPropagation();
171
+ });
172
+
173
+ this.addEventListener('mouseLeave', () => {
174
+ this.isHovered = false;
175
+ this.isPressed = false;
176
+ cursor('');
177
+ });
178
+
179
+ this.addEventListener('press', () => {
180
+ if (!this.enabled) return;
181
+ this.isPressed = true;
182
+ });
183
+
184
+ this.addEventListener('release', () => {
185
+ this.isPressed = false;
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Renders the button with hover/press visuals
191
+ */
192
+ show() {
193
+ if (this.enableShadow) {
194
+ this.drawShadow();
195
+ }
196
+
197
+ push();
198
+ beginClip();
199
+ rect(this.x, this.y, this.width, this.height, this.cornerRadius);
200
+ endClip();
201
+ translate(this.x, this.y);
202
+
203
+ // Background
204
+ fill(this.getBackgroundColor());
205
+ rect(0, 0, this.width, this.height, this.cornerRadius);
206
+
207
+ // Text
208
+ fill(this.getTextColor());
209
+ this.renderText();
210
+
211
+ // Border
212
+ if (this.borderFlag) {
213
+ noFill();
214
+ stroke(this.getBorderColor());
215
+ strokeWeight(this.borderWidth);
216
+ rect(0, 0, this.width, this.height, this.cornerRadius);
217
+ }
218
+
219
+ pop();
220
+ }
221
+
222
+ /**
223
+ * Enables or disables the button
224
+ * @param {boolean} enabled - True to enable, false to disable
225
+ */
226
+ setEnabled(enabled) {
227
+ this.enabled = enabled;
228
+ if (!enabled) {
229
+ this.isHovered = false;
230
+ this.isPressed = false;
231
+ }
232
+ }
233
+
234
+ getBackgroundColor() {
235
+ if (!this.enabled) {
236
+ return this.backgroundColor;
237
+ }
238
+
239
+ if (this.isPressed && this.pressedBackgroundColor !== null) {
240
+ return this.pressedBackgroundColor;
241
+ }
242
+
243
+ if (this.isHovered && this.hoverBackgroundColor !== null) {
244
+ return this.hoverBackgroundColor;
245
+ }
246
+
247
+ return this.backgroundColor;
248
+ }
249
+
250
+ getTextColor() {
251
+ if (!this.enabled) {
252
+ return this.textColor;
253
+ }
254
+
255
+ if (this.isPressed && this.pressedTextColor !== null) {
256
+ return this.pressedTextColor;
257
+ }
258
+
259
+ if (this.isHovered && this.hoverTextColor !== null) {
260
+ return this.hoverTextColor;
261
+ }
262
+
263
+ return this.textColor;
264
+ }
265
+
266
+ getBorderColor() {
267
+ if (!this.enabled) {
268
+ return this.borderColor;
269
+ }
270
+
271
+ if (this.isPressed) {
272
+ return color('#6a6a9a');
273
+ }
274
+
275
+ if (this.isHovered) {
276
+ return color('#5a5a7a');
277
+ }
278
+
279
+ return this.borderColor;
280
+ }
281
+ }