local Bottons = {} Bottons.RenderTimeout = 300 function Bottons:Clear() self.Instances = {} self.ActiveDrag = nil end function Bottons:Constructor() if (self.initialized) then return self end if (not Responsive or not _G.Responsive) then _G.Responsive = Require('Interface/Responsive') end if (not Parent or not _G.Parent) then _G.Parent = Require('Interface/Parent') end if (not Cursor or not _G.Cursor) then _G.Cursor = Require('Interface/Cursor') end self.Parent = _G.Parent self.Responsive = _G.Responsive self.initialized = true self.ActiveDrag = nil self.Instances = {} return self end function Bottons:IsRecentlyRendered(btn) return btn.lastRender and (getTickCount() - btn.lastRender <= (btn.renderTimeout or self.RenderTimeout)) end function Bottons:SimpleSliderVertical(id, x, y, w, h, minV, maxV, value, opts, Alpha) opts = opts or {} Alpha = Alpha or 1 local btn = self.Instances[id] local function clamp(v) v = tonumber(v) or minV if v < minV then v = minV end if v > maxV then v = maxV end return v end if not btn then btn = { id = id, x = x, y = y, w = w, h = h, value = clamp(value or minV), parent = opts.parent or Parent:GetActive(), dragging = false, onClick = opts.onClick or function() return true end, canDrag = opts.canDrag, renderTimeout = opts.renderTimeout or self.RenderTimeout, } function btn:Click() if not Bottons:IsRecentlyRendered(self) then return false end if Bottons.ActiveDrag and Bottons.ActiveDrag ~= self.id then return false end if self.canDrag and not self.canDrag(self.value) then return false end if self.onClick and not self:onClick() then return false end if Cursor:IsInBox(self.parent, self.x, self.y, self.w, self.h) then self.dragging = true Bottons.ActiveDrag = self.id return true end return false end self.Instances[id] = btn else if value ~= nil and not btn.dragging then local newValue = clamp(value) if btn.value ~= newValue then btn.value = newValue end end end btn.id = id btn.x = x btn.y = y btn.w = w btn.h = h btn.parent = opts.parent or Parent:GetActive() btn.canDrag = opts.canDrag btn.onClick = opts.onClick or btn.onClick local _, cy = Cursor:GetData(btn.parent) local down = getKeyState('mouse1') local allowed = true if btn.canDrag then allowed = btn.canDrag(btn.value) end if btn.dragging then if down and allowed then local percent = 1 - ((cy - btn.y) / btn.h) percent = math.max(0, math.min(1, percent)) local newValue = clamp(minV + percent * (maxV - minV)) if newValue ~= btn.value then btn.value = newValue if opts.onChange then opts.onChange(btn.value) end end else btn.dragging = false if Bottons.ActiveDrag == btn.id then Bottons.ActiveDrag = nil end if opts.onRelease then opts.onRelease(btn.value) end end end btn.lastRender = getTickCount() return btn.value end function Bottons:Rectangle(id, x, y, w, h, radius, conditionsColors, onClick, Alpha, p, renderTimeout, down) assert(x and y and w and h, 'Invalid Botton parameters') assert(type(conditionsColors) == 'table' or not conditionsColors, 'Invalid conditionsColors parameter') local btn = self.Instances[id] if not btn then btn = { id = id, x = x, y = y, w = w, h = h, radius = radius, conditionsColors = conditionsColors, onClick = onClick, parent = p or Parent:GetActive(), renderTimeout = renderTimeout or self.RenderTimeout, down = down, } function btn:Click() if not Bottons:IsRecentlyRendered(self) then return false end if self.onClick then if self.parent then if Cursor:IsInBox(self.parent, self.x, self.y, self.w, self.h) then return self:onClick() end else if Cursor:IsInBox(nil, self.x, self.y, self.w, self.h) then return self:onClick() end end end end self.Instances[id] = btn else btn.x = x btn.y = y btn.w = w btn.h = h btn.radius = radius btn.conditionsColors = conditionsColors btn.onClick = onClick end local p = btn.parent or Parent:GetActive() local isHover = Cursor:IsInBox(p, btn.x, btn.y, btn.w, btn.h) local ctx = { btn = btn, hover = isHover, parent = p, id = id } local states = {} for _, st in ipairs(btn.conditionsColors or {}) do local state = { callback = st.onProgress, color = st.color, speed = st.speed, animType = st.animType, } if st.default then state.default = true else state.cond = st.condition and st.condition(ctx) or false end table.insert(states, state) end btn.lastRender = getTickCount() local r, g, b, a = AnimLib:CreateHovers("btn_" .. id, states, {separatedValues = true}) if Alpha > 0 then Draw:RectFilledSVG(btn.x, btn.y, btn.w, btn.h, btn.radius, rgba(r, g, b, a * Alpha), 0, 0, 0, false, false ) end return r, g, b, a end function Bottons:SimpleSlider(id, x, y, width, height, minV, maxV, value, opts, Alpha) assert(id, 'Invalid id') opts = opts or {} Alpha = Alpha or 1 local btn = self.Instances[id] local function clamp(v) v = tonumber(v) or minV if v < minV then v = minV end if v > maxV then v = maxV end return v end if not btn then btn = { id = id, value = clamp(value or minV), parent = opts.parent or Parent:GetActive(), dragging = false, onClick = opts.onClick or function() return true end, renderTimeout = opts.renderTimeout or self.RenderTimeout, } function btn:Click() if not Bottons:IsRecentlyRendered(self) then return false end if Bottons.ActiveDrag and Bottons.ActiveDrag ~= self.id then return false end if self.onClick and not self:onClick() then return false end if Cursor:IsInBox(self.parent, self.x, self.y, self.w, self.h) then self.dragging = true Bottons.ActiveDrag = self.id return true end return false end self.Instances[id] = btn end btn.x = x btn.y = y btn.w = width btn.h = height btn.parent = opts.parent or Parent:GetActive() btn.onClick = opts.onClick or btn.onClick if value ~= nil and not btn.dragging then local newValue = clamp(value) if btn.value ~= newValue then btn.value = newValue end end local sliderX = x local sliderY = y + (height / 2) - 3 local sliderW = width local sliderH = 6 local cx = select(1, Cursor:GetData(btn.parent)) local down = getKeyState('mouse1') if btn.dragging then if down then local pct = math.max(0, math.min(1, (cx - sliderX) / sliderW)) local newValue = clamp(minV + pct * (maxV - minV)) if newValue ~= btn.value then btn.value = newValue if opts.onChange then opts.onChange(btn.value) end end else btn.dragging = false if Bottons.ActiveDrag == btn.id then Bottons.ActiveDrag = nil end if opts.onRelease then opts.onRelease(btn.value) end end end local percent = ((btn.value - minV) / math.max(1, (maxV - minV))) * 100 percent = math.max(0, math.min(100, percent)) if Alpha > 0 then Draw:RectFilledSVG(sliderX, sliderY, sliderW, sliderH, 3, rgba(255, 255, 255, 0.08 * Alpha), 0, 0, 0, false, false ) local filledW = select(1, Draw:Rect('progress_'..id, sliderX, sliderY, sliderW, sliderH, rgba(255, 255 ,255, Alpha), { w = percent, h = 100 }, 0, 0, 0, 0, 0, false,'blend', 4, nil, 'horizontal' ) ) local size = 12 local thumbX = sliderX + filledW - (size / 2) local thumbY = y + (height / 2) - (size / 2) Draw:RectFilledSVG(thumbX, thumbY, size, size, size / 2, rgba(255, 255, 255, Alpha), 0, 0, 0, false, false ) end btn.lastRender = getTickCount() return btn.value end function Bottons:Slide(id, x, y, mainWidth, height, font, value, opts, Alpha) assert(id, 'Invalid Slide id') assert(x and y and mainWidth and height, 'Invalid Slide parameters') opts = opts or {} Alpha = Alpha or 1 local btn = self.Instances[id] local minV = opts.min or 0 local maxV = opts.max or 100 local step = opts.step or 1 local suffix = opts.suffix or '' local function clamp(v) v = tonumber(v) or minV if v < minV then v = minV end if v > maxV then v = maxV end return v end local function round(v) v = tonumber(v) or minV return math.floor((v / step) + 0.5) * step end if not btn then btn = { id = id, value = round(clamp(value)), parent = opts.parent or Parent:GetActive(), dragging = false, onClick = opts.onClick or function() return true end, renderTimeout = opts.renderTimeout or self.RenderTimeout, } Draw:Input(id, 0, 0, 0, 0, 0, 0, 0, 0, 1, font, '0%', 'left', 'center', rgba(255 ,255, 255, Alpha), 3, '[0-9]*([\\.,][0-9]*)?', function(v) if v == '' then return '0'..suffix end return (math.max(minV, math.min(maxV, tonumber(v) or 0))) .. suffix end, btn.parent, false, false, false) Draw:SetInput(id, tostring(value)) function btn:Click() if not Bottons:IsRecentlyRendered(self) then return false end local changed = false if not self.onClick() then return false end local gap = opts.gap or 16 local mainX = btn.x + (opts.sideButtonSize or height) + gap local sliderX = mainX + (opts.padSlider or 16) local sliderW = mainWidth - ((opts.sliderPadding or 16) * 2) - Responsive:RespcInverted(Draw:TextWidth('100' .. suffix, font, 1)) - 16 if self.leftBox and Cursor:IsInBox(self.parent, unpack(self.leftBox)) then self.value = round(clamp(self.value - step)) changed = true elseif self.rightBox and Cursor:IsInBox(self.parent, unpack(self.rightBox)) then self.value = round(clamp(self.value + step)) changed = true elseif Cursor:IsInBox(self.parent, sliderX, btn.y, sliderW, height) then if Bottons.ActiveDrag and Bottons.ActiveDrag ~= self.id then return false end if not self.dragging then self.dragging = true Bottons.ActiveDrag = self.id changed = true end end if changed then if opts.onChange then opts.onChange(self.value) end if id and (not Draw:IsInputFocused(id)) then Draw:SetInput(id, tostring(self.value)) end return true end return false end self.Instances[id] = btn else btn.parent = opts.parent or Parent:GetActive() btn.x = x btn.y = y end local sideBtn = opts.sideButtonSize or height local gap = opts.gap or 16 local padSlider = opts.sliderPadding or 16 local textWidth = Responsive:RespcInverted(Draw:TextWidth('100' .. suffix, font, 1)) local leftBtnX = x local mainX = x + sideBtn + gap local rightBtnX = mainX + mainWidth + gap btn.leftBox = { leftBtnX, y, sideBtn, height } btn.rightBox = { rightBtnX, y, sideBtn, height } local a = Animation('slide_left' .. id .. '_alpha', {value = 1, target = btn.leftBox and Cursor:IsInBox(btn.parent, unpack(btn.leftBox)) and 0.5 or 1, speed = 250, animType = 'OutQuad', onProgress = callUpdatesAnimations }) or 1 Draw:RectFilledSVG(leftBtnX, y, sideBtn, height, 4, rgba(255 ,255, 255, 0.08 * Alpha * a), 0, 0, 0, false,false) Draw:Image(leftBtnX, y, sideBtn, height, AsyncElements:Icon(sideBtn, height, false, [[ ]], 'clamp', 2), 0, 0, 0, tocolor(255, 255, 255, Alpha * a)) Draw:RectFilledSVG(mainX, y, mainWidth, height, 4, rgba(255 ,255, 255, 0.05 * Alpha), 0, 0, 0, false,false) local sliderX = mainX + padSlider local sliderW = mainWidth - (padSlider * 2) - textWidth - 16 local sliderY = y + 13 local sliderH = 7 do local cx = select(1, Cursor:GetData(btn.parent)) local down = getKeyState('mouse1') if btn.dragging then if down then local pct = math.max(0, math.min(1, (cx - sliderX) / sliderW)) local n = minV + pct * (maxV - minV) n = round(clamp(n)) if n ~= btn.value then btn.value = n if opts.onChange then opts.onChange(btn.value) end if id and (not Draw:IsInputFocused(id)) then Draw:SetInput(id, tostring(btn.value)) end end else btn.dragging = false if Bottons.ActiveDrag == btn.id then Bottons.ActiveDrag = nil end if opts.onRelease then opts.onRelease(btn.value) end end end end local percent = ((btn.value - minV) / math.max(1, (maxV - minV))) * 100 percent = math.max(0, math.min(100, percent)) local filledW = select(1, Draw:Rect('progress_'..id, sliderX, sliderY, sliderW, sliderH, rgba(255, 255, 255, Alpha), { w = percent, h = 100 }, 0, 0, 0, 0, 0, false, 'blend', 4, nil, 'horizontal' )) Draw:Image( sliderX + filledW - 6, y + 10, 12, 12, AsyncElements:Icon(12,12,false, [[]], 'clamp', 2 ), 0, 0, 0, tocolor(255, 255, 255, Alpha)) local inputX = mainX + mainWidth - 16 - textWidth if Draw:IsInputFocused(id) then Draw:RectFilledSVG(inputX - 4, y + 1, mainWidth - (inputX - mainX) + 4, height - 2, 4, rgba(255, 255, 255, 0.1 * Alpha), 0, 0, 0, false,false) end local inputValue = Draw:Input(id, inputX, y + 1, textWidth, height, inputX, y, textWidth, height, 1, font, '0%', 'center', 'center', rgba(255 ,255, 255, Alpha), 3, '[0-9]*([\\.,][0-9]*)?', function(v) if v == '' then return '0'..suffix end return (math.max(minV, math.min(maxV, tonumber(v) or 0))) .. suffix end, btn.parent, false, false, false) if inputValue ~= nil then local n = round(clamp(inputValue)) if n ~= btn.value then btn.value = n if opts.onChange then opts.onChange(btn.value) end end if id and (not Draw:IsInputFocused(id)) then Draw:SetInput(id, tostring(btn.value)) end end local a = Animation('slide_right' .. id .. '_alpha', {value = 1, target = btn.rightBox and Cursor:IsInBox(btn.parent, unpack(btn.rightBox)) and 0.5 or 1, speed = 250, animType = 'OutQuad', onProgress = callUpdatesAnimations }) or 1 Draw:RectFilledSVG(rightBtnX, y, sideBtn, height, 4, rgba(255, 255, 255, 0.08 * Alpha * a), 0, 0, 0, false, false) Draw:Image(rightBtnX, y, sideBtn, height, AsyncElements:Icon(sideBtn, height, false, [[ ]], 'clamp', 2), 0, 0, 0, tocolor(255, 255, 255, Alpha * a)) btn.lastRender = getTickCount() return btn.value end function Bottons:Options(id, x, y, w, h, values, suffix, font, opts, Alpha) assert(id, 'Invalid Options id') assert(type(x) == 'number' and type(y) == 'number', 'Options: x/y inválidos') assert(type(w) == 'number' and type(h) == 'number', 'Options: w/h inválidos') assert(type(values) == 'table' and #values > 0, 'Options: values inválido') opts = opts or {} Alpha = Alpha or 1 suffix = suffix or '' font = font or Fonts:Get('poppins-regular', 14, true) local btn = self.Instances[id] if not btn then btn = { id = id, values = values, index = opts.default or 1, parent = opts.parent or Parent:GetActive(), onClick = opts.onClick or function() return true end, renderTimeout = opts.renderTimeout or self.RenderTimeout, } if btn.index < 1 then btn.index = 1 end if btn.index > #values then btn.index = #values end function btn:Click() if not Bottons:IsRecentlyRendered(self) then return false end if not self.onClick() then return false end if self.leftBox and Cursor:IsInBox(self.parent, unpack(self.leftBox)) then self.index = self.index - 1 if self.index < 1 then self.index = #self.values end if opts.onChange then opts.onChange(self.values[self.index], self.index) end return true end if self.rightBox and Cursor:IsInBox(self.parent, unpack(self.rightBox)) then self.index = self.index + 1 if self.index > #self.values then self.index = 1 end if opts.onChange then opts.onChange(self.values[self.index], self.index) end return true end return false end self.Instances[id] = btn else btn.parent = opts.parent or Parent:GetActive() btn.values = values if btn.index > #btn.values then btn.index = #btn.values end if btn.index < 1 then btn.index = 1 end end local sideBtn = h local gap = opts.gap or 16 local leftBtnX = x local mainX = x + sideBtn + gap local rightBtnX = mainX + w + gap btn.leftBox = {leftBtnX, y, sideBtn, h} btn.rightBox = {rightBtnX, y, sideBtn, h} local a = Animation('slide_left' .. id .. '_alpha', {value = 1, target = btn.leftBox and Cursor:IsInBox(btn.parent, unpack(btn.leftBox)) and 0.5 or 1, speed = 250, animType = 'OutQuad', onProgress = callUpdatesAnimations }) or 1 Draw:RectFilledSVG(leftBtnX, y, sideBtn, h, 4, rgba(255 ,255, 255, 0.08 * Alpha * a), 0, 0, 0, false,false) Draw:Image(leftBtnX, y, sideBtn, h, AsyncElements:Icon(sideBtn, h, false, [[ ]], 'clamp', 2), 0, 0, 0, tocolor(255, 255, 255, Alpha * a)) Draw:RectFilledSVG(mainX, y, w, h, 4, rgba(255 ,255, 255, 0.05 * Alpha), 0, 0, 0, false,false) local text = tostring(btn.values[btn.index]) .. suffix Draw:Text(text, mainX, y, w, h, rgba(255 ,255, 255, Alpha), 1, font, {'center', 'center'}, false, false) local a = Animation('slide_right' .. id .. '_alpha', {value = 1, target = btn.leftBox and Cursor:IsInBox(btn.parent, unpack(btn.rightBox)) and 0.5 or 1, speed = 250, animType = 'OutQuad', onProgress = callUpdatesAnimations }) or 1 Draw:RectFilledSVG(rightBtnX, y, sideBtn, h, 4, rgba(255 ,255, 255, 0.08 * Alpha * a), 0, 0, 0, false,false) Draw:Image(rightBtnX, y, sideBtn, h, AsyncElements:Icon(sideBtn, h, false, [[ ]], 'clamp', 2), 0, 0, 0, tocolor(255, 255, 255, Alpha * a)) btn.lastRender = getTickCount() return btn.values[btn.index], btn.index end function Bottons:Toggle(id, x, y, w, h, font, value, opts, Alpha) assert(id, 'Invalid Toggle id') assert(x and y and w and h, 'Invalid Toggle parameters') opts = opts or {} Alpha = Alpha or 1 local btn = self.Instances[id] if not btn then btn = { id = id, value = not not value, parent = opts.parent or Parent:GetActive(), onClick = opts.onClick or function() return true end, renderTimeout = opts.renderTimeout or self.RenderTimeout, } function btn:Click() if not Bottons:IsRecentlyRendered(self) then return false end if Cursor:IsInBox(self.parent, self.x, self.y, self.w, self.h) then if self.onClick() then self.value = not self.value if opts.onChange then opts.onChange(self.value) end return true end end return false end self.Instances[id] = btn else btn.parent = opts.parent or Parent:GetActive() end btn.x, btn.y, btn.w, btn.h = x, y, w, h local onText = opts.onText or 'Ativado' local offText = opts.offText or 'Desativado' Draw:RectFilledSVG( x, y, w, h, 4, rgba(255, 255, 255, 0.05 * Alpha), 0, 0, 0, false, false ) Draw:Text( btn.value and onText or offText, x + 16, y, w, h, rgba(255, 255, 255, Alpha), 1, font, { 'left', 'center' } ) local r, g, b, a = Animation:CreateHover( 'toggle_color_' .. id, {255, 255, 255}, {255, 255, 255}, {255, 255, 255}, {0.10, 1}, btn.value, 250, 'InOutSine', true ) local kr, kg, kb = Animation:CreateHover( 'toggle_knob_color_' .. id, {255, 30}, {255, 35}, {255, 39}, nil, btn.value, 250, 'InOutSine', true) local knobX = Animation('toggle_knob_' .. id, { value = 0, target = btn.value and 22 or 0, speed = 250, animType = 'OutQuad', ForceInt = false, onProgress = callUpdatesAnimations}) or 0 local switchX = x + w - 55 local switchY = y + 8 Draw:RectFilledSVG( switchX, switchY, 38, 16, 8, rgba(r, g, b, a * Alpha), 0, 0, 0, false, false ) Draw:RectFilledSVG( switchX + 2 + knobX, switchY + 2, 12, 12, 6, rgba(kr, kg, kb, Alpha), 0, 0, 0, false, false ) btn.lastRender = getTickCount() return btn.value end return Bottons