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