From: Mark Slee Date: Tue, 17 Sep 2013 01:07:06 +0000 (-0700) Subject: Overhaul UI code to make it more flexible and less redrawing, little hierarchy framework X-Git-Url: https://git.piment-noir.org/?p=SugarCubes.git;a=commitdiff_plain;h=d626bc9b0197a1b5fd51a86f33f666a2a46579a2 Overhaul UI code to make it more flexible and less redrawing, little hierarchy framework --- diff --git a/DanKaminsky.pde b/DanKaminsky.pde index 59bde73..3fbd86b 100644 --- a/DanKaminsky.pde +++ b/DanKaminsky.pde @@ -215,7 +215,7 @@ import processing.serial.*; List gparams; -class DualBlender extends SCEffect { + class DualBlender extends SCEffect { int lastSeen; BasicParameter p1 = new BasicParameter("p1", 0); BasicParameter p2 = new BasicParameter("p2", 0); @@ -250,9 +250,9 @@ class DualBlender extends SCEffect { if(p==p7) { gparams.get(6).setValue(p.getValuef()); } if(p==p8) { gparams.get(7).setValue(p.getValuef()); } } - + void doApply(int[] colors){ - if(doDual==true){ + if (enabled) { //gplay.onActive(); gplay.go(millis()-lastSeen); lastSeen=millis(); diff --git a/DanUtil.pde b/DanUtil.pde old mode 100755 new mode 100644 index d9990dd..9577673 --- a/DanUtil.pde +++ b/DanUtil.pde @@ -80,7 +80,7 @@ public class DPat extends SCPattern int unmapRow (int a) { return btwn(a,0 , 4) ? a+53 : a; } void SetLight (int row, int col, int clr){ if (midiout != null) midiout.sendNoteOn(col, unmapRow(row), clr); } void keypad (int row, int col) { println(row + " " + col); } - void onInactive() { bIsActive=false; DanTextLine1 = ""; DanTextLine2 = "";} + void onInactive() { bIsActive=false; DanTextLine1 = ""; DanTextLine2 = ""; uiDebugText.setText(""); } void onActive () { bIsActive=true; zSpinHue = 0; for (int i=0; i 0); break; + case MAPPING_MODE_CHANNEL: cubeOn = (indexOfCubeInChannel > 0); break; } if (cubeOn) { if (mappingMode == MAPPING_MODE_CHANNEL) { @@ -408,7 +412,10 @@ class MappingTool extends TestPattern { } ++ci; } - + } + + public void setCube(int index) { + cubeIndex = index % model.cubes.size(); } public void incCube() { @@ -421,6 +428,11 @@ class MappingTool extends TestPattern { cubeIndex += model.cubes.size(); } } + + public void setChannel(int index) { + channelIndex = index % numChannels; + setChannel(); + } public void incChannel() { channelIndex = (channelIndex + 1) % numChannels; @@ -432,6 +444,10 @@ class MappingTool extends TestPattern { setChannel(); } + public void setStrip(int index) { + stripIndex = index % Cube.STRIPS_PER_CUBE; + } + public void incStrip() { stripIndex = (stripIndex + 1) % Cube.STRIPS_PER_CUBE; } @@ -440,7 +456,7 @@ class MappingTool extends TestPattern { stripIndex = (stripIndex + Cube.STRIPS_PER_CUBE - 1) % Cube.STRIPS_PER_CUBE; } - public void keyPressed() { + public void keyPressed(UIMapping uiMapping) { switch (keyCode) { case UP: if (mappingMode == MAPPING_MODE_CHANNEL) incChannel(); else incCube(); break; case DOWN: if (mappingMode == MAPPING_MODE_CHANNEL) decChannel(); else decCube(); break; @@ -452,5 +468,10 @@ class MappingTool extends TestPattern { case 'g': channelModeGreen = !channelModeGreen; break; case 'b': channelModeBlue = !channelModeBlue; break; } + uiMapping.setChannelID(channelIndex+1); + uiMapping.setCubeID(cubeIndex+1); + uiMapping.setStripID(stripIndex+1); + uiMapping.redraw(); } + } diff --git a/_DebugUI.pde b/_DebugUI.pde new file mode 100644 index 0000000..5b99d43 --- /dev/null +++ b/_DebugUI.pde @@ -0,0 +1,283 @@ +/** + * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND + * + * //\\ //\\ //\\ //\\ + * ///\\\ ///\\\ ///\\\ ///\\\ + * \\\/// \\\/// \\\/// \\\/// + * \\// \\// \\// \\// + * + * EXPERTS ONLY!! EXPERTS ONLY!! + * + * Overlay UI that indicates pattern control, etc. This will be moved + * into the Processing library once it is stabilized and need not be + * regularly modified. + */ + +class DebugUI { + + final ChannelMapping[] channelList; + final int debugX = 5; + final int debugY = 5; + final int debugXSpacing = 28; + final int debugYSpacing = 21; + final int[][] debugState; + final int[] indexState; + + final int CUBE_STATE_UNUSED = 0; + final int CUBE_STATE_USED = 1; + final int CUBE_STATE_DUPLICATED = 2; + + final int DEBUG_STATE_ANIM = 0; + final int DEBUG_STATE_WHITE = 1; + final int DEBUG_STATE_OFF = 2; + final int DEBUG_STATE_UNUSED = 3; + + DebugUI(PandaMapping[] pandaMappings) { + int totalChannels = pandaMappings.length * PandaMapping.CHANNELS_PER_BOARD; + debugState = new int[totalChannels+1][ChannelMapping.CUBES_PER_CHANNEL+1]; + indexState = new int[glucose.model.cubes.size()+1]; + + channelList = new ChannelMapping[totalChannels]; + int channelIndex = 0; + for (PandaMapping pm : pandaMappings) { + for (ChannelMapping channel : pm.channelList) { + channelList[channelIndex++] = channel; + } + } + for (int i = 0; i < debugState.length; ++i) { + for (int j = 0; j < debugState[i].length; ++j) { + debugState[i][j] = DEBUG_STATE_ANIM; + } + } + + for (int rawIndex = 0; rawIndex < glucose.model.cubes.size()+1; ++rawIndex) { + indexState[rawIndex] = CUBE_STATE_UNUSED; + } + for (ChannelMapping channel : channelList) { + for (int rawCubeIndex : channel.objectIndices) { + if (rawCubeIndex > 0) + ++indexState[rawCubeIndex]; + } + } + } + + void draw() { + noStroke(); + int xBase = debugX; + int yPos = debugY; + + textSize(10); + + fill(#000000); + rect(0, 0, debugX + 5*debugXSpacing, height); + + int channelNum = 0; + for (ChannelMapping channel : channelList) { + int xPos = xBase; + drawNumBox(xPos, yPos, channelNum+1, debugState[channelNum][0]); + xPos += debugXSpacing; + + switch (channel.mode) { + case ChannelMapping.MODE_CUBES: + int stateIndex = 0; + boolean first = true; + for (int rawCubeIndex : channel.objectIndices) { + if (rawCubeIndex < 0) { + break; + } + if (first) { + first = false; + } else { + stroke(#999999); + line(xPos - 12, yPos + 8, xPos, yPos + 8); + } + drawNumBox(xPos, yPos, rawCubeIndex, debugState[channelNum][stateIndex+1], indexState[rawCubeIndex]); + ++stateIndex; + xPos += debugXSpacing; + } + break; + case ChannelMapping.MODE_BASS: + drawNumBox(xPos, yPos, "B", debugState[channelNum][1]); + break; + case ChannelMapping.MODE_SPEAKER: + drawNumBox(xPos, yPos, "S" + channel.objectIndices[0], debugState[channelNum][1]); + break; + case ChannelMapping.MODE_STRUTS_AND_FLOOR: + drawNumBox(xPos, yPos, "F", debugState[channelNum][1]); + break; + case ChannelMapping.MODE_NULL: + break; + default: + throw new RuntimeException("Unhandled channel mapping mode: " + channel.mode); + } + + yPos += debugYSpacing; + ++channelNum; + } + drawNumBox(xBase, yPos, "A", debugState[channelNum][0]); + yPos += debugYSpacing * 2; + + noFill(); + fill(#CCCCCC); + text("Unused Cubes", xBase, yPos + 12); + yPos += debugYSpacing; + + int xIndex = 0; + for (int rawIndex = 1; rawIndex <= glucose.model.cubes.size(); ++rawIndex) { + if (indexState[rawIndex] == CUBE_STATE_UNUSED) { + drawNumBox(xBase + (xIndex * debugXSpacing), yPos, rawIndex, DEBUG_STATE_UNUSED); + ++xIndex; + if (xIndex > 4) { + xIndex = 0; + yPos += debugYSpacing + 2; + } + } + } + } + + + void drawNumBox(int xPos, int yPos, int label, int state) { + drawNumBox(xPos, yPos, "" + label, state); + } + + void drawNumBox(int xPos, int yPos, String label, int state) { + drawNumBox(xPos, yPos, "" + label, state, CUBE_STATE_USED); + } + + void drawNumBox(int xPos, int yPos, int label, int state, int cubeState) { + drawNumBox(xPos, yPos, "" + label, state, cubeState); + } + + void drawNumBox(int xPos, int yPos, String label, int state, int cubeState) { + noFill(); + color textColor = #cccccc; + switch (state) { + case DEBUG_STATE_ANIM: + noStroke(); + fill(#880000); + rect(xPos, yPos, 16, 8); + fill(#000088); + rect(xPos, yPos+8, 16, 8); + noFill(); + stroke(textColor); + break; + case DEBUG_STATE_WHITE: + stroke(textColor); + fill(#e9e9e9); + textColor = #333333; + break; + case DEBUG_STATE_OFF: + stroke(textColor); + break; + case DEBUG_STATE_UNUSED: + stroke(textColor); + fill(#880000); + break; + } + + if (cubeState >= CUBE_STATE_DUPLICATED) { + stroke(textColor = #FF0000); + } + + rect(xPos, yPos, 16, 16); + noStroke(); + fill(textColor); + text(label, xPos + 2, yPos + 12); + } + + void maskColors(color[] colors) { + color white = #FFFFFF; + color off = #000000; + int channelIndex = 0; + int state; + for (ChannelMapping channel : channelList) { + switch (channel.mode) { + case ChannelMapping.MODE_CUBES: + int cubeIndex = 1; + for (int rawCubeIndex : channel.objectIndices) { + if (rawCubeIndex >= 0) { + state = debugState[channelIndex][cubeIndex]; + if (state != DEBUG_STATE_ANIM) { + color debugColor = (state == DEBUG_STATE_WHITE) ? white : off; + Cube cube = glucose.model.getCubeByRawIndex(rawCubeIndex); + for (Point p : cube.points) { + colors[p.index] = debugColor; + } + } + } + ++cubeIndex; + } + break; + + case ChannelMapping.MODE_BASS: + state = debugState[channelIndex][1]; + if (state != DEBUG_STATE_ANIM) { + color debugColor = (state == DEBUG_STATE_WHITE) ? white : off; + for (Strip s : glucose.model.bassBox.boxStrips) { + for (Point p : s.points) { + colors[p.index] = debugColor; + } + } + } + break; + + case ChannelMapping.MODE_STRUTS_AND_FLOOR: + state = debugState[channelIndex][1]; + if (state != DEBUG_STATE_ANIM) { + color debugColor = (state == DEBUG_STATE_WHITE) ? white : off; + for (Point p : glucose.model.boothFloor.points) { + colors[p.index] = debugColor; + } + for (Strip s : glucose.model.bassBox.struts) { + for (Point p : s.points) { + colors[p.index] = debugColor; + } + } + } + break; + + case ChannelMapping.MODE_SPEAKER: + state = debugState[channelIndex][1]; + if (state != DEBUG_STATE_ANIM) { + color debugColor = (state == DEBUG_STATE_WHITE) ? white : off; + for (Point p : glucose.model.speakers.get(channel.objectIndices[0]).points) { + colors[p.index] = debugColor; + } + } + break; + + case ChannelMapping.MODE_NULL: + break; + + default: + throw new RuntimeException("Unhandled channel mapping mode: " + channel.mode); + } + ++channelIndex; + } + } + + boolean mousePressed() { + int dx = (mouseX - debugX) / debugXSpacing; + int dy = (mouseY - debugY) / debugYSpacing; + if ((dy < 0) || (dy >= debugState.length)) { + return false; + } + if ((dx < 0) || (dx >= debugState[dy].length)) { + return false; + } + int newState = debugState[dy][dx] = (debugState[dy][dx] + 1) % 3; + if (dy == debugState.length-1) { + for (int[] states : debugState) { + for (int i = 0; i < states.length; ++i) { + states[i] = newState; + } + } + } else if (dx == 0) { + for (int i = 0; i < debugState[dy].length; ++i) { + debugState[dy][i] = newState; + } + } + return true; + } +} + diff --git a/_Internals.pde b/_Internals.pde index 5035ed5..57a0161 100644 --- a/_Internals.pde +++ b/_Internals.pde @@ -48,17 +48,28 @@ MappingTool mappingTool; LXPattern[] patterns; LXTransition[] transitions; LXEffect[] effects; -OverlayUI ui; -ControlUI controlUI; -MappingUI mappingUI; PandaDriver[] pandaBoards; boolean mappingMode = false; boolean debugMode = false; DebugUI debugUI; +String displayMode; + +UIContext[] overlays; +UIPatternDeck uiPatternA; +UIMapping uiMapping; +UIDebugText uiDebugText; // Camera variables float eyeR, eyeA, eyeX, eyeY, eyeZ, midX, midY, midZ; +LXPattern[] _patterns(GLucose glucose) { + LXPattern[] patterns = patterns(glucose); + for (LXPattern p : patterns) { + p.setTransition(new DissolveTransition(glucose.lx).setDuration(1000)); + } + return patterns; +} + void setup() { startMillis = lastMillis = millis(); @@ -76,12 +87,15 @@ void setup() { logTime("Built GLucose engine"); // Set the patterns - glucose.lx.setPatterns(patterns = patterns(glucose)); + Engine engine = lx.engine; + glucose.setTransitions(transitions = transitions(glucose)); + logTime("Built transitions"); + engine.setPatterns(patterns = _patterns(glucose)); + engine.addDeck(_patterns(glucose)); + engine.getDeck(1).setBlendTransition(transitions[0]); logTime("Built patterns"); glucose.lx.addEffects(effects = effects(glucose)); logTime("Built effects"); - glucose.setTransitions(transitions = transitions(glucose)); - logTime("Built transitions"); // Build output driver PandaMapping[] pandaMappings = buildPandaList(); @@ -94,9 +108,20 @@ void setup() { logTime("Built PandaDriver"); // Build overlay UI - ui = controlUI = new ControlUI(); - mappingUI = new MappingUI(mappingTool); debugUI = new DebugUI(pandaMappings); + overlays = new UIContext[] { + uiPatternA = new UIPatternDeck(lx.engine.getDeck(0), "PATTERN A", 4, 4, 140, 344), + new UICrossfader(4, 352, 140, 152), + new UIOutput(4, 508, 140, 122), + + new UIPatternDeck(lx.engine.getDeck(1), "PATTERN B", width-144, 4, 140, 344), + new UIEffects(width-144, 352, 140, 144), + new UITempo(width-144, 498, 140, 50), + + uiDebugText = new UIDebugText(4, height-64, width-8, 44), + uiMapping = new UIMapping(mappingTool, 4, 4, 140, 344), + }; + uiMapping.setVisible(false); logTime("Built overlay UI"); // MIDI devices @@ -107,12 +132,12 @@ void setup() { logTime("Setup MIDI devices"); // Setup camera - midX = TRAILER_WIDTH/2. + 20; + midX = TRAILER_WIDTH/2.; midY = glucose.model.yMax/2; midZ = TRAILER_DEPTH/2.; eyeR = -290; eyeA = .15; - eyeY = midY + 20; + eyeY = midY + 70; eyeX = midX + eyeR*sin(eyeA); eyeZ = midZ + eyeR*cos(eyeA); addMouseWheelListener(new java.awt.event.MouseWheelListener() { @@ -152,7 +177,12 @@ void logTime(String evt) { void draw() { // Draws the simulation and the 2D UI overlay background(40); - color[] colors = glucose.getColors(); + color[] colors = glucose.getColors();; + if (displayMode == "A") { + colors = lx.engine.getDeck(0).getColors(); + } else if (displayMode == "B") { + colors = lx.engine.getDeck(1).getColors(); + } if (debugMode) { debugUI.maskColors(colors); } @@ -163,6 +193,8 @@ void draw() { 0, -1, 0 ); + translate(0, 10, 0); + noStroke(); fill(#141414); drawBox(0, -TRAILER_HEIGHT, 0, 0, 0, 0, TRAILER_WIDTH, TRAILER_HEIGHT, TRAILER_DEPTH, TRAILER_HEIGHT/2.); @@ -197,16 +229,13 @@ void draw() { } endShape(); - // 2D Overlay - camera(); - javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL(); - gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT); - ((PGraphicsOpenGL)g).endGL(); - strokeWeight(1); + // 2D Overlay UI drawUI(); - + + // Send output colors + color[] sendColors = glucose.getColors(); if (debugMode) { - debugUI.draw(); + debugUI.maskColors(colors); } // Gamma correction here. Apply a cubic to the brightness @@ -333,28 +362,37 @@ void drawBox(float x, float y, float z, float rx, float ry, float rz, float xd, } void drawUI() { + camera(); + javax.media.opengl.GL gl = ((PGraphicsOpenGL)g).beginGL(); + gl.glClear(javax.media.opengl.GL.GL_DEPTH_BUFFER_BIT); + ((PGraphicsOpenGL)g).endGL(); + strokeWeight(1); + if (uiOn) { - ui.draw(); - } else { - ui.drawHelpTip(); + for (UIContext context : overlays) { + context.draw(); + } + } + + // Always draw FPS meter + fill(#555555); + textSize(9); + textAlign(LEFT, BASELINE); + text("FPS: " + ((int) (frameRate*10)) / 10. + " / " + targetFramerate + " (-/+)", 4, height-4); + + if (debugMode) { + debugUI.draw(); } - ui.drawFPS(); - ui.drawDanText(); } boolean uiOn = true; -int restoreToIndex = -1; - -boolean doDual = false; +LXPattern restoreToPattern = null; void keyPressed() { if (mappingMode) { - mappingTool.keyPressed(); + mappingTool.keyPressed(uiMapping); } switch (key) { - case 'w': - doDual = !doDual; - break; case '-': case '_': frameRate(--targetFramerate); @@ -369,20 +407,14 @@ void keyPressed() { break; case 'm': mappingMode = !mappingMode; + uiPatternA.setVisible(!mappingMode); + uiMapping.setVisible(mappingMode); if (mappingMode) { - LXPattern pattern = lx.getPattern(); - for (int i = 0; i < patterns.length; ++i) { - if (pattern == patterns[i]) { - restoreToIndex = i; - break; - } - } - ui = mappingUI; + restoreToPattern = lx.getPattern(); lx.setPatterns(new LXPattern[] { mappingTool }); } else { - ui = controlUI; lx.setPatterns(patterns); - lx.goIndex(restoreToIndex); + lx.goPattern(restoreToPattern); } break; case 'p': @@ -398,20 +430,25 @@ void keyPressed() { int mx, my; void mousePressed() { - ui.mousePressed(); - if (mouseX < ui.leftPos) { - if (debugMode) { - debugUI.mousePressed(); - } - mx = mouseX; - my = mouseY; + boolean debugged = false; + if (debugMode) { + debugged = debugUI.mousePressed(); + } + if (!debugged) { + for (UIContext context : overlays) { + context.mousePressed(mouseX, mouseY); + } } + mx = mouseX; + my = mouseY; } void mouseDragged() { - if (mouseX > ui.leftPos) { - ui.mouseDragged(); - } else { + boolean dragged = false; + for (UIContext context : overlays) { + dragged |= context.mouseDragged(mouseX, mouseY); + } + if (!dragged) { int dx = mouseX - mx; int dy = mouseY - my; mx = mouseX; @@ -424,13 +461,20 @@ void mouseDragged() { } void mouseReleased() { - ui.mouseReleased(); + for (UIContext context : overlays) { + context.mouseReleased(mouseX, mouseY); + } + + // ui.mouseReleased(); } void mouseWheel(int delta) { - if (mouseX > ui.leftPos) { - ui.mouseWheel(delta); - } else { + boolean wheeled = false; + for (UIContext context : overlays) { + wheeled |= context.mouseWheel(mouseX, mouseY, delta); + } + + if (!wheeled) { eyeR = constrain(eyeR - delta, -500, -80); eyeX = midX + eyeR*sin(eyeA); eyeZ = midZ + eyeR*cos(eyeA); diff --git a/_Overlay.pde b/_Overlay.pde deleted file mode 100644 index 1b13b03..0000000 --- a/_Overlay.pde +++ /dev/null @@ -1,1028 +0,0 @@ -import java.lang.reflect.*; - -/** - * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND - * - * //\\ //\\ //\\ //\\ - * ///\\\ ///\\\ ///\\\ ///\\\ - * \\\/// \\\/// \\\/// \\\/// - * \\// \\// \\// \\// - * - * EXPERTS ONLY!! EXPERTS ONLY!! - * - * Overlay UI that indicates pattern control, etc. This will be moved - * into the Processing library once it is stabilized and need not be - * regularly modified. - */ -abstract class OverlayUI { - protected final PFont titleFont = createFont("Myriad Pro", 10); - protected final color titleColor = #AAAAAA; - protected final PFont itemFont = createFont("Lucida Grande", 11); - protected final PFont knobFont = titleFont; - protected final int w = 140; - protected final int leftPos; - protected final int leftTextPos; - protected final int lineHeight = 20; - protected final int sectionSpacing = 12; - protected final int controlSpacing = 18; - protected final int tempoHeight = 20; - protected final int knobSize = 28; - protected final float knobIndent = .4; - protected final int knobSpacing = 6; - protected final int knobLabelHeight = 14; - protected final int scrollWidth = 14; - protected final color lightBlue = #666699; - protected final color lightGreen = #669966; - protected final int toggleButtonSize = 10; - - private PImage logo; - - protected final int STATE_DEFAULT = 0; - protected final int STATE_ACTIVE = 1; - protected final int STATE_PENDING = 2; - - protected int[] pandaLeft = new int[pandaBoards.length]; - protected final int pandaWidth = 64; - protected final int pandaHeight = 13; - protected final int pandaTop = height-16; - - protected int eligibleLeft; - - protected OverlayUI() { - leftPos = width - w; - leftTextPos = leftPos + 4; - logo = loadImage("logo-sm.png"); - } - - protected void drawLogoAndBackground() { - image(logo, 4, 4); - stroke(color(0, 0, 100)); - // fill(color(0, 0, 50, 50)); // alpha is bad for perf - fill(color(0, 0, 30)); - rect(leftPos-1, -1, w+2, height+2); - } - - protected void drawToggleTip(String s) { - fill(#999999); - textFont(itemFont); - textAlign(LEFT); - text(s, leftTextPos, height-6); - } - - protected void drawHelpTip() { - textFont(itemFont); - textAlign(RIGHT); - text("Tap 'u' to restore UI", width-4, height-6); - } - - public void drawDanText() { - textFont(itemFont); - textAlign(LEFT); - fill(#FFFFFF); - text(DanTextLine1, 4, height-50); - text(DanTextLine2, 4, height-30); - } - - public void drawFPS() { - textFont(titleFont); - textAlign(LEFT); - noStroke(); - fill(#666666); - int lPos = 4; - String fps = "FPS: " + (((int)(frameRate * 10)) / 10.); - text(fps, lPos, height-6); - lPos += 48; - - String target = "Target (-/+):"; - text(target, lPos, height-6); - fill(#000000); - lPos += textWidth(target) + 4; - rect(lPos, height-16, 24, 13); - fill(#666666); - text("" + targetFramerate, lPos + (24 - textWidth("" + targetFramerate))/2, height-6); - lPos += 32; - String pandaOutput = "PandaOutput (p):"; - text(pandaOutput, lPos, height-6); - lPos += textWidth(pandaOutput)+4; - int pi = 0; - for (PandaDriver p : pandaBoards) { - pandaLeft[pi++] = lPos; - fill(p.enabled ? #666666 : #000000); - rect(lPos, pandaTop, pandaWidth, pandaHeight); - fill(p.enabled ? #000000 : #666666); - text(p.ip, lPos + (pandaWidth - textWidth(p.ip)) / 2, height-6); - lPos += pandaWidth + 8; - } - - } - - protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod) { - int sz = (items != null) ? items.length : 0; - return drawObjectList(yPos, title, items, stateMethod, sz, 0); - } - - protected int drawObjectList(int yPos, String title, Object[] items, Method stateMethod, int scrollLength, int scrollPos) { - return drawObjectList(yPos, title, items, classNameArray(items, null), stateMethod, scrollLength, scrollPos); - } - - protected int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod) { - int sz = (items != null) ? items.length : 0; - return drawObjectList(yPos, title, items, names, stateMethod, sz, 0); - } - - protected void drawToggleButton(float x, float y, boolean eligible, color textColor) { - noFill(); - stroke(textColor); - rect(x, y, toggleButtonSize, toggleButtonSize); - if (eligible) { - noStroke(); - fill(textColor); - rect(x + 2, y + 2, toggleButtonSize - 4, toggleButtonSize - 4); - } - } - - protected int drawObjectList(int yPos, String title, Object[] items, String[] names, Method stateMethod, int scrollLength, int scrollPos) { - noStroke(); - fill(titleColor); - textFont(titleFont); - textAlign(LEFT); - text(title, leftTextPos, yPos += lineHeight); - if (items != null) { - boolean hasScroll = (scrollPos > 0) || (scrollLength < items.length); - textFont(itemFont); - color textColor; - boolean even = true; - int yTop = yPos+6; - for (int i = scrollPos; i < items.length && i < (scrollPos + scrollLength); ++i) { - Object o = items[i]; - int state = STATE_DEFAULT; - try { - state = ((Integer) stateMethod.invoke(this, o)).intValue(); - } catch (Exception x) { - throw new RuntimeException(x); - } - switch (state) { - case STATE_ACTIVE: - fill(lightGreen); - textColor = #eeeeee; - break; - case STATE_PENDING: - fill(lightBlue); - textColor = color(0, 0, 75 + 15*sin(millis()/200.));; - break; - default: - textColor = 0; - fill(even ? #666666 : #777777); - break; - } - noStroke(); - rect(leftPos, yPos+6, w, lineHeight); - fill(textColor); - text(names[i], leftTextPos, yPos += lineHeight); - if (lx.isAutoTransitionEnabled() && items[i] instanceof LXPattern) { - boolean eligible = ((LXPattern)items[i]).isEligible(); - eligibleLeft = leftPos + w - (hasScroll ? scrollWidth : 0) - 15; - drawToggleButton(eligibleLeft, yPos-8, eligible, textColor); - } - even = !even; - } - if (hasScroll) { - int yHere = yPos+6; - noStroke(); - fill(color(0, 0, 0, 50)); - rect(leftPos + w - scrollWidth, yTop, scrollWidth, yHere - yTop); - fill(#666666); - rect(leftPos + w - scrollWidth + 2, yTop + (yHere-yTop) * (scrollPos / (float)items.length), scrollWidth - 4, (yHere - yTop) * (scrollLength / (float)items.length)); - - } - - } - return yPos; - } - - protected String[] classNameArray(Object[] objects, String suffix) { - if (objects == null) { - return null; - } - String[] names = new String[objects.length]; - for (int i = 0; i < objects.length; ++i) { - names[i] = className(objects[i], suffix); - } - return names; - } - - protected String className(Object p, String suffix) { - String s = p.getClass().getName(); - int li; - if ((li = s.lastIndexOf(".")) > 0) { - s = s.substring(li + 1); - } - if (s.indexOf("SugarCubes$") == 0) { - s = s.substring("SugarCubes$".length()); - } - if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) { - s = s.substring(0, li); - } - return s; - } - - protected int objectClickIndex(int firstItemY) { - return (mouseY - firstItemY) / lineHeight; - } - - abstract public void draw(); - abstract public void mousePressed(); - abstract public void mouseDragged(); - abstract public void mouseReleased(); - abstract public void mouseWheel(int delta); -} - -/** - * UI for control of patterns, transitions, effects. - */ -class ControlUI extends OverlayUI { - private final String[] patternNames; - private final String[] transitionNames; - private final String[] effectNames; - - private int firstPatternY; - private int firstPatternKnobY; - private int firstTransitionY; - private int firstTransitionKnobY; - private int firstEffectY; - private int firstEffectKnobY; - - private int autoRotateX; - private int autoRotateY; - - private final int PATTERN_LIST_LENGTH = 8; - private int patternScrollPos = 0; - - private int tempoY; - - private Method patternStateMethod; - private Method transitionStateMethod; - private Method effectStateMethod; - - ControlUI() { - patternNames = classNameArray(patterns, "Pattern"); - transitionNames = classNameArray(transitions, "Transition"); - effectNames = classNameArray(effects, "Effect"); - - try { - patternStateMethod = getClass().getMethod("getState", LXPattern.class); - effectStateMethod = getClass().getMethod("getState", LXEffect.class); - transitionStateMethod = getClass().getMethod("getState", LXTransition.class); - } catch (Exception x) { - throw new RuntimeException(x); - } - } - - public void draw() { - drawLogoAndBackground(); - int yPos = 0; - autoRotateX = leftPos + w - 29; - autoRotateY = yPos + 12; - drawToggleButton(autoRotateX, autoRotateY, lx.isAutoTransitionEnabled(), #999999); - fill(lx.isAutoTransitionEnabled() ? #222222: #999999); - text("A", autoRotateX + 2, autoRotateY + 9); - firstPatternY = yPos + lineHeight + 6; - yPos = drawObjectList(yPos, "PATTERN", patterns, patternNames, patternStateMethod, PATTERN_LIST_LENGTH, patternScrollPos); - yPos += controlSpacing; - firstPatternKnobY = yPos; - int xPos = leftTextPos; - for (int i = 0; i < glucose.NUM_PATTERN_KNOBS/2; ++i) { - drawKnob(xPos, yPos, knobSize, glucose.patternKnobs.get(i)); - drawKnob(xPos, yPos + knobSize + knobSpacing + knobLabelHeight, knobSize, glucose.patternKnobs.get(glucose.NUM_PATTERN_KNOBS/2 + i)); - xPos += knobSize + knobSpacing; - } - yPos += 2*(knobSize + knobLabelHeight) + knobSpacing; - - yPos += sectionSpacing; - firstTransitionY = yPos + lineHeight + 6; - yPos = drawObjectList(yPos, "TRANSITION", transitions, transitionNames, transitionStateMethod); - yPos += controlSpacing; - firstTransitionKnobY = yPos; - xPos = leftTextPos; - for (VirtualTransitionKnob knob : glucose.transitionKnobs) { - drawKnob(xPos, yPos, knobSize, knob); - xPos += knobSize + knobSpacing; - } - yPos += knobSize + knobLabelHeight; - - yPos += sectionSpacing; - firstEffectY = yPos + lineHeight + 6; - yPos = drawObjectList(yPos, "FX", effects, effectNames, effectStateMethod); - yPos += controlSpacing; - firstEffectKnobY = yPos; - xPos = leftTextPos; - for (VirtualEffectKnob knob : glucose.effectKnobs) { - drawKnob(xPos, yPos, knobSize, knob); - xPos += knobSize + knobSpacing; - } - yPos += knobSize + knobLabelHeight; - - yPos += sectionSpacing; - yPos = drawObjectList(yPos, "TEMPO", null, null, null); - yPos += 6; - tempoY = yPos; - stroke(#111111); - fill(tempoDown ? lightGreen : color(0, 0, 35 - 8*lx.tempo.rampf())); - rect(leftPos + 4, yPos, w - 8, tempoHeight); - fill(0); - textAlign(CENTER); - text("" + ((int)(lx.tempo.bpmf() * 100) / 100.), leftPos + w/2., yPos + tempoHeight - 6); - yPos += tempoHeight; - - drawToggleTip("Tap 'u' to hide"); - } - - public LXParameter getOrNull(List items, int index) { - if (index < items.size()) { - return items.get(index); - } - return null; - } - - public int getState(LXPattern p) { - if (p == lx.getPattern()) { - return STATE_ACTIVE; - } else if (p == lx.getNextPattern()) { - return STATE_PENDING; - } - return STATE_DEFAULT; - } - - public int getState(LXEffect e) { - if (e.isEnabled()) { - return STATE_PENDING; - } else if (e == glucose.getSelectedEffect()) { - return STATE_ACTIVE; - } - return STATE_DEFAULT; - } - - public int getState(LXTransition t) { - if (t == lx.getTransition()) { - return STATE_PENDING; - } else if (t == glucose.getSelectedTransition()) { - return STATE_ACTIVE; - } - return STATE_DEFAULT; - } - - private void drawKnob(int xPos, int yPos, int knobSize, LXParameter knob) { - final float knobValue = knob.getValuef(); - String knobLabel = knob.getLabel(); - if (knobLabel == null) { - knobLabel = "-"; - } else if (knobLabel.length() > 4) { - knobLabel = knobLabel.substring(0, 4); - } - - ellipseMode(CENTER); - noStroke(); - fill(#222222); - // For some reason this arc call really crushes drawing performance. Presumably - // because openGL is drawing it and when we overlap the second set of arcs it - // does a bunch of depth buffer intersection tests? Ellipse with a trapezoid cut out is faster - // arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent)); - ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize); - - float endArc = HALF_PI + knobIndent + (TWO_PI-2*knobIndent)*knobValue; - fill(lightGreen); - arc(xPos + knobSize/2, yPos + knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, endArc); - - // Mask notch out of knob - fill(color(0, 0, 30)); - beginShape(); - vertex(xPos + knobSize/2, yPos + knobSize/2.); - vertex(xPos + knobSize/2 - 6, yPos + knobSize); - vertex(xPos + knobSize/2 + 6, yPos + knobSize); - endShape(); - - // Center circle of knob - fill(#333333); - ellipse(xPos + knobSize/2, yPos + knobSize/2, knobSize/2, knobSize/2); - - fill(0); - rect(xPos, yPos + knobSize + 2, knobSize, knobLabelHeight - 2); - fill(#999999); - textAlign(CENTER); - textFont(knobFont); - text(knobLabel, xPos + knobSize/2, yPos + knobSize + knobLabelHeight - 2); - } - - private int patternKnobIndex = -1; - private int transitionKnobIndex = -1; - private int effectKnobIndex = -1; - private boolean patternScrolling = false; - - private int lastY; - private int releaseEffect = -1; - private boolean tempoDown = false; - - public void mousePressed() { - lastY = mouseY; - patternKnobIndex = transitionKnobIndex = effectKnobIndex = -1; - releaseEffect = -1; - patternScrolling = false; - - for (int p = 0; p < pandaLeft.length; ++p) { - int xp = pandaLeft[p]; - if ((mouseX >= xp) && - (mouseX < xp + pandaWidth) && - (mouseY >= pandaTop) && - (mouseY < pandaTop + pandaHeight)) { - pandaBoards[p].toggle(); - } - } - - if (mouseX < leftPos) { - return; - } - - if ((mouseX >= autoRotateX) && - (mouseX < autoRotateX + toggleButtonSize) && - (mouseY >= autoRotateY) && - (mouseY < autoRotateY + toggleButtonSize)) { - if (lx.isAutoTransitionEnabled()) { - lx.disableAutoTransition(); - println("Auto pattern transition disabled"); - } else { - lx.enableAutoTransition(60000); - println("Auto pattern transition enabled"); - } - return; - } - - if (mouseY > tempoY) { - if (mouseY - tempoY < tempoHeight) { - lx.tempo.tap(); - tempoDown = true; - } - } else if ((mouseY >= firstEffectKnobY) && (mouseY < firstEffectKnobY + knobSize + knobLabelHeight)) { - effectKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); - } else if (mouseY > firstEffectY) { - int effectIndex = objectClickIndex(firstEffectY); - if (effectIndex < effects.length) { - if (effects[effectIndex] == glucose.getSelectedEffect()) { - effects[effectIndex].enable(); - releaseEffect = effectIndex; - } - glucose.setSelectedEffect(effectIndex); - } - } else if ((mouseY >= firstTransitionKnobY) && (mouseY < firstTransitionKnobY + knobSize + knobLabelHeight)) { - transitionKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); - } else if (mouseY > firstTransitionY) { - int transitionIndex = objectClickIndex(firstTransitionY); - if (transitionIndex < transitions.length) { - glucose.setSelectedTransition(transitionIndex); - } - } else if ((mouseY >= firstPatternKnobY) && (mouseY < firstPatternKnobY + 2*(knobSize+knobLabelHeight) + knobSpacing)) { - patternKnobIndex = (mouseX - leftTextPos) / (knobSize + knobSpacing); - if (mouseY >= firstPatternKnobY + knobSize + knobLabelHeight + knobSpacing) { - patternKnobIndex += glucose.NUM_PATTERN_KNOBS / 2; - } - } else if (mouseY > firstPatternY) { - if ((patterns.length > PATTERN_LIST_LENGTH) && (mouseX > width - scrollWidth)) { - patternScrolling = true; - } else { - int patternIndex = objectClickIndex(firstPatternY); - if (patternIndex < patterns.length) { - if (lx.isAutoTransitionEnabled() && (mouseX > eligibleLeft)) { - patterns[patternIndex + patternScrollPos].toggleEligible(); - } else { - lx.goIndex(patternIndex + patternScrollPos); - } - } - } - } - } - - int scrolldy = 0; - public void mouseDragged() { - int dy = lastY - mouseY; - scrolldy += dy; - lastY = mouseY; - if (patternKnobIndex >= 0 && patternKnobIndex < glucose.NUM_PATTERN_KNOBS) { - LXParameter p = glucose.patternKnobs.get(patternKnobIndex); - p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); - } else if (effectKnobIndex >= 0 && effectKnobIndex < glucose.NUM_EFFECT_KNOBS) { - LXParameter p = glucose.effectKnobs.get(effectKnobIndex); - p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); - } else if (transitionKnobIndex >= 0 && transitionKnobIndex < glucose.NUM_TRANSITION_KNOBS) { - LXParameter p = glucose.transitionKnobs.get(transitionKnobIndex); - p.setValue(constrain(p.getValuef() + dy*.01, 0, 1)); - } else if (patternScrolling) { - int scroll = scrolldy / lineHeight; - scrolldy = scrolldy % lineHeight; - patternScrollPos = constrain(patternScrollPos - scroll, 0, patterns.length - PATTERN_LIST_LENGTH); - } - } - - public void mouseReleased() { - patternScrolling = false; - tempoDown = false; - if (releaseEffect >= 0) { - effects[releaseEffect].trigger(); - releaseEffect = -1; - } - } - - public void mouseWheel(int delta) { - if (mouseY > firstPatternY) { - int patternIndex = objectClickIndex(firstPatternY); - if (patternIndex < PATTERN_LIST_LENGTH) { - patternScrollPos = constrain(patternScrollPos + delta, 0, patterns.length - PATTERN_LIST_LENGTH); - } - } - } - -} - -/** - * UI for control of mapping. - */ -class MappingUI extends OverlayUI { - - private MappingTool mappingTool; - - private final String MAPPING_MODE_ALL = "All On"; - private final String MAPPING_MODE_CHANNEL = "Channel"; - private final String MAPPING_MODE_SINGLE_CUBE = "Single Cube"; - - private final String[] mappingModes = { - MAPPING_MODE_ALL, - MAPPING_MODE_CHANNEL, - MAPPING_MODE_SINGLE_CUBE - }; - private final Method mappingModeStateMethod; - - private final String CUBE_MODE_ALL = "All Strips"; - private final String CUBE_MODE_SINGLE_STRIP = "Single Strip"; - private final String CUBE_MODE_STRIP_PATTERN = "Strip Pattern"; - private final String[] cubeModes = { - CUBE_MODE_ALL, - CUBE_MODE_SINGLE_STRIP, - CUBE_MODE_STRIP_PATTERN - }; - private final Method cubeModeStateMethod; - - private final String CHANNEL_MODE_RED = "Red"; - private final String CHANNEL_MODE_GREEN = "Green"; - private final String CHANNEL_MODE_BLUE = "Blue"; - private final String[] channelModes = { - CHANNEL_MODE_RED, - CHANNEL_MODE_GREEN, - CHANNEL_MODE_BLUE, - }; - private final Method channelModeStateMethod; - - private int firstMappingY; - private int firstCubeY; - private int firstChannelY; - private int channelFieldY; - private int cubeFieldY; - private int stripFieldY; - - private boolean dragCube; - private boolean dragStrip; - private boolean dragChannel; - - MappingUI(MappingTool mappingTool) { - this.mappingTool = mappingTool; - try { - mappingModeStateMethod = getClass().getMethod("getMappingState", Object.class); - channelModeStateMethod = getClass().getMethod("getChannelState", Object.class); - cubeModeStateMethod = getClass().getMethod("getCubeState", Object.class); - } catch (Exception x) { - throw new RuntimeException(x); - } - } - - public int getMappingState(Object mappingMode) { - boolean active = false; - if (mappingMode == MAPPING_MODE_ALL) { - active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_ALL; - } else if (mappingMode == MAPPING_MODE_CHANNEL) { - active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_CHANNEL; - } else if (mappingMode == MAPPING_MODE_SINGLE_CUBE) { - active = mappingTool.mappingMode == mappingTool.MAPPING_MODE_SINGLE_CUBE; - } - return active ? STATE_ACTIVE : STATE_DEFAULT; - } - - public int getChannelState(Object channelMode) { - boolean active = false; - if (channelMode == CHANNEL_MODE_RED) { - active = mappingTool.channelModeRed; - } else if (channelMode == CHANNEL_MODE_GREEN) { - active = mappingTool.channelModeGreen; - } else if (channelMode == CHANNEL_MODE_BLUE) { - active = mappingTool.channelModeBlue; - } - return active ? STATE_ACTIVE : STATE_DEFAULT; - } - - public int getCubeState(Object cubeMode) { - boolean active = false; - if (cubeMode == CUBE_MODE_ALL) { - active = mappingTool.cubeMode == mappingTool.CUBE_MODE_ALL; - } else if (cubeMode == CUBE_MODE_SINGLE_STRIP) { - active = mappingTool.cubeMode == mappingTool.CUBE_MODE_SINGLE_STRIP; - } else if (cubeMode == CUBE_MODE_STRIP_PATTERN) { - active = mappingTool.cubeMode == mappingTool.CUBE_MODE_STRIP_PATTERN; - } - return active ? STATE_ACTIVE : STATE_DEFAULT; - } - - public void draw() { - drawLogoAndBackground(); - - int yPos = 0; - firstMappingY = yPos + lineHeight + 6; - yPos = drawObjectList(yPos, "MAPPING MODE", mappingModes, mappingModes, mappingModeStateMethod); - yPos += sectionSpacing; - - firstCubeY = yPos + lineHeight + 6; - yPos = drawObjectList(yPos, "CUBE MODE", cubeModes, cubeModes, cubeModeStateMethod); - yPos += sectionSpacing; - - firstChannelY = yPos + lineHeight + 6; - yPos = drawObjectList(yPos, "CHANNELS", channelModes, channelModes, channelModeStateMethod); - yPos += sectionSpacing; - - channelFieldY = yPos + lineHeight + 6; - yPos = drawValueField(yPos, "CHANNEL ID", mappingTool.channelIndex + 1); - yPos += sectionSpacing; - - cubeFieldY = yPos + lineHeight + 6; - yPos = drawValueField(yPos, "CUBE ID", glucose.model.getRawIndexForCube(mappingTool.cubeIndex)); - yPos += sectionSpacing; - - stripFieldY = yPos + lineHeight + 6; - yPos = drawValueField(yPos, "STRIP ID", mappingTool.stripIndex + 1); - - drawToggleTip("Tap 'm' to return"); - } - - private int drawValueField(int yPos, String label, int value) { - yPos += lineHeight; - textAlign(LEFT); - textFont(titleFont); - fill(titleColor); - text(label, leftTextPos, yPos); - fill(0); - yPos += 6; - rect(leftTextPos, yPos, w-8, lineHeight); - yPos += lineHeight; - - fill(#999999); - textAlign(CENTER); - textFont(itemFont); - text("" + value, leftTextPos + (w-8)/2, yPos - 5); - - return yPos; - } - - private int lastY; - - public void mousePressed() { - dragCube = dragStrip = dragChannel = false; - lastY = mouseY; - - if (mouseX < leftPos) { - return; - } - - if (mouseY >= stripFieldY) { - if (mouseY < stripFieldY + lineHeight) { - dragStrip = true; - } - } else if (mouseY >= cubeFieldY) { - if (mouseY < cubeFieldY + lineHeight) { - dragCube = true; - } - } else if (mouseY >= channelFieldY) { - if (mouseY < channelFieldY + lineHeight) { - dragChannel = true; - } - } else if (mouseY >= firstChannelY) { - int index = objectClickIndex(firstChannelY); - switch (index) { - case 0: mappingTool.channelModeRed = !mappingTool.channelModeRed; break; - case 1: mappingTool.channelModeGreen = !mappingTool.channelModeGreen; break; - case 2: mappingTool.channelModeBlue = !mappingTool.channelModeBlue; break; - } - } else if (mouseY >= firstCubeY) { - int index = objectClickIndex(firstCubeY); - switch (index) { - case 0: mappingTool.cubeMode = mappingTool.CUBE_MODE_ALL; break; - case 1: mappingTool.cubeMode = mappingTool.CUBE_MODE_SINGLE_STRIP; break; - case 2: mappingTool.cubeMode = mappingTool.CUBE_MODE_STRIP_PATTERN; break; - } - } else if (mouseY >= firstMappingY) { - int index = objectClickIndex(firstMappingY); - switch (index) { - case 0: mappingTool.mappingMode = mappingTool.MAPPING_MODE_ALL; break; - case 1: mappingTool.mappingMode = mappingTool.MAPPING_MODE_CHANNEL; break; - case 2: mappingTool.mappingMode = mappingTool.MAPPING_MODE_SINGLE_CUBE; break; - } - } - } - - public void mouseReleased() {} - public void mouseWheel(int delta) {} - - public void mouseDragged() { - final int DRAG_THRESHOLD = 5; - int dy = lastY - mouseY; - if (abs(dy) >= DRAG_THRESHOLD) { - lastY = mouseY; - if (dragCube) { - if (dy < 0) { - mappingTool.decCube(); - } else { - mappingTool.incCube(); - } - } else if (dragStrip) { - if (dy < 0) { - mappingTool.decStrip(); - } else { - mappingTool.incStrip(); - } - } else if (dragChannel) { - if (dy < 0) { - mappingTool.decChannel(); - } else { - mappingTool.incChannel(); - } - } - } - - } -} - -class DebugUI { - - final ChannelMapping[] channelList; - final int debugX = 5; - final int debugY = 5; - final int debugXSpacing = 28; - final int debugYSpacing = 21; - final int[][] debugState; - final int[] indexState; - - final int CUBE_STATE_UNUSED = 0; - final int CUBE_STATE_USED = 1; - final int CUBE_STATE_DUPLICATED = 2; - - final int DEBUG_STATE_ANIM = 0; - final int DEBUG_STATE_WHITE = 1; - final int DEBUG_STATE_OFF = 2; - final int DEBUG_STATE_UNUSED = 3; - - DebugUI(PandaMapping[] pandaMappings) { - int totalChannels = pandaMappings.length * PandaMapping.CHANNELS_PER_BOARD; - debugState = new int[totalChannels+1][ChannelMapping.CUBES_PER_CHANNEL+1]; - indexState = new int[glucose.model.cubes.size()+1]; - - channelList = new ChannelMapping[totalChannels]; - int channelIndex = 0; - for (PandaMapping pm : pandaMappings) { - for (ChannelMapping channel : pm.channelList) { - channelList[channelIndex++] = channel; - } - } - for (int i = 0; i < debugState.length; ++i) { - for (int j = 0; j < debugState[i].length; ++j) { - debugState[i][j] = DEBUG_STATE_ANIM; - } - } - - for (int rawIndex = 0; rawIndex < glucose.model.cubes.size()+1; ++rawIndex) { - indexState[rawIndex] = CUBE_STATE_UNUSED; - } - for (ChannelMapping channel : channelList) { - for (int rawCubeIndex : channel.objectIndices) { - if (rawCubeIndex > 0) - ++indexState[rawCubeIndex]; - } - } - } - - void draw() { - noStroke(); - int xBase = debugX; - int yPos = debugY; - - fill(#000000); - rect(0, 0, debugX + 5*debugXSpacing, height); - - int channelNum = 0; - for (ChannelMapping channel : channelList) { - int xPos = xBase; - drawNumBox(xPos, yPos, channelNum+1, debugState[channelNum][0]); - xPos += debugXSpacing; - - switch (channel.mode) { - case ChannelMapping.MODE_CUBES: - int stateIndex = 0; - boolean first = true; - for (int rawCubeIndex : channel.objectIndices) { - if (rawCubeIndex < 0) { - break; - } - if (first) { - first = false; - } else { - stroke(#999999); - line(xPos - 12, yPos + 8, xPos, yPos + 8); - } - drawNumBox(xPos, yPos, rawCubeIndex, debugState[channelNum][stateIndex+1], indexState[rawCubeIndex]); - ++stateIndex; - xPos += debugXSpacing; - } - break; - case ChannelMapping.MODE_BASS: - drawNumBox(xPos, yPos, "B", debugState[channelNum][1]); - break; - case ChannelMapping.MODE_SPEAKER: - drawNumBox(xPos, yPos, "S" + channel.objectIndices[0], debugState[channelNum][1]); - break; - case ChannelMapping.MODE_STRUTS_AND_FLOOR: - drawNumBox(xPos, yPos, "F", debugState[channelNum][1]); - break; - case ChannelMapping.MODE_NULL: - break; - default: - throw new RuntimeException("Unhandled channel mapping mode: " + channel.mode); - } - - yPos += debugYSpacing; - ++channelNum; - } - drawNumBox(xBase, yPos, "A", debugState[channelNum][0]); - yPos += debugYSpacing * 2; - - noFill(); - fill(#CCCCCC); - text("Unused Cubes", xBase, yPos + 12); - yPos += debugYSpacing; - - int xIndex = 0; - for (int rawIndex = 1; rawIndex <= glucose.model.cubes.size(); ++rawIndex) { - if (indexState[rawIndex] == CUBE_STATE_UNUSED) { - drawNumBox(xBase + (xIndex * debugXSpacing), yPos, rawIndex, DEBUG_STATE_UNUSED); - ++xIndex; - if (xIndex > 4) { - xIndex = 0; - yPos += debugYSpacing + 2; - } - } - } - } - - - void drawNumBox(int xPos, int yPos, int label, int state) { - drawNumBox(xPos, yPos, "" + label, state); - } - - void drawNumBox(int xPos, int yPos, String label, int state) { - drawNumBox(xPos, yPos, "" + label, state, CUBE_STATE_USED); - } - - void drawNumBox(int xPos, int yPos, int label, int state, int cubeState) { - drawNumBox(xPos, yPos, "" + label, state, cubeState); - } - - void drawNumBox(int xPos, int yPos, String label, int state, int cubeState) { - noFill(); - color textColor = #cccccc; - switch (state) { - case DEBUG_STATE_ANIM: - noStroke(); - fill(#880000); - rect(xPos, yPos, 16, 8); - fill(#000088); - rect(xPos, yPos+8, 16, 8); - noFill(); - stroke(textColor); - break; - case DEBUG_STATE_WHITE: - stroke(textColor); - fill(#e9e9e9); - textColor = #333333; - break; - case DEBUG_STATE_OFF: - stroke(textColor); - break; - case DEBUG_STATE_UNUSED: - stroke(textColor); - fill(#880000); - break; - } - - if (cubeState >= CUBE_STATE_DUPLICATED) { - stroke(textColor = #FF0000); - } - - rect(xPos, yPos, 16, 16); - noStroke(); - fill(textColor); - text(label, xPos + 2, yPos + 12); - } - - void maskColors(color[] colors) { - color white = #FFFFFF; - color off = #000000; - int channelIndex = 0; - int state; - for (ChannelMapping channel : channelList) { - switch (channel.mode) { - case ChannelMapping.MODE_CUBES: - int cubeIndex = 1; - for (int rawCubeIndex : channel.objectIndices) { - if (rawCubeIndex >= 0) { - state = debugState[channelIndex][cubeIndex]; - if (state != DEBUG_STATE_ANIM) { - color debugColor = (state == DEBUG_STATE_WHITE) ? white : off; - Cube cube = glucose.model.getCubeByRawIndex(rawCubeIndex); - for (Point p : cube.points) { - colors[p.index] = debugColor; - } - } - } - ++cubeIndex; - } - break; - - case ChannelMapping.MODE_BASS: - state = debugState[channelIndex][1]; - if (state != DEBUG_STATE_ANIM) { - color debugColor = (state == DEBUG_STATE_WHITE) ? white : off; - for (Strip s : glucose.model.bassBox.boxStrips) { - for (Point p : s.points) { - colors[p.index] = debugColor; - } - } - } - break; - - case ChannelMapping.MODE_STRUTS_AND_FLOOR: - state = debugState[channelIndex][1]; - if (state != DEBUG_STATE_ANIM) { - color debugColor = (state == DEBUG_STATE_WHITE) ? white : off; - for (Point p : glucose.model.boothFloor.points) { - colors[p.index] = debugColor; - } - for (Strip s : glucose.model.bassBox.struts) { - for (Point p : s.points) { - colors[p.index] = debugColor; - } - } - } - break; - - case ChannelMapping.MODE_SPEAKER: - state = debugState[channelIndex][1]; - if (state != DEBUG_STATE_ANIM) { - color debugColor = (state == DEBUG_STATE_WHITE) ? white : off; - for (Point p : glucose.model.speakers.get(channel.objectIndices[0]).points) { - colors[p.index] = debugColor; - } - } - break; - - case ChannelMapping.MODE_NULL: - break; - - default: - throw new RuntimeException("Unhandled channel mapping mode: " + channel.mode); - } - ++channelIndex; - } - } - - void mousePressed() { - int dx = (mouseX - debugX) / debugXSpacing; - int dy = (mouseY - debugY) / debugYSpacing; - if ((dy >= 0) && (dy < debugState.length)) { - if ((dx >= 0) && (dx < debugState[dy].length)) { - int newState = debugState[dy][dx] = (debugState[dy][dx] + 1) % 3; - if (dy == debugState.length-1) { - for (int[] states : debugState) { - for (int i = 0; i < states.length; ++i) { - states[i] = newState; - } - } - } else if (dx == 0) { - for (int i = 0; i < debugState[dy].length; ++i) { - debugState[dy][i] = newState; - } - } - } - } - } -} diff --git a/_PandaDriver.pde b/_PandaDriver.pde index a65a0fe..2d28cd6 100644 --- a/_PandaDriver.pde +++ b/_PandaDriver.pde @@ -15,6 +15,13 @@ import oscP5.*; * will be moved into GLucose once stabilized. */ public static class PandaDriver { + + interface Listener { + public void onToggle(boolean enabled); + } + + private Listener listener = null; + // IP address public final String ip; @@ -35,23 +42,6 @@ public static class PandaDriver { private static final int NO_POINT = -1; - public PandaDriver(String ip) { - this.ip = ip; - - // Initialize our OSC output stuff - address = new NetAddress(ip, 9001); - message = new OscMessage("/shady/pointbuffer"); - - // Build the array of points, initialize all to nothing - points = new int[PandaMapping.PIXELS_PER_BOARD]; - for (int i = 0; i < points.length; ++i) { - points[i] = NO_POINT; - } - } - - private final static int FORWARD = -1; - private final static int BACKWARD = -2; - //////////////////////////////////////////////////////////////// // // READ THIS RIGHT NOW BEFORE YOU MODIFY THE BELOW!!!!!!!!!!!!! @@ -85,6 +75,8 @@ public static class PandaDriver { // //////////////////////////////////////////////////////////////// + private final static int FORWARD = -1; + private final static int BACKWARD = -2; /** * These constant arrays indicate the order in which the strips of a cube @@ -164,7 +156,21 @@ public static class PandaDriver { {3, BACKWARD }, } }; - + + public PandaDriver(String ip) { + this.ip = ip; + + // Initialize our OSC output stuff + address = new NetAddress(ip, 9001); + message = new OscMessage("/shady/pointbuffer"); + + // Build the array of points, initialize all to nothing + points = new int[PandaMapping.PIXELS_PER_BOARD]; + for (int i = 0; i < points.length; ++i) { + points[i] = NO_POINT; + } + } + public PandaDriver(String ip, Model model, PandaMapping pm) { this(ip); @@ -271,23 +277,31 @@ public static class PandaDriver { return pi; } - public void disable() { - if (enabled) { - enabled = false; - println("PandaBoard/" + ip + ": OFF"); + public PandaDriver setListener(Listener listener) { + this.listener = listener; + return this; + } + + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + this.enabled = enabled; + println("PandaBoard/" + ip + ": " + (enabled ? "ON" : "OFF")); + if (listener != null) { + listener.onToggle(enabled); + } } } + + public void disable() { + setEnabled(false); + } public void enable() { - if (!enabled) { - enabled = true; - println("PandaBoard/" + ip + ": ON"); - } + setEnabled(true); } public void toggle() { - enabled = !enabled; - println("PandaBoard/" + ip + ": " + (enabled ? "ON" : "OFF")); + setEnabled(!enabled); } public final void send(int[] colors) { diff --git a/_UIFramework.pde b/_UIFramework.pde new file mode 100644 index 0000000..a2946fb --- /dev/null +++ b/_UIFramework.pde @@ -0,0 +1,825 @@ +/** + * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND + * + * //\\ //\\ //\\ //\\ + * ///\\\ ///\\\ ///\\\ ///\\\ + * \\\/// \\\/// \\\/// \\\/// + * \\// \\// \\// \\// + * + * EXPERTS ONLY!! EXPERTS ONLY!! + * + * Little UI framework in progress to handle mouse events, layout, + * redrawing, etc. + */ + +final color lightGreen = #669966; +final color lightBlue = #666699; +final color bgGray = #444444; +final color defaultTextColor = #999999; +final PFont defaultItemFont = createFont("Lucida Grande", 11); +final PFont defaultTitleFont = createFont("Myriad Pro", 10); + +public abstract class UIObject { + + protected final List children = new ArrayList(); + + protected boolean needsRedraw = true; + protected boolean childNeedsRedraw = true; + + protected float x=0, y=0, w=0, h=0; + + public UIContainer parent = null; + + protected boolean visible = true; + + public UIObject() {} + + public UIObject(float x, float y, float w, float h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + public boolean isVisible() { + return visible; + } + + public UIObject setVisible(boolean visible) { + if (visible != this.visible) { + this.visible = visible; + redraw(); + } + return this; + } + + public final UIObject setPosition(float x, float y) { + this.x = x; + this.y = y; + redraw(); + return this; + } + + public final UIObject setSize(float w, float h) { + this.w = w; + this.h = h; + redraw(); + return this; + } + + public final UIObject addToContainer(UIContainer c) { + c.children.add(this); + this.parent = c; + return this; + } + + public final UIObject removeFromContainer(UIContainer c) { + c.children.remove(this); + this.parent = null; + return this; + } + + public final UIObject redraw() { + _redraw(); + UIObject p = this.parent; + while (p != null) { + p.childNeedsRedraw = true; + p = p.parent; + } + return this; + } + + private final void _redraw() { + needsRedraw = true; + for (UIObject child : children) { + childNeedsRedraw = true; + child._redraw(); + } + } + + public final void draw(PGraphics pg) { + if (!visible) { + return; + } + if (needsRedraw) { + needsRedraw = false; + onDraw(pg); + } + if (childNeedsRedraw) { + childNeedsRedraw = false; + for (UIObject child : children) { + if (needsRedraw || child.needsRedraw || child.childNeedsRedraw) { + pg.pushMatrix(); + pg.translate(child.x, child.y); + child.draw(pg); + pg.popMatrix(); + } + } + } + } + + public final boolean contains(float x, float y) { + return + (x >= this.x && x < (this.x + this.w)) && + (y >= this.y && y < (this.y + this.h)); + } + + protected void onDraw(PGraphics pg) {} + protected void onMousePressed(float mx, float my) {} + protected void onMouseReleased(float mx, float my) {} + protected void onMouseDragged(float mx, float my, float dx, float dy) {} + protected void onMouseWheel(float mx, float my, float dx) {} +} + +public class UIContainer extends UIObject { + + private UIObject focusedChild = null; + + public UIContainer() {} + + public UIContainer(float x, float y, float w, float h) { + super(x, y, w, h); + } + + public UIContainer(UIObject[] children) { + for (UIObject child : children) { + child.addToContainer(this); + } + } + + protected void onMousePressed(float mx, float my) { + for (int i = children.size() - 1; i >= 0; --i) { + UIObject child = children.get(i); + if (child.contains(mx, my)) { + child.onMousePressed(mx - child.x, my - child.y); + focusedChild = child; + break; + } + } + } + + protected void onMouseReleased(float mx, float my) { + if (focusedChild != null) { + focusedChild.onMouseReleased(mx - focusedChild.x, my - focusedChild.y); + } + focusedChild = null; + } + + protected void onMouseDragged(float mx, float my, float dx, float dy) { + if (focusedChild != null) { + focusedChild.onMouseDragged(mx - focusedChild.x, my - focusedChild.y, dx, dy); + } + } + + protected void onMouseWheel(float mx, float my, float delta) { + for (UIObject child : children) { + if (child.contains(mx, my)) { + child.onMouseWheel(mx - child.x, mx - child.y, delta); + } + } + } + +} + +public class UIContext extends UIContainer { + + final public PGraphics pg; + + UIContext(float x, float y, float w, float h) { + super(x, y, w, h); + pg = createGraphics((int)w, (int)h, JAVA2D); + pg.smooth(); + } + + public void draw() { + if (!visible) { + return; + } + if (needsRedraw || childNeedsRedraw) { + pg.beginDraw(); + draw(pg); + pg.endDraw(); + } + image(pg, x, y); + } + + private float px, py; + private boolean dragging = false; + + public boolean mousePressed(float mx, float my) { + if (!visible) { + return false; + } + if (contains(mx, my)) { + dragging = true; + px = mx; + py = my; + onMousePressed(mx - x, my - y); + return true; + } + return false; + } + + public boolean mouseReleased(float mx, float my) { + if (!visible) { + return false; + } + dragging = false; + onMouseReleased(mx - x, my - y); + return true; + } + + public boolean mouseDragged(float mx, float my) { + if (!visible) { + return false; + } + if (dragging) { + float dx = mx - px; + float dy = my - py; + onMouseDragged(mx - x, my - y, dx, dy); + px = mx; + py = my; + return true; + } + return false; + } + + public boolean mouseWheel(float mx, float my, float delta) { + if (!visible) { + return false; + } + if (contains(mx, my)) { + onMouseWheel(mx - x, my - y, delta); + return true; + } + return false; + } +} + +public class UIWindow extends UIContext { + + protected final static int titleHeight = 24; + + public UIWindow(String label, float x, float y, float w, float h) { + super(x, y, w, h); + new UILabel(6, 8, w-6, titleHeight-8) { + protected void onMouseDragged(float mx, float my, float dx, float dy) { + parent.x = constrain(parent.x + dx, 0, width - w); + parent.y = constrain(parent.y + dy, 0, height - h); + } + }.setLabel(label).setFont(defaultTitleFont).addToContainer(this); + } + + protected void onDraw(PGraphics pg) { + pg.noStroke(); + pg.fill(#444444); + pg.stroke(#292929); + pg.rect(0, 0, w-1, h-1); + } +} + +public class UILabel extends UIObject { + + private PFont font = defaultTitleFont; + private color fontColor = #CCCCCC; + private String label = ""; + + public UILabel(float x, float y, float w, float h) { + super(x, y, w, h); + } + + protected void onDraw(PGraphics pg) { + pg.textAlign(LEFT, TOP); + pg.textFont(font); + pg.fill(fontColor); + pg.text(label, 0, 0); + } + + public UILabel setFont(PFont font) { + this.font = font; + redraw(); + return this; + } + + public UILabel setFontColor(color fontColor) { + this.fontColor = fontColor; + redraw(); + return this; + } + + public UILabel setLabel(String label) { + this.label = label; + redraw(); + return this; + } +} + +public class UIButton extends UIObject { + + private boolean active = false; + private boolean isMomentary = false; + private color borderColor = #666666; + private color inactiveColor = #222222; + private color activeColor = #669966; + private color labelColor = #999999; + private String label = ""; + + public UIButton(float x, float y, float w, float h) { + super(x, y, w, h); + } + + public UIButton setMomentary(boolean momentary) { + isMomentary = momentary; + return this; + } + + protected void onDraw(PGraphics pg) { + pg.stroke(borderColor); + pg.fill(active ? activeColor : inactiveColor); + pg.rect(0, 0, w, h); + if (label != null && label.length() > 0) { + pg.fill(active ? #FFFFFF : labelColor); + pg.textFont(defaultItemFont); + pg.textAlign(CENTER); + pg.text(label, w/2, h-5); + } + } + + protected void onMousePressed(float mx, float my) { + if (isMomentary) { + setActive(true); + } else { + setActive(!active); + } + } + + protected void onMouseReleased(float mx, float my) { + if (isMomentary) { + setActive(false); + } + } + + public UIButton setActive(boolean active) { + this.active = active; + onToggle(active); + redraw(); + return this; + } + + public UIButton toggle() { + return setActive(!active); + } + + protected void onToggle(boolean active) {} + + public UIButton setBorderColor(color borderColor) { + if (this.borderColor != borderColor) { + this.borderColor = borderColor; + redraw(); + } + return this; + } + + public UIButton setActiveColor(color activeColor) { + if (this.activeColor != activeColor) { + this.activeColor = activeColor; + if (active) { + redraw(); + } + } + return this; + } + + public UIButton setInactiveColor(color inactiveColor) { + if (this.inactiveColor != inactiveColor) { + this.inactiveColor = inactiveColor; + if (!active) { + redraw(); + } + } + return this; + } + + public UIButton setLabelColor(color labelColor) { + if (this.labelColor != labelColor) { + this.labelColor = labelColor; + redraw(); + } + return this; + } + + public UIButton setLabel(String label) { + if (!this.label.equals(label)) { + this.label = label; + redraw(); + } + return this; + } + + public void onMousePressed() { + setActive(!active); + } +} + +public class UIToggleSet extends UIObject { + + private String[] options; + private int[] boundaries; + private String value; + + public UIToggleSet(float x, float y, float w, float h) { + super(x, y, w, h); + } + + public UIToggleSet setOptions(String[] options) { + this.options = options; + boundaries = new int[options.length]; + int totalLength = 0; + for (String s : options) { + totalLength += s.length(); + } + int lengthSoFar = 0; + for (int i = 0; i < options.length; ++i) { + lengthSoFar += options[i].length(); + boundaries[i] = (int) (lengthSoFar * w / totalLength); + } + value = options[0]; + redraw(); + return this; + } + + public String getValue() { + return value; + } + + public UIToggleSet setValue(String option) { + value = option; + onToggle(value); + redraw(); + return this; + } + + public void onDraw(PGraphics pg) { + pg.stroke(#666666); + pg.fill(#222222); + pg.rect(0, 0, w, h); + for (int b : boundaries) { + pg.line(b, 1, b, h-1); + } + pg.noStroke(); + pg.textAlign(CENTER); + pg.textFont(defaultItemFont); + int leftBoundary = 0; + + for (int i = 0; i < options.length; ++i) { + boolean isActive = options[i] == value; + if (isActive) { + pg.fill(lightGreen); + pg.rect(leftBoundary + 1, 1, boundaries[i] - leftBoundary - 1, h-1); + } + pg.fill(isActive ? #FFFFFF : #999999); + pg.text(options[i], (leftBoundary + boundaries[i]) / 2., h-6); + leftBoundary = boundaries[i]; + } + } + + public void onMousePressed(float mx, float my) { + for (int i = 0; i < boundaries.length; ++i) { + if (mx < boundaries[i]) { + setValue(options[i]); + break; + } + } + } + + protected void onToggle(String option) {} + +} + + +public abstract class UIParameterControl extends UIObject implements LXParameter.Listener { + protected LXParameter parameter = null; + + protected UIParameterControl(float x, float y, float w, float h) { + super(x, y, w, h); + } + + public void onParameterChanged(LXParameter parameter) { + redraw(); + } + + public UIParameterControl setParameter(LXParameter parameter) { + if (this.parameter != null) { + if (this.parameter instanceof LXListenableParameter) { + ((LXListenableParameter)this.parameter).removeListener(this); + } + } + this.parameter = parameter; + if (this.parameter != null) { + if (this.parameter instanceof LXListenableParameter) { + ((LXListenableParameter)this.parameter).addListener(this); + } + } + redraw(); + return this; + } +} + +public class UIParameterKnob extends UIParameterControl { + private int knobSize = 28; + private final float knobIndent = .4; + private final int knobLabelHeight = 14; + + public UIParameterKnob(float x, float y) { + this(x, y, 0, 0); + setSize(knobSize, knobSize + knobLabelHeight); + } + + public UIParameterKnob(float x, float y, float w, float h) { + super(x, y, w, h); + } + + protected void onDraw(PGraphics pg) { + float knobValue = (parameter != null) ? parameter.getValuef() : 0; + + pg.ellipseMode(CENTER); + pg.noStroke(); + + pg.fill(bgGray); + pg.rect(0, 0, knobSize, knobSize); + + // Full outer dark ring + pg.fill(#222222); + pg.arc(knobSize/2, knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent)); + + // Light ring indicating value + pg.fill(lightGreen); + pg.arc(knobSize/2, knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + knobValue*(TWO_PI-2*knobIndent)); + + // Center circle of knob + pg.fill(#333333); + pg.ellipse(knobSize/2, knobSize/2, knobSize/2, knobSize/2); + + String knobLabel = (parameter != null) ? parameter.getLabel() : null; + if (knobLabel == null) { + knobLabel = "-"; + } else if (knobLabel.length() > 4) { + knobLabel = knobLabel.substring(0, 4); + } + pg.fill(#000000); + pg.rect(0, knobSize + 2, knobSize, knobLabelHeight - 2); + pg.fill(#999999); + pg.textAlign(CENTER); + pg.textFont(defaultTitleFont); + pg.text(knobLabel, knobSize/2, knobSize + knobLabelHeight - 2); + } + + public void onMouseDragged(float mx, float my, float dx, float dy) { + if (parameter != null) { + float value = constrain(parameter.getValuef() - dy / 100., 0, 1); + parameter.setValue(value); + } + } +} + +public class UIParameterSlider extends UIParameterControl { + + private static final float handleWidth = 12; + + UIParameterSlider(float x, float y, float w, float h) { + super(x, y, w, h); + } + + protected void onDraw(PGraphics pg) { + pg.noStroke(); + pg.fill(#333333); + pg.rect(0, 0, w, h); + pg.fill(#222222); + pg.rect(4, h/2-2, w-8, 4); + pg.fill(#666666); + pg.stroke(#222222); + pg.rect((int) (4 + parameter.getValuef() * (w-8-handleWidth)), 4, handleWidth, h-8); + } + + private boolean editing = false; + protected void onMousePressed(float mx, float my) { + float handleLeft = 4 + parameter.getValuef() * (w-8-handleWidth); + if (mx >= handleLeft && mx < handleLeft + handleWidth) { + editing = true; + } + } + + protected void onMouseReleased(float mx, float my) { + editing = false; + } + + protected void onMouseDragged(float mx, float my, float dx, float dy) { + if (editing) { + parameter.setValue(constrain((mx - handleWidth/2. - 4) / (w-8-handleWidth), 0, 1)); + } + } +} + +public class UIScrollList extends UIObject { + + private List items = new ArrayList(); + + private PFont itemFont = defaultItemFont; + private int itemHeight = 20; + private color selectedColor = lightGreen; + private color pendingColor = lightBlue; + private int scrollOffset = 0; + private int numVisibleItems = 0; + + private boolean hasScroll; + private float scrollYStart; + private float scrollYHeight; + + public UIScrollList(float x, float y, float w, float h) { + super(x, y, w, h); + } + + protected void onDraw(PGraphics pg) { + int yp = 0; + boolean even = true; + for (int i = 0; i < numVisibleItems; ++i) { + if (i + scrollOffset >= items.size()) { + break; + } + ScrollItem item = items.get(i + scrollOffset); + color itemColor; + color labelColor = #FFFFFF; + if (item.isSelected()) { + itemColor = selectedColor; + } else if (item.isPending()) { + itemColor = pendingColor; + } else { + labelColor = #000000; + itemColor = even ? #666666 : #777777; + } + pg.noStroke(); + pg.fill(itemColor); + pg.rect(0, yp, w, itemHeight); + pg.fill(labelColor); + pg.textFont(itemFont); + pg.textAlign(LEFT, TOP); + pg.text(item.getLabel(), 6, yp+4); + + yp += itemHeight; + even = !even; + } + if (hasScroll) { + pg.noStroke(); + pg.fill(color(0, 0, 100, 15)); + pg.rect(w-12, 0, 12, h); + pg.fill(#333333); + pg.rect(w-12, scrollYStart, 12, scrollYHeight); + } + + } + + private boolean scrolling = false; + private ScrollItem pressedItem = null; + + public void onMousePressed(float mx, float my) { + pressedItem = null; + if (hasScroll && mx >= w-12) { + if (my >= scrollYStart && my < (scrollYStart + scrollYHeight)) { + scrolling = true; + dAccum = 0; + } + } else { + int index = (int) my / itemHeight; + if (scrollOffset + index < items.size()) { + pressedItem = items.get(scrollOffset + index); + pressedItem.onMousePressed(); + pressedItem.select(); + redraw(); + } + } + } + + public void onMouseReleased(float mx, float my) { + scrolling = false; + if (pressedItem != null) { + pressedItem.onMouseReleased(); + redraw(); + } + } + + private float dAccum = 0; + public void onMouseDragged(float mx, float my, float dx, float dy) { + if (scrolling) { + dAccum += dy; + float scrollOne = h / items.size(); + int offset = (int) (dAccum / scrollOne); + if (offset != 0) { + dAccum -= offset * scrollOne; + setScrollOffset(scrollOffset + offset); + } + } + } + + private float wAccum = 0; + public void onMouseWheel(float mx, float my, float delta) { + wAccum += delta; + int offset = (int) (wAccum / 5); + if (offset != 0) { + wAccum -= offset * 5; + setScrollOffset(scrollOffset + offset); + } + } + + public void setScrollOffset(int offset) { + scrollOffset = constrain(offset, 0, items.size() - numVisibleItems); + scrollYStart = (int) (scrollOffset * h / items.size()); + scrollYHeight = (int) (numVisibleItems * h / (float) items.size()); + redraw(); + } + + public UIScrollList setItems(List items) { + this.items = items; + numVisibleItems = (int) (h / itemHeight); + hasScroll = items.size() > numVisibleItems; + setScrollOffset(0); + redraw(); + return this; + } +} + +public interface ScrollItem { + public boolean isSelected(); + public boolean isPending(); + public String getLabel(); + public void select(); + public void onMousePressed(); + public void onMouseReleased(); +} + +public abstract class AbstractScrollItem implements ScrollItem { + public boolean isPending() { + return false; + } + public void select() {} + public void onMousePressed() {} + public void onMouseReleased() {} +} + +public class UIIntegerBox extends UIObject { + + private int minValue = 0; + private int maxValue = MAX_INT; + private int value = 0; + + UIIntegerBox(float x, float y, float w, float h) { + super(x, y, w, h); + } + + public UIIntegerBox setRange(int minValue, int maxValue) { + this.minValue = minValue; + this.maxValue = maxValue; + setValue(constrain(value, minValue, maxValue)); + return this; + } + + protected void onDraw(PGraphics pg) { + pg.stroke(#999999); + pg.fill(#222222); + pg.rect(0, 0, w, h); + pg.textAlign(CENTER, CENTER); + pg.textFont(defaultItemFont); + pg.fill(#999999); + pg.text("" + value, w/2, h/2); + } + + protected void onValueChange(int value) {} + + float dAccum = 0; + protected void onMousePressed(float mx, float my) { + dAccum = 0; + } + + protected void onMouseDragged(float mx, float my, float dx, float dy) { + dAccum -= dy; + int offset = (int) (dAccum / 5); + dAccum = dAccum - (offset * 5); + setValue(value + offset); + } + + public int getValue() { + return value; + } + + public UIIntegerBox setValue(int value) { + if (this.value != value) { + int range = (maxValue - minValue + 1); + while (value < minValue) { + value += range; + } + this.value = minValue + (value - minValue) % range; + this.onValueChange(this.value); + redraw(); + } + return this; + } +} diff --git a/_UIImplementation.pde b/_UIImplementation.pde new file mode 100644 index 0000000..c8b9930 --- /dev/null +++ b/_UIImplementation.pde @@ -0,0 +1,450 @@ +/** + * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND + * + * //\\ //\\ //\\ //\\ + * ///\\\ ///\\\ ///\\\ ///\\\ + * \\\/// \\\/// \\\/// \\\/// + * \\// \\// \\// \\// + * + * EXPERTS ONLY!! EXPERTS ONLY!! + * + * Custom UI components using the framework. + */ + +class UIPatternDeck extends UIWindow { + + Engine.Deck deck; + + public UIPatternDeck(Engine.Deck deck, String label, float x, float y, float w, float h) { + super(label, x, y, w, h); + this.deck = deck; + int yp = titleHeight; + + List items = new ArrayList(); + for (LXPattern p : deck.getPatterns()) { + items.add(new PatternScrollItem(p)); + } + final UIScrollList patternList = new UIScrollList(1, yp, w-2, 160).setItems(items); + patternList.addToContainer(this); + yp += patternList.h + 10; + + final UIParameterKnob[] parameterKnobs = new UIParameterKnob[12]; + for (int ki = 0; ki < parameterKnobs.length; ++ki) { + parameterKnobs[ki] = new UIParameterKnob(5 + 34*(ki % 4), yp + (ki/4) * 48); + parameterKnobs[ki].addToContainer(this); + } + + Engine.Listener lxListener = new Engine.Listener() { + public void patternWillChange(Engine.Deck deck, LXPattern pattern, LXPattern nextPattern) { + patternList.redraw(); + } + public void patternDidChange(Engine.Deck deck, LXPattern pattern) { + patternList.redraw(); + int pi = 0; + for (LXParameter parameter : pattern.getParameters()) { + if (pi >= parameterKnobs.length) { + break; + } + parameterKnobs[pi++].setParameter(parameter); + } + while (pi < parameterKnobs.length) { + parameterKnobs[pi++].setParameter(null); + } + } + }; + + deck.addListener(lxListener); + lxListener.patternDidChange(deck, deck.getActivePattern()); + + } + + class PatternScrollItem extends AbstractScrollItem { + + private LXPattern pattern; + private String label; + + PatternScrollItem(LXPattern pattern) { + this.pattern = pattern; + label = className(pattern, "Pattern"); + } + + public String getLabel() { + return label; + } + + public boolean isSelected() { + return deck.getActivePattern() == pattern; + } + + public boolean isPending() { + return deck.getNextPattern() == pattern; + } + + public void select() { + deck.goPattern(pattern); + } + } +} + +class UICrossfader extends UIWindow { + + public UICrossfader(float x, float y, float w, float h) { + super("CROSSFADER", x, y, w, h); + + List items = new ArrayList(); + for (LXTransition t : transitions) { + items.add(new TransitionScrollItem(t)); + } + new UIScrollList(1, titleHeight, w-2, 60).setItems(items).addToContainer(this); + new UIParameterSlider(6, titleHeight + 66, w-12, 24).setParameter(lx.engine.getDeck(1).getCrossfader()).addToContainer(this); + new UIToggleSet(6, 122, w-12, 20) { + protected void onToggle(String value) { + displayMode = value; + } + }.setOptions(new String[] { "A", "COMP", "B" }).setValue(displayMode = "COMP").addToContainer(this); + } +} + +class TransitionScrollItem extends AbstractScrollItem { + private final LXTransition transition; + private String label; + + TransitionScrollItem(LXTransition transition) { + this.transition = transition; + label = className(transition, "Transition"); + } + + public String getLabel() { + return label; + } + + public boolean isSelected() { + return transition == lx.engine.getDeck(1).getBlendTransition(); + } + + public boolean isPending() { + return false; + } + + public void select() { + lx.engine.getDeck(1).setBlendTransition(transition); + } +} + +class UIEffects extends UIWindow { + UIEffects(float x, float y, float w, float h) { + super("FX", x, y, w, h); + + int yp = titleHeight; + List items = new ArrayList(); + for (LXEffect fx : glucose.lx.getEffects()) { + items.add(new FXScrollItem(fx)); + } + final UIScrollList effectsList = new UIScrollList(1, yp, w-2, 60).setItems(items); + effectsList.addToContainer(this); + yp += effectsList.h + 10; + + final UIParameterKnob[] parameterKnobs = new UIParameterKnob[4]; + for (int ki = 0; ki < parameterKnobs.length; ++ki) { + parameterKnobs[ki] = new UIParameterKnob(5 + 34*(ki % 4), yp + (ki/4) * 48); + parameterKnobs[ki].addToContainer(this); + } + + GLucose.EffectListener fxListener = new GLucose.EffectListener() { + public void effectSelected(LXEffect effect) { + int i = 0; + for (LXParameter p : effect.getParameters()) { + if (i >= parameterKnobs.length) { + break; + } + parameterKnobs[i++].setParameter(p); + } + while (i < parameterKnobs.length) { + parameterKnobs[i++].setParameter(null); + } + } + }; + + glucose.addEffectListener(fxListener); + fxListener.effectSelected(glucose.getSelectedEffect()); + + } + + class FXScrollItem extends AbstractScrollItem { + + private LXEffect effect; + private String label; + + FXScrollItem(LXEffect effect) { + this.effect = effect; + label = className(effect, "Effect"); + } + + public String getLabel() { + return label; + } + + public boolean isSelected() { + return !effect.isEnabled() && (glucose.getSelectedEffect() == effect); + } + + public boolean isPending() { + return effect.isEnabled(); + } + + public void select() { + glucose.setSelectedEffect(effect); + } + + public void onMousePressed() { + if (glucose.getSelectedEffect() == effect) { + if (effect.isMomentary()) { + effect.enable(); + } else { + effect.toggle(); + } + } + } + + public void onMouseReleased() { + if (effect.isMomentary()) { + effect.disable(); + } + } + + } + +} + +class UIOutput extends UIWindow { + public UIOutput(float x, float y, float w, float h) { + super("OUTPUT", x, y, w, h); + float yp = titleHeight; + for (final PandaDriver panda : pandaBoards) { + final UIButton button = new UIButton(4, yp, w-8, 20) { + protected void onToggle(boolean active) { + panda.setEnabled(active); + } + }.setLabel(panda.ip); + button.addToContainer(this); + panda.setListener(new PandaDriver.Listener() { + public void onToggle(boolean active) { + button.setActive(active); + } + }); + yp += 24; + } + } +} + +class UITempo extends UIWindow { + + private final UIButton tempoButton; + + UITempo(float x, float y, float w, float h) { + super("TEMPO", x, y, w, h); + tempoButton = new UIButton(4, titleHeight, w-8, 20) { + protected void onToggle(boolean active) { + if (active) { + lx.tempo.tap(); + } + } + }.setMomentary(true); + tempoButton.addToContainer(this); + } + + public void draw() { + tempoButton.setLabel("" + ((int)(lx.tempo.bpm() * 10)) / 10.); + super.draw(); + + // Overlay tempo thing with openGL, redraw faster than button UI + fill(color(0, 0, 24 - 8*lx.tempo.rampf())); + noStroke(); + rect(x + 8, y + titleHeight + 5, 12, 12); + } +} + +class UIMapping extends UIWindow { + + private static final String MAP_MODE_ALL = "ALL"; + private static final String MAP_MODE_CHANNEL = "CHNL"; + private static final String MAP_MODE_CUBE = "CUBE"; + + private static final String CUBE_MODE_ALL = "ALL"; + private static final String CUBE_MODE_STRIP = "SNGL"; + private static final String CUBE_MODE_PATTERN = "PTRN"; + + private final MappingTool mappingTool; + + private final UIIntegerBox channelBox; + private final UIIntegerBox cubeBox; + private final UIIntegerBox stripBox; + + UIMapping(MappingTool tool, float x, float y, float w, float h) { + super("MAPPING", x, y, w, h); + mappingTool = tool; + + int yp = titleHeight; + new UIToggleSet(4, yp, w-8, 20) { + protected void onToggle(String value) { + if (value == MAP_MODE_ALL) mappingTool.mappingMode = mappingTool.MAPPING_MODE_ALL; + else if (value == MAP_MODE_CHANNEL) mappingTool.mappingMode = mappingTool.MAPPING_MODE_CHANNEL; + else if (value == MAP_MODE_CUBE) mappingTool.mappingMode = mappingTool.MAPPING_MODE_SINGLE_CUBE; + } + }.setOptions(new String[] { MAP_MODE_ALL, MAP_MODE_CHANNEL, MAP_MODE_CUBE }).addToContainer(this); + yp += 24; + new UILabel(4, yp+8, w-8, 20).setLabel("CHANNEL ID").addToContainer(this); + yp += 24; + (channelBox = new UIIntegerBox(4, yp, w-8, 20) { + protected void onValueChange(int value) { + mappingTool.setChannel(value-1); + } + }).setRange(1, mappingTool.numChannels()).addToContainer(this); + yp += 24; + + new UILabel(4, yp+8, w-8, 20).setLabel("CUBE ID").addToContainer(this); + yp += 24; + (cubeBox = new UIIntegerBox(4, yp, w-8, 20) { + protected void onValueChange(int value) { + mappingTool.setCube(value-1); + } + }).setRange(1, glucose.model.cubes.size()).addToContainer(this); + yp += 24; + + new UILabel(4, yp+8, w-8, 20).setLabel("COLORS").addToContainer(this); + yp += 24; + + new UIScrollList(1, yp, w-2, 60).setItems(Arrays.asList(new ScrollItem[] { + new ColorScrollItem(ColorScrollItem.COLOR_RED), + new ColorScrollItem(ColorScrollItem.COLOR_GREEN), + new ColorScrollItem(ColorScrollItem.COLOR_BLUE), + })).addToContainer(this); + yp += 64; + + new UILabel(4, yp+8, w-8, 20).setLabel("STRIP MODE").addToContainer(this); + yp += 24; + + new UIToggleSet(4, yp, w-8, 20) { + protected void onToggle(String value) { + if (value == CUBE_MODE_ALL) mappingTool.cubeMode = mappingTool.CUBE_MODE_ALL; + else if (value == CUBE_MODE_STRIP) mappingTool.cubeMode = mappingTool.CUBE_MODE_SINGLE_STRIP; + else if (value == CUBE_MODE_PATTERN) mappingTool.cubeMode = mappingTool.CUBE_MODE_STRIP_PATTERN; + } + }.setOptions(new String[] { CUBE_MODE_ALL, CUBE_MODE_STRIP, CUBE_MODE_PATTERN }).addToContainer(this); + + yp += 24; + new UILabel(4, yp+8, w-8, 20).setLabel("STRIP ID").addToContainer(this); + + yp += 24; + (stripBox = new UIIntegerBox(4, yp, w-8, 20) { + protected void onValueChange(int value) { + mappingTool.setStrip(value-1); + } + }).setRange(1, Cube.STRIPS_PER_CUBE).addToContainer(this); + + } + + public void setChannelID(int value) { + channelBox.setValue(value); + } + + public void setCubeID(int value) { + cubeBox.setValue(value); + } + + public void setStripID(int value) { + stripBox.setValue(value); + } + + class ColorScrollItem extends AbstractScrollItem { + + public static final int COLOR_RED = 1; + public static final int COLOR_GREEN = 2; + public static final int COLOR_BLUE = 3; + + private final int colorChannel; + + ColorScrollItem(int colorChannel) { + this.colorChannel = colorChannel; + } + + public String getLabel() { + switch (colorChannel) { + case COLOR_RED: return "Red"; + case COLOR_GREEN: return "Green"; + case COLOR_BLUE: return "Blue"; + } + return ""; + } + + public boolean isSelected() { + switch (colorChannel) { + case COLOR_RED: return mappingTool.channelModeRed; + case COLOR_GREEN: return mappingTool.channelModeGreen; + case COLOR_BLUE: return mappingTool.channelModeBlue; + } + return false; + } + + public void select() { + switch (colorChannel) { + case COLOR_RED: mappingTool.channelModeRed = !mappingTool.channelModeRed; break; + case COLOR_GREEN: mappingTool.channelModeGreen = !mappingTool.channelModeGreen; break; + case COLOR_BLUE: mappingTool.channelModeBlue = !mappingTool.channelModeBlue; break; + } + } + } +} + +class UIDebugText extends UIContext { + + private String line1 = ""; + private String line2 = ""; + + UIDebugText(float x, float y, float w, float h) { + super(x, y, w, h); + } + + public UIDebugText setText(String line1) { + return setText(line1, ""); + } + + public UIDebugText setText(String line1, String line2) { + if (!line1.equals(this.line1) || !line2.equals(this.line2)) { + this.line1 = line1; + this.line2 = line2; + setVisible(line1.length() + line2.length() > 0); + redraw(); + } + return this; + } + + protected void onDraw(PGraphics pg) { + super.onDraw(pg); + if (line1.length() + line2.length() > 0) { + pg.noStroke(); + pg.fill(#444444); + pg.rect(0, 0, w, h); + pg.textFont(defaultItemFont); + pg.textAlign(LEFT, TOP); + pg.fill(#cccccc); + pg.text(line1, 4, 4); + pg.text(line2, 4, 24); + } + } +} + +String className(Object p, String suffix) { + String s = p.getClass().getName(); + int li; + if ((li = s.lastIndexOf(".")) > 0) { + s = s.substring(li + 1); + } + if (s.indexOf("SugarCubes$") == 0) { + s = s.substring("SugarCubes$".length()); + } + if ((suffix != null) && ((li = s.indexOf(suffix)) != -1)) { + s = s.substring(0, li); + } + return s; +} diff --git a/code/GLucose.jar b/code/GLucose.jar index c36c6e9..6d09722 100755 Binary files a/code/GLucose.jar and b/code/GLucose.jar differ diff --git a/code/HeronLX.jar b/code/HeronLX.jar index 0445f9c..145f480 100755 Binary files a/code/HeronLX.jar and b/code/HeronLX.jar differ