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 static int DOUBLE_CLICK_THRESHOLD = 300;
26 protected final List<UIObject> children = new ArrayList<UIObject>();
28 protected boolean needsRedraw = true;
29 protected boolean childNeedsRedraw = true;
31 protected float x=0, y=0, w=0, h=0;
33 public UIContainer parent = null;
35 protected boolean visible = true;
39 public UIObject(float x, float y, float w, float h) {
46 public boolean isVisible() {
50 public UIObject setVisible(boolean visible) {
51 if (visible != this.visible) {
52 this.visible = visible;
58 public final UIObject setPosition(float x, float y) {
65 public final UIObject setSize(float w, float h) {
72 public final UIObject addToContainer(UIContainer c) {
78 public final UIObject removeFromContainer(UIContainer c) {
79 c.children.remove(this);
84 public final UIObject redraw() {
86 UIObject p = this.parent;
88 p.childNeedsRedraw = true;
94 private final void _redraw() {
96 for (UIObject child : children) {
97 childNeedsRedraw = true;
102 public final void draw(PGraphics pg) {
110 if (childNeedsRedraw) {
111 childNeedsRedraw = false;
112 for (UIObject child : children) {
113 if (needsRedraw || child.needsRedraw || child.childNeedsRedraw) {
115 pg.translate(child.x, child.y);
123 public final boolean contains(float x, float y) {
125 (x >= this.x && x < (this.x + this.w)) &&
126 (y >= this.y && y < (this.y + this.h));
129 protected void onDraw(PGraphics pg) {}
130 protected void onMousePressed(float mx, float my) {}
131 protected void onMouseReleased(float mx, float my) {}
132 protected void onMouseDragged(float mx, float my, float dx, float dy) {}
133 protected void onMouseWheel(float mx, float my, float dx) {}
136 public class UIContainer extends UIObject {
138 private UIObject focusedChild = null;
140 public UIContainer() {}
142 public UIContainer(float x, float y, float w, float h) {
146 public UIContainer(UIObject[] children) {
147 for (UIObject child : children) {
148 child.addToContainer(this);
152 protected void onMousePressed(float mx, float my) {
153 for (int i = children.size() - 1; i >= 0; --i) {
154 UIObject child = children.get(i);
155 if (child.contains(mx, my)) {
156 child.onMousePressed(mx - child.x, my - child.y);
157 focusedChild = child;
163 protected void onMouseReleased(float mx, float my) {
164 if (focusedChild != null) {
165 focusedChild.onMouseReleased(mx - focusedChild.x, my - focusedChild.y);
170 protected void onMouseDragged(float mx, float my, float dx, float dy) {
171 if (focusedChild != null) {
172 focusedChild.onMouseDragged(mx - focusedChild.x, my - focusedChild.y, dx, dy);
176 protected void onMouseWheel(float mx, float my, float delta) {
177 for (UIObject child : children) {
178 if (child.contains(mx, my)) {
179 child.onMouseWheel(mx - child.x, mx - child.y, delta);
186 public class UIContext extends UIContainer {
188 final public PGraphics pg;
190 UIContext(float x, float y, float w, float h) {
192 pg = createGraphics((int)w, (int)h, JAVA2D);
200 if (needsRedraw || childNeedsRedraw) {
208 private float px, py;
209 private boolean dragging = false;
211 public boolean mousePressed(float mx, float my) {
215 if (contains(mx, my)) {
219 onMousePressed(mx - x, my - y);
225 public boolean mouseReleased(float mx, float my) {
230 onMouseReleased(mx - x, my - y);
234 public boolean mouseDragged(float mx, float my) {
241 onMouseDragged(mx - x, my - y, dx, dy);
249 public boolean mouseWheel(float mx, float my, float delta) {
253 if (contains(mx, my)) {
254 onMouseWheel(mx - x, my - y, delta);
261 public class UIWindow extends UIContext {
263 protected final static int titleHeight = 24;
265 public UIWindow(String label, float x, float y, float w, float h) {
267 new UILabel(6, 8, w-6, titleHeight-8) {
268 protected void onMouseDragged(float mx, float my, float dx, float dy) {
269 parent.x = constrain(parent.x + dx, 0, width - w);
270 parent.y = constrain(parent.y + dy, 0, height - h);
272 }.setLabel(label).setFont(defaultTitleFont).addToContainer(this);
275 protected void onDraw(PGraphics pg) {
279 pg.rect(0, 0, w-1, h-1);
283 public class UILabel extends UIObject {
285 private PFont font = defaultTitleFont;
286 private color fontColor = #CCCCCC;
287 private String label = "";
289 public UILabel(float x, float y, float w, float h) {
293 protected void onDraw(PGraphics pg) {
294 pg.textAlign(LEFT, TOP);
297 pg.text(label, 0, 0);
300 public UILabel setFont(PFont font) {
306 public UILabel setFontColor(color fontColor) {
307 this.fontColor = fontColor;
312 public UILabel setLabel(String label) {
319 public class UICheckbox extends UIButton {
321 private boolean firstDraw = true;
323 public UICheckbox(float x, float y, float w, float h) {
328 public void onDraw(PGraphics pg) {
329 pg.stroke(borderColor);
330 pg.fill(active ? activeColor : inactiveColor);
334 pg.textFont(defaultItemFont);
335 pg.textAlign(LEFT, CENTER);
336 pg.text(label, h + 4, h/2);
343 public class UIButton extends UIObject {
345 protected boolean active = false;
346 protected boolean isMomentary = false;
347 protected color borderColor = #666666;
348 protected color inactiveColor = #222222;
349 protected color activeColor = #669966;
350 protected color labelColor = #999999;
351 protected String label = "";
353 public UIButton(float x, float y, float w, float h) {
357 public UIButton setMomentary(boolean momentary) {
358 isMomentary = momentary;
362 protected void onDraw(PGraphics pg) {
363 pg.stroke(borderColor);
364 pg.fill(active ? activeColor : inactiveColor);
366 if (label != null && label.length() > 0) {
367 pg.fill(active ? #FFFFFF : labelColor);
368 pg.textFont(defaultItemFont);
369 pg.textAlign(CENTER);
370 pg.text(label, w/2, h-5);
374 protected void onMousePressed(float mx, float my) {
382 protected void onMouseReleased(float mx, float my) {
388 public boolean isActive() {
392 public UIButton setActive(boolean active) {
393 this.active = active;
399 public UIButton toggle() {
400 return setActive(!active);
403 protected void onToggle(boolean active) {}
405 public UIButton setBorderColor(color borderColor) {
406 if (this.borderColor != borderColor) {
407 this.borderColor = borderColor;
413 public UIButton setActiveColor(color activeColor) {
414 if (this.activeColor != activeColor) {
415 this.activeColor = activeColor;
423 public UIButton setInactiveColor(color inactiveColor) {
424 if (this.inactiveColor != inactiveColor) {
425 this.inactiveColor = inactiveColor;
433 public UIButton setLabelColor(color labelColor) {
434 if (this.labelColor != labelColor) {
435 this.labelColor = labelColor;
441 public UIButton setLabel(String label) {
442 if (!this.label.equals(label)) {
449 public void onMousePressed() {
454 public class UIToggleSet extends UIObject {
456 private String[] options;
457 private int[] boundaries;
458 private String value;
460 public UIToggleSet(float x, float y, float w, float h) {
464 public UIToggleSet setOptions(String[] options) {
465 this.options = options;
466 boundaries = new int[options.length];
468 for (String s : options) {
469 totalLength += s.length();
472 for (int i = 0; i < options.length; ++i) {
473 lengthSoFar += options[i].length();
474 boundaries[i] = (int) (lengthSoFar * w / totalLength);
481 public String getValue() {
485 public UIToggleSet setValue(String option) {
492 public void onDraw(PGraphics pg) {
496 for (int b : boundaries) {
497 pg.line(b, 1, b, h-1);
500 pg.textAlign(CENTER);
501 pg.textFont(defaultItemFont);
502 int leftBoundary = 0;
504 for (int i = 0; i < options.length; ++i) {
505 boolean isActive = options[i] == value;
508 pg.rect(leftBoundary + 1, 1, boundaries[i] - leftBoundary - 1, h-1);
510 pg.fill(isActive ? #FFFFFF : #999999);
511 pg.text(options[i], (leftBoundary + boundaries[i]) / 2., h-6);
512 leftBoundary = boundaries[i];
516 public void onMousePressed(float mx, float my) {
517 for (int i = 0; i < boundaries.length; ++i) {
518 if (mx < boundaries[i]) {
519 setValue(options[i]);
525 protected void onToggle(String option) {}
530 public abstract class UIParameterControl extends UIObject implements LXParameter.Listener {
531 protected LXParameter parameter = null;
533 protected UIParameterControl(float x, float y, float w, float h) {
537 public void onParameterChanged(LXParameter parameter) {
541 public UIParameterControl setParameter(LXParameter parameter) {
542 if (this.parameter != null) {
543 if (this.parameter instanceof LXListenableParameter) {
544 ((LXListenableParameter)this.parameter).removeListener(this);
547 this.parameter = parameter;
548 if (this.parameter != null) {
549 if (this.parameter instanceof LXListenableParameter) {
550 ((LXListenableParameter)this.parameter).addListener(this);
558 public class UIParameterKnob extends UIParameterControl {
559 private int knobSize = 28;
560 private final float knobIndent = .4;
561 private final int knobLabelHeight = 14;
563 public UIParameterKnob(float x, float y) {
565 setSize(knobSize, knobSize + knobLabelHeight);
568 public UIParameterKnob(float x, float y, float w, float h) {
572 protected void onDraw(PGraphics pg) {
573 float knobValue = (parameter != null) ? parameter.getValuef() : 0;
575 pg.ellipseMode(CENTER);
579 pg.rect(0, 0, knobSize, knobSize);
581 // Full outer dark ring
583 pg.arc(knobSize/2, knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + (TWO_PI-2*knobIndent));
585 // Light ring indicating value
587 pg.arc(knobSize/2, knobSize/2, knobSize, knobSize, HALF_PI + knobIndent, HALF_PI + knobIndent + knobValue*(TWO_PI-2*knobIndent));
589 // Center circle of knob
591 pg.ellipse(knobSize/2, knobSize/2, knobSize/2, knobSize/2);
593 String knobLabel = (parameter != null) ? parameter.getLabel() : null;
594 if (knobLabel == null) {
596 } else if (knobLabel.length() > 4) {
597 knobLabel = knobLabel.substring(0, 4);
600 pg.rect(0, knobSize + 2, knobSize, knobLabelHeight - 2);
602 pg.textAlign(CENTER);
603 pg.textFont(defaultTitleFont);
604 pg.text(knobLabel, knobSize/2, knobSize + knobLabelHeight - 2);
607 private long lastMousePress = 0;
608 public void onMousePressed(float mx, float my) {
609 super.onMousePressed(mx, my);
611 if (now - lastMousePress < DOUBLE_CLICK_THRESHOLD) {
615 lastMousePress = now;
619 public void onMouseDragged(float mx, float my, float dx, float dy) {
620 if (parameter != null) {
621 float value = constrain(parameter.getValuef() - dy / 100., 0, 1);
622 parameter.setValue(value);
627 public class UIParameterSlider extends UIParameterControl {
629 private static final float handleWidth = 12;
631 UIParameterSlider(float x, float y, float w, float h) {
635 protected void onDraw(PGraphics pg) {
640 pg.rect(4, h/2-2, w-8, 4);
643 pg.rect((int) (4 + parameter.getValuef() * (w-8-handleWidth)), 4, handleWidth, h-8);
646 private boolean editing = false;
647 private long lastClick = 0;
648 private float doubleClickMode = 0;
649 private float doubleClickX = 0;
650 protected void onMousePressed(float mx, float my) {
652 float handleLeft = 4 + parameter.getValuef() * (w-8-handleWidth);
653 if (mx >= handleLeft && mx < handleLeft + handleWidth) {
656 if ((now - lastClick) < DOUBLE_CLICK_THRESHOLD && abs(mx - doubleClickX) < 3) {
657 parameter.setValue(doubleClickMode);
662 } else if (mx > w*.75) {
665 doubleClickMode = 0.5;
671 protected void onMouseReleased(float mx, float my) {
675 protected void onMouseDragged(float mx, float my, float dx, float dy) {
677 parameter.setValue(constrain((mx - handleWidth/2. - 4) / (w-8-handleWidth), 0, 1));
682 public class UIScrollList extends UIObject {
684 private List<ScrollItem> items = new ArrayList<ScrollItem>();
686 private PFont itemFont = defaultItemFont;
687 private int itemHeight = 20;
688 private color selectedColor = lightGreen;
689 private color pendingColor = lightBlue;
690 private int scrollOffset = 0;
691 private int numVisibleItems = 0;
693 private boolean hasScroll;
694 private float scrollYStart;
695 private float scrollYHeight;
697 public UIScrollList(float x, float y, float w, float h) {
701 protected void onDraw(PGraphics pg) {
704 for (int i = 0; i < numVisibleItems; ++i) {
705 if (i + scrollOffset >= items.size()) {
708 ScrollItem item = items.get(i + scrollOffset);
710 color labelColor = #FFFFFF;
711 if (item.isSelected()) {
712 itemColor = selectedColor;
713 } else if (item.isPending()) {
714 itemColor = pendingColor;
716 labelColor = #000000;
719 float factor = even ? .92 : 1.08;
720 itemColor = lx.scaleBrightness(itemColor, factor);
724 pg.rect(0, yp, w, itemHeight);
726 pg.textFont(itemFont);
727 pg.textAlign(LEFT, TOP);
728 pg.text(item.getLabel(), 6, yp+4);
736 pg.rect(w-12, 0, 12, h);
738 pg.rect(w-12, scrollYStart, 12, scrollYHeight);
743 private boolean scrolling = false;
744 private ScrollItem pressedItem = null;
746 public void onMousePressed(float mx, float my) {
748 if (hasScroll && mx >= w-12) {
749 if (my >= scrollYStart && my < (scrollYStart + scrollYHeight)) {
754 int index = (int) my / itemHeight;
755 if (scrollOffset + index < items.size()) {
756 pressedItem = items.get(scrollOffset + index);
757 pressedItem.onMousePressed();
763 public void onMouseReleased(float mx, float my) {
765 if (pressedItem != null) {
766 pressedItem.onMouseReleased();
771 private float dAccum = 0;
772 public void onMouseDragged(float mx, float my, float dx, float dy) {
775 float scrollOne = h / items.size();
776 int offset = (int) (dAccum / scrollOne);
778 dAccum -= offset * scrollOne;
779 setScrollOffset(scrollOffset + offset);
784 private float wAccum = 0;
785 public void onMouseWheel(float mx, float my, float delta) {
787 int offset = (int) (wAccum / 5);
789 wAccum -= offset * 5;
790 setScrollOffset(scrollOffset + offset);
794 public void setScrollOffset(int offset) {
795 scrollOffset = constrain(offset, 0, max(0, items.size() - numVisibleItems));
796 scrollYStart = round(scrollOffset * h / items.size());
797 scrollYHeight = round(numVisibleItems * h / items.size());
801 public UIScrollList setItems(List<ScrollItem> items) {
803 numVisibleItems = (int) (h / itemHeight);
804 hasScroll = items.size() > numVisibleItems;
811 public interface ScrollItem {
812 public boolean isSelected();
813 public boolean isPending();
814 public String getLabel();
815 public void onMousePressed();
816 public void onMouseReleased();
819 public abstract class AbstractScrollItem implements ScrollItem {
820 public boolean isPending() {
823 public void select() {}
824 public void onMousePressed() {}
825 public void onMouseReleased() {}
828 public class UIIntegerBox extends UIObject {
830 private int minValue = 0;
831 private int maxValue = MAX_INT;
832 private int value = 0;
834 UIIntegerBox(float x, float y, float w, float h) {
838 public UIIntegerBox setRange(int minValue, int maxValue) {
839 this.minValue = minValue;
840 this.maxValue = maxValue;
841 setValue(constrain(value, minValue, maxValue));
845 protected void onDraw(PGraphics pg) {
849 pg.textAlign(CENTER, CENTER);
850 pg.textFont(defaultItemFont);
852 pg.text("" + value, w/2, h/2);
855 protected void onValueChange(int value) {}
858 protected void onMousePressed(float mx, float my) {
862 protected void onMouseDragged(float mx, float my, float dx, float dy) {
864 int offset = (int) (dAccum / 5);
865 dAccum = dAccum - (offset * 5);
866 setValue(value + offset);
869 public int getValue() {
873 public UIIntegerBox setValue(int value) {
874 if (this.value != value) {
875 int range = (maxValue - minValue + 1);
876 while (value < minValue) {
879 this.value = minValue + (value - minValue) % range;
880 this.onValueChange(this.value);