SugarCubes sketch initial code dump
[SugarCubes.git] / _Overlay.pde
1 /**
2 * Overlay UI that indicates pattern control, etc. This will be moved
3 * into the Processing library once it is stabilized and need not be
4 * regularly modified.
5 */
6 class OverlayUI {
7
8 private final PFont titleFont = createFont("Myriad Pro", 10);
9 private final PFont itemFont = createFont("Myriad Pro", 11);
10 private final PFont knobFont = titleFont;
11 private final int w = 140;
12 private final int leftPos;
13 private final int leftTextPos;
14 private final int lineHeight = 20;
15 private final int sectionSpacing = 12;
16 private final int tempoHeight = 20;
17 private final int knobSize = 28;
18 private final int knobSpacing = 6;
19 private final int knobLabelHeight = 14;
20 private final color lightBlue = #666699;
21 private final color lightGreen = #669966;
22
23 private final String[] patternNames;
24 private final String[] transitionNames;
25 private final String[] effectNames;
26
27 private PImage logo;
28
29 private int firstPatternY;
30 private int firstTransitionY;
31 private int firstEffectY;
32 private int firstKnobY;
33 private int tempoY;
34
35 private Method patternStateMethod;
36 private Method transitionStateMethod;
37 private Method effectStateMethod;
38
39 OverlayUI() {
40 leftPos = width - w;
41 leftTextPos = leftPos + 4;
42 logo = loadImage("logo-sm.png");
43
44 patternNames = classNameArray(patterns);
45 transitionNames = classNameArray(transitions);
46 effectNames = classNameArray(effects);
47
48 try {
49 patternStateMethod = getClass().getMethod("getState", LXPattern.class);
50 effectStateMethod = getClass().getMethod("getState", LXEffect.class);
51 transitionStateMethod = getClass().getMethod("getState", LXTransition.class);
52 } catch (Exception x) {
53 throw new RuntimeException(x);
54 }
55 }
56
57 void drawHelpTip() {
58 textFont(itemFont);
59 textAlign(RIGHT);
60 text("Tap 'u' to restore UI", width-4, height-6);
61 }
62
63 void draw() {
64 image(logo, 4, 4);
65
66 stroke(color(0, 0, 100));
67 // fill(color(0, 0, 50, 50)); // alpha is bad for perf
68 fill(color(0, 0, 30));
69 rect(leftPos-1, -1, w+2, height+2);
70
71 int yPos = 0;
72 firstPatternY = yPos + lineHeight + 6;
73 yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod);
74
75 yPos += sectionSpacing;
76 yPos = drawObjectList(yPos, "CONTROL", null, null, null);
77 yPos += 6;
78 firstKnobY = yPos;
79 int xPos = leftTextPos;
80 for (int i = 0; i < glucose.NUM_PATTERN_KNOBS/2; ++i) {
81 drawKnob(xPos, yPos, knobSize, glucose.patternKnobs[i]);
82 drawKnob(xPos, yPos + knobSize + knobSpacing + knobLabelHeight, knobSize, glucose.patternKnobs[glucose.NUM_PATTERN_KNOBS/2 + i]);
83 xPos += knobSize + knobSpacing;
84 }
85 yPos += 2*(knobSize + knobLabelHeight) + knobSpacing;
86
87 yPos += sectionSpacing;
88 firstTransitionY = yPos + lineHeight + 6;
89 yPos = drawObjectList(yPos, "TRANSITION", transitions, transitionNames, transitionStateMethod);
90
91 yPos += sectionSpacing;
92 firstEffectY = yPos + lineHeight + 6;
93 yPos = drawObjectList(yPos, "FX", effects, effectNames, effectStateMethod);
94
95 yPos += sectionSpacing;
96 yPos = drawObjectList(yPos, "TEMPO", null, null, null);
97 yPos += 6;
98 tempoY = yPos;
99 stroke(#111111);
100 fill(tempoDown ? lightGreen : color(0, 0, 35 - 8*lx.tempo.rampf()));
101 rect(leftPos + 4, yPos, w - 8, tempoHeight);
102 fill(0);
103 textAlign(CENTER);
104 text("" + ((int)(lx.tempo.bpmf() * 100) / 100.), leftPos + w/2., yPos + tempoHeight - 6);
105 yPos += tempoHeight;
106
107 fill(#999999);
108 textFont(itemFont);
109 textAlign(LEFT);
110 text("Tap 'u' to hide UI (~+3FPS)", leftTextPos, height-6);
111 }
112
113 public Knob getOrNull(List<Knob> items, int index) {
114 if (index < items.size()) {
115 return items.get(index);
116 }
117 return null;
118 }
119
120 public void drawFPS() {
121 textFont(titleFont);
122 textAlign(LEFT);
123 fill(#666666);
124 text("FPS: " + (((int)(frameRate * 10)) / 10.), 4, height-6);
125 }
126
127 private final int STATE_DEFAULT = 0;
128 private final int STATE_ACTIVE = 1;
129 private final int STATE_PENDING = 2;
130
131 public int getState(LXPattern p) {
132 if (p == lx.getPattern()) {
133 return STATE_ACTIVE;
134 } else if (p == lx.getNextPattern()) {
135 return STATE_PENDING;
136 }
137 return STATE_DEFAULT;
138 }
139
140 public int getState(LXEffect e) {
141 return e.isEnabled() ? STATE_ACTIVE : STATE_DEFAULT;
142 }
143
144 public int getState(LXTransition t) {
145 if (t == lx.getTransition()) {
146 return STATE_PENDING;
147 } else if (t == transitions[activeTransitionIndex]) {
148 return STATE_ACTIVE;
149 }
150 return STATE_DEFAULT;
151 }
152
153 protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) {
154 return drawObjectList(yPos, title, items, classNameArray(items), stateMethod);
155 }
156
157 private int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) {
158 noStroke();
159 fill(#aaaaaa);
160 textFont(titleFont);
161 textAlign(LEFT);
162 text(title, leftTextPos, yPos += lineHeight);
163 if (items != null) {
164 textFont(itemFont);
165 color textColor;
166 boolean even = true;
167 for (int i = 0; i < items.length; ++i) {
168 Object o = items[i];
169 int state = STATE_DEFAULT;
170 try {
171 state = ((Integer) stateMethod.invoke(this, o)).intValue();
172 } catch (Exception x) {
173 throw new RuntimeException(x);
174 }
175 switch (state) {
176 case STATE_ACTIVE:
177 fill(lightGreen);
178 textColor = #eeeeee;
179 break;
180 case STATE_PENDING:
181 fill(lightBlue);
182 textColor = color(0, 0, 75 + 15*sin(millis()/200.));;
183 break;
184 default:
185 textColor = 0;
186 fill(even ? #666666 : #777777);
187 break;
188 }
189 rect(leftPos, yPos+6, width, lineHeight);
190 fill(textColor);
191 text(names[i], leftTextPos, yPos += lineHeight);
192 even = !even;
193 }
194 }
195 return yPos;
196 }
197
198 private void drawKnob(int xPos, int yPos, int knobSize, Knob knob) {
199 final float knobIndent = .4;
200 final float knobValue = knob.getValuef();
201 String knobLabel = knob.getLabel();
202 if (knobLabel.length() > 4) {
203 knobLabel = knobLabel.substring(0, 4);
204 } else if (knobLabel.length() == 0) {
205 knobLabel = "-";
206 }
207
208 ellipseMode(CENTER);
209 fill(#222222);
210 arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent));
211
212 fill(lightGreen);
213 arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue);
214
215 fill(#333333);
216 ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize/2, knobSize/2);
217
218 fill(0);
219 rect(xPos, yPos + knobSize + 2, knobSize, knobLabelHeight - 2);
220 fill(#999999);
221 textAlign(CENTER);
222 textFont(knobFont);
223 text(knobLabel, xPos + knobSize/2, yPos + knobSize + knobLabelHeight - 2);
224
225 }
226
227 private String[] classNameArray(Object[] objects) {
228 if (objects == null) {
229 return null;
230 }
231 String[] names = new String[objects.length];
232 for (int i = 0; i < objects.length; ++i) {
233 names[i] = className(objects[i]);
234 }
235 return names;
236 }
237
238 private String className(Object p) {
239 String s = p.getClass().getName();
240 int li;
241 if ((li = s.lastIndexOf(".")) > 0) {
242 s = s.substring(li + 1);
243 }
244 if (s.indexOf("SugarCubes$") == 0) {
245 return s.substring("SugarCubes$".length());
246 }
247 return s;
248 }
249
250 private int knobIndex = -1;
251 private int lastY;
252 private int releaseEffect = -1;
253 private boolean tempoDown = false;
254
255 public void mousePressed() {
256 lastY = mouseY;
257 knobIndex = -1;
258 releaseEffect = -1;
259 if (mouseY > tempoY) {
260 if (mouseY - tempoY < tempoHeight) {
261 lx.tempo.tap();
262 tempoDown = true;
263 }
264 } else if (mouseY > firstEffectY) {
265 int effectIndex = (mouseY - firstEffectY) / lineHeight;
266 if (effectIndex < effects.length) {
267 if (effects[effectIndex].isMomentary()) {
268 effects[effectIndex].enable();
269 releaseEffect = effectIndex;
270 } else {
271 effects[effectIndex].toggle();
272 }
273 }
274 } else if (mouseY > firstTransitionY) {
275 int transitionIndex = (mouseY - firstTransitionY) / lineHeight;
276 if (transitionIndex < transitions.length) {
277 activeTransitionIndex = transitionIndex;
278 }
279 } else if ((mouseY >= firstKnobY) && (mouseY < firstKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) {
280 knobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
281 if (mouseY >= firstKnobY + knobSize + knobLabelHeight + knobSpacing) {
282 knobIndex += glucose.NUM_PATTERN_KNOBS / 2;
283 }
284 } else if (mouseY > firstPatternY) {
285 int patternIndex = (mouseY - firstPatternY) / lineHeight;
286 if (patternIndex < patterns.length) {
287 patterns[patternIndex].setTransition(transitions[activeTransitionIndex]);
288 lx.goIndex(patternIndex);
289 }
290 }
291 }
292
293 public void mouseDragged() {
294 int dy = lastY - mouseY;
295 lastY = mouseY;
296 if (knobIndex >= 0 && knobIndex < glucose.NUM_PATTERN_KNOBS) {
297 Knob k = glucose.patternKnobs[knobIndex];
298 k.setValue(k.getValuef() + dy*.01);
299 }
300 }
301
302 public void mouseReleased() {
303 tempoDown = false;
304 if (releaseEffect >= 0) {
305 effects[releaseEffect].trigger();
306 releaseEffect = -1;
307 }
308 }
309 }
310
311 void mousePressed() {
312 if (mouseX > ui.leftPos) {
313 ui.mousePressed();
314 }
315 }
316
317 void mouseReleased() {
318 if (mouseX > ui.leftPos) {
319 ui.mouseReleased();
320 }
321 }
322
323 void mouseDragged() {
324 if (mouseX > ui.leftPos) {
325 ui.mouseDragged();
326 }
327 }
328