// UI TORTURING TEST 1 // by arielm - May 22, 2003 // http://www.chronotext.org // Slider is inspired by javax.swing.JSlider // ScrollBar is inspired by javax.swing.JScrollBar Slider slider_T, slider_B, slider_L; ScrollBar scrollbar_R; float box_min, box_max, box_h; float sheet_w, sheet_h, sheets_min, sheets_max; int sheets_n, sheet_selected; float paper_top, paper_offset; float m; void setup() { size(300, 300); framerate(25); background(0); sheet_w = 50.0; sheet_h = 10.0; sheets_min = 0.0; sheets_max = 20.0; sheets_n = 10; sheet_selected = 10; box_min = 0.0; box_max = 100.0; box_h = 50.0; slider_T = new Slider(8.0, 8.0, 284.0, 9.0, Slider.LEFT, Slider.TOP, Slider.HORIZONTAL, sheets_min, sheets_max, sheets_n, color(255, 255, 255), color(0, 0, 0)); slider_T.setTickSpacing(1.0); slider_T.setPaintTicks(true); slider_T.setSnapToTicks(true); slider_B = new Slider(292.0, 292.0, 284.0, 9.0, Slider.RIGHT, Slider.BOTTOM, Slider.HORIZONTAL, box_min, box_max, box_h, color(255, 153, 0), color(0, 0, 0)); slider_L = new Slider(8.0, 150.0, 9.0, 250.0, Slider.LEFT, Slider.CENTER, Slider.VERTICAL, 0.0, sheets_n, sheet_selected, color(255, 102, 0), color(0, 0, 0)); slider_L.setTickSpacing(1.0); slider_L.setPaintTicks(true); slider_L.setSnapToTicks(true); scrollbar_R = new ScrollBar(292.0, 150.0, 13.0, 250.0, ScrollBar.RIGHT, ScrollBar.CENTER, ScrollBar.VERTICAL, 0.0, sheets_n * sheet_h, 0.0, box_h, color(127, 127, 127), color(51, 51, 51), true); scrollbar_R.setUnitIncrement(sheet_h / 10.0); // it will take 10 frames (pressing a scrollbar button) to move by one sheet... //scrollbar_R.setBlockIncrement(sheet_h); // instead of (by default) moving by the height of the box, the "knob" will move by the height of one sheet (each frame, when pressing on the track, above or under the knob)... m = 0.0; // used for rotation and mumbling... } void loop() { // leave these 4 lines around the beginning... slider_T.run(); slider_B.run(); slider_L.run(); scrollbar_R.run(); sheets_n = (int)slider_T.getValue(); box_h = slider_B.getValue(); paper_top = -box_h / 2.0; slider_L.setMaximum(sheets_n); sheet_selected = (int)slider_L.getValue(); scrollbar_R.setMaximum(sheets_n * sheet_h); scrollbar_R.setExtent(box_h); paper_offset = -scrollbar_R.getValue(); // startMumble(); slider_T.setSize(abs(284.0 * cos(m)), 9.0); slider_L.setSize(9.0, abs(250.0 * sin(m))); slider_B.setSize(abs(284.0 * cos(m)), 9.0); scrollbar_R.setSize(13.0, abs(250.0 * sin(m))); // endMumble(); beginCamera(); ortho(-100.0 ,100.0, -100.0, 100.0, 100.0 , -100.0); rotateX(radians(15.0)); rotateY(radians(45.0) + abs(HALF_PI - m)); endCamera(); drawBox(); drawPaper(); m = (m + radians(1.0)) % PI; resetMatrix(); // otherwise, the following should not draw well... // leave these 4 lines around the end.... slider_T.draw(); slider_B.draw(); slider_L.draw(); scrollbar_R.draw(); } void drawBox() { stroke(255, 153, 0); fill(255, 255, 204); box(100.0, box_h, 25.0); } void drawPaper() { if (sheets_n > 0) { float y = paper_top + paper_offset; float o = 0.0; if (sheet_selected > 0) { stroke(255, 102, 0); for (float i = 1.0; i < 4.0; i++) { line(-sheet_w / 2.0 + i * sheet_w / 4.0, y + sheet_h * (float)(sheet_selected - 1), cos(PI * (float)(sheet_selected - 1)) * sheet_h / 4.0, -sheet_w / 2.0 + i * sheet_w / 4.0, y + sheet_h * (float)sheet_selected, cos(PI * (float)sheet_selected) * sheet_h / 4.0); } } stroke(127); fill(255); beginShape(QUAD_STRIP); for (int i = 0; i <= sheets_n; i++) { vertex(-sheet_w / 2.0 * cos(o), y, cos(o) * sheet_h / 4.0); vertex(sheet_w / 2.0 * cos(o), y, cos(o) * sheet_h / 4.0); y += sheet_h; o += PI; } endShape(); } } void keyPressed() // used only for debugging... { switch(key) { case 't': slider_T.setEnabled(!slider_T.getEnabled()); break; case 'b': slider_B.setEnabled(!slider_B.getEnabled()); break; case 'r': scrollbar_R.setEnabled(!scrollbar_R.getEnabled()); case 'l': slider_L.setEnabled(!slider_L.getEnabled()); break; } } // --- class AbstractSlider { static final int HORIZONTAL = 0; static final int VERTICAL = 1; static final int LEFT = 0; static final int RIGHT = 1; static final int CENTER = 2; static final int TOP = 3; static final int BOTTOM = 4; float min, max, value, extent; float old_min, old_max, old_value, old_extent; int orientation; float x, y, width, height; color color1, color2; int halign, valign; boolean enabled = true; float left, top; boolean enablable, degraded; int c1, c2; float[] knob_size = new float[2]; float[] comp = new float[2]; float[] m_comp = new float[2]; float offset; boolean armed; boolean over, over_track, over_knob; boolean knob_locked; AbstractSlider(float min, float max, float value, float extent) { // the following contraints must be satisfied: min <= value <= value+extent <= max if (!(max >= min && value >= min && (value + extent) >= value && (value + extent) <= max)) { throw new IllegalArgumentException(getClass() + ": invalid range properties"); } this.min = old_min = min; this.max = old_max = max; this.value = old_value = value; this.extent = old_extent = extent; } float getMinimum() { return min; } float getMaximum() { return max; } float getValue() { return value; } float getExtent() { return extent; } void setMinimum(float n) { max = max(n, max); value = max(n, value); extent = min(max - value, extent); min = n; updateRangeProperties(); } void setMaximum(float n) { min = min(n, min); extent = min(n - min, extent); value = min(n - extent, value); max = n; updateRangeProperties(); } void setValue(float n) { value = constrain(n, min, max - extent); updateRangeProperties(); } void setExtent(float n) { extent = constrain(n, 0.0, max - value); updateRangeProperties(); } void setColors(color color1, color color2) { this.color1 = color1; this.color2 = color2; } void setLocation(float x, float y) { setBounds(x, y, width, height); } void setSize(float width, float height) { setBounds(x, y, width, height); } void setBounds(float x, float y, float width, float height) { this.left = x - (halign == LEFT ? 0.0 : (halign == RIGHT ? width : width / 2.0));; this.top = y - (valign == TOP ? 0.0 : (valign == BOTTOM ? height : height / 2.0)); this.x = x; this.y = y; this.width = width; this.height = height; recalc(); } boolean getEnabled() { return enabled; } void setEnabled(boolean b) { enabled = b; } void updateRangeProperties() { enablable = getIsEnablable(); rangePropertiesChanged(); updateComp(); updateExtent(); } boolean getIsEnablable() // always overriden... { return true; } void updateComp() {} void updateExtent() {} void recalc() {} void rangePropertiesChanged() { // this is the place to implement an event-posting system... } } class Slider extends AbstractSlider { float tickSpacing; boolean paintTicks, snapToTicks; float[] pos = new float[2], size = new float[2]; Slider(float x, float y, float width, float height, int halign, int valign, int orientation, float min, float max, float value, color color1, color color2) { super(min, max, value, 0.0); // extent is not relevant here... this.halign = halign; this.valign = valign; this.orientation = orientation; this.color1 = color1; this.color2 = color2; c1 = orientation; c2 = 1 - c1; enablable = getIsEnablable(); setBounds(x, y, width, height); } void setValue(float n) { if (snapToTicks) { n = min + round((n - min) / tickSpacing) * tickSpacing; } super.setValue(n); } void setTickSpacing(float spacing) { tickSpacing = spacing; } void setPaintTicks(boolean b) { paintTicks = b; } void setSnapToTicks(boolean b) { snapToTicks = b; } void run() { knob_locked = knob_locked && enablable && enabled && mousePressed; m_comp[0] = mouseX; m_comp[1] = mouseY; over_track = !degraded && enablable && enabled && !armed && !knob_locked && mousePressed && (m_comp[0] >= pos[0] && m_comp[0] < (pos[0] + size[0]) && m_comp[1] >= pos[1] && m_comp[1] < (pos[1] + size[1])); armed = !over_track && mousePressed; if (degraded) { return; } over_knob = over_track && (m_comp[c1] >= (pos[c1] + comp[c1]) && m_comp[c1] < (pos[c1] + comp[c1] + knob_size[c1]) && m_comp[c2] >= (pos[c2] + comp[c2]) && m_comp[c2] < (pos[c2] + comp[c2] + knob_size[c2])); if (over_knob) { knob_locked = true; offset = (m_comp[c1] - pos[c1] - comp[c1]) * (max - min) / (size[c1] - knob_size[c1]); } else if (over_track) { setValue(min + (m_comp[c1] - pos[c1] - knob_size[c1] / 2.0) * (max - min) / (size[c1] - knob_size[c1])); } if (knob_locked) { setValue((m_comp[c1] - pos[c1]) * (max - min) / (size[c1] - knob_size[c1]) - offset); } } boolean getIsEnablable() { return max != min; } void updateComp() { if (!degraded) { comp[c1] = !enablable ? 0.0 : (value - min) / (max - min) * (size[c1] - knob_size[c1]); } } void recalc() { size[c1] = orientation == HORIZONTAL ? width : height; size[c2] = orientation == HORIZONTAL ? height : width; pos[c1] = orientation == HORIZONTAL ? left : top; pos[c2] = orientation == HORIZONTAL ? top : left; knob_size[c1] = size[c2]; knob_size[c2] = size[c2]; degraded = knob_size[c1] > size[c1]; // gracefull degradation at paranormal sizes updateComp(); comp[c2] = 0.0; } void draw() { drawTrack(); if (!degraded) { drawKnob(); if (paintTicks) { drawTicks(); } } } void drawTrack() { noStroke(); rectMode(CORNER); fill(color2); rect(pos[0], pos[1], size[0], size[1]); fill(color1); if (orientation == HORIZONTAL) { rect(pos[0], pos[1] + size[1] / 2.0, size[0], 1.0); } else { rect(pos[0] + size[0] / 2.0, pos[1], 1.0, size[1]); } } void drawTicks() { float s = tickSpacing / (max - min) * (size[c1] - knob_size[c1]); if (s >= 2.0) { noStroke(); rectMode(CENTER_DIAMETER); float o = pos[c1] + knob_size[c1] / 2.0; float i; int n_ticks = (int)((max - min) / tickSpacing); for (int n = 0; n <= n_ticks; n++) { i = o + n * s; if (knob_locked && i >= pos[c1] + comp[c1] && i < pos[c1] + comp[c1] + knob_size[c1]) { fill(color2); } else { fill(color1); } if (orientation == HORIZONTAL) { rect(i, pos[1] + size[1] / 2.0, 1.0, size[1] / 3.0); } else { rect(pos[0] + size[0] / 2.0, i, size[0] / 3.0, 1.0); } } } } void drawKnob() { stroke(color1); fill(knob_locked ? color1 : color2); rectMode(CORNER); rect(pos[0] + comp[0], pos[1] + comp[1], knob_size[0] - 1.0, knob_size[1] - 1.0); } } class ScrollBar extends AbstractSlider { boolean hasButtons; float unitIncrement = 1.0; float blockIncrement; boolean dirtyBI; float[] track_pos = new float[2], track_size = new float[2]; float[] button_plus_pos = new float[2], button_size = new float[2]; float direction; boolean over_button_minus, over_button_plus; boolean track_locked, button_minus_locked, button_plus_locked; ScrollBar(float x, float y, float width, float height, int halign, int valign, int orientation, float min, float max, float value, float extent, color color1, color color2, boolean hasButtons) { super(min, max, value, extent); this.halign = halign; this.valign = valign; this.orientation = orientation; this.color1 = color1; this.color2 = color2; this.hasButtons = hasButtons; c1 = orientation; c2 = 1 - c1; enablable = getIsEnablable(); setBounds(x, y, width, height); } void setUnitIncrement(float unitIncrement) { this.unitIncrement = unitIncrement; } void setBlockIncrement(float blockIncrement) { this.blockIncrement = blockIncrement; dirtyBI = true; } void run() { button_minus_locked = enablable && enabled && hasButtons && button_minus_locked && mousePressed; button_plus_locked = enablable && enabled && hasButtons && button_plus_locked && mousePressed; track_locked = enablable && enabled && track_locked && mousePressed; knob_locked = enablable && enabled && knob_locked && mousePressed; m_comp[0] = mouseX; m_comp[1] = mouseY; over = !degraded && enablable && enabled && !armed && !button_minus_locked && !button_plus_locked && !track_locked && !knob_locked && mousePressed && (m_comp[0] >= left && m_comp[0] < (left + width) && m_comp[1] >= top && m_comp[1] < (top + height)); armed = !over && mousePressed; if (degraded) { return; } over_button_minus = hasButtons && over && (m_comp[0] >= left && m_comp[0] < (left + button_size[0]) && m_comp[1] >= top && m_comp[1] < (top + button_size[1])); over_button_plus = hasButtons &&over && !over_button_minus && (m_comp[c1] >= button_plus_pos[c1] && m_comp[c1] < (button_plus_pos[c1] + button_size[c1]) && m_comp[c2] >= button_plus_pos[c2] && m_comp[c2] < (button_plus_pos[c2] + button_size[c2])); over_track = over && !over_button_minus && !over_button_plus && (m_comp[c1] >= track_pos[c1] && m_comp[c1] < (track_pos[c1] + track_size[c1]) && m_comp[c2] >= track_pos[c2] && m_comp[c2] < (track_pos[c2] + track_size[c2])); over_knob = over_track && (m_comp[c1] >= (track_pos[c1] + comp[c1]) && m_comp[c1] < (track_pos[c1] + comp[c1] + knob_size[c1]) && m_comp[c2] >= top && (m_comp[c2] + comp[c2]) < (track_pos[c2] + comp[c2] + knob_size[c2])); if (over_button_minus) { button_minus_locked = true; direction = -1.0; } else if (over_button_plus) { button_plus_locked = true; direction = 1.0; } else if (over_knob) { knob_locked = true; offset = (m_comp[c1] - track_pos[c1] - comp[c1]) * (max - min) / track_size[c1]; } else if (over_track) { track_locked = true; direction = (m_comp[c1] - track_pos[c1] > comp[c1]) ? 1.0 : -1.0; } if (button_minus_locked || button_plus_locked) { setValue(value + unitIncrement * direction); } else if (track_locked) { if ((direction < 0.0 && m_comp[c1] - track_pos[c1] < comp[c1]) || (direction > 0.0 && m_comp[c1] - track_pos[c1] - knob_size[c1] > comp[c1])) { setValue(value + blockIncrement * direction); } } else if (knob_locked) { setValue((m_comp[c1] - track_pos[c1]) * (max - min) / track_size[c1] - offset); } } boolean getIsEnablable() { return !(extent == 0.0 || extent == max); } void updateComp() { if (!degraded) { comp[c1] = !enablable ? 0.0 : (value - min) / (max - min) * track_size[c1]; } } void updateExtent() { if (!degraded) { knob_size[c1] = !enablable ? 0.0 : extent * track_size[c1] / (max - min); if (!dirtyBI) { blockIncrement = extent; } } } void recalc() { static final float gap = 1.0; if (hasButtons) { button_size[c1] = orientation == HORIZONTAL ? height : width; button_size[c2] = button_size[c1]; button_plus_pos[c1] = (orientation == HORIZONTAL ? (left + width) : (top + height)) - button_size[c1]; button_plus_pos[c2] = (orientation == HORIZONTAL ? top : left); } track_size[c1] = (orientation == HORIZONTAL ? width : height) - (hasButtons ? (2.0 * (button_size[c1] + gap)) : 0.0); track_size[c2] = orientation == HORIZONTAL ? height : width; track_pos[c1] = (orientation == HORIZONTAL ? left : top) + (hasButtons ? (button_size[c1] + gap) : 0.0); track_pos[c2] = orientation == HORIZONTAL ? top : left; degraded = track_size[c1] < 1.0; // gracefull degradation at paranormal sizes if (degraded) { if (hasButtons && track_size[c1] < -2.0 * gap) { button_size[c1] = (orientation == HORIZONTAL ? width : height) / 2.0; button_plus_pos[c1] = (orientation == HORIZONTAL ? (left + width) : (top + height)) - button_size[c1]; } } updateExtent(); knob_size[c2] = track_size[c2]; updateComp(); comp[c2] = 0.0; } void draw() { if (hasButtons) { drawButton(left, top, (orientation == HORIZONTAL ? 0 : 2) + 0, button_minus_locked); drawButton(button_plus_pos[0], button_plus_pos[1], (orientation == HORIZONTAL ? 0 : 2) + 1, button_plus_locked); } if (!degraded) { drawTrack(); if (enablable && enabled) { drawKnob(); } } } void drawButton(float l, float t, int dir, boolean isLocked) { static final float[][] m = {{1.5f, 3.0f, 3.0f, 2.0f, 1.5f, 1.5f}, {3.0f, 3.0f, 1.5f, 2.0f, 3.0f, 1.5f}, {3.0f, 1.5f, 2.0f, 3.0f, 1.5f, 1.5f}, {3.0f, 3.0f, 2.0f, 1.5f, 1.5f, 3.0f}}; stroke(color1); fill(isLocked ? color1 : color2); rectMode(CORNER); rect(l, t, button_size[0] - 1.0, button_size[1] - 1.0); stroke(isLocked ? color2 : color1); beginShape(LINE_STRIP); vertex(l + button_size[0] / m[dir][0], t + button_size[1] / m[dir][1]); vertex(l + button_size[0] / m[dir][2], t + button_size[1] / m[dir][3]); vertex(l + button_size[0] / m[dir][4], t + button_size[1] / m[dir][5]); endShape(); } void drawTrack() { noStroke(); fill(track_locked ? (direction > 0.0 ? color1 : color2) : color2); rectMode(CORNER); rect(track_pos[0], track_pos[1], track_size[0], track_size[1]); if (track_locked) { fill(direction > 0.0 ? color2 : color1); if (orientation == HORIZONTAL) { rect(track_pos[0], track_pos[1], comp[0], track_size[1]); } else { rect(track_pos[0], track_pos[1], track_size[0], comp[1]); } } } void drawKnob() { stroke(color1); fill(knob_locked ? color1 : color2); rectMode(CORNER); rect(track_pos[0] + comp[0], track_pos[1] + comp[1], knob_size[0] - 1.0, knob_size[1] - 1.0); } }