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([[ %s ]], 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