/** * spxlSuperKnob * 2010-02-27 by subpixel * http://subpixels.com * * Inspired by code by bmud posted at * http://processing.org/discourse/yabb2/YaBB.pl?num=1235508056/3#3 */ final int sketchBackgroundColor = 0xffc8c8c8; // Pale grey final int gridCols = 2; final int gridRows = 2; final int gridSizeX = 100; final int gridSizeY = 100; final int knobFontSize = 12; ArrayList knobs; Knob adjustingKnob; // Only set if mouse pressed near the knob void setup() { size(gridCols * gridSizeX, gridRows * gridSizeY); smooth(); ellipseMode(RADIUS); textFont(createFont("", knobFontSize)); textAlign(CENTER, CENTER); int offX = gridSizeX >> 1; int offY = gridSizeY >> 1; // Create a grid of SuperKnobs with references stored in the knobs ArrayList knobs = new ArrayList(gridCols * gridRows); for (int col = 0; col < gridCols; col++) { for (int row = 0; row < gridRows; row++) { SuperKnob knob = new SuperKnob(col * gridSizeX + offX, row * gridSizeY + offY); knob.maxRevs = row * gridCols + col + 1; knob.setAngle(knob.maxRevs * random(-TWO_PI, TWO_PI)); knobs.add(knob); } } } void draw() { background(sketchBackgroundColor); for (int i = 0; i < knobs.size(); i++) ((Knob)knobs.get(i)).draw(); } void mousePressed() { for (int i = 0; i < knobs.size(); i++) { Knob knob = (Knob) knobs.get(i); if (knob.hit(mouseX, mouseY)) { adjustingKnob = knob; adjustingKnob.initAdjust(mouseX, mouseY); break; } } } void mouseDragged() { if (adjustingKnob != null) adjustingKnob.adjust(mouseX, mouseY, pmouseX, pmouseY); } void mouseReleased() { if (adjustingKnob != null) { adjustingKnob.endAdjust(mouseX, mouseY); adjustingKnob = null; } } abstract class Knob { public PVector knobPos; public float knobSize = 20; float angle; // Current angle public abstract void draw(); public abstract boolean hit(int x, int y); public abstract void initAdjust(int mx, int my); public abstract void adjust(int mx, int my, int px, int py); public abstract void endAdjust(int mx, int my); public abstract void setAngle(float angle); public abstract float value(); } class SuperKnob extends Knob { public int maxRevs = 5; private float adjustAngle = angle; // Angle attempting to be set by mouse movement boolean lock = false; // Set true when at a grabby point (or max or min angle) int knobBodyColor = 0xffffffff; // White int knobOutlineColor = 0xff000000; // Black int knobMarkerColor = 0xffcc9900; // Orange int knobMarkerLockColor = 0xffff0000; // Red int cuffColorNeg = 0xff00aaff; // Blue-cyan int cuffColorPos = 0xffffaa00; // Orange int cuffColorZero = 0xff336633; // Dark green int cuffOutlineColor = 0xff000000; // Black int cuffDivsColor = 0xff333333; // Dark grey int revsMarkerColor = 0xff000000; // Black int revsMarkerHilightColor = 0xffffffff; // White int revsMarkerLockColor = 0xffffff00; // Yellow int textColor = 0xff000000; // Black public SuperKnob(float posx, float posy) { knobPos = new PVector(posx, posy); setAngle(0); } public void draw() { pushStyle(); pushMatrix(); fill(textColor); text(nfs(value(), 0, 3), knobPos.x, knobPos.y + knobSize + knobFontSize); translate(knobPos.x, knobPos.y); rotate(-PI/2); // Make "up" the zero angle (like "12 oclock") float maxAngle = maxRevs * TWO_PI; float cuffSize = knobSize * 1.3; float cuffAngle = TWO_PI / 3.0f; // Fill the cuff based on revolutions away from zero int maxColor = angle < 0 ? cuffColorNeg : cuffColorPos; fill(lerpColor(cuffColorZero, maxColor, abs(angle) / maxAngle)); stroke(cuffOutlineColor); strokeWeight(2); arc(0, 0, cuffSize, cuffSize, TWO_PI - cuffAngle, TWO_PI + cuffAngle); float a, ca, sa, r1, r2; // angle, cos, sin, inner/outer radius for (int i = 0; i <= maxRevs; i++) { a = (float) i / maxRevs * cuffAngle; ca = cos(a); sa = sin(a); r1 = (i == maxRevs) ? knobSize : cuffSize + 5; r2 = cuffSize + 9; line(r1 * ca, r1 * sa, r2 * ca, r2 * sa); line(r1 * ca, -r1 * sa, r2 * ca, -r2 * sa); } // Revs marker a = angle / maxAngle * cuffAngle; ca = cos(a); sa = sin(a); float x1 = knobSize * ca; float y1 = knobSize * sa; float x2 = cuffSize * ca; float y2 = cuffSize * sa; // Revs marker outline stroke(revsMarkerColor); strokeWeight(9); line(x1, y1, x2, y2); // Revs marker hilight if (lock) stroke(revsMarkerLockColor); else stroke(revsMarkerHilightColor); strokeWeight(3); line(x1, y1, x2, y2); // Knob body fill(knobBodyColor); stroke(knobOutlineColor); strokeWeight(4); ellipse(0, 0, knobSize, knobSize); // Knob angle marker ca = cos(angle); sa = sin(angle); r1 = knobSize * 0.35; r2 = knobSize * 0.85; if (lock) stroke(knobMarkerLockColor); else stroke(knobMarkerColor); strokeWeight(4); line(r1 * ca, r1 * sa, r2 * ca, r2 * sa); popMatrix(); popStyle(); } public boolean hit(int x, int y) { return dist(x, y, knobPos.x, knobPos.y) <= knobSize * 1.5 + 15; } public void initAdjust(int mx, int my) { adjustAngle = angle; knobSize += 10; } public void adjust(int mx, int my, int px, int py) { float thisAngle = atan2(my - knobPos.y, mx - knobPos.x); float prevAngle = atan2(py - knobPos.y, px - knobPos.x); float diff = thisAngle - prevAngle; if (diff >= PI) diff -= TWO_PI; else if (diff <= -PI) diff += TWO_PI; adjustAngle += diff; setAngle(adjustAngle); } public void endAdjust(int mx, int my) { knobSize -= 10; } public void setAngle(float setAngle) { float absAngle = abs(setAngle); int sign = setAngle < 0 ? -1 : 1; int revs = round(absAngle / TWO_PI); float off = absAngle - revs * TWO_PI; if (abs(off) < 0.06) { // Note: Maybe really only want to do this // when absAngle is near zero, not on other revolutions angle = sign * revs * TWO_PI; lock = true; } else { angle = setAngle; lock = false; } // Make sure we don't rotate past the max rotations float maxAngle = maxRevs * TWO_PI; if (angle < -maxAngle) { angle = -maxAngle; lock = true; } else if (angle > maxAngle) { angle = maxAngle; lock = true; } } public float value() { return angle / TWO_PI; } }