├── .gitignore ├── README.md ├── preview_slidebar.png └── slidebar.lua /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Library slidebar 2 | DX library that allows you to easily create slidebar. 3 | 4 | # License 5 | #### This is library free, you can freely use and edit. 6 | 7 | # About 8 | This is a simple library for create slidebars which allows easy creation of some resources 9 | 10 | # How to use 11 | You need download the file ```slidebar.lua``` and put it in your project, but don't forget to load it in the meta.xml 12 | 13 | # Functions 14 | Create a new slidebar 15 | ```lua 16 | slidebar = createSlideBar(x, y, width, height, radiusBorder, minValue, maxValue, circleScale, postGUI) 17 | ``` 18 | Preview
19 | ![Preview](https://github.com/LODSX/slidebar/blob/main/preview_slidebar.png) 20 | 21 | Destroy the slidebar 22 | ```lua 23 | slidebar:destroy() 24 | ``` 25 | Change a property of slide bar 26 | ```lua 27 | slidebar:setProperty(property, value) 28 | ``` 29 | 30 | Set a new offset 31 | ```lua 32 | slidebar:setScrollOffset(value) 33 | ``` 34 | 35 | Get the scroll input 36 | ```lua 37 | slidebar:onScroll() 38 | ``` 39 | 40 | Get the output the scroll input 41 | ```lua 42 | slidebar:onScrollEnd() 43 | ``` 44 | -------------------------------------------------------------------------------- /preview_slidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lodsdev/slidebar/80ff8411d7de51d813cf25a7eba40fc1a6586eb0/preview_slidebar.png -------------------------------------------------------------------------------- /slidebar.lua: -------------------------------------------------------------------------------- 1 | -- small optimizations 2 | local tblInsert = table.insert 3 | local tblRemove = table.remove 4 | local strFormat = string.format 5 | local floor = math.floor 6 | local ceil = math.ceil 7 | local tblMax = table.maxn 8 | local max = math.max 9 | local min = math.min 10 | local interpolate = interpolateBetween 11 | 12 | local screenW, screenH = guiGetScreenSize() 13 | 14 | local scrollInput = {} 15 | local inputHover 16 | local posI, posF 17 | local typeProgress, tickSmooth 18 | 19 | scrollInput.inputs = {} 20 | 21 | -- create the scroll input 22 | function createSliderBar(x, y, width, height, indicatorScale, radiusBar, radiusIndicator, sizeStroke, minValue, maxValue, postGUI) 23 | if (not (x or y)) then 24 | local input = (not x and "Error in argument #1. Define a position X") or (not y and "Error in argument #2. Define a position Y") 25 | warn(input) 26 | end 27 | if (not (width or height)) then 28 | local input = (not width and "Error in argument #3. Define a width") or (not height and "Error in argument #4. Define a height") 29 | warn(input) 30 | end 31 | if ((width == 0) or (height == 0)) then 32 | local input = ((width == 0) and "Error in argument #3. Define a width greater than 0") or ((height == 0) and "Error in argument #4. Define a height greater than 0") 33 | warn(input) 34 | end 35 | if (minValue > maxValue) then 36 | warn("Error in argument #5. The min value don't can't be greater than max value") 37 | end 38 | if (maxValue < minValue) then 39 | warn("Error in argument #6. The max value don't can't be smaller than min value") 40 | end 41 | 42 | radius = radiusBar and height / 2 or 0 43 | indicatorScale = indicatorScale or 10 44 | radiusIndicator = radiusIndicator or 0 45 | sizeStroke = sizeStroke or 0 46 | 47 | local rawDataCircle = strFormat([[ 48 | 49 | 50 | 51 | ]], indicatorScale, indicatorScale, radiusIndicator, indicatorScale, indicatorScale) 52 | 53 | local rawDataBar = strFormat([[ 54 | 55 | 56 | 57 | ]], width, height, radius, width, height) 58 | 59 | local datas = { 60 | x = x, 61 | y = y, 62 | width = width, 63 | height = height, 64 | circlePos = 0, 65 | circleY = 0, 66 | minValue = minValue or 0, 67 | maxValue = maxValue or 100, 68 | radius = radius, 69 | indicatorScale = indicatorScale, 70 | bgBarColor = {255, 255, 255}, 71 | barColor = {255, 255, 255}, 72 | barColorHover = {255, 255, 255}, 73 | indicatorColor = {255, 255, 255}, 74 | borderColor = {255, 255, 255}, 75 | sizeStroke = sizeStroke, 76 | postGUI = postGUI or false, 77 | circle = svgCreate(indicatorScale, indicatorScale, rawDataCircle), 78 | barInput = svgCreate(width, height, rawDataBar), 79 | scrolling = false, 80 | changeColorBar = false, 81 | animationIndicator = false, 82 | scrollOffset = 100, 83 | -- Events Methods 84 | scroll_event = false, 85 | scrollEnd_event = false, 86 | -- Technicals 87 | endedScrolling = true 88 | } 89 | datas.circlePos = (datas.x + datas.width) 90 | datas.circleY = datas.y + ((datas.height / 2) - (datas.indicatorScale / 2)) 91 | 92 | setmetatable(datas, {__index = scrollInput}) 93 | tblInsert(scrollInput.inputs, datas) 94 | 95 | if (tblMax(scrollInput.inputs) == 1) then 96 | addEventHandler('onClientRender', root, renderSlidebar, false, 'low-5') 97 | addEventHandler('onClientClick', root, clickSliderBar, false, 'low-5') 98 | addEventHandler('onClientKey', root, keySliderBar, false, 'low-5') 99 | end 100 | return datas 101 | end 102 | 103 | -- create vector images for the scroll input 104 | local function dxDrawSVG(svg, x, y, width, height, color, postGUI) 105 | if (not svg) then 106 | warn("Error in argument #1. Define a SVG.") 107 | end 108 | if (not (width or height)) then 109 | local input = (not width and 'Error in argument #2. Define a width') or (not height and 'Error in argument #3. Define a height') 110 | warn(input) 111 | end 112 | 113 | dxSetBlendMode('add') 114 | dxDrawImage(x, y, width, height, svg, 0, 0, 0, color, postGUI) 115 | dxSetBlendMode('blend') 116 | end 117 | 118 | -- render the scroll input 119 | function renderSlidebar() 120 | if (not scrollInput.inputs or (not (#scrollInput.inputs > 0))) then 121 | return 122 | end 123 | 124 | inputHover = nil 125 | 126 | for _, self in ipairs(scrollInput.inputs) do 127 | local value = ((self.circlePos - self.x) / self.width) * 100 128 | local barValue = (self.width / 100) * value 129 | local barSelectedColor = self.barColor 130 | 131 | if (typeProgress == 'in_progress') then 132 | local progress = (getTickCount()-tickSmooth)/250 133 | local newCirclePos = interpolate(posI, 0, 0, posF, 0, 0, progress, 'Linear') 134 | 135 | if (progress >= 1) then 136 | typeProgress = nil 137 | tickSmooth = nil 138 | posI = nil 139 | posF = nil 140 | end 141 | 142 | self.circlePos = newCirclePos 143 | end 144 | 145 | if (isCursorOnElement(self.x, self.y, ((barValue + self.indicatorScale) * self.width) / 100, self.height) or isCursorOnElement(self.circlePos - (self.indicatorScale/2), self.circleY, self.indicatorScale, self.indicatorScale)) then 146 | if (self.changeColorBar) then 147 | barSelectedColor = self.barColorHover 148 | end 149 | inputHover = self 150 | end 151 | 152 | if (isCursorOnElement(self.x, self.y, self.width, self.height)) then 153 | inputHover = self 154 | end 155 | 156 | if (self.scrolling) then 157 | local mx, _ = getCursorPosition() 158 | local cursorX = mx * screenW 159 | 160 | self.circlePos = clamp(cursorX, self.x, (self.x + self.width)) 161 | self.scrollOffset = floor(clamp(((self.circlePos - self.x) / self.width) * 100, self.minValue, self.maxValue)) 162 | 163 | if (self.scroll_event) then 164 | self.scroll_event(self.scrollOffset) 165 | end 166 | 167 | if (self.changeColorBar) then 168 | barSelectedColor = self.barColorHover 169 | end 170 | end 171 | 172 | dxDrawSVG(self.barInput, self.x, self.y, self.width, self.height, tocolor(unpack(self.bgBarColor)), self.postGUI) -- background bar 173 | dxDrawSVG(self.barInput, self.x, self.y, barValue, self.height, tocolor(unpack(barSelectedColor)), self.postGUI) -- bar 174 | -- dxDrawSVG(self.circle, (self.circlePos - (self.indicatorScale/2)), self.circleY, self.indicatorScale, self.indicatorScale, ((self.borderColor ~= self.indicatorColor) and tocolor(unpack(self.borderColor)) or tocolor(unpack(self.indicatorColor))), self.postGUI) -- indicator border 175 | dxDrawSVG(self.circle, self.circlePos - (self.indicatorScale/2), self.circleY, self.indicatorScale, self.indicatorScale, tocolor(unpack(self.borderColor)), self.postGUI) 176 | 177 | if (self.sizeStroke and self.sizeStroke ~= 0) then 178 | dxDrawSVG(self.circle, self.circlePos - (self.indicatorScale/2) + (self.sizeStroke/2), self.circleY + (self.sizeStroke/2), self.indicatorScale - self.sizeStroke, self.indicatorScale - self.sizeStroke, tocolor(unpack(self.indicatorColor)), self.postGUI) 179 | end 180 | 181 | if (getKeyState('mouse1')) then 182 | if (isCursorOnElement(self.circlePos - (self.indicatorScale/2), self.circleY, self.indicatorScale, self.indicatorScale)) then 183 | self.scrolling = true 184 | 185 | if (self.endedScrolling) then 186 | self.endedScrolling = false 187 | end 188 | end 189 | else 190 | if (not self.endedScrolling) then 191 | self.scrolling = false 192 | self.endedScrolling = true 193 | 194 | if (self.scrollEnd_event) then 195 | self.scrollEnd_event(self.scrollOffset) 196 | end 197 | end 198 | end 199 | end 200 | end 201 | 202 | -- function to check if the cursor is on the element and change the position of circle 203 | function clickSliderBar(button, state) 204 | if (not scrollInput.inputs or (not (#scrollInput.inputs > 0))) then 205 | return 206 | end 207 | 208 | if (inputHover) then 209 | if (button == 'left' and state == 'down') then 210 | local mx, _ = getCursorPosition() 211 | local cursorX = mx * screenW 212 | 213 | if (inputHover.smoothScroll) then 214 | local newCirclePos = clamp(cursorX, inputHover.x, (inputHover.x + inputHover.width)) 215 | inputHover.scrollOffset = floor(clamp(((newCirclePos - inputHover.x) / inputHover.width) * 100, inputHover.minValue, inputHover.maxValue)) 216 | 217 | typeProgress = 'in_progress' 218 | tickSmooth = getTickCount() 219 | posI, posF = inputHover.circlePos, newCirclePos 220 | else 221 | inputHover.circlePos = clamp(cursorX, inputHover.x, (inputHover.x + inputHover.width)) 222 | inputHover.scrollOffset = floor(clamp(((inputHover.circlePos - inputHover.x) / inputHover.width) * 100, inputHover.minValue, inputHover.maxValue)) 223 | end 224 | 225 | if (inputHover.scrollEnd_event) then 226 | inputHover.scrollEnd_event(inputHover.scrollOffset) 227 | end 228 | end 229 | end 230 | end 231 | 232 | -- function to change the offset of slidebar on scroll 233 | function keySliderBar(button, press) 234 | if (not scrollInput.inputs or (not (#scrollInput.inputs > 0))) then 235 | return 236 | end 237 | 238 | if (not press) then return end 239 | 240 | if (inputHover) then 241 | if (button == 'mouse_wheel_up' and inputHover.scrollOffset < inputHover.maxValue) then 242 | inputHover.scrollOffset = inputHover.scrollOffset + 1 243 | inputHover:setScrollOffset(inputHover.scrollOffset) 244 | 245 | if (inputHover.scroll_event) then 246 | inputHover.scroll_event(inputHover.scrollOffset) 247 | end 248 | elseif (button == 'mouse_wheel_down' and inputHover.scrollOffset > inputHover.minValue) then 249 | inputHover.scrollOffset = inputHover.scrollOffset - 1 250 | inputHover:setScrollOffset(inputHover.scrollOffset) 251 | 252 | if (inputHover.scroll_event) then 253 | inputHover.scroll_event(inputHover.scrollOffset) 254 | end 255 | end 256 | end 257 | end 258 | 259 | -- function to destroy the scroll input 260 | function scrollInput:destroy() 261 | if (not self) then 262 | warn("Error in argument #1. Define a object.") 263 | end 264 | 265 | for i, v in ipairs(scrollInput.inputs) do 266 | if (v == self) then 267 | -- free memory 268 | if (isElement(v.circle)) then 269 | destroyElement(v.circle) 270 | end 271 | if (isElement(v.barInput)) then 272 | destroyElement(v.barInput) 273 | end 274 | tblRemove(scrollInput.inputs, i) 275 | end 276 | end 277 | 278 | if (not (tblMax(scrollInput.inputs) > 0)) then 279 | removeEventHandler('onClientRender', root, renderSlidebar) 280 | removeEventHandler('onClientClick', root, clickSliderBar) 281 | removeEventHandler('onClientKey', root, keySliderBar) 282 | end 283 | end 284 | 285 | -- change the width of bar using the value of scrolloffset 286 | function scrollInput:setScrollOffset(value) 287 | if (not self) then 288 | warn("Error in argument #1. Define a object.") 289 | end 290 | 291 | if (not value) then 292 | warn("Error in argument #2. Defina a value.") 293 | end 294 | 295 | value = clamp(value, self.minValue, self.maxValue) 296 | 297 | if (self.smoothScroll) then 298 | self.scrollOffset = value 299 | local newCirclePos = self.x + ((self.width / self.maxValue) * self.scrollOffset) 300 | 301 | typeProgress = 'in_progress' 302 | tickSmooth = getTickCount() 303 | posI, posF = self.circlePos, newCirclePos 304 | else 305 | self.scrollOffset = value 306 | self.circlePos = self.x + ((self.width / self.maxValue) * self.scrollOffset) 307 | end 308 | end 309 | 310 | -- function to get the scroll input 311 | function scrollInput:onScroll(func) 312 | self.scroll_event = func 313 | end 314 | 315 | -- function to get output the scroll input 316 | function scrollInput:onScrollEnd(func) 317 | self.scrollEnd_event = func 318 | end 319 | 320 | local changeableProperties = { 321 | ['x'] = true, 322 | ['y'] = true, 323 | ['width'] = true, 324 | ['height'] = true, 325 | ['minValue'] = true, 326 | ['maxValue'] = true, 327 | ['scrollOffset'] = true, 328 | ['smoothScroll'] = true, 329 | ['bgBarColor'] = true, 330 | ['barColor'] = true, 331 | ['barColorHover'] = true, 332 | ['indicatorColor'] = true, 333 | ['borderColor'] = true, 334 | ['animationIndicator'] = true, 335 | -- ['strokeCircle'] = true, 336 | -- ['strokeindicatorColor'] = true, 337 | ['postGUI'] = true, 338 | } 339 | 340 | -- function to change the property of scrollbar 341 | ---comment 342 | ---@param property string 343 | ---@param value any 344 | ---@return boolean 345 | function scrollInput:setProperty(property, value) 346 | if (not self) then 347 | warn("No elements were found.") 348 | end 349 | 350 | if (not property or (type(property) ~= 'string')) then 351 | local input = (not property and "Error in argument #1. Define a property") or (type(property) ~= 'string' and "Error in argument #1. Define a valid property") 352 | warn(input) 353 | end 354 | 355 | for propertyIndex, active in pairs(changeableProperties) do 356 | if (propertyIndex == property and active) then 357 | self[propertyIndex] = value 358 | break 359 | end 360 | end 361 | end 362 | 363 | -- get the max and min of a value 364 | function clamp(number, min, max) 365 | if (number < min) then 366 | return min 367 | elseif (number > max) then 368 | return max 369 | end 370 | return number 371 | end 372 | 373 | -- function to send error in debugscript 374 | function warn(message) 375 | return error(tostring(message), 2) 376 | end 377 | 378 | -- function to get the mouse position absolute 379 | function isCursorOnElement(x, y, w, h) 380 | if (not isCursorShowing()) then return end 381 | local cursor = {getCursorPosition ()} 382 | local mx, my = cursor[1] * screenW, cursor[2] * screenH 383 | return (mx >= x and mx <= x + w and my >= y and my <= y + h) 384 | end 385 | --------------------------------------------------------------------------------