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