1 module glui.scrollbar; 2 3 import raylib; 4 5 import std.algorithm; 6 7 import glui.node; 8 import glui.utils; 9 import glui.input; 10 import glui.style; 11 12 13 @safe: 14 15 16 /// Create a new vertical scroll bar. 17 alias vscrollBar = simpleConstructor!GluiScrollBar; 18 19 /// Create a new horizontal scroll bar. 20 alias hscrollBar = simpleConstructor!(GluiScrollBar, (a) { 21 22 a.horizontal = true; 23 24 }); 25 26 /// 27 class GluiScrollBar : GluiInput!GluiNode { 28 29 /// Styles defined by this node: 30 /// 31 /// `backgroundStyle` — style defined for the background part of the scrollbar, 32 /// 33 /// `pressStyle` — style to activate while the scrollbar is pressed. 34 mixin DefineStyles!( 35 "backgroundStyle", q{ Style.init }, 36 "pressStyle", q{ style }, 37 ); 38 39 mixin ImplHoveredRect; 40 41 public { 42 43 /// Multipler of the scroll speed; applies to keyboard scroll only. 44 /// 45 /// This is actually number of pixels per mouse wheel event, as `GluiScrollable` determines mouse scroll speed 46 /// based on this. 47 enum scrollSpeed = 15.0; 48 49 /// If true, the scrollbar will be horizontal. 50 bool horizontal; 51 52 /// Amount of pixels the page is scrolled down. 53 size_t position; 54 55 /// Available space to scroll. 56 /// 57 /// Note: page length, and therefore scrollbar handle length, are determined from the space occupied by the 58 /// scrollbar. 59 size_t availableSpace; 60 61 /// Width of the scrollbar. 62 size_t width = 10; 63 64 } 65 66 protected { 67 68 /// If true, the inner part of the scrollbar is hovered. 69 bool innerHovered; 70 71 /// Page length as determined in drawImpl. 72 double pageLength; 73 74 /// Length of the scrollbar as determined in drawImpl. 75 double scrollbarLength; 76 77 /// Length of the handle. 78 double handleLength; 79 80 /// Position of the scrollbar on the screen. 81 Vector2 scrollbarPosition; 82 83 /// Position where the mouse grabbed the scrollbar. 84 Vector2 grabPosition; 85 86 /// Start position of the mouse at the beginning of the grab. 87 size_t startPosition; 88 89 } 90 91 this(Args...)(Args args) { 92 93 super(args); 94 95 } 96 97 /// Set the scroll to a value clamped between start and end. 98 void setScroll(ptrdiff_t value) { 99 100 position = cast(size_t) value.clamp(0, scrollMax); 101 102 } 103 104 /// Ditto 105 void setScroll(float value) { 106 107 position = cast(size_t) value.clamp(0, scrollMax); 108 109 } 110 111 /// Get the maximum value this container can be scrolled to. Requires at least one draw. 112 size_t scrollMax() const { 113 114 return cast(size_t) max(0, availableSpace - pageLength); 115 116 } 117 118 /// Set the total size of the scrollbar. Will always fill the available space in the target direction. 119 override protected void resizeImpl(Vector2 space) { 120 121 // Get minSize 122 minSize = horizontal 123 ? Vector2(space.x, width) 124 : Vector2(width, space.y); 125 126 // Get the expected page length 127 pageLength = horizontal 128 ? space.x + style.padding.sideX[].sum + style.margin.sideX[].sum 129 : space.y + style.padding.sideY[].sum + style.margin.sideY[].sum; 130 131 } 132 133 override protected void drawImpl(Rectangle paddingBox, Rectangle contentBox) @trusted { 134 135 // Clamp the values first 136 setScroll(position); 137 138 // Draw the background 139 backgroundStyle.drawBackground(paddingBox); 140 141 // Calculate the size of the scrollbar 142 scrollbarPosition = Vector2(contentBox.x, contentBox.y); 143 scrollbarLength = horizontal ? contentBox.width : contentBox.height; 144 handleLength = availableSpace 145 ? max(50, scrollbarLength^^2 / availableSpace) 146 : 0; 147 148 const handlePosition = (scrollbarLength - handleLength) * position / scrollMax; 149 150 // Now get the size of the inner rect 151 auto innerRect = contentBox; 152 153 if (horizontal) { 154 155 innerRect.x += handlePosition; 156 innerRect.w = handleLength; 157 158 } 159 160 else { 161 162 innerRect.y += handlePosition; 163 innerRect.h = handleLength; 164 165 } 166 167 // Check if the inner part is hovered 168 innerHovered = innerRect.contains(GetMousePosition); 169 170 // Get the inner style 171 const innerStyle = pickStyle(); 172 173 innerStyle.drawBackground(innerRect); 174 175 } 176 177 override protected const(Style) pickStyle() const { 178 179 const up = super.pickStyle(); 180 181 // The outer part is being hovered... 182 if (up is hoverStyle) { 183 184 // Check if the inner part is 185 return innerHovered 186 ? hoverStyle 187 : style; 188 189 } 190 191 return up; 192 193 } 194 195 override protected void mouseImpl() @trusted { 196 197 const triggerButton = MouseButton.MOUSE_LEFT_BUTTON; 198 199 // Ignore if we can't scroll 200 if (availableSpace == 0) return; 201 202 // Pressed the scrollbar 203 if (IsMouseButtonPressed(triggerButton)) { 204 205 // Remember the grab position 206 grabPosition = GetMousePosition; 207 scope (exit) startPosition = position; 208 209 // Didn't press the handle 210 if (!innerHovered) { 211 212 // Get the position 213 const posdir = horizontal ? scrollbarPosition.x : scrollbarPosition.y; 214 const grabdir = horizontal ? grabPosition.x : grabPosition.y; 215 const screenPos = grabdir - posdir - handleLength/2; 216 217 // Move it to this position 218 setScroll(screenPos * availableSpace / scrollbarLength); 219 220 } 221 222 } 223 224 // Mouse is held down 225 else if (IsMouseButtonDown(triggerButton)) { 226 227 const mouse = GetMousePosition; 228 229 const float move = horizontal 230 ? mouse.x - grabPosition.x 231 : mouse.y - grabPosition.y; 232 233 // Move the scrollbar 234 setScroll(startPosition + move * availableSpace / scrollbarLength); 235 236 } 237 238 } 239 240 override protected bool keyboardImpl() @trusted { 241 242 const plusKey = horizontal 243 ? KeyboardKey.KEY_RIGHT 244 : KeyboardKey.KEY_DOWN; 245 const minusKey = horizontal 246 ? KeyboardKey.KEY_LEFT 247 : KeyboardKey.KEY_UP; 248 249 const arrowSpeed = scrollSpeed * 20 * GetFrameTime; 250 const pageSpeed = scrollbarLength * 3/4; 251 252 const move = IsKeyPressed(KeyboardKey.KEY_PAGE_DOWN) ? +pageSpeed 253 : IsKeyPressed(KeyboardKey.KEY_PAGE_UP) ? -pageSpeed 254 : IsKeyDown(plusKey) ? +arrowSpeed 255 : IsKeyDown(minusKey) ? -arrowSpeed 256 : 0; 257 258 if (move != 0) { 259 260 setScroll(position + move); 261 262 if (changed) changed(); 263 264 return true; 265 266 } 267 268 return false; 269 270 } 271 272 }