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