Commit | Line | Data |
---|---|---|
49815cc0 | 1 | /** |
1ecdb44a MS |
2 | * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND |
3 | * | |
4 | * //\\ //\\ //\\ //\\ | |
5 | * ///\\\ ///\\\ ///\\\ ///\\\ | |
6 | * \\\/// \\\/// \\\/// \\\/// | |
7 | * \\// \\// \\// \\// | |
8 | * | |
9 | * EXPERTS ONLY!! EXPERTS ONLY!! | |
10 | * | |
49815cc0 MS |
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 | |
13 | * regularly modified. | |
14 | */ | |
15 | class OverlayUI { | |
16 | ||
17 | private final PFont titleFont = createFont("Myriad Pro", 10); | |
3f8be614 | 18 | private final PFont itemFont = createFont("Lucida Grande", 11); |
49815cc0 MS |
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; | |
3f8be614 | 25 | private final int controlSpacing = 18; |
49815cc0 MS |
26 | private final int tempoHeight = 20; |
27 | private final int knobSize = 28; | |
3f8be614 | 28 | private final float knobIndent = .4; |
49815cc0 MS |
29 | private final int knobSpacing = 6; |
30 | private final int knobLabelHeight = 14; | |
31 | private final color lightBlue = #666699; | |
32 | private final color lightGreen = #669966; | |
33 | ||
34 | private final String[] patternNames; | |
35 | private final String[] transitionNames; | |
36 | private final String[] effectNames; | |
37 | ||
38 | private PImage logo; | |
39 | ||
40 | private int firstPatternY; | |
3f8be614 | 41 | private int firstPatternKnobY; |
49815cc0 | 42 | private int firstTransitionY; |
3f8be614 | 43 | private int firstTransitionKnobY; |
49815cc0 | 44 | private int firstEffectY; |
3f8be614 MS |
45 | private int firstEffectKnobY; |
46 | ||
49815cc0 MS |
47 | private int tempoY; |
48 | ||
49 | private Method patternStateMethod; | |
50 | private Method transitionStateMethod; | |
51 | private Method effectStateMethod; | |
3f8be614 | 52 | |
49815cc0 MS |
53 | OverlayUI() { |
54 | leftPos = width - w; | |
55 | leftTextPos = leftPos + 4; | |
56 | logo = loadImage("logo-sm.png"); | |
57 | ||
3f8be614 MS |
58 | patternNames = classNameArray(patterns, "Pattern"); |
59 | transitionNames = classNameArray(transitions, "Transition"); | |
60 | effectNames = classNameArray(effects, "Effect"); | |
61 | ||
49815cc0 MS |
62 | try { |
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); | |
68 | } | |
69 | } | |
70 | ||
71 | void drawHelpTip() { | |
72 | textFont(itemFont); | |
73 | textAlign(RIGHT); | |
74 | text("Tap 'u' to restore UI", width-4, height-6); | |
75 | } | |
76 | ||
77 | void draw() { | |
78 | image(logo, 4, 4); | |
79 | ||
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); | |
84 | ||
85 | int yPos = 0; | |
86 | firstPatternY = yPos + lineHeight + 6; | |
87 | yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod); | |
3f8be614 MS |
88 | yPos += controlSpacing; |
89 | firstPatternKnobY = yPos; | |
49815cc0 | 90 | int xPos = leftTextPos; |
cc9fcf4b MS |
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)); | |
49815cc0 MS |
94 | xPos += knobSize + knobSpacing; |
95 | } | |
96 | yPos += 2*(knobSize + knobLabelHeight) + knobSpacing; | |
97 | ||
98 | yPos += sectionSpacing; | |
99 | firstTransitionY = yPos + lineHeight + 6; | |
100 | yPos = drawObjectList(yPos, "TRANSITION", transitions, transitionNames, transitionStateMethod); | |
3f8be614 MS |
101 | yPos += controlSpacing; |
102 | firstTransitionKnobY = yPos; | |
103 | xPos = leftTextPos; | |
cc9fcf4b MS |
104 | for (VirtualTransitionKnob knob : glucose.transitionKnobs) { |
105 | drawKnob(xPos, yPos, knobSize, knob); | |
3f8be614 MS |
106 | xPos += knobSize + knobSpacing; |
107 | } | |
108 | yPos += knobSize + knobLabelHeight; | |
49815cc0 MS |
109 | |
110 | yPos += sectionSpacing; | |
111 | firstEffectY = yPos + lineHeight + 6; | |
112 | yPos = drawObjectList(yPos, "FX", effects, effectNames, effectStateMethod); | |
3f8be614 MS |
113 | yPos += controlSpacing; |
114 | firstEffectKnobY = yPos; | |
115 | xPos = leftTextPos; | |
cc9fcf4b MS |
116 | for (VirtualEffectKnob knob : glucose.effectKnobs) { |
117 | drawKnob(xPos, yPos, knobSize, knob); | |
3f8be614 MS |
118 | xPos += knobSize + knobSpacing; |
119 | } | |
120 | yPos += knobSize + knobLabelHeight; | |
49815cc0 MS |
121 | |
122 | yPos += sectionSpacing; | |
123 | yPos = drawObjectList(yPos, "TEMPO", null, null, null); | |
124 | yPos += 6; | |
125 | tempoY = yPos; | |
126 | stroke(#111111); | |
127 | fill(tempoDown ? lightGreen : color(0, 0, 35 - 8*lx.tempo.rampf())); | |
128 | rect(leftPos + 4, yPos, w - 8, tempoHeight); | |
129 | fill(0); | |
130 | textAlign(CENTER); | |
131 | text("" + ((int)(lx.tempo.bpmf() * 100) / 100.), leftPos + w/2., yPos + tempoHeight - 6); | |
132 | yPos += tempoHeight; | |
133 | ||
134 | fill(#999999); | |
135 | textFont(itemFont); | |
136 | textAlign(LEFT); | |
e8f5282d | 137 | text("Tap 'u' to hide UI", leftTextPos, height-6); |
49815cc0 MS |
138 | } |
139 | ||
3f8be614 | 140 | public LXParameter getOrNull(List<LXParameter> items, int index) { |
49815cc0 MS |
141 | if (index < items.size()) { |
142 | return items.get(index); | |
143 | } | |
144 | return null; | |
145 | } | |
146 | ||
147 | public void drawFPS() { | |
148 | textFont(titleFont); | |
149 | textAlign(LEFT); | |
150 | fill(#666666); | |
151 | text("FPS: " + (((int)(frameRate * 10)) / 10.), 4, height-6); | |
152 | } | |
153 | ||
154 | private final int STATE_DEFAULT = 0; | |
155 | private final int STATE_ACTIVE = 1; | |
156 | private final int STATE_PENDING = 2; | |
157 | ||
158 | public int getState(LXPattern p) { | |
159 | if (p == lx.getPattern()) { | |
160 | return STATE_ACTIVE; | |
161 | } else if (p == lx.getNextPattern()) { | |
162 | return STATE_PENDING; | |
163 | } | |
164 | return STATE_DEFAULT; | |
165 | } | |
166 | ||
167 | public int getState(LXEffect e) { | |
3f8be614 MS |
168 | if (e.isEnabled()) { |
169 | return STATE_PENDING; | |
cc9fcf4b | 170 | } else if (e == glucose.getSelectedEffect()) { |
3f8be614 MS |
171 | return STATE_ACTIVE; |
172 | } | |
173 | return STATE_DEFAULT; | |
49815cc0 MS |
174 | } |
175 | ||
176 | public int getState(LXTransition t) { | |
177 | if (t == lx.getTransition()) { | |
178 | return STATE_PENDING; | |
cc9fcf4b | 179 | } else if (t == glucose.getSelectedTransition()) { |
49815cc0 MS |
180 | return STATE_ACTIVE; |
181 | } | |
182 | return STATE_DEFAULT; | |
183 | } | |
184 | ||
185 | protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) { | |
3f8be614 | 186 | return drawObjectList(yPos, title, items, classNameArray(items, null), stateMethod); |
49815cc0 MS |
187 | } |
188 | ||
189 | private int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) { | |
190 | noStroke(); | |
191 | fill(#aaaaaa); | |
192 | textFont(titleFont); | |
193 | textAlign(LEFT); | |
194 | text(title, leftTextPos, yPos += lineHeight); | |
195 | if (items != null) { | |
196 | textFont(itemFont); | |
197 | color textColor; | |
198 | boolean even = true; | |
199 | for (int i = 0; i < items.length; ++i) { | |
200 | Object o = items[i]; | |
201 | int state = STATE_DEFAULT; | |
202 | try { | |
203 | state = ((Integer) stateMethod.invoke(this, o)).intValue(); | |
204 | } catch (Exception x) { | |
205 | throw new RuntimeException(x); | |
206 | } | |
207 | switch (state) { | |
208 | case STATE_ACTIVE: | |
209 | fill(lightGreen); | |
210 | textColor = #eeeeee; | |
211 | break; | |
212 | case STATE_PENDING: | |
213 | fill(lightBlue); | |
214 | textColor = color(0, 0, 75 + 15*sin(millis()/200.));; | |
215 | break; | |
216 | default: | |
217 | textColor = 0; | |
218 | fill(even ? #666666 : #777777); | |
219 | break; | |
220 | } | |
221 | rect(leftPos, yPos+6, width, lineHeight); | |
222 | fill(textColor); | |
223 | text(names[i], leftTextPos, yPos += lineHeight); | |
224 | even = !even; | |
225 | } | |
226 | } | |
227 | return yPos; | |
228 | } | |
229 | ||
3f8be614 MS |
230 | private void drawKnob(int xPos, int yPos, int knobSize, LXParameter knob) { |
231 | if (!knobsOn) { | |
232 | return; | |
233 | } | |
49815cc0 MS |
234 | final float knobValue = knob.getValuef(); |
235 | String knobLabel = knob.getLabel(); | |
3f8be614 | 236 | if (knobLabel == null) { |
49815cc0 | 237 | knobLabel = "-"; |
3f8be614 MS |
238 | } else if (knobLabel.length() > 4) { |
239 | knobLabel = knobLabel.substring(0, 4); | |
49815cc0 MS |
240 | } |
241 | ||
242 | ellipseMode(CENTER); | |
87f6fa39 | 243 | noStroke(); |
49815cc0 | 244 | fill(#222222); |
3f8be614 MS |
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); | |
250 | ||
251 | float endArc = HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue; | |
49815cc0 | 252 | fill(lightGreen); |
3f8be614 MS |
253 | arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, endArc); |
254 | ||
3f8be614 MS |
255 | // Mask notch out of knob |
256 | fill(color(0, 0, 30)); | |
257 | beginShape(); | |
4eae387e MS |
258 | vertex(xPos + knobSize/2, yPos + knobSize/2.); |
259 | vertex(xPos + knobSize/2 - 6, yPos + knobSize); | |
260 | vertex(xPos + knobSize/2 + 6, yPos + knobSize); | |
3f8be614 | 261 | endShape(); |
4eae387e MS |
262 | |
263 | // Center circle of knob | |
264 | fill(#333333); | |
265 | ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize/2, knobSize/2); | |
49815cc0 MS |
266 | |
267 | fill(0); | |
268 | rect(xPos, yPos + knobSize + 2, knobSize, knobLabelHeight - 2); | |
269 | fill(#999999); | |
270 | textAlign(CENTER); | |
271 | textFont(knobFont); | |
272 | text(knobLabel, xPos + knobSize/2, yPos + knobSize + knobLabelHeight - 2); | |
273 | ||
274 | } | |
275 | ||
3f8be614 | 276 | private String[] classNameArray(Object[] objects, String suffix) { |
49815cc0 MS |
277 | if (objects == null) { |
278 | return null; | |
279 | } | |
280 | String[] names = new String[objects.length]; | |
281 | for (int i = 0; i < objects.length; ++i) { | |
3f8be614 | 282 | names[i] = className(objects[i], suffix); |
49815cc0 MS |
283 | } |
284 | return names; | |
285 | } | |
286 | ||
3f8be614 | 287 | private String className(Object p, String suffix) { |
49815cc0 MS |
288 | String s = p.getClass().getName(); |
289 | int li; | |
290 | if ((li = s.lastIndexOf(".")) > 0) { | |
291 | s = s.substring(li + 1); | |
292 | } | |
293 | if (s.indexOf("SugarCubes$") == 0) { | |
3f8be614 MS |
294 | s = s.substring("SugarCubes$".length()); |
295 | } | |
296 | if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) { | |
297 | s = s.substring(0, li); | |
49815cc0 MS |
298 | } |
299 | return s; | |
300 | } | |
3f8be614 MS |
301 | |
302 | private int patternKnobIndex = -1; | |
303 | private int transitionKnobIndex = -1; | |
304 | private int effectKnobIndex = -1; | |
49815cc0 | 305 | |
49815cc0 MS |
306 | private int lastY; |
307 | private int releaseEffect = -1; | |
308 | private boolean tempoDown = false; | |
309 | ||
310 | public void mousePressed() { | |
311 | lastY = mouseY; | |
3f8be614 | 312 | patternKnobIndex = transitionKnobIndex = effectKnobIndex = -1; |
49815cc0 MS |
313 | releaseEffect = -1; |
314 | if (mouseY > tempoY) { | |
315 | if (mouseY - tempoY < tempoHeight) { | |
316 | lx.tempo.tap(); | |
317 | tempoDown = true; | |
318 | } | |
3f8be614 MS |
319 | } else if ((mouseY >= firstEffectKnobY) && (mouseY < firstEffectKnobY + knobSize + knobLabelHeight)) { |
320 | effectKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); | |
49815cc0 MS |
321 | } else if (mouseY > firstEffectY) { |
322 | int effectIndex = (mouseY - firstEffectY) / lineHeight; | |
323 | if (effectIndex < effects.length) { | |
cc9fcf4b | 324 | if (effects[effectIndex] == glucose.getSelectedEffect()) { |
49815cc0 MS |
325 | effects[effectIndex].enable(); |
326 | releaseEffect = effectIndex; | |
49815cc0 | 327 | } |
cc9fcf4b | 328 | glucose.setSelectedEffect(effectIndex); |
49815cc0 | 329 | } |
3f8be614 MS |
330 | } else if ((mouseY >= firstTransitionKnobY) && (mouseY < firstTransitionKnobY + knobSize + knobLabelHeight)) { |
331 | transitionKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); | |
49815cc0 MS |
332 | } else if (mouseY > firstTransitionY) { |
333 | int transitionIndex = (mouseY - firstTransitionY) / lineHeight; | |
334 | if (transitionIndex < transitions.length) { | |
cc9fcf4b | 335 | glucose.setSelectedTransition(transitionIndex); |
49815cc0 | 336 | } |
3f8be614 MS |
337 | } else if ((mouseY >= firstPatternKnobY) && (mouseY < firstPatternKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) { |
338 | patternKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); | |
339 | if (mouseY >= firstPatternKnobY + knobSize + knobLabelHeight + knobSpacing) { | |
cc9fcf4b | 340 | patternKnobIndex += glucose.NUM_PATTERN_KNOBS / 2; |
49815cc0 MS |
341 | } |
342 | } else if (mouseY > firstPatternY) { | |
343 | int patternIndex = (mouseY - firstPatternY) / lineHeight; | |
344 | if (patternIndex < patterns.length) { | |
49815cc0 MS |
345 | lx.goIndex(patternIndex); |
346 | } | |
347 | } | |
348 | } | |
349 | ||
350 | public void mouseDragged() { | |
351 | int dy = lastY - mouseY; | |
352 | lastY = mouseY; | |
cc9fcf4b MS |
353 | if (patternKnobIndex >= 0 && patternKnobIndex < glucose.NUM_PATTERN_KNOBS) { |
354 | LXParameter p = glucose.patternKnobs.get(patternKnobIndex); | |
3f8be614 | 355 | p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); |
cc9fcf4b MS |
356 | } else if (effectKnobIndex >= 0 && effectKnobIndex < glucose.NUM_EFFECT_KNOBS) { |
357 | LXParameter p = glucose.effectKnobs.get(effectKnobIndex); | |
3f8be614 | 358 | p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); |
cc9fcf4b MS |
359 | } else if (transitionKnobIndex >= 0 && transitionKnobIndex < glucose.NUM_TRANSITION_KNOBS) { |
360 | LXParameter p = glucose.transitionKnobs.get(transitionKnobIndex); | |
3f8be614 | 361 | p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); |
49815cc0 MS |
362 | } |
363 | } | |
364 | ||
365 | public void mouseReleased() { | |
366 | tempoDown = false; | |
367 | if (releaseEffect >= 0) { | |
368 | effects[releaseEffect].trigger(); | |
369 | releaseEffect = -1; | |
370 | } | |
371 | } | |
3f8be614 | 372 | |
49815cc0 MS |
373 | } |
374 | ||
375 | void mousePressed() { | |
376 | if (mouseX > ui.leftPos) { | |
377 | ui.mousePressed(); | |
378 | } | |
379 | } | |
380 | ||
381 | void mouseReleased() { | |
382 | if (mouseX > ui.leftPos) { | |
383 | ui.mouseReleased(); | |
384 | } | |
385 | } | |
386 | ||
387 | void mouseDragged() { | |
388 | if (mouseX > ui.leftPos) { | |
389 | ui.mouseDragged(); | |
390 | } | |
391 | } | |
392 |