2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
9 * EXPERTS ONLY!! EXPERTS ONLY!!
11 * Little UI framework in progress to handle mouse events, layout,
15 final color lightGreen = #669966;
16 final color lightBlue = #666699;
17 final color bgGray = #444444;
18 final color defaultTextColor = #999999;
19 final PFont defaultItemFont = createFont("Lucida Grande", 11);
20 final PFont defaultTitleFont = createFont("Myriad Pro", 10);
22 public abstract class UIObject {
24 protected final List<UIObject> children = new ArrayList<UIObject>();
26 protected boolean needsRedraw = true;
27 protected boolean childNeedsRedraw = true;
29 protected float x=0, y=0, w=0, h=0;
31 public UIContainer parent = null;
33 protected boolean visible = true;
37 public UIObject(float x, float y, float w, float h) {
44 public boolean isVisible() {
48 public UIObject setVisible(boolean visible) {
49 if (visible != this.visible) {
50 this.visible = visible;
56 public final UIObject setPosition(float x, float y) {
63 public final UIObject setSize(float w, float h) {
70 public final UIObject addToContainer(UIContainer c) {
76 public final UIObject removeFromContainer(UIContainer c) {
77 c.children.remove(this);
82 public final UIObject redraw() {
84 UIObject p = this.parent;
86 p.childNeedsRedraw = true;
92 private final void _redraw() {
94 for (UIObject child : children) {
95 childNeedsRedraw = true;
100 public final void draw(PGraphics pg) {
108 if (childNeedsRedraw) {
109 childNeedsRedraw = false;
110 for (UIObject child : children) {
111 if (needsRedraw || child.needsRedraw || child.childNeedsRedraw) {
113 pg.translate(child.x, child.y);
121 public final boolean contains(float x, float y) {
123 (x >= this.x && x < (this.x + this.w)) &&
124 (y >= this.y && y < (this.y + this.h));
127 protected void onDraw(PGraphics pg) {}
128 protected void onMousePressed(float mx, float my) {}
129 protected void onMouseReleased(float mx, float my) {}
130 protected void onMouseDragged(float mx, float my, float dx, float dy) {}
131 protected void onMouseWheel(float mx, float my, float dx) {}
134 public class UIContainer extends UIObject {
136 private UIObject focusedChild = null;
138 public UIContainer() {}
140 public UIContainer(float x, float y, float w, float h) {
144 public UIContainer(UIObject[] children) {
145 for (UIObject child : children) {
146 child.addToContainer(this);
150 protected void onMousePressed(float mx, float my) {
151 for (int i = children.size() - 1; i >= 0; --i) {
152 UIObject child = children.get(i);
153 if (child.contains(mx, my)) {
154 child.onMousePressed(mx - child.x, my - child.y);
155 focusedChild = child;
161 protected void onMouseReleased(float mx, float my) {
162 if (focusedChild != null) {
163 focusedChild.onMouseReleased(mx - focusedChild.x, my - focusedChild.y);
168 protected void onMouseDragged(float mx, float my, float dx, float dy) {
169 if (focusedChild != null) {
170 focusedChild.onMouseDragged(mx - focusedChild.x, my - focusedChild.y, dx, dy);
174 protected void onMouseWheel(float mx, float my, float delta) {
175 for (UIObject child : children) {
176 if (child.contains(mx, my)) {
177 child.onMouseWheel(mx - child.x, mx - child.y, delta);
184 public class UIContext extends UIContainer {
186 final public PGraphics pg;
188 UIContext(float x, float y, float w, float h) {
190 pg = createGraphics((int)w, (int)h, JAVA2D);
198 if (needsRedraw || childNeedsRedraw) {
206 private float px, py;
207 private boolean dragging = false;
209 public boolean mousePressed(float mx, float my) {
213 if (contains(mx, my)) {
217 onMousePressed(mx - x, my - y);
223 public boolean mouseReleased(float mx, float my) {
228 onMouseReleased(mx - x, my - y);
232 public boolean mouseDragged(float mx, float my) {
239 onMouseDragged(mx - x, my - y, dx, dy);
247 public boolean mouseWheel(float mx, float my, float delta) {
251 if (contains(mx, my)) {
252 onMouseWheel(mx - x, my - y, delta);
259 public class UIWindow extends UIContext {
261 protected final static int titleHeight = 24;
263 public UIWindow(String label, float x, float y, float w, float h) {
265 new UILabel(6, 8, w-6, titleHeight-8) {
266 protected void onMouseDragged(float mx, float my, float dx, float dy) {
267 parent.x = constrain(parent.x + dx, 0, width - w);
268 parent.y = constrain(parent.y + dy, 0, height - h);
270 }.setLabel(label).setFont(defaultTitleFont).addToContainer(this);
273 protected void onDraw(PGraphics pg) {
277 pg.rect(0, 0, w-1, h-1);
281 public class UILabel extends UIObject {
283 private PFont font = defaultTitleFont;
284 private color fontColor = #CCCCCC;
285 private String label = "";
287 public UILabel(float x, float y, float w, float h) {
291 protected void onDraw(PGraphics pg) {
292 pg.textAlign(LEFT, TOP);
295 pg.text(label, 0, 0);
298 public UILabel setFont(PFont font) {
304 public UILabel setFontColor(color fontColor) {
305 this.fontColor = fontColor;
310 public UILabel setLabel(String label) {
317 public class UIButton extends UIObject {
319 private boolean active = false;
320 private boolean isMomentary = false;
321 private color borderColor = #666666;
322 private color inactiveColor = #222222;
323 private color activeColor = #669966;
324 private color labelColor = #999999;
325 private String label = "";
327 public UIButton(float x, float y, float w, float h) {
331 public UIButton setMomentary(boolean momentary) {
332 isMomentary = momentary;
336 protected void onDraw(PGraphics pg) {
337 pg.stroke(borderColor);
338 pg.fill(active ? activeColor : inactiveColor);
340 if (label != null && label.length() > 0) {
341 pg.fill(active ? #FFFFFF : labelColor);
342 pg.textFont(defaultItemFont);
343 pg.textAlign(CENTER);
344 pg.text(label, w/2, h-5);
348 protected void onMousePressed(float mx, float my) {
356 protected void onMouseReleased(float mx, float my) {
362 public UIButton setActive(boolean active) {
363 this.active = active;
369 public UIButton toggle() {
370 return setActive(!active);
373 protected void onToggle(boolean active) {}
375 public UIButton setBorderColor(color borderColor) {
376 if (this.borderColor != borderColor) {
377 this.borderColor = borderColor;
383 public UIButton setActiveColor(color activeColor) {
384 if (this.activeColor != activeColor) {
385 this.activeColor = activeColor;
393 public UIButton setInactiveColor(color inactiveColor) {
394 if (this.inactiveColor != inactiveColor) {
395 this.inactiveColor = inactiveColor;
403 public UIButton setLabelColor(color labelColor) {
404 if (this.labelColor != labelColor) {
405 this.labelColor = labelColor;
411 public UIButton setLabel(String label) {
412 if (!this.label.equals(label)) {
419 public void onMousePressed() {
424 public class UIToggleSet extends UIObject {
426 private String[] options;
427 private int[] boundaries;
428 private String value;
430 public UIToggleSet(float x, float y, float w, float h) {
434 public UIToggleSet setOptions(String[] options) {
435 this.options = options;
436 boundaries = new int[options.length];
438 for (String s : options) {
439 totalLength += s.length();
442 for (int i = 0; i < options.length; ++i) {
443 lengthSoFar += options[i].length();
444 boundaries[i] = (int) (lengthSoFar * w / totalLength);
451 public String getValue() {
455 public UIToggleSet setValue(String option) {
462 public void onDraw(PGraphics pg) {
466 for (int b : boundaries) {
467 pg.line(b, 1, b, h-1);
470 pg.textAlign(CENTER);
471 pg.textFont(defaultItemFont);
472 int leftBoundary = 0;
474 for (int i = 0; i < options.length; ++i) {
475 boolean isActive = options[i] == value;
478 pg.rect(leftBoundary + 1, 1, boundaries[i] - leftBoundary - 1, h-1);
480 pg.fill(isActive ? #FFFFFF : #999999);
481 pg.text(options[i], (leftBoundary + boundaries[i]) / 2., h-6);
482 leftBoundary = boundaries[i];
486 public void onMousePressed(float mx, float my) {
487 for (int i = 0; i < boundaries.length; ++i) {
488 if (mx < boundaries[i]) {
489 setValue(options[i]);
495 protected void onToggle(String option) {}
500 public abstract class UIParameterControl extends UIObject implements LXParameter.Listener {
501 protected LXParameter parameter = null;
503 protected UIParameterControl(float x, float y, float w, float h) {
507 public void onParameterChanged(LXParameter parameter) {
511 public UIParameterControl setParameter(LXParameter parameter) {
512 if (this.parameter != null) {
513 if (this.parameter instanceof LXListenableParameter) {
514 ((LXListenableParameter)this.parameter).removeListener(this);
517 this.parameter = parameter;
518 if (this.parameter != null) {
519 if (this.parameter instanceof LXListenableParameter) {
520 ((LXListenableParameter)this.parameter).addListener(this);
528 public class UIParameterKnob extends UIParameterControl {
529 private int knobSize = 28;
530 private final float knobIndent = .4;
531 private final int knobLabelHeight = 14;
533 public UIParameterKnob(float x, float y) {
535 setSize(knobSize, knobSize + knobLabelHeight);
538 public UIParameterKnob(float x, float y, float w, float h) {
542 protected void onDraw(PGraphics pg) {
543 float knobValue = (parameter != null) ? parameter.getValuef() : 0;
545 pg.ellipseMode(CENTER);
549 pg.rect(0, 0, knobSize, knobSize);
551 // Full outer dark ring
553 pg.arc(knobSize/2, knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent));
555 // Light ring indicating value
557 pg.arc(knobSize/2, knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + knobValue*(TWO_PI-2*knobIndent));
559 // Center circle of knob
561 pg.ellipse(knobSize/2, knobSize/2, knobSize/2, knobSize/2);
563 String knobLabel = (parameter != null) ? parameter.getLabel() : null;
564 if (knobLabel == null) {
566 } else if (knobLabel.length() > 4) {
567 knobLabel = knobLabel.substring(0, 4);
570 pg.rect(0, knobSize + 2, knobSize, knobLabelHeight - 2);
572 pg.textAlign(CENTER);
573 pg.textFont(defaultTitleFont);
574 pg.text(knobLabel, knobSize/2, knobSize + knobLabelHeight - 2);
577 public void onMouseDragged(float mx, float my, float dx, float dy) {
578 if (parameter != null) {
579 float value = constrain(parameter.getValuef() - dy / 100., 0, 1);
580 parameter.setValue(value);
585 public class UIParameterSlider extends UIParameterControl {
587 private static final float handleWidth = 12;
589 UIParameterSlider(float x, float y, float w, float h) {
593 protected void onDraw(PGraphics pg) {
598 pg.rect(4, h/2-2, w-8, 4);
601 pg.rect((int) (4 + parameter.getValuef() * (w-8-handleWidth)), 4, handleWidth, h-8);
604 private boolean editing = false;
605 protected void onMousePressed(float mx, float my) {
606 float handleLeft = 4 + parameter.getValuef() * (w-8-handleWidth);
607 if (mx >= handleLeft && mx < handleLeft + handleWidth) {
612 protected void onMouseReleased(float mx, float my) {
616 protected void onMouseDragged(float mx, float my, float dx, float dy) {
618 parameter.setValue(constrain((mx - handleWidth/2. - 4) / (w-8-handleWidth), 0, 1));
623 public class UIScrollList extends UIObject {
625 private List<ScrollItem> items = new ArrayList<ScrollItem>();
627 private PFont itemFont = defaultItemFont;
628 private int itemHeight = 20;
629 private color selectedColor = lightGreen;
630 private color pendingColor = lightBlue;
631 private int scrollOffset = 0;
632 private int numVisibleItems = 0;
634 private boolean hasScroll;
635 private float scrollYStart;
636 private float scrollYHeight;
638 public UIScrollList(float x, float y, float w, float h) {
642 protected void onDraw(PGraphics pg) {
645 for (int i = 0; i < numVisibleItems; ++i) {
646 if (i + scrollOffset >= items.size()) {
649 ScrollItem item = items.get(i + scrollOffset);
651 color labelColor = #FFFFFF;
652 if (item.isSelected()) {
653 itemColor = selectedColor;
654 } else if (item.isPending()) {
655 itemColor = pendingColor;
657 labelColor = #000000;
660 float factor = even ? .92 : 1.08;
661 itemColor = color(hue(itemColor), saturation(itemColor), min(100, factor*brightness(itemColor)));
665 pg.rect(0, yp, w, itemHeight);
667 pg.textFont(itemFont);
668 pg.textAlign(LEFT, TOP);
669 pg.text(item.getLabel(), 6, yp+4);
676 pg.fill(color(0, 0, 100, 15));
677 pg.rect(w-12, 0, 12, h);
679 pg.rect(w-12, scrollYStart, 12, scrollYHeight);
684 private boolean scrolling = false;
685 private ScrollItem pressedItem = null;
687 public void onMousePressed(float mx, float my) {
689 if (hasScroll && mx >= w-12) {
690 if (my >= scrollYStart && my < (scrollYStart + scrollYHeight)) {
695 int index = (int) my / itemHeight;
696 if (scrollOffset + index < items.size()) {
697 pressedItem = items.get(scrollOffset + index);
698 pressedItem.onMousePressed();
699 pressedItem.select();
705 public void onMouseReleased(float mx, float my) {
707 if (pressedItem != null) {
708 pressedItem.onMouseReleased();
713 private float dAccum = 0;
714 public void onMouseDragged(float mx, float my, float dx, float dy) {
717 float scrollOne = h / items.size();
718 int offset = (int) (dAccum / scrollOne);
720 dAccum -= offset * scrollOne;
721 setScrollOffset(scrollOffset + offset);
726 private float wAccum = 0;
727 public void onMouseWheel(float mx, float my, float delta) {
729 int offset = (int) (wAccum / 5);
731 wAccum -= offset * 5;
732 setScrollOffset(scrollOffset + offset);
736 public void setScrollOffset(int offset) {
737 scrollOffset = constrain(offset, 0, items.size() - numVisibleItems);
738 scrollYStart = (int) (scrollOffset * h / items.size());
739 scrollYHeight = (int) (numVisibleItems * h / (float) items.size());
743 public UIScrollList setItems(List<ScrollItem> items) {
745 numVisibleItems = (int) (h / itemHeight);
746 hasScroll = items.size() > numVisibleItems;
753 public interface ScrollItem {
754 public boolean isSelected();
755 public boolean isPending();
756 public String getLabel();
757 public void select();
758 public void onMousePressed();
759 public void onMouseReleased();
762 public abstract class AbstractScrollItem implements ScrollItem {
763 public boolean isPending() {
766 public void select() {}
767 public void onMousePressed() {}
768 public void onMouseReleased() {}
771 public class UIIntegerBox extends UIObject {
773 private int minValue = 0;
774 private int maxValue = MAX_INT;
775 private int value = 0;
777 UIIntegerBox(float x, float y, float w, float h) {
781 public UIIntegerBox setRange(int minValue, int maxValue) {
782 this.minValue = minValue;
783 this.maxValue = maxValue;
784 setValue(constrain(value, minValue, maxValue));
788 protected void onDraw(PGraphics pg) {
792 pg.textAlign(CENTER, CENTER);
793 pg.textFont(defaultItemFont);
795 pg.text("" + value, w/2, h/2);
798 protected void onValueChange(int value) {}
801 protected void onMousePressed(float mx, float my) {
805 protected void onMouseDragged(float mx, float my, float dx, float dy) {
807 int offset = (int) (dAccum / 5);
808 dAccum = dAccum - (offset * 5);
809 setValue(value + offset);
812 public int getValue() {
816 public UIIntegerBox setValue(int value) {
817 if (this.value != value) {
818 int range = (maxValue - minValue + 1);
819 while (value < minValue) {
822 this.value = minValue + (value - minValue) % range;
823 this.onValueChange(this.value);