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