if (type (Require) ~= 'function') then
loadstring(exports['nc_libraries']:extend('Extend'), 'Require')()
end
-- local functions
local _dDL = dxDrawLine
local _dDT = dxDrawText
local _dGTW = dxGetTextWidth
local _dDR = dxDrawRectangle
local _dDI = dxDrawImage
local _dDIS = dxDrawImageSection
local _dSRT = dxSetRenderTarget
local string_format = _G.string.format
local math_max = _G.math.max
local math_min = _G.math.min
local svgCreate = _G.svgCreate
local bitExtract = _G.bitExtract
local type = _G.type
-- class functions
local Draw = {};
Draw.Shaders = {};
function Draw:Constructor()
if not self.Initialized then
self.Initialized = true
else
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 Fonts or not _G.Fonts) then
_G.Fonts = Require('Interface/Fonts')
end
if (not AsyncElements or not _G.AsyncElements) then
_G.AsyncElements = Require('Interface/AsyncElements')
end
if (not Cursor or not _G.Cursor) then
_G.Cursor = Require('Interface/Cursor')
end
if (not Textures or not _G.Textures) then
_G.Textures = Require('Interface/Textures')
end
self.Responsive = Responsive
self.Parent = Parent
self.Fonts = Fonts
self.AsyncElements = AsyncElements
self.Cursor = Cursor
self.Textures = Textures
Require('Table')
Require('Math')
Require('Thread')
self.cache = {}
return self;
end
-- Render Target "Hook"
function Draw:SetRenderTarget(Element, Clear, Callback, ...)
local Update = _dSRT(Element, Clear)
if (Update and isElement(Element)) then
self.InRenderTarget = true
if (type(Callback) == 'function') then
Callback(...)
_dSRT()
self.InRenderTarget = false
end
else
self.InRenderTarget = false
end
return Update
end
-- Line
function Draw:Line(x1, y1, x2, y2, color, width, postGUI, subPixelPositioning)
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
x1, y1, x2, y2 = Parent.x + self.Responsive:Respc(x1), Parent.y + self.Responsive:Respc(y1), Parent.x + self.Responsive:Respc(x2), Parent.y + self.Responsive:Respc(y2)
else
x1, y1, x2, y2 = self.Responsive:Respc(x1), self.Responsive:Respc(y1), self.Responsive:Respc(x2), self.Responsive:Respc(y2)
end
end
return _dDL(x1, y1, x2, y2, color, width, postGUI, subPixelPositioning)
end
-- material
function Draw:MaterialSize(material)
if not isElement(material) then
return 0, 0
end
if not self.cache['materialSize'] then
self.cache['materialSize'] = {}
end
if not self.cache['materialSize'][material] then
self.cache['materialSize'][material] = {dxGetMaterialSize(material)}
end
local w = self.cache['materialSize'][material][1]
local h = self.cache['materialSize'][material][2]
return w, h
end
-- Text
function Draw:Text(text, x, y, w, h, color, scale, font, align, clip, wordBreak, postGUI, colorCoded, subPixelPositioning, fRotation, fRotationCenterX, fRotationCenterY)
assert(type(tostring(text)) == 'string', 'Draw:Text: text is not a string')
if (not font) then
font = 'default'
end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
x, y, w, h = Parent.x + self.Responsive:RespcX(x), Parent.y + self.Responsive:RespcY(y), self.Responsive:RespcX(w), self.Responsive:RespcY(h)
else
x, y, w, h = self.Responsive:RespcX(x), self.Responsive:RespcY(y), self.Responsive:RespcX(w), self.Responsive:RespcY(h)
end
end
local alignX, alignY = ((align and align[1]) or 'left'), ((align and align[2]) or 'top')
return _dDT(text, x, y, x + w, y + h, color, scale, font, alignX, alignY, clip, wordBreak, postGUI, colorCoded, subPixelPositioning, fRotation, fRotationCenterX, fRotationCenterY)
end
function Draw:TextWidth(text, font, scale, colorCoded)
if (not text or not font) then
return 0
end
if (not self.cache['textWidth']) then
self.cache['textWidth'] = {}
end
local scale, colorCoded = scale or 1, colorCoded or false
local index = text..scale..tostring(font)..tostring(colorCoded)..tostring(self.InRenderTarget)
if (not self.cache['textWidth'][index]) then
self.cache['textWidth'][index] = _dGTW(text, scale, font, colorCoded)
end
return self.cache['textWidth'][index]
end
function Draw:TextSize( Text, MaxSx, Scale, Font, Wordbreak )
assert(type(Text) == 'string', 'Draw:TextSize: Text is not a string')
assert(type(MaxSx) == 'number', 'Draw:TextSize: MaxSx is not a number')
assert(type(Scale) == 'number', 'Draw:TextSize: Scale is not a number')
if (not self.cache['textSize']) then
self.cache['textSize'] = {}
end
local FontHeight = self.Responsive:RespcYInverted(dxGetFontHeight( Scale, Font ))
Text = Text:gsub( "\n", " ` " )
local Index = Text..MaxSx..Scale..tostring(Font)..tostring(Wordbreak)
if (not self.cache['textSize'][Index]) then
local Strs = { }
local LineSx = 0
local LinesCount = 1
for word in Text:gmatch( "%S+" ) do
word = word == "`" and "" or ( word .. " " )
local WordSx = word == "" and 0 or self:TextWidth(word, Font, Scale)
LineSx = LineSx + WordSx
if word == "" or LineSx > MaxSx then
LinesCount = LinesCount + 1
Strs[ #Strs + 1 ] = "\n"
Strs[ #Strs + 1 ] = word
LineSx = WordSx
else
Strs[ #Strs + 1 ] = word
end
end
self.cache['textSize'][Index] = {MaxSx = MaxSx, LinesCount = LinesCount * FontHeight, Strs = table.concat(Strs)}
end
return self.cache['textSize'][Index].MaxSx, self.cache['textSize'][Index].LinesCount, self.cache['textSize'][Index].Strs
end
-- Image
function Draw:Image(X, Y, Width, Height, Image, Rotation, RotationCenterOffsetX, RotationCenterOffsetY, Color, PostGUI)
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
if type(Image) == 'string' and not Image:find('.svg') and not isElement(Image) then
Image = AsyncElements:Texture(Image, 30, {false, 'argb', true, 'clamp'})
end
Textures:UpdateTextureLastUsage(Image)
return Image, _dDI(X, Y, Width, Height, Image, Rotation or 0, RotationCenterOffsetX or 0, RotationCenterOffsetY or 0, Color, PostGUI)
end
function Draw:ImageSection(X, Y, Width, Height, uX, uY, uWidth, uHeight, Image, Rotation, RotationCenterOffsetX, RotationCenterOffsetY, Color, PostGUI)
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
-- uX, uY, uWidth, uHeight = self.Responsive:Respc(uX), self.Responsive:Respc(uY), self.Responsive:Respc(uWidth), self.Responsive:Respc(uHeight)
end
if type(Image) == 'string' and not Image:find('.svg') and not isElement(Image) then
Image = AsyncElements:Texture(Image, 30, {false, 'argb', true, 'clamp'})
end
Textures:UpdateTextureLastUsage(Image)
return Image, _dDIS(X, Y, Width, Height, uX, uY, uWidth, uHeight, Image, Rotation or 0, RotationCenterOffsetX or 0, RotationCenterOffsetY or 0, Color, PostGUI)
end
-- Rect SVG
local function generateRoundedRectPath(x, y, w, h, tl, tr, br, bl)
local path = string_format('M %f %f H %f', x + tl, y, x + w - tr)
if (tr > 0) then
path = path..string_format(' A %f %f 0 0 1 %f %f', tr, tr, x + w, y + tr)
else
path = path..string_format(' V %f', y)
end
path = path..string_format(' V %f', y + h - br)
if (br > 0) then
path = path..string_format(' A %f %f 0 0 1 %f %f', br, br, x + w - br, y + h)
else
path = path..string_format(' H %f', x + w)
end
path = path..string_format(' H %f', x + bl)
if (bl > 0) then
path = path..string_format(' A %f %f 0 0 1 %f %f', bl, bl, x, y + h - bl)
else
path = path..string_format(' V %f', y + h)
end
path = path..string_format(' V %f', y + tl)
if (tl > 0) then
path = path..string_format(' A %f %f 0 0 1 %f %f', tl, tl, x + tl, y)
else
path = path..string_format(' H %f', x)
end
path = path .. ' Z'
return path
end
function Draw:RectFilledSVG(X, Y, Width, Height, Radius, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning)
assert(type(X) == "number", "Draw:RectFilledSVG - X must be a number")
assert(type(Y) == "number", "Draw:RectFilledSVG - Y must be a number")
assert(type(Width) == "number", "Draw:RectFilledSVG - Width must be a number")
assert(type(Height) == "number", "Draw:RectFilledSVG - Height must be a number")
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if not self.InRenderTarget then
if Parent.x and Parent.y then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
if (Width <= 0 or Height <= 0) then
return false
end
if ((not Radius or Radius == 0) and (not Rotation or (Rotation % 360) == 0)) then
return _dDR(X, Y, Width, Height, Color, PostGUI, SubPixelPositioning)
end
local isFade = type(Color) == "table" and (#Color >= 2)
local function buildPath()
if type(Radius) == "table" then
return generateRoundedRectPath(
0, 0, Width, Height,
math.max(1, self.Responsive:Respc(Radius[1] or 0)),
math.max(1, self.Responsive:Respc(Radius[2] or 0)),
math.max(1, self.Responsive:Respc(Radius[3] or 0)),
math.max(1, self.Responsive:Respc(Radius[4] or 0))
)
else
local r = math.max(1, self.Responsive:Respc(Radius or 0))
return generateRoundedRectPath(0, 0, Width, Height, r, r, r, r)
end
end
local path = buildPath()
if isFade then
local Fade = {}
for i = 1, #Color do Fade[i] = Color[i] end
local dir = "vertical"
local AlphaG = 1
local prelast = Fade[#Fade - 1]
if type(prelast) == "string" and (prelast == "horizontal" or prelast == "vertical") then
dir = prelast
table.remove(Fade, #Fade - 1)
end
local last = Fade[#Fade]
if type(last) == "number" then
AlphaG = last
table.remove(Fade, #Fade)
end
local entries = {}
for i = 1, #Fade, 2 do
if type(Fade[i]) == "string" then
table.insert(entries, { Fade[i], Fade[i + 1] or 1 })
end
end
if #entries < 2 then
entries = { {"#FFFFFF", 1}, {"#FFFFFF", 1} }
end
local stops = {}
for i, v in ipairs(entries) do
local offset = math.floor(((i - 1) / (#entries - 1)) * 100)
stops[#stops + 1] = string.format(
"",
offset, v[1], v[2]
)
end
local gradAttrs = (dir == "horizontal")
and "x1='0%' y1='0%' x2='100%' y2='0%'"
or "x1='0%' y1='0%' x2='0%' y2='100%'"
local svgDef = string.format([[
]],
Width, Height, Width, Height,
gradAttrs, table.concat(stops, "\n"), path
)
local tex = Textures:RequestIcon(Width, Height, false, svgDef)
if tex then
Textures:UpdateIconLastUsage(tex)
return tex, _dDI(X, Y, Width, Height, tex, Rotation or 0, CenterX or 0, CenterY or 0, tocolor(255, 255, 255, AlphaG), PostGUI, SubPixelPositioning)
end
end
self.cache = self.cache or {}
self.cache.rectFilled = self.cache.rectFilled or {}
local id = tostring(Radius) .. Width .. Height
if not self.cache.rectFilled[id] or not isElement(self.cache.rectFilled[id]) then
self.cache.rectFilled[id] = nil
local svgDef = string.format([[
]], Width, Height, Width, Height, path)
local tex = Textures:RequestIcon(Width, Height, false, svgDef)
if tex then
self.cache.rectFilled[id] = tex
addEventHandler('onClientElementDestroy', tex, function()
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
if self.cache and self.cache.rectFilled and self.cache.rectFilled[id] == source then
self.cache.rectFilled[id] = nil
end
end)
end
end
if self.cache.rectFilled[id] then
Textures:UpdateIconLastUsage(self.cache.rectFilled[id])
return self.cache.rectFilled[id], _dDI(X, Y, Width, Height, self.cache.rectFilled[id], Rotation or 0, CenterX or 0, CenterY or 0, Color, PostGUI, SubPixelPositioning)
end
return false
end
function Draw:RectBorderSVG(X, Y, Width, Height, Radius, StrokeThickness, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning)
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
local sStroke = math.max(1, math.floor(self.Responsive:Respc(StrokeThickness)))
local margin = math.ceil(sStroke / 2)
local sRadius
if (Radius) then
if type(Radius) == 'table' then
sRadius = {
math.floor(self.Responsive:Respc(Radius[1] or 0)),
math.floor(self.Responsive:Respc(Radius[2] or 0)),
math.floor(self.Responsive:Respc(Radius[3] or 0)),
math.floor(self.Responsive:Respc(Radius[4] or 0))
}
else
sRadius = math.floor(self.Responsive:Respc(Radius))
end
local rId = type(sRadius) == "table" and table.concat(sRadius, "_") or sRadius
local id = string_format("rb_%d_%d_%s_%d", Width, Height, rId, sStroke)
if (not self.cache['rectBorder']) then
self.cache['rectBorder'] = {}
end
if (not self.cache['rectBorder'][id]) then
local canvasW, canvasH = Width + (margin * 2), Height + (margin * 2)
local xml = ""
if type(sRadius) == 'table' then
xml = string_format([[
]], canvasW, canvasH, canvasW, canvasH,
generateRoundedRectPath(0, 0, Width, Height, sRadius[1], sRadius[2], sRadius[3], sRadius[4]),
sStroke, margin, margin)
else
xml = string_format([[
]], canvasW, canvasH, canvasW, canvasH, margin, margin, Width, Height, sRadius, sStroke)
end
local tex = Textures:RequestIcon(canvasW, canvasH, false, xml, function(svg)
dxSetTextureEdge(svg, 'clamp')
end)
if (tex) then
self.cache['rectBorder'][id] = tex
addEventHandler('onClientElementDestroy', tex, function()
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
if self.cache and self.cache['rectBorder'] and self.cache['rectBorder'][id] == source then
self.cache['rectBorder'][id] = nil
end
end)
end
end
local texture = self.cache['rectBorder'][id]
if texture then
Textures:UpdateIconLastUsage(texture)
local drawW, drawH = Width + (margin * 2), Height + (margin * 2)
return texture, _dDI(X - margin, Y - margin, drawW, drawH, texture, Rotation or 0, CenterX or 0, CenterY or 0, Color, PostGUI, SubPixelPositioning)
end
end
return false, _dDR(X, Y, sStroke, Height, Color, PostGUI, SubPixelPositioning),
_dDR(X, Y, Width, sStroke, Color, PostGUI, SubPixelPositioning),
_dDR(X + sStroke, Y, Width - (sStroke * 2), sStroke, Color, PostGUI, SubPixelPositioning),
_dDR(X + sStroke, Y + Height - sStroke, Width - (sStroke * 2), sStroke, Color, PostGUI, SubPixelPositioning)
end
local function _isGradient(color)
return (type(color) == "table" and type(color[1]) == "string")
end
local function _isRGBA(color)
return (type(color) == "table" and type(color[1]) == "number")
end
function createVector(width, height, rawData, ID)
local svg = Textures:RequestIcon(width, height, false, rawData, nil, ID)
if not isElement(svg) then
error(string.format("createVector: falha ao criar SVG (%dx%d)", width, height))
return false
end
local svgXML = svgGetDocumentXML(svg)
if not svgXML then
destroyElement(svg)
error("createVector: falha ao obter XML do SVG.")
return false
end
local rect = xmlFindChild(svgXML, "rect", 0)
return {
svg = svg,
xml = svgXML,
rect = rect
}
end
function Draw:Circle(ID, X, Y, Diameter, Color, Rotation, CenterX, CenterY, PostGUI, BlendMode, Stroke, Percent, FillPercent)
if (not ID) then
return error("Draw:Circle => defina um 'ID' único para este círculo.", 2)
end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Diameter = Parent.x + self.Responsive:Respc(X), Parent.y + self.Responsive:Respc(Y), self.Responsive:Respc(Diameter)
else
X, Y, Diameter = self.Responsive:Respc(X), self.Responsive:Respc(Y), self.Responsive:Respc(Diameter)
end
end
self.cache['circles'] = self.cache['circles'] or {}
self.cache['circleStrokes'] = self.cache['circleStrokes'] or {}
self.cache['progressCircle'] = self.cache['progressCircle'] or {}
self.cache['progressFill'] = self.cache['progressFill'] or {}
self.cache['progressState'] = self.cache['progressState'] or {}
local function _polar(cx, cy, R, angDeg)
local a = math.rad(angDeg)
return (cx + R * math.cos(a)), (cy + R * math.sin(a))
end
local function _ensureProgressCircle(id, diameter, strokeWidth)
local existing = self.cache['progressCircle'][id]
if existing then return existing end
local d = diameter
local sW = strokeWidth
local r = (d - sW) / 2
local cx, cy = d / 2, d / 2
local len = 2 * math.pi * r
local svg = string.format([[
]], d, d, d, d, cx, cy, r, sW, len, len)
local handle = Textures:RequestIcon(d, d, false, svg, function(svgEl)
dxSetTextureEdge(svgEl, 'clamp')
end)
local entry = { svg = handle, radius = r, length = len, stroke = sW, width = d, height = d }
self.cache['progressCircle'][id] = entry
self.cache['progressState'][id] = self.cache['progressState'][id] or { false, 0, getTickCount(), false }
addEventHandler('onClientElementDestroy', handle, function() -- fast collect garbage
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
if self.cache['progressCircle'] and self.cache['progressCircle'][id] then
self.cache['progressCircle'][id] = nil
end
if self.cache['progressState'] and self.cache['progressState'][id] then
self.cache['progressState'][id] = nil
end
end)
return entry
end
local function _ensureProgressFill(id, diameter)
local existing = self.cache['progressFill'][id]
if existing then return existing end
local d = diameter
local R = d / 2
local cx, cy = R, R
local svg = string.format([[
]], d, d, d, d, cx, cy)
local handle = Textures:RequestIcon(d, d, false, svg, function(svgEl)
dxSetTextureEdge(svgEl, 'clamp')
end)
local entry = { svg = handle, radius = R, cx = cx, cy = cy, width = d, height = d }
self.cache['progressFill'][id] = entry
self.cache['progressState'][id] = self.cache['progressState'][id] or { false, 0, getTickCount(), false }
addEventHandler('onClientElementDestroy', handle, function() -- fast collect garbage
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
if self.cache['progressFill'] and self.cache['progressFill'][id] then
self.cache['progressFill'][id] = nil
end
if self.cache['progressState'] and self.cache['progressState'][id] then
self.cache['progressState'][id] = nil
end
end)
return entry
end
local function _setProgressPercent(v, id, percent)
local state = self.cache['progressState'][id]
percent = math.max(0, math.min(100, percent or 0))
if (not state or not state[4]) then
state = { false, percent, nil, true }
self.cache['progressState'][id] = state
xmlNodeSetAttribute(xmlFindChild(svgGetDocumentXML(v.svg), "circle", 0), "stroke-dashoffset", tostring(v.length - (v.length * (percent / 100))))
svgSetDocumentXML(v.svg, svgGetDocumentXML(v.svg))
return
end
if state[2] ~= percent then
if not state[1] then state[3] = getTickCount(); state[1] = true end
local elapsed = (getTickCount() - state[3]) / 1000
local cur = state[2]; local t = 0.2
local val = cur + (percent - cur) * t
if math.abs(percent - val) < 0.01 or elapsed > 1 then
val = percent; state[1] = false; state[3] = nil
end
state[2] = val
xmlNodeSetAttribute(xmlFindChild(svgGetDocumentXML(v.svg), "circle", 0), "stroke-dashoffset", tostring(v.length - (v.length * (val / 100))))
svgSetDocumentXML(v.svg, svgGetDocumentXML(v.svg))
elseif state[1] then
state[1] = false
end
end
local function _setFillPercent(v, id, percent)
local state = self.cache['progressState'][id]
percent = math.max(0, math.min(100, percent or 0))
local svgXML = svgGetDocumentXML(v.svg)
local pathNode = xmlFindChild(svgXML, "path", 0)
local sweep = (percent / 100) * 360
local dAttr
if sweep <= 0.001 then
dAttr = string.format("M %f %f", v.cx, v.cy)
elseif sweep >= 359.999 then
local x0, y0 = _polar(v.cx, v.cy, v.radius, -90)
local x180, y180 = _polar(v.cx, v.cy, v.radius, 90)
dAttr = string.format(
"M %f %f L %f %f A %f %f 0 1 1 %f %f A %f %f 0 1 1 %f %f Z",
v.cx, v.cy, x0, y0, v.radius, v.radius, x180, y180, v.radius, v.radius, x0, y0
)
else
local startA, endA = -90, -90 + sweep
local x1, y1 = _polar(v.cx, v.cy, v.radius, startA)
local x2, y2 = _polar(v.cx, v.cy, v.radius, endA)
local large = (sweep > 180) and 1 or 0
dAttr = string.format(
"M %f %f L %f %f A %f %f 0 %d 1 %f %f Z",
v.cx, v.cy, x1, y1, v.radius, v.radius, large, x2, y2
)
end
xmlNodeSetAttribute(pathNode, "d", dAttr)
svgSetDocumentXML(v.svg, svgXML)
end
local drawColor = (type(Color) == "number") and Color
or (type(Color) == "table" and tocolor(Color[1] or 255, Color[2] or 255, Color[3] or 255, Color[4] or 255))
or tocolor(255, 255, 255, 255)
if Percent and Stroke then
local strokeWidth = self.Responsive:Respc(Stroke[2] or 4, true)
local v = _ensureProgressCircle(ID, Diameter, strokeWidth)
_setProgressPercent(v, ID, Percent)
Textures:UpdateIconLastUsage(v.svg)
return _dDI(X, Y, Diameter, Diameter, v.svg, Rotation or 0, CenterX or 0, CenterY or 0,
(type(Stroke[1]) == "number" and Stroke[1]) or drawColor, PostGUI, BlendMode)
end
if FillPercent then
local v = _ensureProgressFill(ID, Diameter)
_setFillPercent(v, ID, FillPercent)
Textures:UpdateIconLastUsage(v.svg)
return _dDI(X, Y, Diameter, Diameter, v.svg, Rotation or 0, CenterX or 0, CenterY or 0, drawColor, PostGUI, BlendMode)
end
local fillIndex = ('circle:%d'):format(Diameter)
if not self.cache['circles'][fillIndex] then
self.cache['circles'][fillIndex] = Textures:RequestIcon(Diameter, Diameter, false, string.format([[
]], Diameter, Diameter, Diameter, Diameter, Diameter/2, Diameter/2, math.max(1, (Diameter/2) - 0.5)), function(svg)
dxSetTextureEdge(svg, 'clamp')
end)
if isElement(self.cache['circles'][fillIndex]) then
addEventHandler('onClientElementDestroy', self.cache['circles'][fillIndex], function() -- fast collect garbage
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
if self.cache['circles'] and self.cache['circles'][fillIndex] then
self.cache['circles'][fillIndex] = nil
end
end)
else
self.cache['circles'][fillIndex] = nil
return false, 'Failed to create circle texture'
end
end
local tex = self.cache['circles'][fillIndex]
if tex then
Textures:UpdateIconLastUsage(tex)
_dDI(X, Y, Diameter, Diameter, tex, Rotation or 0, CenterX or 0, CenterY or 0, drawColor, PostGUI, BlendMode)
end
if Stroke and not Percent then
local sW = self.Responsive:Respc(Stroke[2] or 2, true)
local sIndex = ('stroke:%d:%d'):format(Diameter, sW)
if not self.cache['circleStrokes'][sIndex] then
self.cache['circleStrokes'][sIndex] = Textures:RequestIcon(Diameter + sW, Diameter + sW, false, string.format([[
]], Diameter + sW, Diameter + sW, Diameter + sW, Diameter + sW, (Diameter + sW)/2, (Diameter + sW)/2, math.max(1, (Diameter/2) - sW/2), sW), function(svg)
dxSetTextureEdge(svg, 'border', tocolor(255,255,255))
end)
if isElement(self.cache['circleStrokes'][sIndex]) then
addEventHandler('onClientElementDestroy', self.cache['circleStrokes'][sIndex], function() -- fast collect garbage
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
self.cache['circleStrokes'][sIndex] = nil
end)
else
self.cache['circleStrokes'][sIndex] = nil
return false, 'Failed to create circle stroke texture'
end
end
local sTex = self.cache['circleStrokes'][sIndex]
if sTex then
Textures:UpdateIconLastUsage(sTex)
_dDI(X - sW * 0.5, Y - sW * 0.5, Diameter + sW, Diameter + sW, sTex, Rotation or 0, CenterX or 0, CenterY or 0,
(type(Stroke[1]) == "number" and Stroke[1]) or drawColor, PostGUI, BlendMode)
end
end
end
function Draw:Rect(ID, X, Y, Width, Height, Color, Percent, MinW, MinH, Rotation, CenterX, CenterY, PostGUI, BlendMode, Radius, Stroke, FadeDir)
if not ID then return error("Draw:Rect => defina um 'ID' único para este retângulo.", 2) end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if not self.InRenderTarget then
if Parent.x and Parent.y then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
self.cache['rectSolid'] = self.cache['rectSolid'] or {}
self.cache['rectAnim'] = self.cache['rectAnim'] or {}
local function _anim2d(state, aIdx, vIdx, tIdx, target)
target = math.max(-100, math.min(100, tonumber(target) or 0))
state[vIdx] = target
state[aIdx] = false
state[tIdx] = nil
return state[vIdx]
end
local function _ensureRectFillSolid(id, w, h)
local v = self.cache['rectSolid'][id]
if v and (v.width ~= w or v.height ~= h) then
self.cache['rectSolid'][id] = nil;
v = nil
end
if v then return v end
local rawFill = string.format([[
]], w, h, w, h, w/2, w/2, h)
local rawStroke = string.format([[
]], w, h, w, h)
local fillSvg = createVector(w, h, rawFill, ID .. ":fill")
local grad = xmlFindChild(xmlFindChild(fillSvg.xml, "defs", 0), "linearGradient", 0)
local s1 = xmlFindChild(grad, "stop", 0)
local s2 = xmlFindChild(grad, "stop", 1)
local fPath = xmlFindChild(fillSvg.xml, "path", 0)
local strokeSvg = createVector(w, h, rawStroke, ID..":stroke")
local sPath = xmlFindChild(strokeSvg.xml, "path", 0)
self.cache['rectSolid'][id] = {width = w, height = h, fill = {svgDetails = fillSvg, grad = grad, s1 = s1, s2 = s2, path = fPath}, stroke = {svgDetails = strokeSvg, path = sPath}}
self.cache['rectAnim'][id] = self.cache['rectAnim'][id] or {false, 0, getTickCount(), false, 0, getTickCount()}
addEventHandler('onClientElementDestroy', fillSvg.svg, function() -- fast collect garbage
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
if isElement(strokeSvg.svg) then
destroyElement(strokeSvg.svg)
end
if self.cache['rectSolid'] and self.cache['rectSolid'][id] then
self.cache['rectSolid'][id] = nil
end
if self.cache['rectAnim'] and self.cache['rectAnim'][id] then
self.cache['rectAnim'][id] = nil
end
end)
addEventHandler('onClientElementDestroy', strokeSvg.svg, function() -- fast collect garbage
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
if isElement(fillSvg.svg) then
destroyElement(fillSvg.svg)
end
if self.cache['rectSolid'] and self.cache['rectSolid'][id] then
self.cache['rectSolid'][id] = nil
end
if self.cache['rectAnim'] and self.cache['rectAnim'][id] then
self.cache['rectAnim'][id] = nil
end
end)
return self.cache['rectSolid'][id]
end
local fadeDir = (FadeDir == "horizontal") and "horizontal" or "vertical"
local v = _ensureRectFillSolid(ID, Width, Height)
local pW, pH = 100, 100
if type(Percent) == "table" then
pW = tonumber(Percent.w or Percent[1]) or 100
pH = tonumber(Percent.h or Percent[2]) or 100
elseif Percent then
pW = tonumber(Percent) or 100
end
local state = self.cache.rectAnim[ID]
local pw = _anim2d(state, 1, 2, 3, pW)
local ph = _anim2d(state, 4, 5, 6, pH)
local inverseH, inverseW = ph < 0, pw < 0
local wpx = math.max(MinW or 0, math.min(Width, math.floor(Width * (math.abs(pw) / 100) + 0.5)))
local hpx = math.max(MinH or 0, math.min(Height, math.floor(Height * (math.abs(ph) / 100) + 0.5)))
wpx, hpx = math.max(1, wpx), math.max(1, hpx)
-- gradiente ou cor simples
if type(Color) == "table" then
if fadeDir == "vertical" then
if inverseH then
xmlNodeSetAttribute(v.fill.grad, "x1", tostring(Width / 2))
xmlNodeSetAttribute(v.fill.grad, "y1", tostring(Height))
xmlNodeSetAttribute(v.fill.grad, "x2", tostring(Width / 2))
xmlNodeSetAttribute(v.fill.grad, "y2", "0")
else
xmlNodeSetAttribute(v.fill.grad, "x1", tostring(Width / 2))
xmlNodeSetAttribute(v.fill.grad, "y1", "0")
xmlNodeSetAttribute(v.fill.grad, "x2", tostring(Width / 2))
xmlNodeSetAttribute(v.fill.grad, "y2", tostring(Height))
end
else
if inverseW then
xmlNodeSetAttribute(v.fill.grad, "x1", tostring(Width))
xmlNodeSetAttribute(v.fill.grad, "y1", tostring(Height / 2))
xmlNodeSetAttribute(v.fill.grad, "x2", "0")
xmlNodeSetAttribute(v.fill.grad, "y2", tostring(Height / 2))
else
xmlNodeSetAttribute(v.fill.grad, "x1", "0")
xmlNodeSetAttribute(v.fill.grad, "y1", tostring(Height / 2))
xmlNodeSetAttribute(v.fill.grad, "x2", tostring(Width))
xmlNodeSetAttribute(v.fill.grad, "y2", tostring(Height / 2))
end
end
xmlNodeSetAttribute(v.fill.s1, "stop-color", tostring(Color[1] or "#FFFFFF"))
xmlNodeSetAttribute(v.fill.s1, "stop-opacity", tostring(Color[2] or 1))
xmlNodeSetAttribute(v.fill.s2, "stop-color", tostring(Color[3] or "#FFFFFF"))
xmlNodeSetAttribute(v.fill.s2, "stop-opacity", tostring(Color[4] or 1))
v.fill.singleColor = nil
else
xmlNodeSetAttribute(v.fill.s1, "stop-color", "#FFFFFF")
xmlNodeSetAttribute(v.fill.s2, "stop-color", "#FFFFFF")
xmlNodeSetAttribute(v.fill.path, "fill", "white")
v.fill.singleColor = Color
end
local rx = self.Responsive:RespcY(Radius or (Height / 2))
local pathFill
if inverseH then
pathFill = generateRoundedRectPath(0, Height - hpx, wpx, hpx, rx, rx, rx, rx)
elseif inverseW then
pathFill = generateRoundedRectPath(Width - wpx, 0, wpx, hpx, rx, rx, rx, rx)
else
pathFill = generateRoundedRectPath(0, 0, wpx, hpx, rx, rx, rx, rx)
end
xmlNodeSetAttribute(v.fill.path, "d", pathFill)
svgSetDocumentXML(v.fill.svgDetails.svg, v.fill.svgDetails.xml)
if type(Stroke) == "table" and Stroke[2] and Stroke[2] > 0 then
local sW = self.Responsive:Respc(Stroke[2], true)
local pathStroke
if inverseH then
pathStroke = generateRoundedRectPath(sW/2, Height - hpx + sW/2, wpx - sW, hpx - sW, rx - sW/2, rx - sW/2, rx - sW/2, rx - sW/2)
elseif inverseW then
pathStroke = generateRoundedRectPath(Width - wpx + sW/2, sW/2, wpx - sW, hpx - sW, rx - sW/2, rx - sW/2, rx - sW/2, rx - sW/2)
else
pathStroke = generateRoundedRectPath(sW/2, sW/2, wpx - sW, hpx - sW, rx - sW/2, rx - sW/2, rx - sW/2, rx - sW/2)
end
xmlNodeSetAttribute(v.stroke.path, "d", pathStroke)
xmlNodeSetAttribute(v.stroke.path, "stroke-width", tostring(sW))
svgSetDocumentXML(v.stroke.svgDetails.svg, v.stroke.svgDetails.xml)
end
_dDI(X, Y, Width, Height, v.fill.svgDetails.svg, Rotation or 0, CenterX or 0, CenterY or 0, v.fill.singleColor or tocolor(255,255,255,255), PostGUI, BlendMode)
if type(Stroke) == "table" then
_dDI(X, Y, Width, Height, v.stroke.svgDetails.svg, Rotation or 0, CenterX or 0, CenterY or 0, Stroke[1], PostGUI, BlendMode)
end
Textures:UpdateIconLastUsage(v.fill.svgDetails.svg)
return wpx, hpx
end
function Draw:Input(Name, X, Y, Width, Height, xMouse, yMouse, wMouse, hMouse, Scale, Font, Placeholder, AlignX, AlignY, Color, MaxLength, ValidateString, Format, Parent, ...)
local _, WordBreak = ...
self.cache.inputs = self.cache.inputs or {}
local inputs = self.cache.inputs
local args = {...}
local ParentActive = Parent or self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if not inputs[Name] or not isElement(inputs[Name].element) then
local edit = guiCreateEdit(-1000, -1000, 200, 30, "", false)
guiSetVisible(edit, true)
guiEditSetMaxLength(edit, MaxLength or 128)
guiSetProperty(edit, "ValidationString", ValidateString or ".*")
guiSetText(edit, "")
inputs[Name] = { element = edit, selected = false, xMouse = xMouse, yMouse = yMouse, wMouse = wMouse, hMouse = hMouse}
else
inputs[Name].xMouse = xMouse
inputs[Name].yMouse = yMouse
end
local input = inputs[Name]
local element = input.element
local isClicked = getKeyState("mouse1")
if isClicked and not input.lastClick then
if Cursor:IsInBox(ParentActive, xMouse, yMouse, wMouse, hMouse) then
for _,v in pairs(inputs) do v.selected = false end
input.selected = true
guiBringToFront(element)
guiSetInputMode("no_binds_when_editing")
guiEditSetCaretIndex(element, #guiGetText(element))
else
input.selected = false
end
end
input.lastClick = isClicked
if not Animation:Exists("text_cursor") then
Animation:Create("text_cursor", {value = 0, target = 1, speed = 500, animType = "InOutQuad", ForceInt = false, onProgress = callUpdatesAnimations})
end
local blink = Animation:Get("text_cursor") or 0
if blink >= 1 then Animation:Edit("text_cursor", {target = 0, speed = 500, animType = "InOutQuad", ForceInt = false, onProgress = callUpdatesAnimations})
elseif blink <= 0 then Animation:Edit("text_cursor", {target = 1, speed = 500, animType = "InOutQuad", ForceInt = false, onProgress = callUpdatesAnimations}) end
local function setColorAlpha(color, alpha)
local r = bitExtract(color, 16, 8)
local g = bitExtract(color, 8, 8)
local b = bitExtract(color, 0, 8)
local a = bitExtract(color, 24, 8)
return tocolor(r, g, b, math.min(alpha, a / 255))
end
local text = guiGetText(element) or ""
if Format and type(Format) == "function" then text = Format(text) end
if text == "" and Placeholder and not input.selected then
self:Text(Placeholder, X, Y, Width, Height, Color, Scale, Font, {AlignX, AlignY}, ...)
return ""
end
local textW = self.Responsive:RespcInverted(dxGetTextWidth(text, Scale, Font, false))
local lines = {}
if WordBreak and textW >= Width then
local line = ""
for i = 1, #text do
local c = text:sub(i, i)
local testLine = line .. c
if self.Responsive:RespcInverted(dxGetTextWidth(testLine, Scale, Font, false)) > Width then
table.insert(lines, line)
line = c
else
line = testLine
end
end
if line ~= "" then table.insert(lines, line) end
local maxSx, maxSy, formatted = Draw:TextSize('d', 200, 1, Font, true)
for i, line in ipairs(lines) do
self:Text(line, X, Y + (i-1) * self.Responsive:RespcInverted(maxSy), Width, Height, Color, Scale, Font, {AlignX, AlignY}, ...)
if i == #lines and input.selected and blink > 0 then
local textWidth = self.Responsive:RespcInverted(dxGetTextWidth(line, Scale, Font, false))
if AlignX == "center" then textWidth = textWidth / 2 end
self:Text("|", X + textWidth + 2, Y + (i-1) * self.Responsive:RespcInverted(maxSy), Width, Height, setColorAlpha(Color, blink), Scale, Font, {AlignX, AlignY}, ...)
end
end
else
self:Text(text, X, Y, Width, Height, Color, Scale, Font, {AlignX, AlignY}, ...)
if input.selected and blink > 0 then
local textWidth = self.Responsive:RespcInverted(dxGetTextWidth(text, Scale, Font, false))
if AlignX == "center" then textWidth = textWidth / 2 end
if args[1] then
textWidth = math.min(textWidth, Width - 10)
end
self:Text("|", X + textWidth + 2, Y, Width, Height, setColorAlpha(Color, blink), Scale, Font, {AlignX, AlignY}, ...)
end
end
return guiGetText(element) or ""
end
function Draw:GetInputData(name)
if not self.cache.inputs or not self.cache.inputs[name] then return end
local element = self.cache.inputs[name].element
return self.cache.inputs[name]
end
function Draw:SetInput(Name, Text)
if not self.cache.inputs or not self.cache.inputs[Name] then return end
guiSetText(self.cache.inputs[Name].element, tostring(Text) or "")
end
function Draw:GetInput(Name)
return self.cache.inputs and self.cache.inputs[Name] and guiGetText(self.cache.inputs[Name].element) or ""
end
function Draw:InputFreeLength(Name)
if not self.cache.inputs or not self.cache.inputs[Name] then return 0 end
local maxLength = guiEditGetMaxLength(self.cache.inputs[Name].element) or 128
local currentLength = #guiGetText(self.cache.inputs[Name].element)
return math.max(0, maxLength - currentLength)
end
function Draw:IsInputFocused(Name)
return self.cache.inputs and self.cache.inputs[Name] and self.cache.inputs[Name].selected or false
end
function Draw:IsAnyInput()
if not self.cache.inputs then return false end
for name, data in pairs(self.cache.inputs) do
if data.selected then
return true, name
end
end
return false
end
function Draw:DestroyAllInputs()
if not self.cache.inputs then return end
for name, data in pairs(self.cache.inputs) do
if data.element and isElement(data.element) then
destroyElement(data.element)
end
end
Animation:Destroy('text_cursor')
self.cache.inputs = {}
end
function Draw:Scroll2(ID, X, Y, Width, Height, ItemSize, Spacing, PerRow, List, Mode, Sens)
self.cache.scrolls = self.cache.scrolls or {}
local st = self.cache.scrolls[ID] or { drag = false, scroll = 0 }
self.cache.scrolls[ID] = st
PerRow = math.max(1, tonumber(PerRow) or 1)
Mode = Mode or "vertical"
Sens = Sens or 0.5
local isHorizontal = Mode == "horizontal"
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
local Size = isHorizontal and Width or Height
local Row = ItemSize + Spacing
local TotalItems = type(List) == "table" and #List or List
local TotalRows = (TotalItems > 0) and math.ceil(TotalItems / PerRow) or 0
local TotalContent = TotalRows * Row
local MaxScroll = math.max(0, TotalContent - Size)
local Ext = st.drag and (st.scroll or 0) or (Animation:Get(ID) or 0)
Ext = math.max(0, math.min(Ext, MaxScroll))
if MaxScroll > 0 then
local Over = Cursor:IsInBox(ParentActive, X, Y, Width, Height)
local M1 = getKeyState("mouse1")
if Over and M1 and not st.drag then
st.drag = true
local CX, CY = Cursor:GetData(ParentActive)
st.last = isHorizontal and CX or CY
Animation(ID, { value = Ext, target = Ext, speed = 0, animType = "Linear", ForceInt = false })
elseif st.drag and not M1 then
st.drag = false
st.last = nil
Animation(ID, { value = Ext, target = Ext, speed = 0, animType = "Linear", ForceInt = false })
end
if st.drag then
local CX, CY = Cursor:GetData(ParentActive)
local Mouse = isHorizontal and CX or CY
local delta = 0
if st.last then
delta = Mouse - st.last
end
st.last = Mouse
Ext = Ext - self.Responsive:RespcInverted(delta * Sens)
Ext = math.max(0, math.min(Ext, MaxScroll))
st.scroll = Ext
Animation(ID, { value = Ext, target = Ext, speed = 0, animType = "Linear", ForceInt = false })
end
end
return st.scroll or 0
end
function Draw:Scroll(ID, X, Y, Width, Height, ItemHeight, SpacingY, PerRow, List, RailColor, ThumbColor, MinThumb)
self.cache.scrolls = self.cache.scrolls or {}
local st = self.cache.scrolls[ID] or { drag = false, scroll = 0 }
self.cache.scrolls[ID] = st
PerRow = math.max(1, tonumber(PerRow) or 1)
MinThumb = MinThumb or 40
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
local absY = (Parent.y and (Parent.y + self.Responsive:Respc(Y))) or Y
local RowH = ItemHeight + SpacingY
local TotalItems = type(List) == "table" and #List or List
local TotalRows = (TotalItems > 0) and math.ceil(TotalItems / PerRow) or 0
local TotalContent = TotalRows * RowH
local MaxScroll = math.max(0, TotalContent - Height)
local Ext = st.drag and (st.scroll or 0) or (Animation:Get(ID) or 0)
Ext = math.max(0, math.min(Ext, MaxScroll))
if MaxScroll > 0 then
self:RectFilledSVG(X, Y, Width, Height, 4, RailColor)
local Th = math.max((Height / TotalContent) * Height, MinThumb)
local Avail = Height - Th
local Pct = (Ext / MaxScroll)
local BarY = Y + (Avail * Pct)
local OverRail = Cursor:IsInBox(ParentActive, X - Width, Y - Width, Width * 3, Height + Width * 2)
local M1 = getKeyState("mouse1")
if OverRail and M1 and not st.drag then
st.drag = true
Animation(ID, { value = Ext, target = Ext, speed = 0, animType = "Linear", ForceInt = false })
elseif st.drag and not M1 then
st.drag = false
Animation(ID, { value = Ext, target = Ext, speed = 0, animType = "Linear", ForceInt = false })
end
if st.drag then
local _, CY = Cursor:GetData()
local My = self.Responsive:RespcInverted(CY)
-- diferença crucial: aqui ajustamos para o mesmo sistema do rail
local ParentOffset = Parent.y and self.Responsive:RespcInverted(Parent.y) or 0
local NewTop = math.max(Y, math.min(My - ParentOffset - Th * 0.5, Y + Avail))
Pct = (Avail > 0) and ((NewTop - Y) / Avail) or 0
Ext = Pct * MaxScroll
BarY = Y + (Avail * Pct)
st.scroll = Ext
Animation(ID, { value = Ext, target = Ext, speed = 0, animType = "Linear", ForceInt = false })
end
self:RectFilledSVG(X, BarY, Width, Th, 4, ThumbColor)
end
return st.scroll or 0
end
function Draw:SetScrollPosition(ID, Position)
local scrollData = self.cache.scrolls and self.cache.scrolls[ID]
if not scrollData then return end
local Pos = tonumber(Position) or 0
scrollData.scroll = math.max(0, Pos)
Animation(ID, { value = 0, target = scrollData.scroll, speed = 3000, animType = "SoftElastic", ForceInt = false, onProgress = callUpdatesAnimations })
end
function Draw:ResetAllScrolls()
if not self.cache.scrolls then return end
for id, data in pairs(self.cache.scrolls) do
data.scroll = 0
Animation(id, { value = 0, target = 0, speed = 0, animType = "Linear", ForceInt = false })
end
end
function Draw:DestroyAllScrolls()
if not self.cache.scrolls then return end
for id, data in pairs(self.cache.scrolls) do
self:DestroyScroll(id)
end
return true
end
function Draw:DestroyScroll(ID)
if not self.cache.scrolls then return end
if self.cache.scrolls[ID] then
self.cache.scrolls[ID] = nil
Animation:Destroy(ID)
end
return true
end
function Draw:GetScroll(ID)
local scrollData = self.cache.scrolls and self.cache.scrolls[ID]
return scrollData and scrollData.scroll or 0
end
function Draw:Stroke(ID, X, Y, Width, Height, Radius, StrokeSize, Color, Rotation, CenterX, CenterY, PostGUI, BlendMode, Percent, FadeDir)
if not ID then
return error("Draw:Stroke => defina um 'ID' único para este stroke.", 2)
end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
self.cache['strokes'] = self.cache['strokes'] or {}
self.cache['progressStrokeRect'] = self.cache['progressStrokeRect'] or {}
self.cache['progressState'] = self.cache['progressState'] or {}
local FadeDir = (FadeDir == 'horizontal') and 'horizontal' or 'vertical'
local StrokeWidth = self.Responsive:Respc(StrokeSize or 1, true)
local function _roundedRectPerimeter(w, h, r1, r2, r3, r4)
local straights = math_max(0, (w - (r1 + r2)) + (w - (r3 + r4)) + (h - (r2 + r3)) + (h - (r4 + r1)))
local arcs = (math.pi / 2) * (r1 + r2 + r3 + r4)
return straights + arcs
end
local function _polar(cx, cy, R, angDeg)
local a = math.rad(angDeg)
return (cx + R * math.cos(a)), (cy + R * math.sin(a))
end
local function _applyGradient(v, w, h, color, fadeDir)
if not v or type(color) ~= "table" then return end
local grad = v.grad
local s1 = v.s1
local s2 = v.s2
if not (grad and s1 and s2) then return end
if fadeDir == 'horizontal' then
xmlNodeSetAttribute(grad, "x1", "0")
xmlNodeSetAttribute(grad, "y1", tostring(h/2))
xmlNodeSetAttribute(grad, "x2", tostring(w))
xmlNodeSetAttribute(grad, "y2", tostring(h/2))
else
xmlNodeSetAttribute(grad, "x1", tostring(w/2))
xmlNodeSetAttribute(grad, "y1", "0")
xmlNodeSetAttribute(grad, "x2", tostring(w/2))
xmlNodeSetAttribute(grad, "y2", tostring(h))
end
xmlNodeSetAttribute(s1, "stop-color", tostring(color[1] or "#FFFFFF"))
xmlNodeSetAttribute(s1, "stop-opacity", tostring(color[2] or 1))
xmlNodeSetAttribute(s2, "stop-color", tostring(color[3] or "#FFFFFF"))
xmlNodeSetAttribute(s2, "stop-opacity", tostring(color[4] or 1))
end
local function _ensureProgressStrokeRect(id, w, h, radius, typeRectangle, strokeWidth)
local cached = self.cache['progressStrokeRect'][id]
if cached then return cached end
local svgW, svgH = w + strokeWidth, h + strokeWidth
local ox, oy = strokeWidth / 2, strokeWidth / 2
local r1, r2, r3, r4
if typeRectangle == 'semi-rounded' then
r1 = math_max(0, (radius[1] or 0))
r2 = math_max(0, (radius[2] or 0))
r3 = math_max(0, (radius[3] or 0))
r4 = math_max(0, (radius[4] or 0))
else
local r = math_max(0, (radius or 0))
r1, r2, r3, r4 = r, r, r, r
end
local function buildPathStartMid(x, y, W, H, a, b, c, d)
local topStraight = math_max(0, W - (a + b))
local xs = x + a + topStraight * 0.5
local yt = y
local xr = x + W
local xl = x
local yb = y + H
return string.format(
"M %f %f H %f A %f %f 0 0 1 %f %f V %f A %f %f 0 0 1 %f %f H %f A %f %f 0 0 1 %f %f V %f A %f %f 0 0 1 %f %f H %f Z",
xs, yt,
(x + W - b),
b, b, xr, (y + b),
(y + H - c),
c, c, (x + W - c), yb,
(x + d),
d, d, xl, (y + H - d),
(y + a),
a, a, (x + a), yt,
xs
)
end
local isCircle = (math.abs(w - h) < 0.5)
and (math.abs(r1 - w * 0.5) < 0.5)
and (math.abs(r2 - w * 0.5) < 0.5)
and (math.abs(r3 - w * 0.5) < 0.5)
and (math.abs(r4 - w * 0.5) < 0.5)
local raw, length, svg, grad, s1, s2, node
if isCircle then
local R = w * 0.5
local cx = ox + R
local cy = oy + R
length = 2 * math.pi * R
raw = string.format([[
]], svgW, svgH, svgW, svgH, svgW/2, svgW/2, svgH, cx, cy, R, strokeWidth, length, length)
svg = createVector(svgW, svgH, raw)
grad = xmlFindChild(xmlFindChild(svg.xml, "defs", 0), "linearGradient", 0)
s1 = xmlFindChild(grad, "stop", 0)
s2 = xmlFindChild(grad, "stop", 1)
node = xmlFindChild(svg.xml, "circle", 0)
else
local pathData = buildPathStartMid(ox, oy, w, h, r1, r2, r3, r4)
length = _roundedRectPerimeter(w, h, r1, r2, r3, r4)
raw = string.format([[
]], svgW, svgH, svgW, svgH, svgW/2, svgW/2, svgH, pathData, strokeWidth, length, length)
svg = createVector(svgW, svgH, raw)
grad = xmlFindChild(xmlFindChild(svg.xml, "defs", 0), "linearGradient", 0)
s1 = xmlFindChild(grad, "stop", 0)
s2 = xmlFindChild(grad, "stop", 1)
node = xmlFindChild(svg.xml, "path", 0)
end
local entry = {
type='rect-stroke-progress',
width=svgW, height=svgH, length=length,
svgDetails=svg, node=node,
grad=grad, s1=s1, s2=s2, sW=strokeWidth
}
self.cache['progressStrokeRect'][id] = entry
self.cache['progressState'][id] = self.cache['progressState'][id] or { false, 0, getTickCount() }
addEventHandler('onClientElementDestroy', svg.svg, function() -- fast collect garbage
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
self.cache['progressStrokeRect'][id] = nil
self.cache['progressState'][id] = nil
end)
return entry
end
local function _setRectProgressPercent(id, percent)
local v = self.cache['progressStrokeRect'][id]; if not v then return end
percent = math_max(0, math.min(100, tonumber(percent) or 0))
local state = self.cache['progressState'][id] or { false, 0, getTickCount() }
self.cache['progressState'][id] = state
if state[2] ~= percent then
if not state[1] then state[3] = getTickCount(); state[1] = true end
local elapsed = (getTickCount() - state[3]) / 1000
local cur = state[2]; local t = 0.2
local val = cur + (percent - cur) * t
if math.abs(percent - val) < 0.01 or elapsed > 1 then
val = percent; state[1] = false; state[3] = nil
end
state[2] = val
local dashoffset = v.length - (v.length * (val / 100))
xmlNodeSetAttribute(v.node, "stroke-dashoffset", tostring(dashoffset))
svgSetDocumentXML(v.svgDetails.svg, v.svgDetails.xml)
elseif state[1] then
state[1] = false
end
end
if Percent ~= nil then
local typeRectangle = (type(Radius) == 'table') and 'semi-rounded' or 'rounded'
local v = _ensureProgressStrokeRect(ID, Width, Height, Radius, typeRectangle, StrokeWidth)
_applyGradient(v, v.width, v.height, Color, FadeDir)
_setRectProgressPercent(ID, Percent)
local drawCol = (type(Color) == "table") and tocolor(255,255,255, tonumber(Color[5]) or 255) or Color
Textures:UpdateIconLastUsage(v.svgDetails.svg)
return _dDI(X - StrokeWidth*0.5, Y - StrokeWidth*0.5, v.width, v.height, v.svgDetails.svg, Rotation or 0, CenterX or 0, CenterY or 0, drawCol, PostGUI, BlendMode)
end
local typeRectangle = (type(Radius) == 'table') and 'semi-rounded' or 'rounded'
local baseKey = Width..'x'..Height..':'..typeRectangle..':'..StrokeWidth..':'..tostring(FadeDir)
local index = ID..':'..baseKey
if not self.cache['strokes'][index] then
local r1, r2, r3, r4
if typeRectangle == 'semi-rounded' then
r1 = Radius[1]; r2 = Radius[2]; r3 = Radius[3]; r4 = Radius[4]
else
r1 = Radius; r2 = Radius; r3 = Radius; r4 = Radius
end
local pathData = generateRoundedRectPath(
StrokeWidth / 2, StrokeWidth / 2,
(Width) - StrokeWidth,
(Height) - StrokeWidth,
math_max(0, r1 - (StrokeWidth / 2)),
math_max(0, r2 - (StrokeWidth / 2)),
math_max(0, r3 - (StrokeWidth / 2)),
math_max(0, r4 - (StrokeWidth / 2))
)
self.cache['strokes'][index] = Textures:RequestIcon(Width, Height, false, string.format([[
]], Width, Height, Width, Height - 1, pathData, StrokeWidth), function(svg)
dxSetTextureEdge(svg, 'border', tocolor(255,255,255))
end)
if isElement(self.cache['strokes'][index]) then
addEventHandler('onClientElementDestroy', self.cache['strokes'][index], function() -- fast collect garbage
removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func)
self.cache['strokes'][index] = nil
end)
else
self.cache['strokes'][index] = nil
return false, 'Failed to create stroke texture'
end
end
local tex = self.cache['strokes'][index]
if tex then
local drawCol = (type(Color) == "table") and tocolor(255,255,255, tonumber(Color[5]) or 255) or Color
Textures:UpdateIconLastUsage(tex)
_dDI(X, Y, Width, Height, tex, Rotation or 0, CenterX or 0, CenterY or 0, drawCol, PostGUI, BlendMode)
end
end
function Draw:DestroyCircles()
if self.cache['circles'] then
for k, element in pairs(self.cache['circles']) do
if isElement(element) then
destroyElement(element)
end
end
for k, element in pairs(self.cache['circleStrokes']) do
if isElement(element) then
destroyElement(element)
end
end
self.cache['circles'] = nil
self.cache['circleStrokes'] = nil
self.cache['progressCircle'] = nil
self.cache['progressFill'] = nil
self.cache['progressState'] = nil
end
end
function Draw:DestroySolid()
if self.cache['rectSolid'] then
for k, v in pairs(self.cache['rectSolid']) do
if isElement(v.fill and v.fill.svgDetails) then
destroyElement(v.fill.svgDetails)
end
if isElement(v.stroke and v.stroke.svgDetails) then
destroyElement(v.stroke.svgDetails)
end
end
self.cache['rectSolid'] = nil
self.cache['rectAnim'] = nil
end
end
function Draw:DestroyStrokes()
if self.cache['strokes'] then
for k, element in pairs(self.cache['strokes']) do
if isElement(element) then
destroyElement(element)
end
end
self.cache['strokes'] = nil
self.cache['progressState'] = nil
self.cache['progressStrokeRect'] = nil
end
end
function Draw:DestroyBorderSVG()
if self.cache['rectBorder'] then
for k, element in pairs(self.cache['rectBorder']) do
if isElement(element) then
destroyElement(element)
end
self.cache['rectBorder'][k] = nil
end
end
end
function Draw:DestroySVG()
if self.cache['rectFilled'] then
for k, element in pairs(self.cache['rectFilled']) do
if isElement(element) then
destroyElement(element)
end
self.cache['rectFilled'][k] = nil
end
self.cache['rectFilled'] = {}
end
end
function Draw:RectFilledShader(...)
local Id, X, Y, Width, Height, Radius, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning
if type(select(1, ...)) == 'string' then
Id, X, Y, Width, Height, Radius, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning = ...
else
X, Y, Width, Height, Radius, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning = ...
end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
if (Radius and ((type(Radius) == 'number' and Radius > 0) or (type (Radius) == 'table' and (Radius[1] > 0 or Radius[2] > 0 or Radius[3] > 0 or Radius[4] > 0)))) then
local Cache = self:GetRectangleShader(Id, Width, Height, Color, Radius, {0, 0}, false, false, false, false)
if Cache.Element then
return Cache.Element, _dDI(X, Y, Width, Height, Cache.Element, Rotation or 0, CenterX or 0, CenterY or 0, Color, PostGUI, SubPixelPositioning)
else
return false, _dDR(X, Y, Width, Height, Color, PostGUI, SubPixelPositioning)
end
else
return false, _dDR(X, Y, Width, Height, Color, PostGUI, SubPixelPositioning)
end
end
function Draw:RectBorderShader(...)
local Id, X, Y, Width, Height, Radius, StrokeThickness, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning
if type(select(1, ...)) == 'string' then
Id, X, Y, Width, Height, Radius, StrokeThickness, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning = ...
else
X, Y, Width, Height, Radius, StrokeThickness, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning = ...
end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
if (Radius and ((type(Radius) == 'number' and Radius > 0) or type (Radius) == 'table' and (Radius[1] > 0 or Radius[2] > 0 or Radius[3] > 0 or Radius[4] > 0))) then
local Cache = self:GetRectangleShader(Id, Width, Height, Color, Radius, {StrokeThickness, StrokeThickness}, false, false, false, true, false)
if Cache.Element then
return Cache.Element, _dDI(X, Y, Width, Height, Cache.Element, Rotation or 0, CenterX or 0, CenterY or 0, Color, PostGUI, SubPixelPositioning)
else
return false, _dDR(X, Y, Width, Height, Color, PostGUI, SubPixelPositioning)
end
else
return false, _dDR(X, Y, StrokeThickness, Height, Color, PostGUI, SubPixelPositioning),
_dDR(X, Y, Width, StrokeThickness, Color, PostGUI, SubPixelPositioning),
_dDR(X + StrokeThickness, Y, Width - (StrokeThickness * 2), StrokeThickness, Color, PostGUI, SubPixelPositioning),
_dDR(X + StrokeThickness, Y + Height - StrokeThickness, Width - (StrokeThickness * 2), StrokeThickness, Color, PostGUI, SubPixelPositioning)
end
end
function Draw:RectFilledShaderWithTexture(...)
local Id, X, Y, Width, Height, Radius, Color, Texture, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning;
if type(select(1, ...)) == 'string' then
Id, X, Y, Width, Height, Radius, Color, Texture, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning = ...
else
X, Y, Width, Height, Radius, Color, Texture, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning = ...
end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
if type(Texture) == 'string' and not Texture:find('.svg') then
Texture = AsyncElements:Texture(Texture, 10, {false, 'argb', true, 'clamp'}, function(texture) return true end)
end
if (type(Radius) == 'number' or type (Radius) == 'table') then
local Cache = self:GetRectangleShader(Id, Width, Height, Color, Radius, {0, 0}, false, Texture, false, false, false)
if Cache.Element then
return Cache.Element, _dDI(X, Y, Width, Height, Cache.Element, Rotation or 0, CenterX or 0, CenterY or 0, Color, PostGUI, SubPixelPositioning)
end
end
return false
end
function Draw:RectFilledShader3D(...)
local Id, X, Y, Width, Height, Radius, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning;
if type(select(1, ...)) == 'string' then
Id, X, Y, Width, Height, Radius, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning = ...
else
X, Y, Width, Height, Radius, Color, Rotation, CenterX, CenterY, PostGUI, SubPixelPositioning = ...
end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
if not Radius then
Radius = 0;
end
local Cache = self:GetRectangleShader(Id, Width, Height, Color, Radius, {0, 0}, false, false, false, false, false)
local Shader = Cache.Element
local Options = Cache.Options
if Shader then
local scaleX = 1
local scaleY = 1
local radX = 0
local radY = 0
if Cursor:GetState() then
local absX, absY = Cursor:GetData()
if absX and absY then
if Cursor:IsInBox(absX, absY, X, Y, Width, Height, Radius) then
-- posição relativa dentro do quad: -1 .. 1
local relX = ((absX - X) / Width - 0.5) * 2
local relY = ((absY - Y) / Height - 0.5) * 2
local maxRot = MaxRot or 5
local rotX = -relY * maxRot
local rotY = relX * maxRot
radX = math.rad(math.abs(rotX))
radY = math.rad(math.abs(rotY))
scaleX = 1 / math.cos(radY)
scaleY = 1 / math.cos(radX)
-- só rotação, sem offsets extras
dxSetShaderTransform(Shader, rotX, rotY, 0)
else
dxSetShaderTransform(Shader, 0, 0, 0)
end
end
end
local renderW = Width * scaleX
local renderH = Height * scaleY
local renderX = X - (renderW - Width) / 2
local renderY = Y - (renderH - Height) / 2
return Shader, _dDI(renderX, renderY, renderW, renderH, Shader, Rotation or 0, CenterX or 0, CenterY or 0, tocolor(255,255,255,255), PostGUI, SubPixelPositioning)
end
return false
end
function Draw:RectFilledWithTexture3D(...)
local Id, X, Y, Width, Height, Radius, Color, Texture, Rotation, CenterX, CenterY, MaxRot, PostGUI, SubPixelPositioning;
if type(select(1, ...)) == 'string' then
Id, X, Y, Width, Height, Radius, Color, Texture, Rotation, CenterX, CenterY, MaxRot, PostGUI, SubPixelPositioning = ...
else
X, Y, Width, Height, Radius, Color, Texture, Rotation, CenterX, CenterY, MaxRot, PostGUI, SubPixelPositioning = ...
end
local ParentActive = self.Parent:GetActive()
local Parent = ParentActive and self.Parent:Get(ParentActive) or {}
if (not self.InRenderTarget) then
if (Parent.x and Parent.y) then
X, Y, Width, Height = Parent.x + self.Responsive:RespcX(X), Parent.y + self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
else
X, Y, Width, Height = self.Responsive:RespcX(X), self.Responsive:RespcY(Y), self.Responsive:RespcX(Width), self.Responsive:RespcY(Height)
end
end
if (not Radius or Radius == 0) then
Radius = 0;
end
local effect3D = false
if Cursor:GetState() then
local absX, absY = Cursor:GetData()
if absX and absY and Cursor:IsInBox(X, Y, Width, Height, Radius) then
-- posição relativa dentro do quad: -1 .. 1
local relX = ((absX - X) / Width - 0.5) * 2
local relY = ((absY - Y) / Height - 0.5) * 2
local maxRot = MaxRot or 5
local rotX = -relY * maxRot
local rotY = relX * maxRot
effect3D = {rotX, rotY, 0}
end
end
local Cache = self:GetRectangleShader(Id, Width, Height, Color, Radius, {0, 0}, false, Texture, effect3D, false, false)
local Shader = Cache.Element
local Options = Cache.Options
if type(Texture) == 'string' and not Texture:find('.svg') then
Texture = AsyncElements:Texture(Texture, 15, {false, 'argb', true, 'clamp'}, function(Texture)
return Draw:GetRectangleShader(Id, Width, Height, Color, Radius, {0, 0}, false, Texture, effect3D, false, true)
end)
end
if Shader then
local scaleX = 1
local scaleY = 1
if Cursor:GetState() then
local absX, absY = Cursor:GetData()
if absX and absY then
if Cursor:IsInBox(X, Y, Width, Height, Radius) then
-- posição relativa dentro do quad: -1 .. 1
local relX = ((absX - X) / Width - 0.5) * 2
local relY = ((absY - Y) / Height - 0.5) * 2
local maxRot = MaxRot or 5
local rotX = -relY * maxRot
local rotY = relX * maxRot
radX = math.rad(math.abs(rotX))
radY = math.rad(math.abs(rotY))
scaleX = 1 / math.cos(radY)
scaleY = 1 / math.cos(radX)
-- só rotação, sem offsets extras
dxSetShaderTransform(Shader, rotX, rotY, 0)
else
dxSetShaderTransform(Shader, 0, 0, 0)
end
end
end
local renderW = Width * scaleX
local renderH = Height * scaleY
local renderX = X - (renderW - Width) / 2
local renderY = Y - (renderH - Height) / 2
-- desenha
return Shader, _dDI(renderX, renderY, renderW, renderH, Shader, Rotation or 0, CenterX or 0, CenterY or 0, tocolor(255,255,255,255), PostGUI, SubPixelPositioning)
end
return false
end
function Draw:GetRectangleShader(Id, Width, Height, Color, Radius, BorderThickness, IsRelative, SourceTexture, Effect3D, BorderOnly, UpdateInstantly)
local Now = getTickCount()
if not self.cache['rectShader'] then
self.cache['rectShader'] = {}
end
if not Id then
Id = 'default'
end
if not self.cache['rectShader'][Id] then
self.cache['rectShader'][Id] = {}
end
local Cache = self.cache['rectShader'][Id]
if not Cache.Element then
local Element = dxCreateShader(Draw.Shaders['Rectangle'])
if isElement(Element) then
Cache.Element = Element
Cache.Options = {}
Cache.Options.LastUpdate = Now
addEventHandler('onClientElementDestroy', Element, function()
removeEventHandler('onClientElementDestroy', Element, debug.getinfo(1).func)
if self.cache['rectShader'][Id] then
self.cache['rectShader'][Id] = nil
end
end)
else
self.cache['rectShader'][Id] = nil
return false, 'Element not found'
end
end
if not self.MainThreadRunning then
if next(self.cache['rectShader']) ~= nil then
self:MainThread()
end
end
if Cache.Element then
local Shader = Cache.Element
local Options = Cache.Options or {}
self:setRectangleBorderOnly(Id, BorderOnly)
self:setRectangleWidth(Id, Width or 512)
self:setRectangleHeight(Id, Height or 512)
self:setRectangleRadius(Id, Radius, IsRelative)
self:setRectangleBorderThickness(Id, BorderThickness[1] ~= 0 and BorderThickness[1] * 2.0 or 0, BorderThickness[2] ~= 0 and BorderThickness[2] * 2.0 or 0)
self:setRectangleTexture(Id, SourceTexture)
self:setRectangleColor(Id, Color, Color)
if Effect3D then
local targetX, targetY, targetZ = Effect3D[1], Effect3D[2], Effect3D[3]
local lerpSpeed = Effect3D[4] or 0.15 -- velocidade de transição (0.1~0.2 é suave)
-- valores atuais armazenados no cache
Cache.Options.RotX = Cache.Options.RotX or 0
Cache.Options.RotY = Cache.Options.RotY or 0
Cache.Options.RotZ = Cache.Options.RotZ or 0
Cache.Options.lerpSpeed = Cache.Options.lerpSpeed or lerpSpeed
-- interpolação linear
Cache.Options.RotX = Cache.Options.RotX + (targetX - Cache.Options.RotX) * lerpSpeed
Cache.Options.RotY = Cache.Options.RotY + (targetY - Cache.Options.RotY) * lerpSpeed
Cache.Options.RotZ = Cache.Options.RotZ + (targetZ - Cache.Options.RotZ) * lerpSpeed
Cache.Options.Effect3D = true
dxSetShaderTransform(Shader, Cache.Options.RotX, Cache.Options.RotY, Cache.Options.RotZ)
else
-- suaviza de volta ao centro
if Cache.Options.Effect3D then
local lerpSpeed = 0.15
Cache.Options.RotX = (Cache.Options.RotX or 0) * (1 - lerpSpeed)
Cache.Options.RotY = (Cache.Options.RotY or 0) * (1 - lerpSpeed)
Cache.Options.RotZ = (Cache.Options.RotZ or 0) * (1 - lerpSpeed)
dxSetShaderTransform(Shader, Cache.Options.RotX, Cache.Options.RotY, Cache.Options.RotZ)
if math.abs(Cache.Options.RotX) < 0.01 and math.abs(Cache.Options.RotY) < 0.01 then
dxSetShaderTransform(Shader, 0, 0, 0) -- trava no zero
Cache.Options.Effect3D = false
end
end
end
Cache.Options.LastUpdate = Now
return Cache
end
end
--// Rectangle Vars
function Draw:setRectangleBorderOnly(Id, BorderOnly)
local Cache = self.cache['rectShader'][Id]
local Options = Cache.Options or {}
local Shader = Cache.Element
Cache.Options.BorderOnly = BorderOnly or false
return true
end
function Draw:setRectangleWidth(Id, Width)
local Cache = self.cache['rectShader'][Id]
if not Cache then
self.cache['rectShader'][Id] = nil
return false, 'Element not found'
end
Cache.Width = Width
return true
end
function Draw:setRectangleHeight(Id, Height)
local Cache = self.cache['rectShader'][Id]
if not Cache then
self.cache['rectShader'][Id] = nil
return false, 'Element not found'
end
Cache.Height = Height
return true
end
--// Rectangle Shader Vars
function Draw:setRectangleRadius(Id, Radius, IsRelative)
local Cache = self.cache['rectShader'][Id]
local Options = Cache.Options or {}
local Shader = Cache.Element
if not isElement(Shader) then
self.cache['rectShader'][Id] = nil
return false, 'Element not found'
end
if Radius then
if type(Radius) == 'table' then
if (not self.InRenderTarget) then
Radius[1], Radius[2], Radius[3], Radius[4] = self.Responsive:Respc(Radius[1]), self.Responsive:Respc(Radius[2]), self.Responsive:Respc(Radius[3]), self.Responsive:Respc(Radius[4])
end
if not table.compare(Radius, (Options.Radius or {})) then
dxSetShaderValue(Shader, 'radius', Radius[1], Radius[2], Radius[3], Radius[4])
Cache.Options.Radius = Radius
end
elseif type(Radius) == 'number' then
if (not self.InRenderTarget) then
Radius = self.Responsive:Respc(Radius)
end
if not table.compare({Radius, Radius, Radius, Radius}, (Options.Radius or {})) then
dxSetShaderValue(Shader, 'radius', Radius, Radius, Radius, Radius)
Cache.Options.Radius = {Radius, Radius, Radius, Radius}
end
end
else
if not table.compare({0, 0, 0, 0}, (Options.Radius or {})) then
dxSetShaderValue(Shader, 'radius', 0, 0, 0, 0)
Cache.Options.Radius = {0, 0, 0, 0}
end
end
if IsRelative then
if IsRelative ~= (Options.IsRelative or false) then
dxSetShaderValue(Shader, 'isRelative', {IsRelative and 1 or 0, IsRelative and 1 or 0, IsRelative and 1 or 0, IsRelative and 1 or 0})
Cache.Options.IsRelative = IsRelative
end
else
dxSetShaderValue(Shader, 'isRelative', {0, 0, 0, 0})
Cache.Options.IsRelative = false
end
end
function Draw:setRectangleBorderThickness(Id, Horizontal, Vertical)
local Cache = self.cache['rectShader'][Id]
local Options = Cache.Options or {}
local Shader = Cache.Element
if not isElement(Shader) then
self.cache['rectShader'][Id] = nil
return false, 'Element not found'
end
if Horizontal and Vertical then
if Horizontal ~= (Options.BorderThickness or {}).Horizontal or Vertical ~= (Options.BorderThickness or {}).Vertical then
dxSetShaderValue(Shader, 'borderThickness', Horizontal / 100, Vertical / 100)
dxSetShaderValue(Shader, "borderSoft", 0.005)
Cache.Options.BorderThickness = {Horizontal = Horizontal, Vertical = Vertical}
end
else
if (Options.BorderThickness or {}).Horizontal ~= 0 or (Options.BorderThickness or {}).Vertical ~= 0 then
dxSetShaderValue(Shader, 'borderThickness', 0, 0)
Cache.Options.BorderThickness = {Horizontal = 0, Vertical = 0}
end
end
end
function Draw:setRectangleTexture(Id, Texture)
local Cache = self.cache['rectShader'][Id]
local Options = Cache.Options or {}
local Shader = Cache.Element
if not isElement(Shader) then
self.cache['rectShader'][Id] = nil
return false, 'Element not found'
end
if isElement(Texture) then
if (Options.SourceTexture or false) ~= Texture then
dxSetShaderValue(Shader, 'sourceTexture', Texture)
dxSetShaderValue(Shader, 'isTexture', true)
Cache.Options.SourceTexture = Texture
end
else
if (Options.SourceTexture or false) then
dxSetShaderValue(Shader, 'sourceTexture', 0)
dxSetShaderValue(Shader, 'isTexture', false)
Cache.Options.SourceTexture = nil
end
end
end
function Draw:setRectangleColor(Id, Color, BorderColor)
local Cache = self.cache['rectShader'][Id]
local Options = Cache.Options or {}
local Shader = Cache.Element
if not isElement(Shader) then
self.cache['rectShader'][Id] = nil
return false, 'Element not found'
end
if Options.BorderOnly then
if BorderColor then
if (Options.BorderColor or false) ~= BorderColor then
local r, g, b, a = self.fromColorToRGBA(BorderColor)
dxSetShaderValue(Shader, 'borderColor', r / 255, g / 255, b / 255, 255 / 255)
dxSetShaderValue(Shader, 'color', r, g, b, 0)
dxSetShaderValue(Shader, 'colorOverwritten', true)
Options.borderColor = BorderColor
Options.Color = tocolor(255, 255, 255, 0)
end
else
if (Options.BorderColor or false) then
dxSetShaderValue(Shader, 'borderColor', 1, 1, 1, 1)
dxSetShaderValue(Shader, 'colorOverwritten', false)
Options.borderColor = tocolor(255, 255, 255, 255)
Options.Color = tocolor(255, 255, 255, 255)
end
end
return true
end
-- if Color then
-- if (Options.Color or false) ~= Color then
-- local r, g, b, a = self.fromColorToRGBA(Color)
-- dxSetShaderValue(Shader, 'colorOverwritten', true)
-- dxSetShaderValue(Shader, 'color', r / 255, g / 255, b / 255, 255 / 255)
-- Options.Color = Color
-- end
-- else
-- local Color = tocolor(255, 255, 255, 255)
-- if (Options.Color or false) ~= Color then
-- dxSetShaderValue(Shader, 'color', 1, 1, 1, 1)
-- dxSetShaderValue(Shader, 'colorOverwritten', false)
-- Options.Color = Color
-- end
-- end
-- if BorderColor then
-- if (Options.BorderColor or false) ~= BorderColor then
-- local r, g, b, a = self.fromColorToRGBA(BorderColor)
-- dxSetShaderValue(Shader, 'borderColor', r / 255, g / 255, b / 255, 255 / 255)
-- Options.BorderColor = BorderColor
-- end
-- else
-- if (Options.BorderColor or false) then
-- Options.BorderColor = tocolor(255, 255, 255, 255)
-- end
-- end
dxSetShaderValue(Shader, 'borderColor', 1, 1, 1, 1)
dxSetShaderValue(Shader, 'color', 1, 1, 1, 1)
dxSetShaderValue(Shader, 'colorOverwritten', false)
return true
end
function Draw:DestroyShader(Id)
if Id then
if (not self.cache['rectShader']) then
return
end
if (not self.cache['rectShader'][Id]) then
return
end
if isElement(self.cache['rectShader'][Id].Element) then
destroyElement(self.cache['rectShader'][Id].Element)
end
self.cache['rectShader'][Id] = nil
else
if (not self.cache['rectShader']) then
return
end
for Id, element in pairs(self.cache['rectShader']) do
if isElement(element.Element) then
destroyElement(element.Element)
end
self.cache['rectShader'][Id] = nil
end
self.cache['rectShader'] = nil
end
end
function Draw:DestroyAll()
self:DestroyShader()
self:DestroyBorderSVG()
self:DestroySVG()
self:DestroySolid()
self:DestroyStrokes()
self:DestroyCircles()
Textures:DestroyAll()
self:DestroyAllScrolls()
end
-- Util's function
function Draw.fromColorToRGBA(color)
local b = color%256
color = (color-b)/256
local g = color%256
color = (color-g)/256
local r = color%256
color = (color-r)/256
local a = color%256
return r,g,b,a
end
function Draw:MainThread()
if self.MainThreadRunning then
return false, 'Main thread already running'
end
self.MainThreadRunning = true
CreateThread(function(self)
Wait(25 * 1000) -- Wait 25 seconds to ensure the cache is populated
while self.MainThreadRunning and self.cache['rectShader'] and next(self.cache['rectShader']) ~= nil do
Wait(3 * 60 * 1000)
for Id, Cache in pairs(self.cache['rectShader']) do
if Cache and isElement(Cache.Element) then
if Cache.Options and Cache.Options.LastUpdate and (Cache.Options.LastUpdate + (5 * 60 * 1000)) < getTickCount() then
self:DestroyShader(Id)
end
else
self.cache['rectShader'][Id] = nil
end
end
end
self.MainThreadRunning = false
end, self)
end
-- Raws Shaders
Draw.Shaders['Rectangle'] = [[
texture sourceTexture;
bool isTexture = false;
float4 color = float4(1,1,1,1);
float4 borderColor = float4(1,1,1,1);
bool textureRotated = false;
float4 isRelative = 1;
float4 radius = 0.2;
float borderSoft = 0.01;
bool colorOverwritten = false;
float2 borderThickness = 0.2;
float radiusMultipler = 1.0;
float4 UV = float4(0,0,1,1);
float textureRot = 0;
float2 textureRotCenter = float2(0.5,0.5);
SamplerState tSampler{
Texture = sourceTexture;
};
float4 rndRect(float2 tex: TEXCOORD0, float4 _color : COLOR0):COLOR0{
float4 result = borderColor;
float alp = 1;
float2 tempTex = (tex*UV.zw+UV.xy);
/*float thetaCos = cos(-textureRot/180.0*PI);
float thetaSin = sin(-textureRot/180.0*PI);
float2x2 rot = float4(thetaCos,-thetaSin,thetaSin,thetaCos);
float2 rotedTex = mul(tempTex-textureRotCenter,rot)+textureRotCenter;*/
float2 tex_bk = tempTex;
float2 dx = ddx(tempTex);
float2 dy = ddy(tempTex);
float2 dd = float2(length(float2(dx.x,dy.x)),length(float2(dx.y,dy.y)));
float a = dd.x/dd.y;
float2 center = 0.5*float2(1/(a<=1?a:1),a<=1?1:a);
float4 nRadius;
float aA = borderSoft*100;
if(a<=1){
tempTex.x /= a;
aA *= dd.y;
nRadius = float4(isRelative.x==1?radius.x/2:radius.x*dd.y,isRelative.y==1?radius.y/2:radius.y*dd.y,isRelative.z==1?radius.z/2:radius.z*dd.y,isRelative.w==1?radius.w/2:radius.w*dd.y);
}else{
tempTex.y *= a;
aA *= dd.x;
nRadius = float4(isRelative.x==1?radius.x/2:radius.x*dd.x,isRelative.y==1?radius.y/2:radius.y*dd.x,isRelative.z==1?radius.z/2:radius.z*dd.x,isRelative.w==1?radius.w/2:radius.w*dd.x);
}
float2 fixedPos = tempTex-center;
tempTex %= 1;
float2 corner[] = {center-nRadius.x,center-nRadius.y,center-nRadius.z,center-nRadius.w};
bool leftTopSideX = fixedPos.x <= -corner[0].x;
bool leftTopSideY = fixedPos.y <= -corner[0].y;
bool rightTopSideX = fixedPos.x >= corner[1].x;
bool rightTopSideY = fixedPos.y <= -corner[1].y;
bool rightBottomSideX = fixedPos.x >= corner[2].x;
bool rightBottomSideY = fixedPos.y >= corner[2].y;
bool leftBottomSideX = fixedPos.x <= -corner[3].x;
bool leftBottomSideY = fixedPos.y >= corner[3].y;
if(leftTopSideX && leftTopSideY){ //LTCorner
float dis = distance(-fixedPos,corner[0]);
alp *= saturate(1-(dis-nRadius.x)/aA-0.5);
}
if(rightTopSideX && rightTopSideY){ //RTCorner
float dis = distance(float2(fixedPos.x,-fixedPos.y),corner[1]);
alp *= saturate(1-(dis-nRadius.y)/aA-0.5);
}
if(rightBottomSideX && rightBottomSideY){ //RBCorner
float dis = distance(float2(fixedPos.x,fixedPos.y),corner[2]);
alp *= saturate(1-(dis-nRadius.z)/aA-0.5);
}
if(leftBottomSideX && leftBottomSideY){ //LBCorner
float dis = distance(float2(-fixedPos.x,fixedPos.y),corner[3]);
alp *= saturate(1-(dis-nRadius.w)/aA-0.5);
}
if(fixedPos.x <= 0){
if(fixedPos.y <= 0){
if (!leftTopSideX && (nRadius[0] || nRadius[1]))
alp *= saturate((fixedPos.y+center.y)/aA+0.5);
if (!leftTopSideY && (nRadius[0] || nRadius[3]))
alp *= saturate((fixedPos.x+center.x)/aA+0.5);
}else{
if (!leftBottomSideX && (nRadius[2] || nRadius[3]))
alp *= saturate((-fixedPos.y+center.y)/aA+0.5);
if (!leftBottomSideY && (nRadius[0] || nRadius[3]))
alp *= saturate((fixedPos.x+center.x)/aA+0.5);
}
}else{
if(fixedPos.y <= 0){
if (!rightTopSideX && (nRadius[0] || nRadius[1]))
alp *= saturate((fixedPos.y+center.y)/aA+0.5);
if (!rightTopSideY && (nRadius[1] || nRadius[2]))
alp *= saturate((-fixedPos.x+center.x)/aA+0.5);
}else{
if (!rightBottomSideX && (nRadius[2] || nRadius[3]))
alp *= saturate((-fixedPos.y+center.y)/aA+0.5);
if (!rightBottomSideY && (nRadius[1] || nRadius[2]))
alp *= saturate((-fixedPos.x+center.x)/aA+0.5);
}
}
alp = saturate(alp);
float nAlp = 1;
if(borderThickness[0] > 0 && borderThickness[1] > 0){
float2 newborderThickness = borderThickness*dd*100;
tex_bk = tex_bk+tex_bk*newborderThickness;
dx = ddx(tex_bk);
dy = ddy(tex_bk);
dd = float2(length(float2(dx.x,dy.x)),length(float2(dx.y,dy.y)));
a = dd.x/dd.y;
center = 0.5*float2(1/(a<=1?a:1),a<=1?1:a);
aA = borderSoft*100;
if(a<=1){
tex_bk.x /= a;
aA *= dd.y;
nRadius = float4(isRelative.x==1?radius.x/2:radius.x*dd.y,isRelative.y==1?radius.y/2:radius.y*dd.y,isRelative.z==1?radius.z/2:radius.z*dd.y,isRelative.w==1?radius.w/2:radius.w*dd.y);
}
else{
tex_bk.y *= a;
aA *= dd.x;
nRadius = float4(isRelative.x==1?radius.x/2:radius.x*dd.x,isRelative.y==1?radius.y/2:radius.y*dd.x,isRelative.z==1?radius.z/2:radius.z*dd.x,isRelative.w==1?radius.w/2:radius.w*dd.x);
}
fixedPos = (tex_bk-center*(newborderThickness+1));
float4 nRadiusHalf = nRadius;
nRadiusHalf.xz -= newborderThickness.x/2;
nRadiusHalf.yw -= newborderThickness.y/2;
corner[0] = center-nRadiusHalf.x;
corner[1] = center-nRadiusHalf.y;
corner[2] = center-nRadiusHalf.z;
corner[3] = center-nRadiusHalf.w;
leftTopSideX = fixedPos.x <= -corner[0].x;
leftTopSideY = fixedPos.y <= -corner[0].y;
rightTopSideX = fixedPos.x >= corner[1].x;
rightTopSideY = fixedPos.y <= -corner[1].y;
rightBottomSideX = fixedPos.x >= corner[2].x;
rightBottomSideY = fixedPos.y >= corner[2].y;
leftBottomSideX = fixedPos.x <= -corner[3].x;
leftBottomSideY = fixedPos.y >= corner[3].y;
if(leftTopSideX && leftTopSideY){ //LTCorner
float dis = distance(-fixedPos,corner[0]);
nAlp *= saturate(1-(dis-nRadiusHalf.x)/aA-0.5);
}
if(rightTopSideX && rightTopSideY){ //RTCorner
float dis = distance(float2(fixedPos.x,-fixedPos.y),corner[1]);
nAlp *= saturate(1-(dis-nRadiusHalf.y)/aA-0.5);
}
if(rightBottomSideX && rightBottomSideY){ //RBCorner
float dis = distance(float2(fixedPos.x,fixedPos.y),corner[2]);
nAlp *= saturate(1-(dis-nRadiusHalf.z)/aA-0.5);
}
if(leftBottomSideX && leftBottomSideY){ //LBCorner
float dis = distance(float2(-fixedPos.x,fixedPos.y),corner[3]);
nAlp *= saturate(1-(dis-nRadiusHalf.w)/aA-0.5);
}
}
if(fixedPos.x <= 0){
if(fixedPos.y <= 0){
if (!leftTopSideX)
nAlp *= saturate((fixedPos.y+center.y)/aA+0.5);
if (!leftTopSideY)
nAlp *= saturate((fixedPos.x+center.x)/aA+0.5);
}else{
if (!leftBottomSideX)
nAlp *= saturate((-fixedPos.y+center.y)/aA+0.5);
if (!leftBottomSideY)
nAlp *= saturate((fixedPos.x+center.x)/aA+0.5);
}
}else{
if(fixedPos.y <= 0){
if (!rightTopSideX)
nAlp *= saturate((fixedPos.y+center.y)/aA+0.5);
if (!rightTopSideY)
nAlp *= saturate((-fixedPos.x+center.x)/aA+0.5);
}else{
if (!rightBottomSideX)
nAlp *= saturate((-fixedPos.y+center.y)/aA+0.5);
if (!rightBottomSideY)
nAlp *= saturate((-fixedPos.x+center.x)/aA+0.5);
}
}
nAlp = 1-saturate(nAlp);
result += (color-result)*(1-clamp(nAlp,0,1));
result.rgb = colorOverwritten?result.rgb:_color.rgb;
result.a *= _color.a*alp;
result *= isTexture?tex2D(tSampler,textureRotated?tex_bk.yx:tex_bk):1;
return result;
}
technique rndRectTech{
pass P0 {
PixelShader = compile ps_2_a rndRect();
}
}
]]
_G.Draw = Draw;
return Draw