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