Restore mappings data to sugarcubes sketch. Called back from GLucose library.
[SugarCubes.git] / _Overlay.pde
1 /**
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
11 * Overlay UI that indicates pattern control, etc. This will be moved
12 * into the Processing library once it is stabilized and need not be
13 * regularly modified.
14 */
15 class OverlayUI {
16
17 private final PFont titleFont = createFont("Myriad Pro", 10);
18 private final PFont itemFont = createFont("Lucida Grande", 11);
19 private final PFont knobFont = titleFont;
20 private final int w = 140;
21 private final int leftPos;
22 private final int leftTextPos;
23 private final int lineHeight = 20;
24 private final int sectionSpacing = 12;
25 private final int controlSpacing = 18;
26 private final int tempoHeight = 20;
27 private final int knobSize = 28;
28 private final float knobIndent = .4;
29 private final int knobSpacing = 6;
30 private final int knobLabelHeight = 14;
31 private final color lightBlue = #666699;
32 private final color lightGreen = #669966;
33
34 private final String[] patternNames;
35 private final String[] transitionNames;
36 private final String[] effectNames;
37
38 private PImage logo;
39
40 private int firstPatternY;
41 private int firstPatternKnobY;
42 private int firstTransitionY;
43 private int firstTransitionKnobY;
44 private int firstEffectY;
45 private int firstEffectKnobY;
46
47 private int tempoY;
48
49 private Method patternStateMethod;
50 private Method transitionStateMethod;
51 private Method effectStateMethod;
52
53 private final int NUM_PATTERN_KNOBS = 8;
54 private final int NUM_TRANSITION_KNOBS = 4;
55 private final int NUM_EFFECT_KNOBS = 4;
56
57 private int activeTransitionIndex = 0;
58 private int activeEffectIndex = 0;
59
60 public final VirtualPatternKnob[] patternKnobs;
61 public final VirtualTransitionKnob[] transitionKnobs;
62 public final VirtualEffectKnob[] effectKnobs;
63
64 OverlayUI() {
65 leftPos = width - w;
66 leftTextPos = leftPos + 4;
67 logo = loadImage("logo-sm.png");
68
69 patternNames = classNameArray(patterns, "Pattern");
70 transitionNames = classNameArray(transitions, "Transition");
71 effectNames = classNameArray(effects, "Effect");
72
73 patternKnobs = new VirtualPatternKnob[NUM_PATTERN_KNOBS];
74 for (int i = 0; i < patternKnobs.length; ++i) {
75 patternKnobs[i] = new VirtualPatternKnob(i);
76 }
77
78 transitionKnobs = new VirtualTransitionKnob[NUM_TRANSITION_KNOBS];
79 for (int i = 0; i < transitionKnobs.length; ++i) {
80 transitionKnobs[i] = new VirtualTransitionKnob(i);
81 }
82
83 effectKnobs = new VirtualEffectKnob[NUM_EFFECT_KNOBS];
84 for (int i = 0; i < effectKnobs.length; ++i) {
85 effectKnobs[i] = new VirtualEffectKnob(i);
86 }
87
88 try {
89 patternStateMethod = getClass().getMethod("getState", LXPattern.class);
90 effectStateMethod = getClass().getMethod("getState", LXEffect.class);
91 transitionStateMethod = getClass().getMethod("getState", LXTransition.class);
92 } catch (Exception x) {
93 throw new RuntimeException(x);
94 }
95 }
96
97 void drawHelpTip() {
98 textFont(itemFont);
99 textAlign(RIGHT);
100 text("Tap 'u' to restore UI", width-4, height-6);
101 }
102
103 void draw() {
104 image(logo, 4, 4);
105
106 stroke(color(0, 0, 100));
107 // fill(color(0, 0, 50, 50)); // alpha is bad for perf
108 fill(color(0, 0, 30));
109 rect(leftPos-1, -1, w+2, height+2);
110
111 int yPos = 0;
112 firstPatternY = yPos + lineHeight + 6;
113 yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod);
114 yPos += controlSpacing;
115 firstPatternKnobY = yPos;
116 int xPos = leftTextPos;
117 for (int i = 0; i < NUM_PATTERN_KNOBS/2; ++i) {
118 drawKnob(xPos, yPos, knobSize, patternKnobs[i]);
119 drawKnob(xPos, yPos + knobSize + knobSpacing + knobLabelHeight, knobSize, patternKnobs[NUM_PATTERN_KNOBS/2 + i]);
120 xPos += knobSize + knobSpacing;
121 }
122 yPos += 2*(knobSize + knobLabelHeight) + knobSpacing;
123
124 yPos += sectionSpacing;
125 firstTransitionY = yPos + lineHeight + 6;
126 yPos = drawObjectList(yPos, "TRANSITION", transitions, transitionNames, transitionStateMethod);
127 yPos += controlSpacing;
128 firstTransitionKnobY = yPos;
129 xPos = leftTextPos;
130 for (int i = 0; i < transitionKnobs.length; ++i) {
131 drawKnob(xPos, yPos, knobSize, transitionKnobs[i]);
132 xPos += knobSize + knobSpacing;
133 }
134 yPos += knobSize + knobLabelHeight;
135
136 yPos += sectionSpacing;
137 firstEffectY = yPos + lineHeight + 6;
138 yPos = drawObjectList(yPos, "FX", effects, effectNames, effectStateMethod);
139 yPos += controlSpacing;
140 firstEffectKnobY = yPos;
141 xPos = leftTextPos;
142 for (int i = 0; i < effectKnobs.length; ++i) {
143 drawKnob(xPos, yPos, knobSize, effectKnobs[i]);
144 xPos += knobSize + knobSpacing;
145 }
146 yPos += knobSize + knobLabelHeight;
147
148 yPos += sectionSpacing;
149 yPos = drawObjectList(yPos, "TEMPO", null, null, null);
150 yPos += 6;
151 tempoY = yPos;
152 stroke(#111111);
153 fill(tempoDown ? lightGreen : color(0, 0, 35 - 8*lx.tempo.rampf()));
154 rect(leftPos + 4, yPos, w - 8, tempoHeight);
155 fill(0);
156 textAlign(CENTER);
157 text("" + ((int)(lx.tempo.bpmf() * 100) / 100.), leftPos + w/2., yPos + tempoHeight - 6);
158 yPos += tempoHeight;
159
160 fill(#999999);
161 textFont(itemFont);
162 textAlign(LEFT);
163 text("Tap 'u' to hide UI", leftTextPos, height-6);
164 }
165
166 public LXParameter getOrNull(List<LXParameter> items, int index) {
167 if (index < items.size()) {
168 return items.get(index);
169 }
170 return null;
171 }
172
173 public void drawFPS() {
174 textFont(titleFont);
175 textAlign(LEFT);
176 fill(#666666);
177 text("FPS: " + (((int)(frameRate * 10)) / 10.), 4, height-6);
178 }
179
180 private final int STATE_DEFAULT = 0;
181 private final int STATE_ACTIVE = 1;
182 private final int STATE_PENDING = 2;
183
184 public int getState(LXPattern p) {
185 if (p == lx.getPattern()) {
186 return STATE_ACTIVE;
187 } else if (p == lx.getNextPattern()) {
188 return STATE_PENDING;
189 }
190 return STATE_DEFAULT;
191 }
192
193 public int getState(LXEffect e) {
194 if (e.isEnabled()) {
195 return STATE_PENDING;
196 } else if (effects[activeEffectIndex] == e) {
197 return STATE_ACTIVE;
198 }
199 return STATE_DEFAULT;
200 }
201
202 public int getState(LXTransition t) {
203 if (t == lx.getTransition()) {
204 return STATE_PENDING;
205 } else if (t == transitions[activeTransitionIndex]) {
206 return STATE_ACTIVE;
207 }
208 return STATE_DEFAULT;
209 }
210
211 protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) {
212 return drawObjectList(yPos, title, items, classNameArray(items, null), stateMethod);
213 }
214
215 private int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) {
216 noStroke();
217 fill(#aaaaaa);
218 textFont(titleFont);
219 textAlign(LEFT);
220 text(title, leftTextPos, yPos += lineHeight);
221 if (items != null) {
222 textFont(itemFont);
223 color textColor;
224 boolean even = true;
225 for (int i = 0; i < items.length; ++i) {
226 Object o = items[i];
227 int state = STATE_DEFAULT;
228 try {
229 state = ((Integer) stateMethod.invoke(this, o)).intValue();
230 } catch (Exception x) {
231 throw new RuntimeException(x);
232 }
233 switch (state) {
234 case STATE_ACTIVE:
235 fill(lightGreen);
236 textColor = #eeeeee;
237 break;
238 case STATE_PENDING:
239 fill(lightBlue);
240 textColor = color(0, 0, 75 + 15*sin(millis()/200.));;
241 break;
242 default:
243 textColor = 0;
244 fill(even ? #666666 : #777777);
245 break;
246 }
247 rect(leftPos, yPos+6, width, lineHeight);
248 fill(textColor);
249 text(names[i], leftTextPos, yPos += lineHeight);
250 even = !even;
251 }
252 }
253 return yPos;
254 }
255
256 private void drawKnob(int xPos, int yPos, int knobSize, LXParameter knob) {
257 if (!knobsOn) {
258 return;
259 }
260 final float knobValue = knob.getValuef();
261 String knobLabel = knob.getLabel();
262 if (knobLabel == null) {
263 knobLabel = "-";
264 } else if (knobLabel.length() > 4) {
265 knobLabel = knobLabel.substring(0, 4);
266 }
267
268 ellipseMode(CENTER);
269 noStroke();
270 fill(#222222);
271 // For some reason this arc call really crushes drawing performance. Presumably
272 // because openGL is drawing it and when we overlap the second set of arcs it
273 // does a bunch of depth buffer intersection tests? Ellipse with a trapezoid cut out is faster
274 // arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent));
275 ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize);
276
277 float endArc = HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue;
278 fill(lightGreen);
279 arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, endArc);
280
281 // Mask notch out of knob
282 fill(color(0, 0, 30));
283 beginShape();
284 vertex(xPos + knobSize/2, yPos + knobSize/2.);
285 vertex(xPos + knobSize/2 - 6, yPos + knobSize);
286 vertex(xPos + knobSize/2 + 6, yPos + knobSize);
287 endShape();
288
289 // Center circle of knob
290 fill(#333333);
291 ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize/2, knobSize/2);
292
293 fill(0);
294 rect(xPos, yPos + knobSize + 2, knobSize, knobLabelHeight - 2);
295 fill(#999999);
296 textAlign(CENTER);
297 textFont(knobFont);
298 text(knobLabel, xPos + knobSize/2, yPos + knobSize + knobLabelHeight - 2);
299
300 }
301
302 private String[] classNameArray(Object[] objects, String suffix) {
303 if (objects == null) {
304 return null;
305 }
306 String[] names = new String[objects.length];
307 for (int i = 0; i < objects.length; ++i) {
308 names[i] = className(objects[i], suffix);
309 }
310 return names;
311 }
312
313 private String className(Object p, String suffix) {
314 String s = p.getClass().getName();
315 int li;
316 if ((li = s.lastIndexOf(".")) > 0) {
317 s = s.substring(li + 1);
318 }
319 if (s.indexOf("SugarCubes$") == 0) {
320 s = s.substring("SugarCubes$".length());
321 }
322 if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) {
323 s = s.substring(0, li);
324 }
325 return s;
326 }
327
328 class VirtualPatternKnob extends LXVirtualParameter {
329 private final int index;
330
331 VirtualPatternKnob(int index) {
332 this.index = index;
333 }
334
335 public LXParameter getRealParameter() {
336 List<LXParameter> parameters = glucose.getPattern().getParameters();
337 if (index < parameters.size()) {
338 return parameters.get(index);
339 }
340 return null;
341 }
342 }
343
344 class VirtualTransitionKnob extends LXVirtualParameter {
345 private final int index;
346
347 VirtualTransitionKnob(int index) {
348 this.index = index;
349 }
350
351 public LXParameter getRealParameter() {
352 List<LXParameter> parameters = transitions[activeTransitionIndex].getParameters();
353 if (index < parameters.size()) {
354 return parameters.get(index);
355 }
356 return null;
357 }
358 }
359
360 class VirtualEffectKnob extends LXVirtualParameter {
361 private final int index;
362
363 VirtualEffectKnob(int index) {
364 this.index = index;
365 }
366
367 public LXParameter getRealParameter() {
368 List<LXParameter> parameters = effects[activeEffectIndex].getParameters();
369 if (index < parameters.size()) {
370 return parameters.get(index);
371 }
372 return null;
373 }
374 }
375
376 private int patternKnobIndex = -1;
377 private int transitionKnobIndex = -1;
378 private int effectKnobIndex = -1;
379
380 private int lastY;
381 private int releaseEffect = -1;
382 private boolean tempoDown = false;
383
384 public void mousePressed() {
385 lastY = mouseY;
386 patternKnobIndex = transitionKnobIndex = effectKnobIndex = -1;
387 releaseEffect = -1;
388 if (mouseY > tempoY) {
389 if (mouseY - tempoY < tempoHeight) {
390 lx.tempo.tap();
391 tempoDown = true;
392 }
393 } else if ((mouseY >= firstEffectKnobY) && (mouseY < firstEffectKnobY + knobSize + knobLabelHeight)) {
394 effectKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
395 } else if (mouseY > firstEffectY) {
396 int effectIndex = (mouseY - firstEffectY) / lineHeight;
397 if (effectIndex < effects.length) {
398 if (activeEffectIndex == effectIndex) {
399 effects[effectIndex].enable();
400 releaseEffect = effectIndex;
401 }
402 activeEffectIndex = effectIndex;
403 }
404 } else if ((mouseY >= firstTransitionKnobY) && (mouseY < firstTransitionKnobY + knobSize + knobLabelHeight)) {
405 transitionKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
406 } else if (mouseY > firstTransitionY) {
407 int transitionIndex = (mouseY - firstTransitionY) / lineHeight;
408 if (transitionIndex < transitions.length) {
409 activeTransitionIndex = transitionIndex;
410 }
411 } else if ((mouseY >= firstPatternKnobY) && (mouseY < firstPatternKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) {
412 patternKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
413 if (mouseY >= firstPatternKnobY + knobSize + knobLabelHeight + knobSpacing) {
414 patternKnobIndex += NUM_PATTERN_KNOBS / 2;
415 }
416 } else if (mouseY > firstPatternY) {
417 int patternIndex = (mouseY - firstPatternY) / lineHeight;
418 if (patternIndex < patterns.length) {
419 patterns[patternIndex].setTransition(transitions[activeTransitionIndex]);
420 lx.goIndex(patternIndex);
421 }
422 }
423 }
424
425 public void mouseDragged() {
426 int dy = lastY - mouseY;
427 lastY = mouseY;
428 if (patternKnobIndex >= 0 && patternKnobIndex < NUM_PATTERN_KNOBS) {
429 LXParameter p = patternKnobs[patternKnobIndex];
430 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
431 } else if (effectKnobIndex >= 0 && effectKnobIndex < NUM_EFFECT_KNOBS) {
432 LXParameter p = effectKnobs[effectKnobIndex];
433 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
434 } else if (transitionKnobIndex >= 0 && transitionKnobIndex < NUM_TRANSITION_KNOBS) {
435 LXParameter p = transitionKnobs[transitionKnobIndex];
436 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
437 }
438 }
439
440 public void mouseReleased() {
441 tempoDown = false;
442 if (releaseEffect >= 0) {
443 effects[releaseEffect].trigger();
444 releaseEffect = -1;
445 }
446 }
447
448 }
449
450 void mousePressed() {
451 if (mouseX > ui.leftPos) {
452 ui.mousePressed();
453 }
454 }
455
456 void mouseReleased() {
457 if (mouseX > ui.leftPos) {
458 ui.mouseReleased();
459 }
460 }
461
462 void mouseDragged() {
463 if (mouseX > ui.leftPos) {
464 ui.mouseDragged();
465 }
466 }
467