Dropping Alex's anim in master
[SugarCubes.git] / _Overlay.pde
CommitLineData
e73ef85d
MS
1import java.lang.reflect.*;
2
49815cc0 3/**
1ecdb44a
MS
4 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
5 *
6 * //\\ //\\ //\\ //\\
7 * ///\\\ ///\\\ ///\\\ ///\\\
8 * \\\/// \\\/// \\\/// \\\///
9 * \\// \\// \\// \\//
10 *
11 * EXPERTS ONLY!! EXPERTS ONLY!!
12 *
49815cc0
MS
13 * Overlay UI that indicates pattern control, etc. This will be moved
14 * into the Processing library once it is stabilized and need not be
15 * regularly modified.
16 */
bf551144
MS
17abstract class OverlayUI {
18 protected final PFont titleFont = createFont("Myriad Pro", 10);
19 protected final color titleColor = #AAAAAA;
20 protected final PFont itemFont = createFont("Lucida Grande", 11);
21 protected final PFont knobFont = titleFont;
22 protected final int w = 140;
23 protected final int leftPos;
24 protected final int leftTextPos;
25 protected final int lineHeight = 20;
26 protected final int sectionSpacing = 12;
27 protected final int controlSpacing = 18;
28 protected final int tempoHeight = 20;
29 protected final int knobSize = 28;
30 protected final float knobIndent = .4;
31 protected final int knobSpacing = 6;
32 protected final int knobLabelHeight = 14;
0ba6ac44 33 protected final int scrollWidth = 14;
bf551144
MS
34 protected final color lightBlue = #666699;
35 protected final color lightGreen = #669966;
6c1a9e53 36 protected final int toggleButtonSize = 10;
49815cc0 37
bf551144
MS
38 private PImage logo;
39
40 protected final int STATE_DEFAULT = 0;
41 protected final int STATE_ACTIVE = 1;
42 protected final int STATE_PENDING = 2;
43
79ae8245 44 protected int[] pandaLeft = new int[pandaBoards.length];
ae80d37a 45 protected final int pandaWidth = 64;
79ae8245
MS
46 protected final int pandaHeight = 13;
47 protected final int pandaTop = height-16;
48
6c1a9e53
MS
49 protected int eligibleLeft;
50
bf551144
MS
51 protected OverlayUI() {
52 leftPos = width - w;
53 leftTextPos = leftPos + 4;
54 logo = loadImage("logo-sm.png");
55 }
56
57 protected void drawLogoAndBackground() {
58 image(logo, 4, 4);
59 stroke(color(0, 0, 100));
60 // fill(color(0, 0, 50, 50)); // alpha is bad for perf
61 fill(color(0, 0, 30));
62 rect(leftPos-1, -1, w+2, height+2);
63 }
64
65 protected void drawToggleTip(String s) {
66 fill(#999999);
67 textFont(itemFont);
68 textAlign(LEFT);
69 text(s, leftTextPos, height-6);
70 }
71
72 protected void drawHelpTip() {
73 textFont(itemFont);
74 textAlign(RIGHT);
75 text("Tap 'u' to restore UI", width-4, height-6);
76 }
77
78 public void drawFPS() {
79 textFont(titleFont);
80 textAlign(LEFT);
81 fill(#666666);
ae80d37a
MS
82 int lPos = 4;
83 String fps = "FPS: " + (((int)(frameRate * 10)) / 10.);
84 text(fps, lPos, height-6);
85 lPos += 48;
86
87 String target = "Target (-/+):";
88 text(target, lPos, height-6);
0e3c5542 89 fill(#000000);
ae80d37a
MS
90 lPos += textWidth(target) + 4;
91 rect(lPos, height-16, 24, 13);
0e3c5542 92 fill(#666666);
ae80d37a
MS
93 text("" + targetFramerate, lPos + (24 - textWidth("" + targetFramerate))/2, height-6);
94 lPos += 32;
95 String pandaOutput = "PandaOutput (p):";
96 text(pandaOutput, lPos, height-6);
97 lPos += textWidth(pandaOutput)+4;
79ae8245
MS
98 int pi = 0;
99 for (PandaDriver p : pandaBoards) {
100 pandaLeft[pi++] = lPos;
101 fill(p.enabled ? #666666 : #000000);
102 rect(lPos, pandaTop, pandaWidth, pandaHeight);
103 fill(p.enabled ? #000000 : #666666);
ae80d37a
MS
104 text(p.ip, lPos + (pandaWidth - textWidth(p.ip)) / 2, height-6);
105 lPos += pandaWidth + 8;
79ae8245 106 }
bfff6bc2 107
bf551144
MS
108 }
109
110 protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) {
0ba6ac44
MS
111 int sz = (items != null) ? items.length : 0;
112 return drawObjectList(yPos, title, items, stateMethod, sz, 0);
bf551144 113 }
0ba6ac44
MS
114
115 protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod, int scrollLength, int scrollPos) {
116 return drawObjectList(yPos, title, items, classNameArray(items, null), stateMethod, scrollLength, scrollPos);
117 }
118
bf551144 119 protected int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) {
0ba6ac44
MS
120 int sz = (items != null) ? items.length : 0;
121 return drawObjectList(yPos, title, items, names, stateMethod, sz, 0);
122 }
123
6c1a9e53
MS
124 protected void drawToggleButton(float x, float y, boolean eligible, color textColor) {
125 noFill();
126 stroke(textColor);
127 rect(x, y, toggleButtonSize, toggleButtonSize);
128 if (eligible) {
129 noStroke();
130 fill(textColor);
131 rect(x + 2, y + 2, toggleButtonSize - 4, toggleButtonSize - 4);
132 }
133 }
134
0ba6ac44 135 protected int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod, int scrollLength, int scrollPos) {
bf551144
MS
136 noStroke();
137 fill(titleColor);
138 textFont(titleFont);
139 textAlign(LEFT);
140 text(title, leftTextPos, yPos += lineHeight);
141 if (items != null) {
6c1a9e53 142 boolean hasScroll = (scrollPos > 0) || (scrollLength < items.length);
bf551144
MS
143 textFont(itemFont);
144 color textColor;
145 boolean even = true;
0ba6ac44
MS
146 int yTop = yPos+6;
147 for (int i = scrollPos; i < items.length && i < (scrollPos + scrollLength); ++i) {
bf551144
MS
148 Object o = items[i];
149 int state = STATE_DEFAULT;
150 try {
151 state = ((Integer) stateMethod.invoke(this, o)).intValue();
152 } catch (Exception x) {
153 throw new RuntimeException(x);
154 }
155 switch (state) {
156 case STATE_ACTIVE:
157 fill(lightGreen);
158 textColor = #eeeeee;
159 break;
160 case STATE_PENDING:
161 fill(lightBlue);
162 textColor = color(0, 0, 75 + 15*sin(millis()/200.));;
163 break;
164 default:
165 textColor = 0;
166 fill(even ? #666666 : #777777);
167 break;
168 }
6c1a9e53 169 noStroke();
0ba6ac44 170 rect(leftPos, yPos+6, w, lineHeight);
bf551144
MS
171 fill(textColor);
172 text(names[i], leftTextPos, yPos += lineHeight);
6c1a9e53
MS
173 if (lx.isAutoTransitionEnabled() && items[i] instanceof LXPattern) {
174 boolean eligible = ((LXPattern)items[i]).isEligible();
175 eligibleLeft = leftPos + w - (hasScroll ? scrollWidth : 0) - 15;
176 drawToggleButton(eligibleLeft, yPos-8, eligible, textColor);
177 }
bf551144
MS
178 even = !even;
179 }
6c1a9e53 180 if (hasScroll) {
0ba6ac44
MS
181 int yHere = yPos+6;
182 noStroke();
183 fill(color(0, 0, 0, 50));
184 rect(leftPos + w - scrollWidth, yTop, scrollWidth, yHere - yTop);
185 fill(#666666);
186 rect(leftPos + w - scrollWidth + 2, yTop + (yHere-yTop) * (scrollPos / (float)items.length), scrollWidth - 4, (yHere - yTop) * (scrollLength / (float)items.length));
187
188 }
189
bf551144
MS
190 }
191 return yPos;
192 }
193
194 protected String[] classNameArray(Object[] objects, String suffix) {
195 if (objects == null) {
196 return null;
197 }
198 String[] names = new String[objects.length];
199 for (int i = 0; i < objects.length; ++i) {
200 names[i] = className(objects[i], suffix);
201 }
202 return names;
203 }
204
205 protected String className(Object p, String suffix) {
206 String s = p.getClass().getName();
207 int li;
208 if ((li = s.lastIndexOf(".")) > 0) {
209 s = s.substring(li + 1);
210 }
211 if (s.indexOf("SugarCubes$") == 0) {
212 s = s.substring("SugarCubes$".length());
213 }
214 if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) {
215 s = s.substring(0, li);
216 }
217 return s;
218 }
219
220 protected int objectClickIndex(int firstItemY) {
221 return (mouseY - firstItemY) / lineHeight;
222 }
49815cc0 223
0e3c5542 224 abstract public void draw();
bf551144
MS
225 abstract public void mousePressed();
226 abstract public void mouseDragged();
227 abstract public void mouseReleased();
0ba6ac44 228 abstract public void mouseWheel(int delta);
bf551144
MS
229}
230
231/**
232 * UI for control of patterns, transitions, effects.
233 */
234class ControlUI extends OverlayUI {
49815cc0
MS
235 private final String[] patternNames;
236 private final String[] transitionNames;
237 private final String[] effectNames;
49815cc0
MS
238
239 private int firstPatternY;
3f8be614 240 private int firstPatternKnobY;
49815cc0 241 private int firstTransitionY;
3f8be614 242 private int firstTransitionKnobY;
49815cc0 243 private int firstEffectY;
3f8be614 244 private int firstEffectKnobY;
0ba6ac44 245
6c1a9e53
MS
246 private int autoRotateX;
247 private int autoRotateY;
248
0ba6ac44
MS
249 private final int PATTERN_LIST_LENGTH = 8;
250 private int patternScrollPos = 0;
3f8be614 251
49815cc0
MS
252 private int tempoY;
253
254 private Method patternStateMethod;
255 private Method transitionStateMethod;
256 private Method effectStateMethod;
b11ff42b 257
bf551144 258 ControlUI() {
3f8be614
MS
259 patternNames = classNameArray(patterns, "Pattern");
260 transitionNames = classNameArray(transitions, "Transition");
261 effectNames = classNameArray(effects, "Effect");
262
49815cc0
MS
263 try {
264 patternStateMethod = getClass().getMethod("getState", LXPattern.class);
265 effectStateMethod = getClass().getMethod("getState", LXEffect.class);
266 transitionStateMethod = getClass().getMethod("getState", LXTransition.class);
267 } catch (Exception x) {
268 throw new RuntimeException(x);
269 }
270 }
bf551144
MS
271
272 public void draw() {
273 drawLogoAndBackground();
274 int yPos = 0;
6c1a9e53
MS
275 autoRotateX = leftPos + w - 29;
276 autoRotateY = yPos + 12;
277 drawToggleButton(autoRotateX, autoRotateY, lx.isAutoTransitionEnabled(), #999999);
278 fill(lx.isAutoTransitionEnabled() ? #222222: #999999);
279 text("A", autoRotateX + 2, autoRotateY + 9);
49815cc0 280 firstPatternY = yPos + lineHeight + 6;
0ba6ac44 281 yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod, PATTERN_LIST_LENGTH, patternScrollPos);
3f8be614
MS
282 yPos += controlSpacing;
283 firstPatternKnobY = yPos;
49815cc0 284 int xPos = leftTextPos;
cc9fcf4b
MS
285 for (int i = 0; i < glucose.NUM_PATTERN_KNOBS/2; ++i) {
286 drawKnob(xPos, yPos, knobSize, glucose.patternKnobs.get(i));
287 drawKnob(xPos, yPos + knobSize + knobSpacing + knobLabelHeight, knobSize, glucose.patternKnobs.get(glucose.NUM_PATTERN_KNOBS/2 + i));
49815cc0
MS
288 xPos += knobSize + knobSpacing;
289 }
290 yPos += 2*(knobSize + knobLabelHeight) + knobSpacing;
291
292 yPos += sectionSpacing;
293 firstTransitionY = yPos + lineHeight + 6;
294 yPos = drawObjectList(yPos, "TRANSITION", transitions, transitionNames, transitionStateMethod);
3f8be614
MS
295 yPos += controlSpacing;
296 firstTransitionKnobY = yPos;
297 xPos = leftTextPos;
cc9fcf4b
MS
298 for (VirtualTransitionKnob knob : glucose.transitionKnobs) {
299 drawKnob(xPos, yPos, knobSize, knob);
3f8be614
MS
300 xPos += knobSize + knobSpacing;
301 }
302 yPos += knobSize + knobLabelHeight;
49815cc0
MS
303
304 yPos += sectionSpacing;
305 firstEffectY = yPos + lineHeight + 6;
306 yPos = drawObjectList(yPos, "FX", effects, effectNames, effectStateMethod);
3f8be614
MS
307 yPos += controlSpacing;
308 firstEffectKnobY = yPos;
309 xPos = leftTextPos;
cc9fcf4b
MS
310 for (VirtualEffectKnob knob : glucose.effectKnobs) {
311 drawKnob(xPos, yPos, knobSize, knob);
3f8be614
MS
312 xPos += knobSize + knobSpacing;
313 }
314 yPos += knobSize + knobLabelHeight;
49815cc0
MS
315
316 yPos += sectionSpacing;
317 yPos = drawObjectList(yPos, "TEMPO", null, null, null);
318 yPos += 6;
319 tempoY = yPos;
320 stroke(#111111);
321 fill(tempoDown ? lightGreen : color(0, 0, 35 - 8*lx.tempo.rampf()));
322 rect(leftPos + 4, yPos, w - 8, tempoHeight);
323 fill(0);
324 textAlign(CENTER);
325 text("" + ((int)(lx.tempo.bpmf() * 100) / 100.), leftPos + w/2., yPos + tempoHeight - 6);
326 yPos += tempoHeight;
327
bf551144 328 drawToggleTip("Tap 'u' to hide");
49815cc0
MS
329 }
330
3f8be614 331 public LXParameter getOrNull(List<LXParameter> items, int index) {
49815cc0
MS
332 if (index < items.size()) {
333 return items.get(index);
334 }
335 return null;
336 }
337
49815cc0
MS
338 public int getState(LXPattern p) {
339 if (p == lx.getPattern()) {
340 return STATE_ACTIVE;
341 } else if (p == lx.getNextPattern()) {
342 return STATE_PENDING;
343 }
344 return STATE_DEFAULT;
345 }
346
347 public int getState(LXEffect e) {
3f8be614
MS
348 if (e.isEnabled()) {
349 return STATE_PENDING;
cc9fcf4b 350 } else if (e == glucose.getSelectedEffect()) {
3f8be614
MS
351 return STATE_ACTIVE;
352 }
353 return STATE_DEFAULT;
49815cc0
MS
354 }
355
356 public int getState(LXTransition t) {
357 if (t == lx.getTransition()) {
358 return STATE_PENDING;
cc9fcf4b 359 } else if (t == glucose.getSelectedTransition()) {
49815cc0
MS
360 return STATE_ACTIVE;
361 }
362 return STATE_DEFAULT;
363 }
49815cc0 364
3f8be614 365 private void drawKnob(int xPos, int yPos, int knobSize, LXParameter knob) {
49815cc0
MS
366 final float knobValue = knob.getValuef();
367 String knobLabel = knob.getLabel();
3f8be614 368 if (knobLabel == null) {
49815cc0 369 knobLabel = "-";
3f8be614
MS
370 } else if (knobLabel.length() > 4) {
371 knobLabel = knobLabel.substring(0, 4);
49815cc0
MS
372 }
373
374 ellipseMode(CENTER);
87f6fa39 375 noStroke();
49815cc0 376 fill(#222222);
3f8be614
MS
377 // For some reason this arc call really crushes drawing performance. Presumably
378 // because openGL is drawing it and when we overlap the second set of arcs it
379 // does a bunch of depth buffer intersection tests? Ellipse with a trapezoid cut out is faster
380 // arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent));
381 ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize);
382
383 float endArc = HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue;
49815cc0 384 fill(lightGreen);
3f8be614
MS
385 arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, endArc);
386
3f8be614
MS
387 // Mask notch out of knob
388 fill(color(0, 0, 30));
389 beginShape();
4eae387e
MS
390 vertex(xPos + knobSize/2, yPos + knobSize/2.);
391 vertex(xPos + knobSize/2 - 6, yPos + knobSize);
392 vertex(xPos + knobSize/2 + 6, yPos + knobSize);
3f8be614 393 endShape();
4eae387e
MS
394
395 // Center circle of knob
396 fill(#333333);
397 ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize/2, knobSize/2);
49815cc0
MS
398
399 fill(0);
400 rect(xPos, yPos + knobSize + 2, knobSize, knobLabelHeight - 2);
401 fill(#999999);
402 textAlign(CENTER);
403 textFont(knobFont);
404 text(knobLabel, xPos + knobSize/2, yPos + knobSize + knobLabelHeight - 2);
49815cc0 405 }
3f8be614
MS
406
407 private int patternKnobIndex = -1;
408 private int transitionKnobIndex = -1;
409 private int effectKnobIndex = -1;
0ba6ac44 410 private boolean patternScrolling = false;
49815cc0 411
49815cc0
MS
412 private int lastY;
413 private int releaseEffect = -1;
414 private boolean tempoDown = false;
415
416 public void mousePressed() {
417 lastY = mouseY;
3f8be614 418 patternKnobIndex = transitionKnobIndex = effectKnobIndex = -1;
49815cc0 419 releaseEffect = -1;
0ba6ac44 420 patternScrolling = false;
79ae8245
MS
421
422 for (int p = 0; p < pandaLeft.length; ++p) {
423 int xp = pandaLeft[p];
424 if ((mouseX >= xp) &&
425 (mouseX < xp + pandaWidth) &&
426 (mouseY >= pandaTop) &&
427 (mouseY < pandaTop + pandaHeight)) {
428 pandaBoards[p].toggle();
429 }
430 }
431
432 if (mouseX < leftPos) {
433 return;
434 }
435
6c1a9e53
MS
436 if ((mouseX >= autoRotateX) &&
437 (mouseX < autoRotateX + toggleButtonSize) &&
438 (mouseY >= autoRotateY) &&
439 (mouseY < autoRotateY + toggleButtonSize)) {
440 if (lx.isAutoTransitionEnabled()) {
441 lx.disableAutoTransition();
442 println("Auto pattern transition disabled");
443 } else {
444 lx.enableAutoTransition(60000);
445 println("Auto pattern transition enabled");
446 }
447 return;
448 }
449
49815cc0
MS
450 if (mouseY > tempoY) {
451 if (mouseY - tempoY < tempoHeight) {
452 lx.tempo.tap();
453 tempoDown = true;
454 }
3f8be614
MS
455 } else if ((mouseY >= firstEffectKnobY) && (mouseY < firstEffectKnobY + knobSize + knobLabelHeight)) {
456 effectKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
49815cc0 457 } else if (mouseY > firstEffectY) {
bf551144 458 int effectIndex = objectClickIndex(firstEffectY);
49815cc0 459 if (effectIndex < effects.length) {
cc9fcf4b 460 if (effects[effectIndex] == glucose.getSelectedEffect()) {
49815cc0
MS
461 effects[effectIndex].enable();
462 releaseEffect = effectIndex;
49815cc0 463 }
cc9fcf4b 464 glucose.setSelectedEffect(effectIndex);
49815cc0 465 }
3f8be614
MS
466 } else if ((mouseY >= firstTransitionKnobY) && (mouseY < firstTransitionKnobY + knobSize + knobLabelHeight)) {
467 transitionKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
49815cc0 468 } else if (mouseY > firstTransitionY) {
bf551144 469 int transitionIndex = objectClickIndex(firstTransitionY);
49815cc0 470 if (transitionIndex < transitions.length) {
cc9fcf4b 471 glucose.setSelectedTransition(transitionIndex);
49815cc0 472 }
3f8be614
MS
473 } else if ((mouseY >= firstPatternKnobY) && (mouseY < firstPatternKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) {
474 patternKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
475 if (mouseY >= firstPatternKnobY + knobSize + knobLabelHeight + knobSpacing) {
cc9fcf4b 476 patternKnobIndex += glucose.NUM_PATTERN_KNOBS / 2;
49815cc0
MS
477 }
478 } else if (mouseY > firstPatternY) {
0ba6ac44
MS
479 if ((patterns.length > PATTERN_LIST_LENGTH) && (mouseX > width - scrollWidth)) {
480 patternScrolling = true;
481 } else {
482 int patternIndex = objectClickIndex(firstPatternY);
483 if (patternIndex < patterns.length) {
775d3394 484 if (lx.isAutoTransitionEnabled() && (mouseX > eligibleLeft)) {
6c1a9e53
MS
485 patterns[patternIndex + patternScrollPos].toggleEligible();
486 } else {
487 lx.goIndex(patternIndex + patternScrollPos);
488 }
0ba6ac44 489 }
49815cc0
MS
490 }
491 }
492 }
493
0ba6ac44 494 int scrolldy = 0;
49815cc0
MS
495 public void mouseDragged() {
496 int dy = lastY - mouseY;
0ba6ac44 497 scrolldy += dy;
49815cc0 498 lastY = mouseY;
cc9fcf4b
MS
499 if (patternKnobIndex >= 0 && patternKnobIndex < glucose.NUM_PATTERN_KNOBS) {
500 LXParameter p = glucose.patternKnobs.get(patternKnobIndex);
3f8be614 501 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
cc9fcf4b
MS
502 } else if (effectKnobIndex >= 0 && effectKnobIndex < glucose.NUM_EFFECT_KNOBS) {
503 LXParameter p = glucose.effectKnobs.get(effectKnobIndex);
3f8be614 504 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
cc9fcf4b
MS
505 } else if (transitionKnobIndex >= 0 && transitionKnobIndex < glucose.NUM_TRANSITION_KNOBS) {
506 LXParameter p = glucose.transitionKnobs.get(transitionKnobIndex);
3f8be614 507 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
0ba6ac44
MS
508 } else if (patternScrolling) {
509 int scroll = scrolldy / lineHeight;
510 scrolldy = scrolldy % lineHeight;
511 patternScrollPos = constrain(patternScrollPos - scroll, 0, patterns.length - PATTERN_LIST_LENGTH);
49815cc0
MS
512 }
513 }
514
515 public void mouseReleased() {
0ba6ac44 516 patternScrolling = false;
49815cc0
MS
517 tempoDown = false;
518 if (releaseEffect >= 0) {
519 effects[releaseEffect].trigger();
520 releaseEffect = -1;
521 }
522 }
3f8be614 523
0ba6ac44
MS
524 public void mouseWheel(int delta) {
525 if (mouseY > firstPatternY) {
526 int patternIndex = objectClickIndex(firstPatternY);
527 if (patternIndex < PATTERN_LIST_LENGTH) {
528 patternScrollPos = constrain(patternScrollPos + delta, 0, patterns.length - PATTERN_LIST_LENGTH);
529 }
530 }
531 }
532
49815cc0
MS
533}
534
bf551144
MS
535/**
536 * UI for control of mapping.
537 */
538class MappingUI extends OverlayUI {
539
540 private MappingTool mappingTool;
541
542 private final String MAPPING_MODE_ALL = "All On";
2bae07c9 543 private final String MAPPING_MODE_CHANNEL = "Channel";
bf551144 544 private final String MAPPING_MODE_SINGLE_CUBE = "Single Cube";
2bae07c9 545
bf551144
MS
546 private final String[] mappingModes = {
547 MAPPING_MODE_ALL,
2bae07c9
MS
548 MAPPING_MODE_CHANNEL,
549 MAPPING_MODE_SINGLE_CUBE
bf551144
MS
550 };
551 private final Method mappingModeStateMethod;
552
553 private final String CUBE_MODE_ALL = "All Strips";
554 private final String CUBE_MODE_SINGLE_STRIP = "Single Strip";
555 private final String CUBE_MODE_STRIP_PATTERN = "Strip Pattern";
556 private final String[] cubeModes = {
557 CUBE_MODE_ALL,
558 CUBE_MODE_SINGLE_STRIP,
559 CUBE_MODE_STRIP_PATTERN
560 };
561 private final Method cubeModeStateMethod;
562
563 private final String CHANNEL_MODE_RED = "Red";
564 private final String CHANNEL_MODE_GREEN = "Green";
565 private final String CHANNEL_MODE_BLUE = "Blue";
566 private final String[] channelModes = {
567 CHANNEL_MODE_RED,
568 CHANNEL_MODE_GREEN,
569 CHANNEL_MODE_BLUE,
570 };
571 private final Method channelModeStateMethod;
572
573 private int firstMappingY;
574 private int firstCubeY;
575 private int firstChannelY;
2bae07c9 576 private int channelFieldY;
bf551144
MS
577 private int cubeFieldY;
578 private int stripFieldY;
579
580 private boolean dragCube;
581 private boolean dragStrip;
2bae07c9 582 private boolean dragChannel;
bf551144
MS
583
584 MappingUI(MappingTool mappingTool) {
585 this.mappingTool = mappingTool;
586 try {
587 mappingModeStateMethod = getClass().getMethod("getMappingState", Object.class);
588 channelModeStateMethod = getClass().getMethod("getChannelState", Object.class);
589 cubeModeStateMethod = getClass().getMethod("getCubeState", Object.class);
590 } catch (Exception x) {
591 throw new RuntimeException(x);
592 }
593 }
594
595 public int getMappingState(Object mappingMode) {
2bae07c9
MS
596 boolean active = false;
597 if (mappingMode == MAPPING_MODE_ALL) {
598 active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_ALL;
599 } else if (mappingMode == MAPPING_MODE_CHANNEL) {
600 active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_CHANNEL;
601 } else if (mappingMode == MAPPING_MODE_SINGLE_CUBE) {
602 active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_SINGLE_CUBE;
603 }
bf551144
MS
604 return active ? STATE_ACTIVE : STATE_DEFAULT;
605 }
606
607 public int getChannelState(Object channelMode) {
608 boolean active = false;
609 if (channelMode == CHANNEL_MODE_RED) {
610 active = mappingTool.channelModeRed;
611 } else if (channelMode == CHANNEL_MODE_GREEN) {
612 active = mappingTool.channelModeGreen;
613 } else if (channelMode == CHANNEL_MODE_BLUE) {
614 active = mappingTool.channelModeBlue;
615 }
616 return active ? STATE_ACTIVE : STATE_DEFAULT;
617 }
618
619 public int getCubeState(Object cubeMode) {
620 boolean active = false;
621 if (cubeMode == CUBE_MODE_ALL) {
622 active = mappingTool.cubeMode == mappingTool.CUBE_MODE_ALL;
623 } else if (cubeMode == CUBE_MODE_SINGLE_STRIP) {
624 active = mappingTool.cubeMode == mappingTool.CUBE_MODE_SINGLE_STRIP;
625 } else if (cubeMode == CUBE_MODE_STRIP_PATTERN) {
626 active = mappingTool.cubeMode == mappingTool.CUBE_MODE_STRIP_PATTERN;
627 }
628 return active ? STATE_ACTIVE : STATE_DEFAULT;
629 }
630
631 public void draw() {
632 drawLogoAndBackground();
633 int yPos = 0;
634 firstMappingY = yPos + lineHeight + 6;
635 yPos = drawObjectList(yPos, "MAPPING MODE", mappingModes, mappingModes, mappingModeStateMethod);
636 yPos += sectionSpacing;
637
638 firstCubeY = yPos + lineHeight + 6;
639 yPos = drawObjectList(yPos, "CUBE MODE", cubeModes, cubeModes, cubeModeStateMethod);
640 yPos += sectionSpacing;
641
642 firstChannelY = yPos + lineHeight + 6;
643 yPos = drawObjectList(yPos, "CHANNELS", channelModes, channelModes, channelModeStateMethod);
644 yPos += sectionSpacing;
2bae07c9
MS
645
646 channelFieldY = yPos + lineHeight + 6;
647 yPos = drawValueField(yPos, "CHANNEL ID", mappingTool.channelIndex + 1);
648 yPos += sectionSpacing;
bf551144
MS
649
650 cubeFieldY = yPos + lineHeight + 6;
651 yPos = drawValueField(yPos, "CUBE ID", glucose.model.getRawIndexForCube(mappingTool.cubeIndex));
652 yPos += sectionSpacing;
653
654 stripFieldY = yPos + lineHeight + 6;
655 yPos = drawValueField(yPos, "STRIP ID", mappingTool.stripIndex + 1);
656
657 drawToggleTip("Tap 'm' to return");
658 }
659
660 private int drawValueField(int yPos, String label, int value) {
661 yPos += lineHeight;
662 textAlign(LEFT);
663 textFont(titleFont);
664 fill(titleColor);
665 text(label, leftTextPos, yPos);
666 fill(0);
667 yPos += 6;
668 rect(leftTextPos, yPos, w-8, lineHeight);
669 yPos += lineHeight;
670
671 fill(#999999);
672 textAlign(CENTER);
673 textFont(itemFont);
674 text("" + value, leftTextPos + (w-8)/2, yPos - 5);
675
676 return yPos;
677 }
678
679 private int lastY;
680
681 public void mousePressed() {
2bae07c9 682 dragCube = dragStrip = dragChannel = false;
bf551144 683 lastY = mouseY;
79ae8245
MS
684
685 if (mouseX < leftPos) {
686 return;
687 }
688
bf551144
MS
689 if (mouseY >= stripFieldY) {
690 if (mouseY < stripFieldY + lineHeight) {
691 dragStrip = true;
692 }
693 } else if (mouseY >= cubeFieldY) {
694 if (mouseY < cubeFieldY + lineHeight) {
695 dragCube = true;
696 }
2bae07c9
MS
697 } else if (mouseY >= channelFieldY) {
698 if (mouseY < channelFieldY + lineHeight) {
699 dragChannel = true;
700 }
bf551144
MS
701 } else if (mouseY >= firstChannelY) {
702 int index = objectClickIndex(firstChannelY);
703 switch (index) {
704 case 0: mappingTool.channelModeRed = !mappingTool.channelModeRed; break;
705 case 1: mappingTool.channelModeGreen = !mappingTool.channelModeGreen; break;
706 case 2: mappingTool.channelModeBlue = !mappingTool.channelModeBlue; break;
707 }
708 } else if (mouseY >= firstCubeY) {
709 int index = objectClickIndex(firstCubeY);
710 switch (index) {
711 case 0: mappingTool.cubeMode = mappingTool.CUBE_MODE_ALL; break;
712 case 1: mappingTool.cubeMode = mappingTool.CUBE_MODE_SINGLE_STRIP; break;
713 case 2: mappingTool.cubeMode = mappingTool.CUBE_MODE_STRIP_PATTERN; break;
714 }
715 } else if (mouseY >= firstMappingY) {
716 int index = objectClickIndex(firstMappingY);
2bae07c9
MS
717 switch (index) {
718 case 0: mappingTool.mappingMode = mappingTool.MAPPING_MODE_ALL; break;
719 case 1: mappingTool.mappingMode = mappingTool.MAPPING_MODE_CHANNEL; break;
720 case 2: mappingTool.mappingMode = mappingTool.MAPPING_MODE_SINGLE_CUBE; break;
bf551144
MS
721 }
722 }
723 }
724
0ba6ac44
MS
725 public void mouseReleased() {}
726 public void mouseWheel(int delta) {}
bf551144
MS
727
728 public void mouseDragged() {
729 final int DRAG_THRESHOLD = 5;
730 int dy = lastY - mouseY;
731 if (abs(dy) >= DRAG_THRESHOLD) {
732 lastY = mouseY;
733 if (dragCube) {
734 if (dy < 0) {
735 mappingTool.decCube();
736 } else {
737 mappingTool.incCube();
738 }
739 } else if (dragStrip) {
740 if (dy < 0) {
741 mappingTool.decStrip();
742 } else {
743 mappingTool.incStrip();
744 }
2bae07c9
MS
745 } else if (dragChannel) {
746 if (dy < 0) {
747 mappingTool.decChannel();
748 } else {
749 mappingTool.incChannel();
750 }
bf551144
MS
751 }
752 }
753
754 }
49815cc0
MS
755}
756
554e38ff
MS
757class DebugUI {
758
84086fa3 759 final ChannelMapping[] channelList;
554e38ff
MS
760 final int debugX = 10;
761 final int debugY = 42;
762 final int debugXSpacing = 28;
763 final int debugYSpacing = 22;
764 final int[][] debugState = new int[17][6];
765
766 final int DEBUG_STATE_ANIM = 0;
767 final int DEBUG_STATE_WHITE = 1;
768 final int DEBUG_STATE_OFF = 2;
769
45f43cc2 770 DebugUI(PandaMapping[] pandaMappings) {
1685dc84 771 int totalChannels = pandaMappings.length * PandaMapping.CHANNELS_PER_BOARD;
84086fa3 772 channelList = new ChannelMapping[totalChannels];
45f43cc2
MS
773 int channelIndex = 0;
774 for (PandaMapping pm : pandaMappings) {
84086fa3 775 for (ChannelMapping channel : pm.channelList) {
45f43cc2
MS
776 channelList[channelIndex++] = channel;
777 }
554e38ff
MS
778 }
779 for (int i = 0; i < debugState.length; ++i) {
780 for (int j = 0; j < debugState[i].length; ++j) {
781 debugState[i][j] = DEBUG_STATE_ANIM;
782 }
783 }
784 }
785
1685dc84 786 void draw() {
554e38ff
MS
787 noStroke();
788 int xBase = debugX;
789 int yPos = debugY;
790
791 fill(color(0, 0, 0, 80));
792 rect(4, 32, 172, 388);
793
794 int channelNum = 0;
84086fa3 795 for (ChannelMapping channel : channelList) {
554e38ff
MS
796 int xPos = xBase;
797 drawNumBox(xPos, yPos, channelNum+1, debugState[channelNum][0]);
84086fa3 798 xPos += debugXSpacing;
554e38ff 799
84086fa3
MS
800 switch (channel.mode) {
801 case ChannelMapping.MODE_CUBES:
802 int stateIndex = 0;
803 boolean first = true;
804 for (int rawCubeIndex : channel.objectIndices) {
805 if (rawCubeIndex < 0) {
806 break;
807 }
808 if (first) {
809 first = false;
810 } else {
811 stroke(#999999);
812 line(xPos - 12, yPos + 8, xPos, yPos + 8);
813 }
814 drawNumBox(xPos, yPos, rawCubeIndex, debugState[channelNum][stateIndex+1]);
815 ++stateIndex;
816 xPos += debugXSpacing;
817 }
554e38ff 818 break;
84086fa3
MS
819 case ChannelMapping.MODE_BASS:
820 drawNumBox(xPos, yPos, "B", debugState[channelNum][1]);
821 break;
822 case ChannelMapping.MODE_SPEAKER:
823 drawNumBox(xPos, yPos, "S" + channel.objectIndices[0], debugState[channelNum][1]);
824 break;
825 case ChannelMapping.MODE_FLOOR:
826 drawNumBox(xPos, yPos, "F", debugState[channelNum][1]);
827 break;
828 case ChannelMapping.MODE_NULL:
829 break;
830 default:
831 throw new RuntimeException("Unhandled channel mapping mode: " + channel.mode);
832 }
554e38ff
MS
833
834 yPos += debugYSpacing;
835 ++channelNum;
836 }
837 drawNumBox(xBase, yPos, "A", debugState[channelNum][0]);
838 }
839
840 void drawNumBox(int xPos, int yPos, int label, int state) {
841 drawNumBox(xPos, yPos, "" + label, state);
842 }
843
844 void drawNumBox(int xPos, int yPos, String label, int state) {
845 noFill();
846 color textColor = #cccccc;
847 switch (state) {
848 case DEBUG_STATE_ANIM:
849 noStroke();
850 fill(#880000);
851 rect(xPos, yPos, 16, 8);
852 fill(#000088);
853 rect(xPos, yPos+8, 16, 8);
854 noFill();
855 stroke(textColor);
856 rect(xPos, yPos, 16, 16);
857 break;
858 case DEBUG_STATE_WHITE:
859 stroke(textColor);
860 fill(#e9e9e9);
861 rect(xPos, yPos, 16, 16);
862 textColor = #333333;
863 break;
864 case DEBUG_STATE_OFF:
865 stroke(textColor);
866 rect(xPos, yPos, 16, 16);
867 break;
868 }
869
870 noStroke();
871 fill(textColor);
872 text(label, xPos + 2, yPos + 12);
873
874 }
875
876 void maskColors(color[] colors) {
877 color white = #FFFFFF;
878 color off = #000000;
879 int channelIndex = 0;
84086fa3
MS
880 int state;
881 for (ChannelMapping channel : channelList) {
882 switch (channel.mode) {
883 case ChannelMapping.MODE_CUBES:
884 int cubeIndex = 1;
885 for (int rawCubeIndex : channel.objectIndices) {
886 if (rawCubeIndex >= 0) {
887 state = debugState[channelIndex][cubeIndex];
888 if (state != DEBUG_STATE_ANIM) {
889 color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
890 Cube cube = glucose.model.getCubeByRawIndex(rawCubeIndex);
891 for (Point p : cube.points) {
892 colors[p.index] = debugColor;
893 }
894 }
554e38ff 895 }
84086fa3 896 ++cubeIndex;
554e38ff 897 }
84086fa3
MS
898 break;
899
900 case ChannelMapping.MODE_BASS:
901 state = debugState[channelIndex][1];
902 if (state != DEBUG_STATE_ANIM) {
903 color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
904 for (Point p : glucose.model.bassBox.points) {
905 colors[p.index] = debugColor;
906 }
907 }
908 break;
909
910 case ChannelMapping.MODE_FLOOR:
911 state = debugState[channelIndex][1];
912 if (state != DEBUG_STATE_ANIM) {
913 color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
914 for (Point p : glucose.model.boothFloor.points) {
915 colors[p.index] = debugColor;
916 }
917 }
918 break;
919
920 case ChannelMapping.MODE_SPEAKER:
921 state = debugState[channelIndex][1];
922 if (state != DEBUG_STATE_ANIM) {
923 color debugColor = (state == DEBUG_STATE_WHITE) ? white : off;
924 for (Point p : glucose.model.speakers.get(channel.objectIndices[0]).points) {
925 colors[p.index] = debugColor;
926 }
927 }
928 break;
929
930 case ChannelMapping.MODE_NULL:
931 break;
932
933 default:
934 throw new RuntimeException("Unhandled channel mapping mode: " + channel.mode);
554e38ff
MS
935 }
936 ++channelIndex;
937 }
938 }
939
940 void mousePressed() {
941 int dx = (mouseX - debugX) / debugXSpacing;
942 int dy = (mouseY - debugY) / debugYSpacing;
943 if ((dy >= 0) && (dy < debugState.length)) {
944 if ((dx >= 0) && (dx < debugState[dy].length)) {
945 int newState = debugState[dy][dx] = (debugState[dy][dx] + 1) % 3;
946 if (dy == 16) {
947 for (int[] states : debugState) {
948 for (int i = 0; i < states.length; ++i) {
949 states[i] = newState;
950 }
951 }
952 } else if (dx == 0) {
953 for (int i = 0; i < debugState[dy].length; ++i) {
954 debugState[dy][i] = newState;
955 }
956 }
957 }
958 }
959 }
960}