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