001package polaris.sdk.ui.controls;
002import polaris.sdk.ui.*;
003import java.awt.event.KeyEvent;
004
005/** Full-width continuous drag, pointer capture via Ui, monotonic visual following. */
006public class Slider extends Widget {
007 protected double min=0,max=100,value=50,step=1;
008 protected boolean snapToStep,pointerMoved;protected float downX;
009 protected float dragOffset,shownFraction=.5f;
010 private boolean painted;
011 public Slider(){focusable=true;w=230;h=32;}
012 public Slider range(double min,double max){if(!Double.isFinite(min)||!Double.isFinite(max)||max<=min)throw new IllegalArgumentException("range");this.min=min;this.max=max;value(value);return this;}
013 /** Explicit step enables quantization; defaults are continuous for pointer input. */
014 public Slider step(double step){if(!Double.isFinite(step)||step<=0)throw new IllegalArgumentException("step");this.step=step;snapToStep=true;return this;}
015 public Slider continuous(){snapToStep=false;return this;}
016 public Slider value(double v){if(!Double.isFinite(v))throw new IllegalArgumentException("value");value=Math.max(min,Math.min(max,v));return this;}
017 public double value(){return value;}
018 public boolean discrete(){return snapToStep;}
019 protected float fraction(){return (float)((value-min)/(max-min));}
020 protected float thumbSize(){return Math.max(6,Math.min(25,h-4));}
021 protected float inset(){return Math.min(w*.5f,thumbSize()*.5f+2);}
022 protected float trackStart(){return ax+inset();}
023 protected float trackLength(){return Math.max(.0001f,w-2*inset());}
024 protected double pointerValue(float px){double f=Math.max(0,Math.min(1,(px-trackStart())/trackLength()));if(f<=0)return min;if(f>=1)return max;double v=min+f*(max-min);return snapToStep?Math.max(min,Math.min(max,min+Math.round((v-min)/step)*step)):v;}
025 protected void setFromPointer(float px){double old=value;value(pointerValue(px));if(old!=value)changed();}
026 protected float displayedFraction(){return painted?shownFraction:fraction();}
027 @Override public void event(UiEvent e){
028  if(e.type==UiEvent.Type.DOWN&&e.button==1){
029   downX=e.x;pointerMoved=false;
030   float thumb=trackStart()+trackLength()*displayedFraction();
031   boolean grabbed=Math.abs(e.x-thumb)<=thumbSize()/2+3;
032   dragOffset=grabbed?e.x-thumb:0;pressed=true;
033   if(!grabbed)setFromPointer(e.x);e.handled=true;
034  }else if(e.type==UiEvent.Type.MOVE&&pressed){pointerMoved|=Math.abs(e.x-downX)>.0001f;if(pointerMoved)setFromPointer(e.x-dragOffset);e.handled=true;
035  }else if(e.type==UiEvent.Type.UP&&e.button==1&&pressed){if(pointerMoved||Math.abs(e.x-downX)>.0001f)setFromPointer(e.x-dragOffset);pressed=false;e.handled=true;
036  }else if(e.type==UiEvent.Type.KEY){double old=value;switch(e.key){
037   case KeyEvent.VK_LEFT:case KeyEvent.VK_DOWN:value(value-step);break;
038   case KeyEvent.VK_RIGHT:case KeyEvent.VK_UP:value(value+step);break;
039   case KeyEvent.VK_HOME:value(min);break;case KeyEvent.VK_END:value(max);break;default:return;
040   }if(old!=value)changed();e.handled=true;
041  }else if(e.type==UiEvent.Type.BLUR){pressed=false;dragOffset=0;}
042 }
043 protected void drawTrack(Painter p,float lo,float hi){
044  float cy=ay+h/2,trackH=Math.max(3,Math.min(15,h*.48f));
045  p.fill(ax,cy-trackH/2,w,trackH,trackH/2,theme().field);
046  float a=lo<=0?ax:trackStart()+trackLength()*lo;
047  float b=hi>=1?ax+w:trackStart()+trackLength()*hi;
048  if(hi>lo)p.fill(a,cy-trackH/2,Math.max(0,b-a),trackH,trackH/2,Theme.alpha(theme().accent,.76f));
049 }
050 protected void drawThumb(Painter p,float f,float pressure){
051  float size=thumbSize()+pressure*1.6f+hoverAmount()*.4f,cx=trackStart()+trackLength()*f,cy=ay+h/2;
052  glassPart(p,cx-size/2,cy-size/2,size,size,size/2,.57f+pressure*.025f,theme().accent,1,theme().blur);
053 }
054 @Override protected void paint(Painter p){
055  shownFraction=MotionValue.clamp(follow("dragValue",fraction(),70));painted=true;
056  drawTrack(p,0,shownFraction);drawThumb(p,shownFraction,pressAmount());
057 }
058}