1 module nanogui.slider; 2 /* 3 nanogui.slider.d -- Fractional slider widget with mouse control 4 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>. 5 The widget drawing code is based on the NanoVG demo application 6 by Mikko Mononen. 7 All rights reserved. Use of this source code is governed by a 8 BSD-style license that can be found in the LICENSE.txt file. 9 */ 10 11 import std.algorithm : min, max; 12 13 import arsd.nanovega; 14 import nanogui.widget : Widget; 15 import nanogui.common : Vector2i, MouseButton, Color, boxGradient, 16 fillColor, Vector2f, radialGradient, linearGradient; 17 18 /// Fractional slider widget with mouse control 19 class Slider(T) : Widget { 20 import std.typecons : tuple, Tuple; 21 import std.datetime : dur, Duration; 22 public: 23 24 import std.traits : isIntegral, isFloatingPoint; 25 26 static assert(isIntegral!T || isFloatingPoint!T || is(T == Duration)); 27 28 this(Widget parent) 29 { 30 super(parent); 31 static if (is(T == Duration)) 32 { 33 mValue = dur!"seconds"(0); 34 mRange = tuple(dur!"seconds"(0), dur!"seconds"(100)); 35 } 36 else 37 { 38 mValue = cast(T) 0; 39 mRange = tuple(0, 100); 40 } 41 42 highlightedRange = tuple(mRange[0], mValue); 43 mHighlightColor = Color(255, 80, 80, 70); 44 mPreferredWidth = 70; 45 } 46 47 final T value() const { return mValue; } 48 final void value(T value) 49 { 50 if (value < mRange[0]) 51 return; 52 if (value > mRange[1]) 53 return; 54 mValue = value; 55 if (mCallback) 56 mCallback(mValue); 57 } 58 59 final ref const(Color) highlightColor() const { return mHighlightColor; } 60 final void highlightColor(ref const(Color) highlightColor) { mHighlightColor = highlightColor; } 61 62 final Tuple!(T, T) range() const { return mRange; } 63 final void range(T lo, T hi) 64 { 65 import std.exception : enforce; 66 enforce(lo < hi, "Low boundary should be less than right boundary"); 67 mRange = tuple(lo, hi); 68 } 69 70 final void range(Tuple!(T, T) range) { mRange = range; } 71 72 final Tuple!(T, T) highlightedRange() const 73 { 74 static if(is(T == Duration)) 75 { 76 const total = (mRange[1] - mRange[0]).total!"hnsecs"; 77 T l = dur!"hnsecs"(cast(long)(mHighlightedRange[0]*total)) + mRange[0]; 78 T h = dur!"hnsecs"(cast(long)(mHighlightedRange[1]*total)) + mRange[0]; 79 } 80 else 81 { 82 const total = (mRange[1] - mRange[0]); 83 T l = cast(T) (mHighlightedRange[0]*total + mRange[0]); 84 T h = cast(T) (mHighlightedRange[1]*total + mRange[0]); 85 } 86 return tuple(l, h); 87 } 88 89 final void highlightedRange(Tuple!(T, T) highlightedRange) 90 { 91 static if(is(T == Duration)) 92 { 93 const total = (mRange[1] - mRange[0]).total!"hnsecs"; 94 mHighlightedRange[0] = (highlightedRange[0] - mRange[0]).total!"hnsecs"/cast(double) total; 95 mHighlightedRange[1] = (highlightedRange[1] - mRange[0]).total!"hnsecs"/cast(double) total; 96 } 97 else 98 { 99 const total = (mRange[1] - mRange[0]); 100 mHighlightedRange[0] = (highlightedRange[0] - mRange[0])/cast(double) total; 101 mHighlightedRange[1] = (highlightedRange[1] - mRange[0])/cast(double) total; 102 } 103 } 104 105 final bool delegate(T) callback() const { return mCallback; } 106 final void callback(bool delegate(T) callback) { mCallback = callback; } 107 108 final bool delegate(T) finalCallback() const { return mCallback; } 109 final void finalCallback(bool delegate(T) callback) { mCallback = callback; } 110 111 final void preferredWidth(int width) 112 { 113 mPreferredWidth = width; 114 } 115 116 override Vector2i preferredSize(NanoContext ctx) const 117 { 118 return Vector2i(mPreferredWidth, 16); 119 } 120 121 private auto calculateNewValue(const Vector2i p) 122 { 123 const double kr = cast(int) (mSize.y * 0.4f); 124 const double kshadow = 3; 125 const double startX = kr + kshadow + mPos.x - 1; 126 const double widthX = mSize.x - 2 * (kr + kshadow); 127 128 static if (is(T == Duration)) 129 { 130 import std.conv : to; 131 132 // used to save precision during converting from floating to int types 133 enum factor = 2^^13; 134 const v1 = factor*(p.x - startX) / cast(double) widthX; 135 const v2 = to!long(v1); 136 const v3 = (mRange[1] - mRange[0])*v2/factor + mRange[0]; 137 mValue = min(max(v3, mRange[0]), mRange[1]); 138 } 139 else 140 { 141 double v = (p.x - startX) / cast(double) widthX; 142 v = v * (mRange[1] - mRange[0]) + mRange[0]; 143 mValue = cast(T) min(max(v, mRange[0]), mRange[1]); 144 } 145 } 146 147 override bool mouseDragEvent(const Vector2i p, const Vector2i rel, MouseButton button, int modifiers) 148 { 149 if (!mEnabled) 150 return false; 151 152 calculateNewValue(p); 153 154 if (mCallback) 155 mCallback(mValue); 156 return true; 157 } 158 159 override bool mouseButtonEvent(const Vector2i p, MouseButton button, bool down, int modifiers) 160 { 161 if (!mEnabled) 162 return false; 163 164 calculateNewValue(p); 165 166 if (mCallback) 167 mCallback(mValue); 168 if (mFinalCallback && !down) 169 mFinalCallback(mValue); 170 return true; 171 } 172 173 override void draw(NanoContext ctx) 174 { 175 Vector2f center = cast(Vector2f) mPos + 0.5f * cast(Vector2f) mSize; 176 float kr = cast(int) (mSize.y * 0.4f); 177 float kshadow = 3; 178 179 float startX = kr + kshadow + mPos.x; 180 float widthX = mSize.x - 2*(kr+kshadow); 181 182 static if (is(T == Duration)) 183 { 184 auto knobPos = Vector2f(startX + (mValue - mRange[0]).total!"hnsecs" / 185 cast(double)(mRange[1] - mRange[0]).total!"hnsecs" * widthX, 186 center.y + 0.5f); 187 } 188 else 189 { 190 auto knobPos = Vector2f(startX + (mValue - mRange[0]) / 191 cast(double)(mRange[1] - mRange[0]) * widthX, 192 center.y + 0.5f); 193 } 194 195 NVGPaint bg = ctx.boxGradient( 196 startX, center.y - 3 + 1, widthX, 6, 3, 3, 197 Color(0, 0, 0, mEnabled ? 32 : 10), Color(0, 0, 0, mEnabled ? 128 : 210)); 198 199 ctx.beginPath; 200 ctx.roundedRect(startX, center.y - 3 + 1, widthX, 6, 2); 201 ctx.fillPaint(bg); 202 ctx.fill; 203 204 if (mHighlightedRange[1] != mHighlightedRange[0]) { 205 ctx.beginPath; 206 ctx.roundedRect(startX + mHighlightedRange[0] * mSize.x, 207 center.y - kshadow + 1, 208 widthX * (mHighlightedRange[1] - mHighlightedRange[0]), 209 kshadow * 2, 2); 210 ctx.fillColor(mHighlightColor); 211 ctx.fill; 212 } 213 214 NVGPaint knobShadow = 215 ctx.radialGradient(knobPos.x, knobPos.y, kr - kshadow, 216 kr + kshadow, Color(0, 0, 0, 64), mTheme.mTransparent); 217 218 ctx.beginPath; 219 ctx.rect(knobPos.x - kr - 5, knobPos.y - kr - 5, kr * 2 + 10, 220 kr * 2 + 10 + kshadow); 221 ctx.circle(knobPos.x, knobPos.y, kr); 222 ctx.pathWinding(NVGSolidity.Hole); 223 ctx.fillPaint(knobShadow); 224 ctx.fill; 225 226 NVGPaint knob = ctx.linearGradient( 227 mPos.x, center.y - kr, mPos.x, center.y + kr, 228 mTheme.mBorderLight, mTheme.mBorderMedium); 229 NVGPaint knobReverse = ctx.linearGradient( 230 mPos.x, center.y - kr, mPos.x, center.y + kr, 231 mTheme.mBorderMedium, 232 mTheme.mBorderLight); 233 234 ctx.beginPath; 235 ctx.circle(knobPos.x, knobPos.y, kr); 236 with (mTheme.mBorderDark) 237 ctx.strokeColor(nvgRGBA(cast(int)(r/255), cast(int)(g/255), cast(int)(b/255), cast(int)(a/255))); 238 ctx.fillPaint(knob); 239 ctx.stroke; 240 ctx.fill; 241 ctx.beginPath; 242 ctx.circle(knobPos.x, knobPos.y, kr/2); 243 ctx.fillColor(Color(150, 150, 150, mEnabled ? 255 : 100)); 244 ctx.strokePaint(knobReverse); 245 ctx.stroke; 246 ctx.fill; 247 } 248 // void save(Serializer &s) const; 249 // bool load(Serializer &s); 250 251 protected: 252 T mValue; 253 bool delegate(T) mCallback; 254 bool delegate(T) mFinalCallback; 255 Tuple!(T, T) mRange; 256 Tuple!(float, float) mHighlightedRange; 257 Color mHighlightColor; 258 int mPreferredWidth; 259 } 260 261 // void Slider::save(Serializer &s) const { 262 // Widget::save(s); 263 // s.set("value", mValue); 264 // s.set("range", mRange); 265 // s.set("highlightedRange", mHighlightedRange); 266 // s.set("highlightColor", mHighlightColor); 267 // } 268 269 // bool Slider::load(Serializer &s) { 270 // if (!Widget::load(s)) return false; 271 // if (!s.get("value", mValue)) return false; 272 // if (!s.get("range", mRange)) return false; 273 // if (!s.get("highlightedRange", mHighlightedRange)) return false; 274 // if (!s.get("highlightColor", mHighlightColor)) return false; 275 // return true; 276 // }