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