public class Slider
{
  float x, y, width, height, labelWidth;
  float valueX = 0;
  float value;
  String label;
  boolean visible = true;
  boolean isPressed = false;
  int sliderId;
  boolean updating = false;
  
  Slider ( float xx, float yy, float ww, float hh, String _label, float _labelWidth, int id) 
  {
    x = xx; 
    y = yy; 
    this.width = ww; 
    this.height = hh;
    
    valueX = x;
    label = _label;
    labelWidth = _labelWidth;
    sliderId = id;
  
    // register it with Guido
    Interactive.add( this );
  }
  
  void mousePressed() {
    isPressed = true;
  }
  
  void dragged()
  {
    if (updating) { return; }
    
    valueX = mouseX - this.height/2.0; // height is also the width of the slider bubble
    if ( valueX < x ) valueX = x;
    if ( valueX > x+this.width-this.height ) valueX = x+this.width-this.height;
    
    value = map( valueX, x, x+this.width-this.height, 0, 1 );
    updateFromSlider();
  }
  
  void draw () 
  {
    if (!menuVisible) {
      return;
    }
    
    if (x <= mouseX && mouseX <= x+this.width &&
        y <= mouseY && mouseY <= y+this.height) {
      strokeWeight(1.5);
      stroke(ds.GUIColor);
    } else {
      noStroke();
    }
    rectMode(CORNER);        
    fill(0,0,0);
    rect(x, y, this.width, this.height, 4);
    fill(ds.GUIColor);
    rect(valueX, y, this.height, this.height, 3);
    fill(0,0,0); // return to black
    
    if (valueX > x+(this.height/2.0)) { // "thermostat" line
      stroke(ds.GUIColor);
      strokeWeight(2.5);
      line( x + this.height/2.0, y + this.height/2.0, valueX, y + this.height/2.0 );
      stroke(#000000);
    }
    
    if (label != null) {
      textSize(ds.menuTextSize);
      textAlign(LEFT);
      text(label, x-labelWidth, y+this.height);
    }
    
    if (isPressed) { dragged(); }
  }
  
  void setValue(float _value) {
    value = _value;
    valueX = map( value, 0, 1, x, x+this.width-this.height );
  }
  
  void updateFromSlider() {
    // Todo: add Id, only update the specific slider being dragged
    
    if (sliderId == 0) { // radius
      ff.radius = ff.minRadius + sliders[sliderId].value * ff.radiusScale;
    }
    
    else if (sliderId == 1) { // Bond distance
      double newBpDist = ff.minBpDist + (sliders[sliderId].value * ff.bpDistScale);
      
      // Directly move residues when not in simulation mode
      if (!simulationMode) {
        double distChange = (newBpDist - ff.bpDist);
        if (!updating && (distChange >= .5 || distChange <= -.5)) {
          updating = true;
          SphereList spheres = currentState.getSpheres();
        
          for (Sphere2D s : spheres) {
            if (s.pairedWForce != null && s.id < s.pairedWForce.id) {
              Sphere2D pairedBase = s.pairedWForce;
              double pairDist = s.distance(pairedBase);
              double moveDist = distChange / 2.0;
              
              if (distChange < 0) {
                if (pairDist <= ff.minBpDist) { // If bond distance is below the minimum
                  continue;
                }
                else if ((pairDist + distChange) < ff.minBpDist) { // If change would take it past the minimum
                  moveDist = (ff.minBpDist - pairDist) / 2.0;
                }
              }
              
              double moveX = (moveDist * (s.x - pairedBase.x)) / pairDist;
              s.x += moveX;
              pairedBase.x -= moveX;
              
              double moveY = (moveDist * (s.y - pairedBase.y)) / pairDist;
              s.y += moveY;
              pairedBase.y -= moveY;
            }
          }
          ff.bpDist = newBpDist;
          updating = false;
        }
      }
      else {
        ff.bpDist = newBpDist;
      }
    }
    
    else if (sliderId == 2) { // Chain length
      ff.backboneDist = (ff.minBackboneDist) + sliders[sliderId].value * ff.backboneDistScale;
    }
    
    else if (sliderId == 3) { // Color mode
      int prevColor = ds.colorMode;
      ds.colorMode = int(sliders[sliderId].value * (DisplaySettings.COLOR_MODE_MAX - 1)) + 1;
      if (ds.colorMode >= DisplaySettings.COLOR_MODE_MAX) ds.colorMode = DisplaySettings.COLOR_MODE_MAX - 1;
      if (prevColor != ds.colorMode) {
        displayColorMode();
      }
    }
    
    else {
      println("Warning: invalid sliderId: " + sliderId);
    }
    
    // ds.padding = sliders[4].value * ds.paddingScale; // Screen boundary size
    // ff.jitterWeight = sliders[5].value * 5000; // temperature
  }
}