2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
9 * EXPERTS ONLY!! EXPERTS ONLY!!
11 * Overlay UI that indicates pattern control, etc. This will be moved
12 * into the Processing library once it is stabilized and need not be
17 private final PFont titleFont = createFont("Myriad Pro", 10);
18 private final PFont itemFont = createFont("Lucida Grande", 11);
19 private final PFont knobFont = titleFont;
20 private final int w = 140;
21 private final int leftPos;
22 private final int leftTextPos;
23 private final int lineHeight = 20;
24 private final int sectionSpacing = 12;
25 private final int controlSpacing = 18;
26 private final int tempoHeight = 20;
27 private final int knobSize = 28;
28 private final float knobIndent = .4;
29 private final int knobSpacing = 6;
30 private final int knobLabelHeight = 14;
31 private final color lightBlue = #666699;
32 private final color lightGreen = #669966;
34 private final String[] patternNames;
35 private final String[] transitionNames;
36 private final String[] effectNames;
40 private int firstPatternY;
41 private int firstPatternKnobY;
42 private int firstTransitionY;
43 private int firstTransitionKnobY;
44 private int firstEffectY;
45 private int firstEffectKnobY;
49 private Method patternStateMethod;
50 private Method transitionStateMethod;
51 private Method effectStateMethod;
55 leftTextPos = leftPos + 4;
56 logo = loadImage("logo-sm.png");
58 patternNames = classNameArray(patterns, "Pattern");
59 transitionNames = classNameArray(transitions, "Transition");
60 effectNames = classNameArray(effects, "Effect");
63 patternStateMethod = getClass().getMethod("getState", LXPattern.class);
64 effectStateMethod = getClass().getMethod("getState", LXEffect.class);
65 transitionStateMethod = getClass().getMethod("getState", LXTransition.class);
66 } catch (Exception x) {
67 throw new RuntimeException(x);
74 text("Tap 'u' to restore UI", width-4, height-6);
80 stroke(color(0, 0, 100));
81 // fill(color(0, 0, 50, 50)); // alpha is bad for perf
82 fill(color(0, 0, 30));
83 rect(leftPos-1, -1, w+2, height+2);
86 firstPatternY = yPos + lineHeight + 6;
87 yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod);
88 yPos += controlSpacing;
89 firstPatternKnobY = yPos;
90 int xPos = leftTextPos;
91 for (int i = 0; i < glucose.NUM_PATTERN_KNOBS/2; ++i) {
92 drawKnob(xPos, yPos, knobSize, glucose.patternKnobs.get(i));
93 drawKnob(xPos, yPos + knobSize + knobSpacing + knobLabelHeight, knobSize, glucose.patternKnobs.get(glucose.NUM_PATTERN_KNOBS/2 + i));
94 xPos += knobSize + knobSpacing;
96 yPos += 2*(knobSize + knobLabelHeight) + knobSpacing;
98 yPos += sectionSpacing;
99 firstTransitionY = yPos + lineHeight + 6;
100 yPos = drawObjectList(yPos, "TRANSITION", transitions, transitionNames, transitionStateMethod);
101 yPos += controlSpacing;
102 firstTransitionKnobY = yPos;
104 for (VirtualTransitionKnob knob : glucose.transitionKnobs) {
105 drawKnob(xPos, yPos, knobSize, knob);
106 xPos += knobSize + knobSpacing;
108 yPos += knobSize + knobLabelHeight;
110 yPos += sectionSpacing;
111 firstEffectY = yPos + lineHeight + 6;
112 yPos = drawObjectList(yPos, "FX", effects, effectNames, effectStateMethod);
113 yPos += controlSpacing;
114 firstEffectKnobY = yPos;
116 for (VirtualEffectKnob knob : glucose.effectKnobs) {
117 drawKnob(xPos, yPos, knobSize, knob);
118 xPos += knobSize + knobSpacing;
120 yPos += knobSize + knobLabelHeight;
122 yPos += sectionSpacing;
123 yPos = drawObjectList(yPos, "TEMPO", null, null, null);
127 fill(tempoDown ? lightGreen : color(0, 0, 35 - 8*lx.tempo.rampf()));
128 rect(leftPos + 4, yPos, w - 8, tempoHeight);
131 text("" + ((int)(lx.tempo.bpmf() * 100) / 100.), leftPos + w/2., yPos + tempoHeight - 6);
137 text("Tap 'u' to hide UI", leftTextPos, height-6);
140 public LXParameter getOrNull(List<LXParameter> items, int index) {
141 if (index < items.size()) {
142 return items.get(index);
147 public void drawFPS() {
151 text("FPS: " + (((int)(frameRate * 10)) / 10.), 4, height-6);
154 private final int STATE_DEFAULT = 0;
155 private final int STATE_ACTIVE = 1;
156 private final int STATE_PENDING = 2;
158 public int getState(LXPattern p) {
159 if (p == lx.getPattern()) {
161 } else if (p == lx.getNextPattern()) {
162 return STATE_PENDING;
164 return STATE_DEFAULT;
167 public int getState(LXEffect e) {
169 return STATE_PENDING;
170 } else if (e == glucose.getSelectedEffect()) {
173 return STATE_DEFAULT;
176 public int getState(LXTransition t) {
177 if (t == lx.getTransition()) {
178 return STATE_PENDING;
179 } else if (t == glucose.getSelectedTransition()) {
182 return STATE_DEFAULT;
185 protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) {
186 return drawObjectList(yPos, title, items, classNameArray(items, null), stateMethod);
189 private int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) {
194 text(title, leftTextPos, yPos += lineHeight);
199 for (int i = 0; i < items.length; ++i) {
201 int state = STATE_DEFAULT;
203 state = ((Integer) stateMethod.invoke(this, o)).intValue();
204 } catch (Exception x) {
205 throw new RuntimeException(x);
214 textColor = color(0, 0, 75 + 15*sin(millis()/200.));;
218 fill(even ? #666666 : #777777);
221 rect(leftPos, yPos+6, width, lineHeight);
223 text(names[i], leftTextPos, yPos += lineHeight);
230 private void drawKnob(int xPos, int yPos, int knobSize, LXParameter knob) {
234 final float knobValue = knob.getValuef();
235 String knobLabel = knob.getLabel();
236 if (knobLabel == null) {
238 } else if (knobLabel.length() > 4) {
239 knobLabel = knobLabel.substring(0, 4);
245 // For some reason this arc call really crushes drawing performance. Presumably
246 // because openGL is drawing it and when we overlap the second set of arcs it
247 // does a bunch of depth buffer intersection tests? Ellipse with a trapezoid cut out is faster
248 // arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent));
249 ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize);
251 float endArc = HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue;
253 arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, endArc);
255 // Mask notch out of knob
256 fill(color(0, 0, 30));
258 vertex(xPos + knobSize/2, yPos + knobSize/2.);
259 vertex(xPos + knobSize/2 - 6, yPos + knobSize);
260 vertex(xPos + knobSize/2 + 6, yPos + knobSize);
263 // Center circle of knob
265 ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize/2, knobSize/2);
268 rect(xPos, yPos + knobSize + 2, knobSize, knobLabelHeight - 2);
272 text(knobLabel, xPos + knobSize/2, yPos + knobSize + knobLabelHeight - 2);
276 private String[] classNameArray(Object[] objects, String suffix) {
277 if (objects == null) {
280 String[] names = new String[objects.length];
281 for (int i = 0; i < objects.length; ++i) {
282 names[i] = className(objects[i], suffix);
287 private String className(Object p, String suffix) {
288 String s = p.getClass().getName();
290 if ((li = s.lastIndexOf(".")) > 0) {
291 s = s.substring(li + 1);
293 if (s.indexOf("SugarCubes$") == 0) {
294 s = s.substring("SugarCubes$".length());
296 if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) {
297 s = s.substring(0, li);
302 private int patternKnobIndex = -1;
303 private int transitionKnobIndex = -1;
304 private int effectKnobIndex = -1;
307 private int releaseEffect = -1;
308 private boolean tempoDown = false;
310 public void mousePressed() {
312 patternKnobIndex = transitionKnobIndex = effectKnobIndex = -1;
314 if (mouseY > tempoY) {
315 if (mouseY - tempoY < tempoHeight) {
319 } else if ((mouseY >= firstEffectKnobY) && (mouseY < firstEffectKnobY + knobSize + knobLabelHeight)) {
320 effectKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
321 } else if (mouseY > firstEffectY) {
322 int effectIndex = (mouseY - firstEffectY) / lineHeight;
323 if (effectIndex < effects.length) {
324 if (effects[effectIndex] == glucose.getSelectedEffect()) {
325 effects[effectIndex].enable();
326 releaseEffect = effectIndex;
328 glucose.setSelectedEffect(effectIndex);
330 } else if ((mouseY >= firstTransitionKnobY) && (mouseY < firstTransitionKnobY + knobSize + knobLabelHeight)) {
331 transitionKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
332 } else if (mouseY > firstTransitionY) {
333 int transitionIndex = (mouseY - firstTransitionY) / lineHeight;
334 if (transitionIndex < transitions.length) {
335 glucose.setSelectedTransition(transitionIndex);
337 } else if ((mouseY >= firstPatternKnobY) && (mouseY < firstPatternKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) {
338 patternKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing);
339 if (mouseY >= firstPatternKnobY + knobSize + knobLabelHeight + knobSpacing) {
340 patternKnobIndex += glucose.NUM_PATTERN_KNOBS / 2;
342 } else if (mouseY > firstPatternY) {
343 int patternIndex = (mouseY - firstPatternY) / lineHeight;
344 if (patternIndex < patterns.length) {
345 lx.goIndex(patternIndex);
350 public void mouseDragged() {
351 int dy = lastY - mouseY;
353 if (patternKnobIndex >= 0 && patternKnobIndex < glucose.NUM_PATTERN_KNOBS) {
354 LXParameter p = glucose.patternKnobs.get(patternKnobIndex);
355 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
356 } else if (effectKnobIndex >= 0 && effectKnobIndex < glucose.NUM_EFFECT_KNOBS) {
357 LXParameter p = glucose.effectKnobs.get(effectKnobIndex);
358 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
359 } else if (transitionKnobIndex >= 0 && transitionKnobIndex < glucose.NUM_TRANSITION_KNOBS) {
360 LXParameter p = glucose.transitionKnobs.get(transitionKnobIndex);
361 p.setValue(constrain(p.getValuef() + dy*.01, 0, 1));
365 public void mouseReleased() {
367 if (releaseEffect >= 0) {
368 effects[releaseEffect].trigger();
375 void mousePressed() {
376 if (mouseX > ui.leftPos) {
381 void mouseReleased() {
382 if (mouseX > ui.leftPos) {
387 void mouseDragged() {
388 if (mouseX > ui.leftPos) {