Skip to content

Instantly share code, notes, and snippets.

@KrabCode
Last active September 4, 2019 18:07
Show Gist options
  • Select an option

  • Save KrabCode/8c8031ad0306696ec2e550c66a4a5848 to your computer and use it in GitHub Desktop.

Select an option

Save KrabCode/8c8031ad0306696ec2e550c66a4a5848 to your computer and use it in GitHub Desktop.
private float rowWidthWindowFraction = 1 / 3f;
private float rowHeightWindowFraction = 1 / 14f;
private float elementPaddingFractionX = .9f;
private float elementPaddingFractionY = .8f;
private float buttonsPerRow = 2;
private float togglesPerRow = 2;
private float slidersPerRow = 1;
private float textActive = 0;
private float textPassive = .5f;
private float pressedFill = .5f;
private float mouseOutsideStroke = .5f;
private float mouseOverStroke = 1f;
private ArrayList<Slider> sliders = new ArrayList<Slider>();
private ArrayList<Button> buttons = new ArrayList<Button>();
private ArrayList<Toggle> toggles = new ArrayList<Toggle>();
private float baseR;
private float vertexCount = 50;
private ArrayList<PVector> cogShape;
private ArrayList<PVector> arrowShape;
private PVector extensionTogglePos = new PVector();
private boolean extensionTogglePressedLastFrame = false;
private float extensionAnimationDuration = 60;
private float extensionAnimationStarted = -extensionAnimationDuration;
private float extensionEasing = -1;
private float extensionAnimationTarget = 1;
private float offsetXextended = (1 / 24f);
private float offsetXretracted = -rowWidthWindowFraction;
private float offsetYWindowFraction = (1 / 24f);
private float backgroundAlpha = .8f;
private int extensionToggleFadeoutDuration = 60;
private int extensionToggleFadeoutDelay = 0;
private int lastInteractedWithExtensionToggle = -extensionToggleFadeoutDelay - extensionToggleFadeoutDuration;
protected void gui() {
gui(true);
}
protected void gui(boolean extendedByDefault) {
if (isGuiEmpty()) {
return;
}
pushMatrix();
pushStyle();
resetMatrixInAnyRenderer();
colorMode(HSB, 1, 1, 1, 1);
drawBackground();
updateExtension(extendedByDefault);
drawExtensionToggle();
for (Toggle t : toggles) {
if (t.lastQueried == frameCount) {
updateDrawToggle(t, getPosition(t));
}
}
for (Button b : buttons) {
if (b.lastQueried == frameCount) {
updateDrawButton(b, getPosition(b));
}
}
for (Slider s : sliders) {
if (s.lastQueried == frameCount) {
updateDrawSlider(s, getPosition(s));
}
}
popStyle();
popMatrix();
}
private boolean isGuiEmpty() {
return toggles.size() == 0 && buttons.size() == 0 && sliders.size() == 0;
}
private void drawBackground() {
noStroke();
fill(0, backgroundAlpha);
PVector offset = getOffset();
float w = width * rowWidthWindowFraction + offset.x;
float h = height * rowHeightWindowFraction * .5f + getPositionOfLastItem().y + offset.y;
rectMode(CORNER);
rect(0, 0, w, h);
}
protected boolean button(String name) {
Button button = findButtonByName(name);
if (button == null) {
button = new Button(name);
}
button.lastQueried = frameCount;
return button.value;
}
protected boolean toggle(String name) {
return toggle(name, false);
}
protected boolean toggle(String name, boolean initial) {
Toggle toggle = findToggleByName(name);
if (toggle == null) {
toggle = new Toggle(name, initial);
}
toggle.lastQueried = frameCount;
return toggle.value;
}
protected float slider(String name) {
return slider(name, 0, 1);
}
protected float slider(String name, float max) {
return slider(name, 0, max);
}
protected float slider(String name, float min, float max) {
float range = max - min;
return slider(name, min, max, min + range / 2);
}
protected float slider(String name, float min, float max, float initial) {
Slider slider = findSliderByName(name);
if (slider == null) {
slider = new Slider(name, min, max, initial);
}
slider.lastQueried = frameCount;
return slider.value;
}
private void resetMatrixInAnyRenderer() {
if (sketchRenderer().equals(P3D)) {
camera();
} else {
resetMatrix();
}
}
private void updateDrawButton(Button button, PVector pos) {
float w = ((width * rowWidthWindowFraction) / buttonsPerRow) * elementPaddingFractionX;
float h = height * rowHeightWindowFraction * elementPaddingFractionY;
boolean wasPressedLastFrame = button.pressed;
boolean mouseOver = isPointInRect(mouseX, mouseY, pos.x, pos.y, w, h);
button.pressed = mousePressed && mouseOver;
button.value = wasPressedLastFrame && !button.pressed && !mousePressed;
noFill();
if (button.pressed) {
fill(pressedFill);
}
stroke(button.pressed ? mouseOverStroke : mouseOutsideStroke);
strokeWeight(1);
rectMode(CORNER);
rect(pos.x, pos.y, w, h);
fill(button.pressed ? textActive : textPassive);
textSize(h * .5f);
textAlign(CENTER, CENTER);
text(button.name, pos.x, pos.y, w, h);
}
private void updateDrawToggle(Toggle toggle, PVector pos) {
float w = ((width * rowWidthWindowFraction) / togglesPerRow) * elementPaddingFractionX;
float h = height * rowHeightWindowFraction * elementPaddingFractionY;
boolean wasPressedLastFrame = toggle.pressed;
boolean mouseOver = isPointInRect(mouseX, mouseY, pos.x, pos.y, w, h);
toggle.pressed = mousePressed && mouseOver;
if (wasPressedLastFrame && !toggle.pressed && !mousePressed) {
toggle.value = !toggle.value;
}
noFill();
if (toggle.value) {
fill(pressedFill);
}
stroke(toggle.pressed ? mouseOverStroke : mouseOutsideStroke);
strokeWeight(1);
rectMode(CORNER);
rect(pos.x, pos.y, w, h);
fill(toggle.value ? textActive : textPassive);
if (toggle.pressed) {
fill(mouseOverStroke);
}
textSize(h * .5f);
textAlign(CENTER, CENTER);
text(toggle.name, pos.x, pos.y, w, h);
}
private void updateDrawSlider(Slider slider, PVector pos) {
float w = ((width * rowWidthWindowFraction) / slidersPerRow) * elementPaddingFractionX;
float h = height * rowHeightWindowFraction * elementPaddingFractionY;
float extraSensitivity = 5;
float gray = mouseOutsideStroke;
float alpha = 1;
if (isPointInRect(mouseX, mouseY, pos.x - extraSensitivity, pos.y, w + extraSensitivity * 2, h)) {
gray = mouseOverStroke;
stroke(gray, alpha);
if (mousePressed) {
slider.value = map(mouseX, pos.x, pos.x + w, slider.min, slider.max);
slider.value = constrain(slider.value, slider.min, slider.max);
}
}
strokeCap(PROJECT);
strokeWeight(1);
stroke(gray, alpha);
rectMode(CORNER);
float sliderY = pos.y + h * .5f;
line(pos.x, sliderY, pos.x + w, sliderY);
float valueX = map(slider.value, slider.min, slider.max, pos.x, pos.x + w);
strokeWeight(3);
stroke(gray, alpha);
line(valueX, pos.y, valueX, pos.y + h * .6f);
fill(gray, alpha);
textAlign(LEFT, CENTER);
float textOffsetX = w * .05f;
float textOffsetY = h * .75f;
float defaultTextSize = h * .5f;
float textWidth = w * 2;
float textSize = defaultTextSize;
while (textWidth > w * .5f) {
textSize(textSize -= .5);
textWidth = textWidth(slider.name);
}
text(slider.name, pos.x + textOffsetX, pos.y + textOffsetY);
textAlign(RIGHT, CENTER);
int floorBoundary = 10;
String humanReadableValue;
if (abs(slider.value) < floorBoundary) {
humanReadableValue = nf(slider.value, 0, 0);
} else {
humanReadableValue = String.valueOf(round(slider.value));
}
text(humanReadableValue, pos.x + w - textOffsetX, pos.y + textOffsetY);
}
private PVector getPosition(GuiElement element) {
float rowHeight = height * rowHeightWindowFraction;
float rowWidth = width * rowWidthWindowFraction;
PVector offset = getOffset();
int activeButtonsCount = getActiveButtonCount();
int activeTogglesount = getActiveToggleCount();
int buttonRows = ceil(activeButtonsCount / buttonsPerRow);
int toggleRows = ceil(activeTogglesount / togglesPerRow);
int row = 0;
int column = 0;
int itemsPerRow = 1;
String simpleName = element.getClass().getSimpleName();
if ("Button".equals(simpleName)) {
int index = buttons.indexOf(element);
itemsPerRow = floor(buttonsPerRow);
row = floor(index / buttonsPerRow);
column = floor(index % buttonsPerRow);
} else if ("Toggle".equals(simpleName)) {
int index = toggles.indexOf(element);
itemsPerRow = floor(togglesPerRow);
row = buttonRows + floor(index / togglesPerRow);
column = floor(index % togglesPerRow);
} else if ("Slider".equals(simpleName)) {
int index = sliders.indexOf(element);
itemsPerRow = floor(slidersPerRow);
row = buttonRows + toggleRows + floor(index / slidersPerRow);
column = floor(index % slidersPerRow);
}
return new PVector(offset.x + column * (rowWidth / itemsPerRow), offset.y + row * rowHeight);
}
private int getActiveButtonCount() {
int result = 0;
for (Button b : buttons) {
if (b.lastQueried == frameCount) {
result++;
}
}
return result;
}
private int getActiveToggleCount() {
int result = 0;
for (Toggle t : toggles) {
if (t.lastQueried == frameCount) {
result++;
}
}
return result;
}
private PVector getPositionOfLastItem() {
if (sliders.size() > 0) {
for (int i = sliders.size() - 1; i >= 0; i--) {
Slider s = sliders.get(i);
if (s.lastQueried == frameCount) {
return getPosition(s);
}
}
}
if (toggles.size() > 0) {
for (int i = toggles.size() - 1; i >= 0; i--) {
Toggle t = toggles.get(i);
if (t.lastQueried == frameCount) {
return getPosition(t);
}
}
}
if (buttons.size() > 0) {
for (int i = buttons.size() - 1; i >= 0; i--) {
Button b = buttons.get(i);
if (b.lastQueried == frameCount) {
return getPosition(b);
}
}
}
return new PVector();
}
private void updateExtension(boolean extendedByDefault) {
float previousBaseR = baseR;
baseR = min(width, height) * rowHeightWindowFraction * .5f;
if (cogShape == null || previousBaseR != baseR) {
cogShape = createCog();
arrowShape = createArrow();
}
float extensionLinearNormalized = constrain(map(frameCount, extensionAnimationStarted, extensionAnimationStarted + extensionAnimationDuration, 0, 1), 0, 1);
extensionEasing = ease(extensionLinearNormalized, 3);
if (extensionAnimationTarget == -1) {
if (extendedByDefault) {
extensionAnimationTarget = 1;
} else {
extensionAnimationTarget = 0;
}
}
if (extensionAnimationTarget == 0) {
extensionEasing = 1 - extensionEasing;
}
}
private ArrayList<PVector> createArrow() {
ArrayList<PVector> arrow = new ArrayList<PVector>();
for (int i = 0; i < vertexCount; i++) {
float iN = map(i, 0, vertexCount - 1, 0, 2);
if (iN < 1) {
float x = baseR * abs(.5f - iN) * 2 - baseR * .15f;
float y = lerp(-baseR, baseR, iN);
arrow.add(new PVector(x, y));
} else {
iN = 2 - iN;
float x = baseR * abs(.5f - iN) * 2 + baseR * .15f;
float y = lerp(-baseR, baseR, iN);
arrow.add(new PVector(x, y));
}
}
return arrow;
}
private ArrayList<PVector> createCog() {
ArrayList<PVector> cog = new ArrayList<PVector>();
float toothR = baseR * .2f;
for (int i = 0; i < vertexCount; i++) {
float iNormalized = map(i, 0, vertexCount - 1, 0, 1);
float a = iNormalized * TWO_PI;
float r = baseR + toothR * (sin(iNormalized * 50) > 0 ? 0 : 1);
cog.add(new PVector(r * cos(a), r * sin(a)));
}
return cog;
}
private void drawExtensionToggle() {
PVector offset = getOffset();
extensionTogglePos.x = offset.x + width * rowWidthWindowFraction + baseR * 1.5f;
extensionTogglePos.y = offset.y + baseR * .5f;
if (extensionEasing > 0) {
lastInteractedWithExtensionToggle = frameCount;
}
float alpha = 1 - constrain(map(frameCount,
lastInteractedWithExtensionToggle + extensionToggleFadeoutDelay,
lastInteractedWithExtensionToggle + extensionToggleFadeoutDelay + extensionToggleFadeoutDuration,
0, 1), 0, 1);
stroke(mouseOutsideStroke, alpha);
noFill();
strokeWeight(2);
boolean atEitherEnd = extensionEasing == 0 || extensionEasing == 1;
boolean justReleasedMouse = extensionTogglePressedLastFrame && !mousePressed;
if (isPointInRect(mouseX, mouseY, extensionTogglePos.x - baseR, extensionTogglePos.y - baseR, baseR * 3, baseR * 3)) {
lastInteractedWithExtensionToggle = frameCount;
if (atEitherEnd && justReleasedMouse) {
startExtensionAnimation();
}
if (mousePressed) {
extensionTogglePressedLastFrame = true;
}
}
if (!mousePressed) {
extensionTogglePressedLastFrame = false;
}
beginShape();
for (int i = 0; i < vertexCount; i++) {
PVector cogVertex = cogShape.get(i);
PVector arrowVertex = arrowShape.get(i);
PVector shapeVector = new PVector(lerp(cogVertex.x, arrowVertex.x, extensionEasing), lerp(cogVertex.y, arrowVertex.y, extensionEasing));
vertex(extensionTogglePos.x + shapeVector.x, extensionTogglePos.y + shapeVector.y);
}
endShape(CLOSE);
}
private void startExtensionAnimation() {
boolean extend = extensionEasing == 0;
extensionAnimationStarted = frameCount;
if (extend) {
extensionAnimationTarget = 1;
} else {
extensionAnimationTarget = 0;
}
}
private PVector getOffset() {
float offsetXWindowFraction = map(extensionEasing, 0, 1, offsetXretracted, offsetXextended);
float offsetX = width * offsetXWindowFraction;
float offsetY = height * offsetYWindowFraction;
return new PVector(offsetX, offsetY);
}
protected float ease(float p, float g) {
if (p < 0.5)
return 0.5f * pow(2 * p, g);
else
return 1 - 0.5f * pow(2 * (1 - p), g);
}
private boolean isPointInRect(float px, float py, float rx, float ry, float rw, float rh) {
return px >= rx && px <= rx + rw && py >= ry && py <= ry + rh;
}
private Slider findSliderByName(String query) {
for (Slider s : sliders) {
if (s.name.equals(query)) {
return s;
}
}
return null;
}
private Button findButtonByName(String query) {
for (Button b : buttons) {
if (b.name.equals(query)) {
return b;
}
}
return null;
}
private Toggle findToggleByName(String query) {
for (Toggle t : toggles) {
if (t.name.equals(query)) {
return t;
}
}
return null;
}
private class GuiElement {
String name;
int lastQueried = 0;
GuiElement(String name) {
this.name = name;
}
}
private class Slider extends GuiElement {
float min, max, initial, value;
Slider(String name, float min, float max, float initial) {
super(name);
this.min = min;
this.max = max;
this.initial = initial;
this.value = initial;
sliders.add(this);
}
}
private class Button extends GuiElement {
boolean pressed, value;
Button(String name) {
super(name);
buttons.add(this);
}
}
private class Toggle extends GuiElement {
boolean value, initial, pressed;
Toggle(String name, boolean initial) {
super(name);
toggles.add(this);
this.initial = initial;
this.value = initial;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment