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}