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