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