X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=_UIFramework.pde;fp=_UIFramework.pde;h=a2946fbad9af7f7da96d6c7d9bbd66a287e3c541;hb=d626bc9b0197a1b5fd51a86f33f666a2a46579a2;hp=0000000000000000000000000000000000000000;hpb=901660321dcb92de046a696c841f1f20d41d6a00;p=SugarCubes.git 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; + } +}