1 module pixelperfectengine.concrete.elements.scrollbar; 2 3 public import pixelperfectengine.concrete.elements.base; 4 import std.math : isNaN, nearbyint; 5 import pixelperfectengine.system.timer; 6 7 abstract class ScrollBar : WindowElement{ 8 protected static enum PLUS_PRESSED = 1<<9; 9 protected static enum MINUS_PRESSED = 1<<10; 10 protected static enum SCROLLMATIC = 1<<11; 11 protected int _value, _maxValue, _barLength; 12 //protected double largeVal; ///Set to double.nan if value is less than travellength, or the ratio between 13 protected double valRatio; ///Ratio between the travel length and the maximum value 14 protected double barLength0; 15 public void delegate(Event ev) onScrolling; ///Called shen the scrollbar's value is changed 16 17 /** 18 * Returns the slider position. 19 */ 20 public @property int value() @nogc @safe pure nothrow const { 21 return _value; 22 } 23 /** 24 * Sets the slider position, and redraws the slider. 25 * Returns the new slider position. 26 */ 27 public @property int value(int val) { 28 if (val < 0) _value = 0; 29 else if (val < _maxValue) _value = val; 30 else _value = _maxValue; 31 draw(); 32 if (onScrolling !is null) 33 onScrolling(new Event(this, EventType.MouseScroll, SourceType.WindowElement)); 34 return _value; 35 } 36 /** 37 * Returns the maximum value of the slider. 38 */ 39 public @property int maxValue() @nogc @safe pure nothrow const { 40 return _maxValue; 41 } 42 /** 43 * Sets the new maximum value and bar lenght of the slider. 44 * Position is kept or lowered if maximum is reached. 45 */ 46 public @property int maxValue(int val) { 47 assert(val >= 0, "Value must be positive!"); 48 const int iconSize = position.width < position.height ? position.width : position.height; 49 const int length = position.width > position.height ? position.width : position.height; 50 _maxValue = val; 51 if (_value > _maxValue) _value = _maxValue; 52 barLength0 = (length - iconSize * 2) / cast(double)(val + 1); 53 _barLength = barLength0 < 1.0 ? 1 : cast(int)nearbyint(barLength0); 54 //largeVal = barLength0 < 1.0 ? 1.0 / barLength0 : double.nan; 55 valRatio = 1.0 / barLength0; 56 return _maxValue; 57 } 58 /** 59 * Returns the length of the scrollbar in pixels. 60 * 61 * Automatically calculated every time maxValue is changed. 62 */ 63 public @property int barLength() @nogc @safe pure nothrow const { 64 return _barLength; 65 } 66 protected void timerEvent() nothrow { 67 try { 68 if (flags & PLUS_PRESSED) { 69 value = value + 1; 70 draw; 71 flags |= SCROLLMATIC; 72 registerTimer(); 73 } else if (flags & MINUS_PRESSED) { 74 value = value - 1; 75 draw; 76 flags |= SCROLLMATIC; 77 registerTimer(); 78 } 79 } catch (Exception e) { 80 81 } 82 } 83 protected void registerTimer() nothrow { 84 timer.register(&timerEvent, msecs(flags & SCROLLMATIC ? 50 : 1000)); 85 } 86 } 87 /** 88 * Vertical scroll bar. 89 */ 90 public class VertScrollBar : ScrollBar { 91 //public int[] brush; 92 93 //private int value, maxValue, barLength; 94 95 public this(int maxValue, string source, Box position){ 96 this.position = position; 97 this.source = source; 98 this.maxValue = maxValue; 99 } 100 public override void draw(){ 101 StyleSheet ss = getStyleSheet(); 102 //draw background 103 parent.drawFilledBox(position, ss.getColor("SliderBackground")); 104 //draw slider 105 //const int travelLength = position.height - (position.width * 2) - _barLength; 106 Box slider; 107 const int value0 = valRatio < 1.0 ? value : cast(int)(value / valRatio); 108 slider.left = position.left; 109 slider.right = position.right; 110 slider.top = position.top + position.width + cast(int)nearbyint(barLength0 * value0); 111 slider.bottom = slider.top + _barLength; 112 parent.drawFilledBox(slider, ss.getColor("SliderColor")); 113 if (isFocused) { 114 parent.drawBoxPattern(slider, ss.pattern["blackDottedLine"]); 115 } 116 //draw buttons 117 parent.bitBLT(position.cornerUL, flags & MINUS_PRESSED ? ss.getImage("upArrowB") : ss.getImage("upArrowA")); 118 Point lower = position.cornerLL; 119 lower.y -= position.width; 120 parent.bitBLT(lower, flags & PLUS_PRESSED ? ss.getImage("downArrowB") : ss.getImage("downArrowA")); 121 if (state == ElementState.Disabled) { 122 parent.bitBLTPattern(position, ss.getImage("ElementDisabledPtrn")); 123 } 124 if (onDraw !is null) { 125 onDraw(); 126 } 127 } 128 public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) { 129 if (state != ElementState.Enabled) return; 130 if (mce.button == MouseButton.Left) { 131 if (mce.y < position.width) { 132 if (!(flags & MINUS_PRESSED) && mce.state == ButtonState.Pressed) { 133 value = _value - 1; 134 flags |= MINUS_PRESSED; 135 registerTimer(); 136 } else if (flags & MINUS_PRESSED && mce.state == ButtonState.Released) { 137 flags &= ~(MINUS_PRESSED | SCROLLMATIC); 138 } 139 } else if (mce.y >= position.height - position.width) { 140 if (!(flags & PLUS_PRESSED) && mce.state == ButtonState.Pressed) { 141 value = _value + 1; 142 flags |= PLUS_PRESSED; 143 registerTimer(); 144 } else if (flags & PLUS_PRESSED && mce.state == ButtonState.Released) { 145 flags &= ~(PLUS_PRESSED | SCROLLMATIC); 146 } 147 } else { 148 import std.math : nearbyint; 149 const double newVal = mce.y - position.width - (_barLength / 2.0); 150 if (newVal >= 0) { 151 //const int travelLength = position.height - (position.width * 2) - _barLength; 152 //const double valRatio = isNaN(largeVal) ? 1.0 : largeVal; 153 //value = cast(int)nearbyint((travelLength / newVal) * valRatio); 154 value = cast(int)nearbyint((newVal) * valRatio); 155 } 156 } 157 } 158 super.passMCE(mec, mce); 159 } 160 public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) { 161 if (state != ElementState.Enabled) return; 162 if (mme.buttonState == MouseButtonFlags.Left && mme.y > position.height && mme.y < position.width - position.height) { 163 value = _value = mme.relY; 164 draw(); 165 } 166 super.passMME(mec, mme); 167 } 168 public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) { 169 if (state != ElementState.Enabled) return; 170 value = _value - mwe.y; 171 super.passMWE(mec, mwe); 172 } 173 174 } 175 /** 176 * Horizontal scrollbar. 177 */ 178 public class HorizScrollBar : ScrollBar { 179 public this(int maxValue, string source, Box position){ 180 this.position = position; 181 this.source = source; 182 this.maxValue = maxValue; 183 } 184 public override void draw(){ 185 StyleSheet ss = getStyleSheet(); 186 //draw background 187 parent.drawFilledBox(position, ss.getColor("SliderBackground")); 188 //draw slider 189 //const int travelLength = position.width - position.height * 2; 190 const int value0 = valRatio < 1.0 ? value : cast(int)(value / valRatio); 191 Box slider; 192 slider.top = position.top; 193 slider.bottom = position.bottom; 194 slider.left = position.left + position.height + cast(int)nearbyint(barLength0 * value0); 195 slider.right = slider.left + _barLength; 196 parent.drawFilledBox(slider, ss.getColor("SliderColor")); 197 if (isFocused) { 198 parent.drawBoxPattern(slider, ss.pattern["blackDottedLine"]); 199 } 200 //draw buttons 201 parent.bitBLT(position.cornerUL, flags & MINUS_PRESSED ? ss.getImage("leftArrowB") : ss.getImage("leftArrowA")); 202 Point lower = position.cornerUR; 203 lower.x -= position.height; 204 parent.bitBLT(lower, flags & PLUS_PRESSED ? ss.getImage("rightArrowB") : ss.getImage("rightArrowA")); 205 if (state == ElementState.Disabled) { 206 parent.bitBLTPattern(position, ss.getImage("ElementDisabledPtrn")); 207 } 208 } 209 public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) { 210 if (state != ElementState.Enabled) return; 211 if (mce.button == MouseButton.Left) { 212 if (mce.x < position.height) { 213 if (!(flags & MINUS_PRESSED) && mce.state == ButtonState.Pressed) { 214 value = _value - 1; 215 flags |= MINUS_PRESSED; 216 registerTimer(); 217 } else if (flags & MINUS_PRESSED && mce.state == ButtonState.Released) { 218 flags &= ~MINUS_PRESSED; 219 } 220 } else if (mce.x >= position.width - position.height) { 221 if (!(flags & PLUS_PRESSED) && mce.state == ButtonState.Pressed) { 222 value = _value + 1; 223 flags |= PLUS_PRESSED; 224 registerTimer(); 225 } else if (flags & PLUS_PRESSED && mce.state == ButtonState.Released) { 226 flags &= ~PLUS_PRESSED; 227 } 228 } else { 229 import std.math : nearbyint; 230 const double newVal = mce.x - position.height - (_barLength / 2.0); 231 if (newVal >= 0) { 232 //const int travelLength = position.width - position.height * 2 - _barLength; 233 //const double valRatio = isNaN(largeVal) ? 1.0 : largeVal; 234 //value = cast(int)nearbyint((travelLength / newVal) * valRatio); 235 value = cast(int)nearbyint((newVal) * valRatio); 236 } 237 } 238 flags &= ~SCROLLMATIC; 239 240 } 241 super.passMCE(mec, mce); 242 } 243 public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) { 244 if (state != ElementState.Enabled) return; 245 if (mme.buttonState == MouseButtonFlags.Left && mme.x > position.width && mme.x < position.height) { 246 value = _value + mme.relX; 247 draw(); 248 } 249 super.passMME(mec, mme); 250 } 251 public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) { 252 if (state != ElementState.Enabled) return; 253 value = _value + mwe.x; 254 super.passMWE(mec, mwe); 255 } 256 }