local DefaultTheme = { background = Color3.fromRGB(37, 37, 37), sidebar = Color3.fromRGB(47, 47, 47), text = Color3.fromRGB(204, 204, 204), keyword = { color = Color3.fromRGB(248, 109, 124), isBold = true, }, builtin = { color = Color3.fromRGB(132, 214, 247), isBold = true, }, string = { color = Color3.fromRGB(173, 241, 149), isBold = false, }, number = { color = Color3.fromRGB(255, 198, 0), isBold = false, }, comment = { color = Color3.fromRGB(102, 102, 102), isBold = false, }, lprop = { color = Color3.fromRGB(97, 161, 241), isBold = false, }, lmethod = { color = Color3.fromRGB(253, 251, 172), isBold = false, }, bool = { color = Color3.fromRGB(255, 198, 0), isBold = true, }, operator = { color = Color3.fromRGB(204, 204, 204), isBold = false, }, method = { color = Color3.fromRGB(253, 251, 172), isBold = false, }, luau = { color = Color3.fromRGB(248, 109, 124), isBold = true, }, ["nil"] = { color = Color3.fromRGB(248, 109, 124), isBold = true, }, ["function"] = { color = Color3.fromRGB(248, 109, 124), isBold = true, }, ["local"] = { color = Color3.fromRGB(248, 109, 124), isBold = true, }, ["self"] = { color = Color3.fromRGB(248, 109, 124), isBold = true, }, } local Lexer = {} local Prefix, Suffix, Cleaner = "^[%c%s]*", "[%c%s]*", "[%c%s]+" local UNICODE = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]+" local NUMBER_A = "0[xX][%da-fA-F_]+" local NUMBER_B = "0[bB][01_]+" local NUMBER_C = "%d+%.?%d*[eE][%+%-]?%d+" local NUMBER_D = "%d+[%._]?[%d_eE]*" local OPERATORS = "[:;<>/~%*%(%)%-={},%.#%^%+%%]+" local BRACKETS = "[%[%]]+" local IDEN = "[%a_][%w_]*" local STRING_EMPTY = "(['\"])%1" local STRING_PLAIN = "(['\"])[^\n]-([^\\]%1)" local STRING_INTER = "`[^\n]-`" local STRING_INCOMP_A = "(['\"]).-\n" local STRING_INCOMP_B = "(['\"])[^\n]*" local STRING_MULTI = "%[(=*)%[.-%]%1%]" local STRING_MULTI_INCOMP = "%[=*%[.-.*" local COMMENT_MULTI = "%-%-%[(=*)%[.-%]%1%]" local COMMENT_MULTI_INCOMP = "%-%-%[=*%[.-.*" local COMMENT_PLAIN = "%-%-.-\n" local COMMENT_INCOMP = "%-%-.*" local language = { keyword = { ["and"] = "keyword", ["break"] = "keyword", ["continue"] = "keyword", ["do"] = "keyword", ["else"] = "keyword", ["elseif"] = "keyword", ["end"] = "keyword", ["export"] = "keyword", ["false"] = "keyword", ["for"] = "keyword", ["function"] = "keyword", ["if"] = "keyword", ["in"] = "keyword", ["local"] = "keyword", ["nil"] = "keyword", ["not"] = "keyword", ["or"] = "keyword", ["repeat"] = "keyword", ["return"] = "keyword", ["self"] = "keyword", ["then"] = "keyword", ["true"] = "keyword", ["type"] = "keyword", ["typeof"] = "keyword", ["until"] = "keyword", ["while"] = "keyword", }, builtin = { ["assert"] = "function", ["error"] = "function", ["getmetatable"] = "function", ["ipairs"] = "function", ["newproxy"] = "function", ["next"] = "function", ["pairs"] = "function", ["pcall"] = "function", ["print"] = "function", ["rawequal"] = "function", ["rawget"] = "function", ["rawlen"] = "function", ["rawset"] = "function", ["select"] = "function", ["setmetatable"] = "function", ["tonumber"] = "function", ["tostring"] = "function", ["unpack"] = "function", ["xpcall"] = "function", ["collectgarbage"] = "function", ["_G"] = "table", ["_VERSION"] = "string", ["bit32"] = "table", ["coroutine"] = "table", ["debug"] = "table", ["math"] = "table", ["os"] = "table", ["string"] = "table", ["table"] = "table", ["utf8"] = "table", ["DebuggerManager"] = "function", ["delay"] = "function", ["gcinfo"] = "function", ["PluginManager"] = "function", ["require"] = "function", ["settings"] = "function", ["spawn"] = "function", ["tick"] = "function", ["time"] = "function", ["UserSettings"] = "function", ["wait"] = "function", ["warn"] = "function", ["Delay"] = "function", ["ElapsedTime"] = "function", ["elapsedTime"] = "function", ["printidentity"] = "function", ["Spawn"] = "function", ["Stats"] = "function", ["stats"] = "function", ["Version"] = "function", ["version"] = "function", ["Wait"] = "function", ["ypcall"] = "function", ["game"] = "Instance", ["plugin"] = "Instance", ["script"] = "Instance", ["shared"] = "Instance", ["workspace"] = "Instance", ["Game"] = "Instance", ["Workspace"] = "Instance", ["Axes"] = "table", ["BrickColor"] = "table", ["CatalogSearchParams"] = "table", ["CFrame"] = "table", ["Color3"] = "table", ["ColorSequence"] = "table", ["ColorSequenceKeypoint"] = "table", ["DateTime"] = "table", ["DockWidgetPluginGuiInfo"] = "table", ["Enum"] = "table", ["Faces"] = "table", ["FloatCurveKey"] = "table", ["Font"] = "table", ["Instance"] = "table", ["NumberRange"] = "table", ["NumberSequence"] = "table", ["NumberSequenceKeypoint"] = "table", ["OverlapParams"] = "table", ["PathWaypoint"] = "table", ["PhysicalProperties"] = "table", ["Random"] = "table", ["Ray"] = "table", ["RaycastParams"] = "table", ["Rect"] = "table", ["Region3"] = "table", ["Region3int16"] = "table", ["RotationCurveKey"] = "table", ["SharedTable"] = "table", ["task"] = "table", ["TweenInfo"] = "table", ["UDim"] = "table", ["UDim2"] = "table", ["Vector2"] = "table", ["Vector2int16"] = "table", ["Vector3"] = "table", ["Vector3int16"] = "table", }, libraries = { bit32 = { arshift = "function", band = "function", bnot = "function", bor = "function", btest = "function", bxor = "function", countlz = "function", countrz = "function", extract = "function", lrotate = "function", lshift = "function", replace = "function", rrotate = "function", rshift = "function", }, buffer = { copy = "function", create = "function", fill = "function", fromstring = "function", len = "function", readf32 = "function", readf64 = "function", readi8 = "function", readi16 = "function", readi32 = "function", readu16 = "function", readu32 = "function", readu8 = "function", readstring = "function", tostring = "function", writef32 = "function", writef64 = "function", writei16 = "function", writei32 = "function", writei8 = "function", writestring = "function", writeu16 = "function", writeu32 = "function", writeu8 = "function", }, coroutine = { close = "function", create = "function", isyieldable = "function", resume = "function", running = "function", status = "function", wrap = "function", yield = "function", }, debug = { dumpheap = "function", getmemorycategory = "function", info = "function", loadmodule = "function", profilebegin = "function", profileend = "function", resetmemorycategory = "function", setmemorycategory = "function", traceback = "function", }, math = { abs = "function", acos = "function", asin = "function", atan2 = "function", atan = "function", ceil = "function", clamp = "function", cos = "function", cosh = "function", deg = "function", exp = "function", floor = "function", fmod = "function", frexp = "function", ldexp = "function", log10 = "function", log = "function", max = "function", min = "function", modf = "function", noise = "function", pow = "function", rad = "function", random = "function", randomseed = "function", round = "function", sign = "function", sin = "function", sinh = "function", sqrt = "function", tan = "function", tanh = "function", huge = "number", pi = "number", }, os = { clock = "function", date = "function", difftime = "function", time = "function", }, string = { byte = "function", char = "function", find = "function", format = "function", gmatch = "function", gsub = "function", len = "function", lower = "function", match = "function", pack = "function", packsize = "function", rep = "function", reverse = "function", split = "function", sub = "function", unpack = "function", upper = "function", }, table = { clear = "function", clone = "function", concat = "function", create = "function", find = "function", foreach = "function", foreachi = "function", freeze = "function", getn = "function", insert = "function", isfrozen = "function", maxn = "function", move = "function", pack = "function", remove = "function", sort = "function", unpack = "function", }, utf8 = { char = "function", codepoint = "function", codes = "function", graphemes = "function", len = "function", nfcnormalize = "function", nfdnormalize = "function", offset = "function", charpattern = "string", }, Axes = { new = "function", }, BrickColor = { Black = "function", Blue = "function", DarkGray = "function", Gray = "function", Green = "function", new = "function", New = "function", palette = "function", Random = "function", random = "function", Red = "function", White = "function", Yellow = "function", }, CatalogSearchParams = { new = "function", }, CFrame = { Angles = "function", fromAxisAngle = "function", fromEulerAngles = "function", fromEulerAnglesXYZ = "function", fromEulerAnglesYXZ = "function", fromMatrix = "function", fromOrientation = "function", lookAt = "function", new = "function", identity = "CFrame", }, Color3 = { fromHex = "function", fromHSV = "function", fromRGB = "function", new = "function", toHSV = "function", }, ColorSequence = { new = "function", }, ColorSequenceKeypoint = { new = "function", }, DateTime = { fromIsoDate = "function", fromLocalTime = "function", fromUniversalTime = "function", fromUnixTimestamp = "function", fromUnixTimestampMillis = "function", now = "function", }, DockWidgetPluginGuiInfo = { new = "function", }, Enum = {}, Faces = { new = "function", }, FloatCurveKey = { new = "function", }, Font = { fromEnum = "function", fromId = "function", fromName = "function", new = "function", }, Instance = { new = "function", }, NumberRange = { new = "function", }, NumberSequence = { new = "function", }, NumberSequenceKeypoint = { new = "function", }, OverlapParams = { new = "function", }, PathWaypoint = { new = "function", }, PhysicalProperties = { new = "function", }, Random = { new = "function", }, Ray = { new = "function", }, RaycastParams = { new = "function", }, Rect = { new = "function", }, Region3 = { new = "function", }, Region3int16 = { new = "function", }, RotationCurveKey = { new = "function", }, SharedTable = { clear = "function", clone = "function", cloneAndFreeze = "function", increment = "function", isFrozen = "function", new = "function", size = "function", update = "function", }, task = { cancel = "function", defer = "function", delay = "function", desynchronize = "function", spawn = "function", synchronize = "function", wait = "function", }, TweenInfo = { new = "function", }, UDim = { new = "function", }, UDim2 = { fromOffset = "function", fromScale = "function", new = "function", }, Vector2 = { new = "function", one = "Vector2", xAxis = "Vector2", yAxis = "Vector2", zero = "Vector2", }, Vector2int16 = { new = "function", }, Vector3 = { fromAxis = "function", FromAxis = "function", fromNormalId = "function", FromNormalId = "function", new = "function", one = "Vector3", xAxis = "Vector3", yAxis = "Vector3", zAxis = "Vector3", zero = "Vector3", }, Vector3int16 = { new = "function", }, }, } local enumLibraryTable = language.libraries.Enum for _, enum in ipairs(Enum:GetEnums()) do enumLibraryTable[tostring(enum)] = "Enum" end local lua_keyword = language.keyword local lua_builtin = language.builtin local lua_libraries = language.libraries Lexer.language = language local lua_matches = { { Prefix .. IDEN .. Suffix, "var" }, { Prefix .. NUMBER_A .. Suffix, "number" }, { Prefix .. NUMBER_B .. Suffix, "number" }, { Prefix .. NUMBER_C .. Suffix, "number" }, { Prefix .. NUMBER_D .. Suffix, "number" }, { Prefix .. STRING_EMPTY .. Suffix, "string" }, { Prefix .. STRING_PLAIN .. Suffix, "string" }, { Prefix .. STRING_INCOMP_A .. Suffix, "string" }, { Prefix .. STRING_INCOMP_B .. Suffix, "string" }, { Prefix .. STRING_MULTI .. Suffix, "string" }, { Prefix .. STRING_MULTI_INCOMP .. Suffix, "string" }, { Prefix .. STRING_INTER .. Suffix, "string_inter" }, { Prefix .. COMMENT_MULTI .. Suffix, "comment" }, { Prefix .. COMMENT_MULTI_INCOMP .. Suffix, "comment" }, { Prefix .. COMMENT_PLAIN .. Suffix, "comment" }, { Prefix .. COMMENT_INCOMP .. Suffix, "comment" }, { Prefix .. OPERATORS .. Suffix, "operator" }, { Prefix .. BRACKETS .. Suffix, "operator" }, { Prefix .. UNICODE .. Suffix, "iden" }, { "^.", "iden" }, } local PATTERNS, TOKENS = {}, {} for i, m in lua_matches do PATTERNS[i] = m[1] TOKENS[i] = m[2] end function Lexer.scan(s: string) local index = 1 local size = #s local previousContent1, previousContent2, previousContent3, previousToken = "", "", "", "" local thread = coroutine.create(function() while index <= size do local matched = false for tokenType, pattern in ipairs(PATTERNS) do local start, finish = string.find(s, pattern, index) if start == nil then continue end index = finish + 1 matched = true local content = string.sub(s, start, finish) local rawToken = TOKENS[tokenType] local processedToken = rawToken if rawToken == "var" then local cleanContent = string.gsub(content, Cleaner, "") if lua_keyword[cleanContent] then processedToken = "keyword" elseif lua_builtin[cleanContent] then processedToken = "builtin" elseif string.find(previousContent1, "%.[%s%c]*$") and previousToken ~= "comment" then local parent = string.gsub(previousContent2, Cleaner, "") local lib = lua_libraries[parent] if lib and lib[cleanContent] and not string.find(previousContent3, "%.[%s%c]*$") then processedToken = "builtin" else processedToken = "iden" end else processedToken = "iden" end elseif rawToken == "string_inter" then if not string.find(content, "[^\\]{") then processedToken = "string" else processedToken = nil local isString = true local subIndex = 1 local subSize = #content while subIndex <= subSize do local subStart, subFinish = string.find(content, "^.-[^\\][{}]", subIndex) if subStart == nil then coroutine.yield("string", string.sub(content, subIndex), index) break end if isString then subIndex = subFinish + 1 coroutine.yield("string", string.sub(content, subStart, subFinish), start + subIndex - 1) isString = false else subIndex = subFinish local subContent = string.sub(content, subStart, subFinish - 1) for innerToken, innerContent, innerTrim in Lexer.scan(subContent) do coroutine.yield(innerToken, innerContent, start + subStart + innerTrim - 2) end isString = true end end end end previousContent3 = previousContent2 previousContent2 = previousContent1 previousContent1 = content previousToken = processedToken or rawToken if processedToken then coroutine.yield(processedToken, content, index) end break end if not matched then return end end return end) return function() if coroutine.status(thread) == "dead" then return end local success, token, content, trim = coroutine.resume(thread) if success and token then return token, content, trim end return end end function Lexer.run(str: string) local scanResult = {} for token, src, trim in Lexer.scan(str) do table.insert(scanResult, {trim = trim, token = token, src = src}) end return scanResult end function Lexer.navigator() local nav = { Source = "", TokenCache = table.create(50), _RealIndex = 0, _UserIndex = 0, _ScanThread = nil, } function nav:Destroy() self.Source = nil self._RealIndex = nil self._UserIndex = nil self.TokenCache = nil self._ScanThread = nil end function nav:SetSource(SourceString) self.Source = SourceString self._RealIndex = 0 self._UserIndex = 0 table.clear(self.TokenCache) self._ScanThread = coroutine.create(function() for Token, Src, trim in Lexer.scan(self.Source) do self._RealIndex += 1 self.TokenCache[self._RealIndex] = { Token, Src } coroutine.yield(Token, Src, trim) end end) end function nav.Next() nav._UserIndex += 1 if nav._RealIndex >= nav._UserIndex then return table.unpack(nav.TokenCache[nav._UserIndex]) else if coroutine.status(nav._ScanThread) == "dead" then return else local success, token, src = coroutine.resume(nav._ScanThread) if success and token then return token, src else return end end end end function nav.Peek(PeekAmount) local GoalIndex = nav._UserIndex + PeekAmount if nav._RealIndex >= GoalIndex then if GoalIndex > 0 then return table.unpack(nav.TokenCache[GoalIndex]) else return end else if coroutine.status(nav._ScanThread) == "dead" then return else local IterationsAway = GoalIndex - nav._RealIndex local success, token, src = nil, nil, nil for _ = 1, IterationsAway do success, token, src = coroutine.resume(nav._ScanThread) if not (success or token) then break end end return token, src end end end return nav end local LuaTable = {}; LuaTable.__index = LuaTable function LuaTable:UpdateSize() local Size = 0 for Line, _ in pairs(self.Code) do if Line > Size then Size = Line end end self.Size = Size return Size end function LuaTable:Push(Line, LuaArray) for RelativeLine, LuaLine in pairs(LuaArray) do table.insert(self.Code, Line + RelativeLine - 1, LuaLine) end self:UpdateSize() return self end function LuaTable:Insert(Line, LuaArray) return self:Push(Line + 1, LuaArray) end function LuaTable:Add(LuaArray) return self:Insert(self.Size, LuaArray) end function LuaTable:Erase(RegionStart, RegionLength) RegionLength = RegionLength or 1 for i = 1, RegionLength do table.remove(self.Code, RegionStart) end self:UpdateSize() return self end function LuaTable:Write(RegionStart, RegionLength, LuaArray) return self :Erase(RegionStart, RegionLength) :Push(RegionStart, LuaArray) end function LuaTable.new(LuaLines) local Fields = { Code = LuaLines, Size = #LuaLines } local self = setmetatable(Fields, LuaTable) self:UpdateSize() return self end function LuaTable.FromArray(LuaArray) return LuaTable.new(LuaArray) end function LuaTable.FromString(str) return LuaTable.new(string.split(str, "\n")) end function LuaTable.FromTuple(...) local LuaLines = {...} return LuaTable.new(LuaLines) end function LuaTable.FromScript(Script) return LuaTable.FromString(Script.Source) end function LuaTable:ToString(RegionStart, RegionLength) RegionStart = math.max(RegionStart or 1, 1) local size = math.min(RegionLength or math.huge, self.Size - RegionStart + 1) for Line = 1, size do local LuaLine = self.Code[Line + RegionStart - 1] or "" self.Code[Line + RegionStart - 1] = LuaLine end local LuaString = table.concat(self.Code, "\n", RegionStart, size + RegionStart - 1) return LuaString end function LuaTable:ToScript(Script, RegionStart, RegionLength) Script.Source = self:ToString(RegionStart, RegionLength) end function LuaTable:ToArray(RegionStart, RegionLength) RegionStart = math.max(RegionStart or 1, 1) local array = {} local size = math.min(RegionLength or math.huge, self.Size - RegionStart + 1) for index = 1, size do local LuaLine = self.Code[index + RegionStart - 1] array[index] = LuaLine or "" end return array end local freeRunnerThread = nil local function acquireRunnerThreadAndCallEventHandler(fn, ...) local acquiredRunnerThread = freeRunnerThread freeRunnerThread = nil fn(...) freeRunnerThread = acquiredRunnerThread end local function runEventHandlerInFreeThread() while true do acquireRunnerThreadAndCallEventHandler(coroutine.yield()) end end local Connection = {} Connection.__index = Connection function Connection.new(signal, fn) return setmetatable({ _connected = true, _signal = signal, _fn = fn, _next = false, }, Connection) end function Connection:Disconnect() self._connected = false if self._signal._handlerListHead == self then self._signal._handlerListHead = self._next else local prev = self._signal._handlerListHead while prev and prev._next ~= self do prev = prev._next end if prev then prev._next = self._next end end end setmetatable(Connection, { __index = function(tb, key) error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2) end, __newindex = function(tb, key, value) error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2) end }) local Signal = {} Signal.__index = Signal function Signal.new() return setmetatable({ _handlerListHead = false, }, Signal) end function Signal:Connect(fn) local connection = Connection.new(self, fn) if self._handlerListHead then connection._next = self._handlerListHead self._handlerListHead = connection else self._handlerListHead = connection end return connection end function Signal:DisconnectAll() self._handlerListHead = false end function Signal:Destroy() self._handlerListHead = false end function Signal:Fire(...) local item = self._handlerListHead while item do if item._connected then if not freeRunnerThread then freeRunnerThread = coroutine.create(runEventHandlerInFreeThread) coroutine.resume(freeRunnerThread) end task.spawn(freeRunnerThread, item._fn, ...) end item = item._next end end function Signal:Wait() local waitingCoroutine = coroutine.running() local cn; cn = self:Connect(function(...) cn:Disconnect() task.spawn(waitingCoroutine, ...) end) return coroutine.yield() end function Signal:Once(fn) local cn; cn = self:Connect(function(...) if cn._connected then cn:Disconnect() end fn(...) end) return cn end setmetatable(Signal, { __index = function(tb, key) error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2) end, __newindex = function(tb, key, value) error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2) end }) SignalModule = Signal local TextService = game:GetService("TextService") function getMultiLinePositions(text) local multilines = string.split(text, "\n") local multilinePositions = {} local cursor = 1 for _, line in multilines do table.insert(multilinePositions, {cursor, cursor + string.len(line)}) cursor += string.len(line) + 1 end return multilines, multilinePositions end function getLinePosition(multilinePositions, position) for i, multilinePosition in multilinePositions do if (position >= multilinePosition[1] and position <= multilinePosition[2]) then return i, position - multilinePosition[1] + 1 end end return -1, -1 end function getLengthOfLine(text, textLabel: TextBox) local absoluteSize = TextService:GetTextSize( text, textLabel.TextSize, textLabel.Font, Vector2.new(math.huge, math.huge) ) return absoluteSize.X end function getShiftAmount(lower, upper, x) if x < lower then return x - lower elseif x > upper then return x - upper else return 0 end end local GetTextBoxScrolling = {} function GetTextBoxScrolling.UpdateShift(textLabel: TextBox, cursorPosition, shift) if cursorPosition == -1 then return 0 end local labelWindow = textLabel.AbsoluteSize.X - 1 local multilines, multilinePositions = getMultiLinePositions(textLabel.Text) local linePosition, positionLength = getLinePosition(multilinePositions, cursorPosition) local line = multilines[linePosition] local stringUntilCursor = string.sub(line, 0, positionLength - 1) local lineLength = getLengthOfLine(stringUntilCursor, textLabel) local relativeShift = getShiftAmount(shift, shift + labelWindow, lineLength) shift += relativeShift return shift end local InitGui = {} InitGui.NIL = newproxy() function trySet(instance, property, value) return pcall(function() instance[property] = value end) end function InitGui.instance(properties) local className = properties.ClassName local instance = Instance.new(className) for k, v in properties do if k ~= "ClassName" and k ~= "Parent" then if v == InitGui.NIL then v = nil end trySet(instance, k, v) end end instance.Parent = properties.Parent return instance end function InitGui.init() local oseBackground = InitGui.instance { Name = "OSEBackground", ClassName = "Frame", BackgroundTransparency = 1, BorderSizePixel = 0, Size = UDim2.new(1, 0, 1, 0) } InitGui.instance { Name = "LineNumberBackground", ClassName = "Frame", BackgroundTransparency = 1, BorderSizePixel = 0, Size = UDim2.new(0, 16, 1, 0), Parent = oseBackground, } local lineNumberContainer = InitGui.instance { Name = "LineNumberContainer", ClassName = "Frame", BackgroundTransparency = 1, Position = UDim2.new(0, 0, 0, 5), Size = UDim2.new(0, 16, 1, -10), ZIndex = 2, Parent = oseBackground } InitGui.instance { ClassName = "UIListLayout", HorizontalAlignment = Enum.HorizontalAlignment.Center, SortOrder = Enum.SortOrder.LayoutOrder, Parent = lineNumberContainer, } local richOverlayContainer = InitGui.instance { Name = "RichOverlayContainer", ClassName = "Frame", BackgroundTransparency = 1, Active = true, Position = UDim2.new(0, 25, 0, 5), Size = UDim2.new(1, -30, 1, -10), ClipsDescendants = true, Parent = oseBackground } local shiftContainer = InitGui.instance { Name = "ShiftContainer", ClassName = "Frame", BackgroundTransparency = 1, Active = true, Position = UDim2.new(), Size = UDim2.new(3, 0, 1, 0), ClipsDescendants = false, Parent = richOverlayContainer, } InitGui.instance { ClassName = "UIListLayout", SortOrder = Enum.SortOrder.LayoutOrder, Parent = shiftContainer, } InitGui.instance { Name = "CodeField", ClassName = "TextBox", BackgroundTransparency = 1, ClearTextOnFocus = false, MultiLine = true, Position = UDim2.new(0, 25, 0, 5), Size = UDim2.new(1, -30, 1, -10), Font = Enum.Font.Code, Text = "", TextColor3 = Color3.new(.8, .8, .8), TextSize = 14, TextTransparency = .5, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, ClipsDescendants = true, Parent = oseBackground, } return oseBackground end function InitGui.storage() local storage = InitGui.instance { Name = "Storage", ClassName = "Folder" } InitGui.instance { Name = "LineNumber", ClassName = "TextLabel", AutomaticSize = Enum.AutomaticSize.XY, BackgroundTransparency = 1, Size = UDim2.new(1, -6, 0, 0), ZIndex = 5, Font = Enum.Font.SourceSans, TextColor3 = Color3.new(.8, .8, .8), TextSize = 14, TextXAlignment = Enum.TextXAlignment.Right, TextYAlignment = Enum.TextYAlignment.Center, Parent = storage, } InitGui.instance { Name = "RichOverlayLabel", ClassName = "TextLabel", AutomaticSize = Enum.AutomaticSize.XY, BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 0), ZIndex = 5, Font = Enum.Font.Code, TextColor3 = Color3.new(.8, .8, .8), RichText = true, TextSize = 14, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Center, Parent = storage, } return storage end local AutoIndent = {} local function testForIndent(codeField, code, start, keyword) if start - keyword:len() <= 0 then return end local previousRawText = codeField.Text:sub(start - keyword:len(), start - 1) local previousText = code:sub(start - keyword:len(), start - 1) if previousRawText == keyword and previousRawText == previousText then return true end end local function countTabs(line) -- local init = 1 -- local count = 0 -- repeat -- local Start, End = string.find(line, "\9", init) -- if Start and End then -- init = Start + 1 -- count += 1 -- end -- until not (Start and End) -- return count return string.match(line, "^\9*"):len() end local indentKeywords = { ["do"] = 1, ["then"] = 1, ["repeat"] = 1, ["{"] = 1, ["function()"] = 1, } function findCurrentLine(code, position) local lines = string.split(code, "\n") local selection = 0 local currentLineNumber = 0 for i, line in lines do selection += 1 + line:len() if position <= selection then currentLineNumber = i break end end if not currentLineNumber then warn("Failed to find current line number") return false end return currentLineNumber, lines end function AutoIndent.OnScriptChange(scriptEditor, data) local code = data.Code local codeField = scriptEditor.Background.CodeField local position = codeField.CursorPosition local canIndent = false local additionalTabs = 0 for keyword, tabs in indentKeywords do if testForIndent(codeField, code, position, keyword .. "\n") and data.Gain == 1 then canIndent = true additionalTabs = tabs break end end local currentLineNumber, lines = findCurrentLine(code, position) local previousLineNumber = currentLineNumber - 1 local currentText = lines[currentLineNumber] local previousText = lines[previousLineNumber] local justReached = previousText and currentText == "" and data.Gain == 1 if not canIndent and justReached then --lonely function case local funcPatterns = { "^%s*function%s+[%w%.:]+%b()%s*", "^%s*local%s+function%s+[%w%.:]+%b()%s*", "^%s*function%s*%b()%s*", } for _, funcPattern in funcPatterns do if previousText:match(funcPattern) == previousText then canIndent = true additionalTabs = 1 break end end end --previous indent case if not canIndent and justReached then local previousTabs = countTabs(previousText) if previousTabs > 0 then canIndent = true additionalTabs = 0 end end if canIndent and previousLineNumber then --print(lines) --print(previousLineNumber) local tabsCount = countTabs(lines[previousLineNumber]) + additionalTabs --print(tabsCount) --print(string.split(code:sub(1, position - 1) .. string.rep(" ", tabsCount) .. code:sub(position), "\n")) data.Cursor += tabsCount data.Code = code:sub(1, position - 1) .. string.rep("\9", tabsCount) .. code:sub(position) end end local AutoWrap = {} function isCharWhitespace(char) return char == "\n" or char == " " end local allowedWraps = { {"\"", "\""}, {"'", "'"}, {"(", ")"}, {"[", "]"}, {"{", "}"}, } function getLastSelectedString(scriptEditor, autoWrapState) local codeField = scriptEditor.Background.CodeField autoWrapState._LastCursorPosition = codeField.CursorPosition autoWrapState._LastSelectionStart = codeField.SelectionStart local start = math.min(autoWrapState._LastCursorPosition, autoWrapState._LastSelectionStart) local finish = math.max(autoWrapState._LastCursorPosition, autoWrapState._LastSelectionStart, 1) - 1 if start == -1 then start = finish + 1 end autoWrapState._LastSelectedString = string.sub(codeField.Text, start, finish) autoWrapState._LastPreviousChar = string.sub(codeField.Text, start - 1, start - 1) end function AutoWrap.OnScriptEditorInstantiation(scriptEditor) local codeField = scriptEditor.Background.CodeField local autoWrapState = { _LastSelectionStart = -1, _LastCursorPosition = -1, _LastSelectedString = "", _LastPreviousChar = "", } scriptEditor._AutoWrapState = autoWrapState codeField:GetPropertyChangedSignal("CursorPosition"):Connect(function() task.defer(getLastSelectedString, scriptEditor, autoWrapState) end) codeField:GetPropertyChangedSignal("SelectionStart"):Connect(function() task.defer(getLastSelectedString, scriptEditor, autoWrapState) end) end function AutoWrap.OnScriptChange(scriptEditor, data) if not scriptEditor.AutoWrapEnabled then return end local autoWrapState = scriptEditor._AutoWrapState local code = data.Code local cursor = data.Cursor local codeField: TextBox = scriptEditor.Background.CodeField if codeField.CursorPosition ~= -1 then -- local lastChar = string.sub(codeField.Text, codeField.CursorPosition - 1, codeField.CursorPosition - 1) local lastCharPosition = codeField.CursorPosition - 1 + cursor local lastChar = string.sub(code, lastCharPosition, lastCharPosition) local lastSelectedString = autoWrapState._LastSelectedString local cursorChar = string.sub(code, lastCharPosition + 1, lastCharPosition + 1) local nextLastChar = string.sub(code, lastCharPosition - 1, lastCharPosition - 1) for _, wrappedCharData in allowedWraps do local startingWrappedChar = wrappedCharData[1] if lastChar == startingWrappedChar and lastSelectedString ~= startingWrappedChar and lastSelectedString ~= "" and (autoWrapState._LastPreviousChar ~= startingWrappedChar or nextLastChar == startingWrappedChar) then code = string.sub(code, 1, lastCharPosition) .. lastSelectedString .. wrappedCharData[2] .. string.sub(code, lastCharPosition + 1) codeField.SelectionStart = if lastSelectedString ~= "" then lastCharPosition + 1 else -1 data.Cursor += lastSelectedString:len() end end end data.Code = code end local Storage = InitGui.storage() Storage.Parent = script local ScriptEditor = {} ScriptEditor.__index = ScriptEditor local lib_methods = { bit32 = true, coroutine = true, debug = true, math = true, os = true, string = true, table = true, utf8 = true, task = true, buffer = true, Axes = 'builtin', BrickColor = 'builtin', CFrame = 'builtin', Color3 = 'builtin', ColorSequence = 'builtin', ColorSequenceKeypoint = 'builtin', DateTime = 'builtin', DockWidgetPluginGuiInfo = 'builtin', Faces = 'builtin', Instance = 'builtin', NumberRange = 'builtin', NumberSequence = 'builtin', NumberSequenceKeypoint = 'builtin', PathWaypoint = 'builtin', PhysicalProperties = 'builtin', Random = 'builtin', Ray = 'builtin', RaycastParams = 'builtin', Rect = 'builtin', Region3 = 'builtin', Region3int16 = 'builtin', TweenInfo = 'builtin', UDim = 'builtin', UDim2 = 'builtin', Vector2 = 'builtin', Vector2int16 = 'builtin', Vector3 = 'builtin', Vector3int16 = 'builtin', CatalogSearchParams = 'builtin', FloatCurveKey = 'builtin', Font = 'builtin', OverlapParams = 'builtin', SharedTable = 'builtin', } local HighlightColorsInfo = { keyword = {"Keyword Color", true}, builtin = {"Built-in Function Color", true}, string = {"String Color", false}, number = {"Number Color", false}, comment = {"Comment Color", false}, lprop = {"Property Color", false}, lmethod = {"Method Color", false}, method = {"Function Name Color", false}, bool = {"Bool Color", true}, operator = {"Operator Color", false}, luau = {"Luau Keyword Color", true}, ["nil"] = {"\"nil\" Color", true}, ["function"] = {"\"function\" Color", true}, ["local"] = {"\"local\" Color", true}, ["self"] = {"\"self\" Color", true}, } local tokenNameTypes = { ["local"] = "local", ["function"] = "function", ["self"] = "self", ["nil"] = "nil", ["true"] = "bool", ["false"] = "bool", ["type"] = "luau", ["typeof"] = "luau", ["export"] = "luau", ["continue"] = "luau", } local function replace(str, s, e, replacementString) local before = string.sub(str, 1, s - 1) local after = string.sub(str, e + 1, str:len()) return before .. replacementString .. after end local function rawWrapString(str, substr, Start, End, color, isBold) local r, g, b = math.ceil(color.R*255), math.ceil(color.G*255), math.ceil(color.B*255) local colorStrStart = '' local bold = '' local afterbold = '' if isBold then bold = '' afterbold = '' end return replace(str, Start, End, colorStrStart .. bold .. substr .. afterbold .. '') end local function iterateThruAndReplace(str, pattern, replacement) local cleanStr, replacements = string.gsub(str, pattern, replacement) return cleanStr end local function trimWhitespace(str) return string.gsub(str, '^%s*(.-)%s*$', '%1') end local function getTrueTokenData(i, tokenData, scanData) local prevTokenData = scanData[i - 1] local nextTokenData = scanData[i + 1] local token = tokenData.token if token == "iden" then local prevIndexData = scanData[i - 2] if prevTokenData and prevTokenData.src == "." then token = "lprop" end if nextTokenData and (nextTokenData.src:match("^%(") or nextTokenData.src:match("^{") or nextTokenData.src:match("^\"") or nextTokenData.src:match("^\'") or nextTokenData.src:match("%[%[")) then if prevTokenData and prevTokenData.src == "." then token = "lmethod" else token = "method" end end end local tokenText = trimWhitespace(tokenData.src) local trueToken = tokenNameTypes[tokenText] if trueToken then token = trueToken end return token end local function escapeRich(code) local cleanCode = iterateThruAndReplace(code, "&", "&") cleanCode = iterateThruAndReplace(cleanCode, "<", "<") cleanCode = iterateThruAndReplace(cleanCode, ">", ">") cleanCode = iterateThruAndReplace(cleanCode, "\"", """) cleanCode = iterateThruAndReplace(cleanCode, "\'", "'") return cleanCode end local function tabsToSpaces(code) return iterateThruAndReplace(code, "\9", " ") end local function spacesToTabs(code) return iterateThruAndReplace(code, " ", "\9") end local function colorify(code, theme) theme = theme or DefaultTheme local highlight = code local size = code:len() local increase = 0 local scanData = Lexer.run(code) for i, tokenData in scanData do local token = tokenData.token token = getTrueTokenData(i, tokenData, scanData) local colorData = theme[token] if colorData then local trim = tokenData.trim local src = tokenData.src local End = trim - 1 + increase local Start = trim - src:len() + increase local color = colorData.color local isBold = colorData.isBold local escapedToken = escapeRich(src) highlight = rawWrapString(highlight, escapedToken, Start, End, color, isBold) increase = math.abs(highlight:len() - size) end end return highlight end local registerEditEvent = true local function rawEditCodeField(scriptEditor, text) registerEditEvent = false scriptEditor.Background.CodeField.Text = text task.defer(function() registerEditEvent = true end) end local function fixCodeFieldLines(scriptEditor, originalSize) local codeField = scriptEditor.Background.CodeField local newCode = codeField.Text if scriptEditor.VisibleLines > originalSize then for i = originalSize + 1, scriptEditor.VisibleLines do local line = scriptEditor.SourceData.Code[scriptEditor.LineFocused + i - 1] if i > scriptEditor.SourceData.Size then break end if not line then break end newCode ..= "\n" .. line end elseif scriptEditor.VisibleLines < originalSize then local lines = string.split(newCode, "\n") local replacementText = lines[1] or "" for i = 2, scriptEditor.VisibleLines do if i + scriptEditor.LineFocused - 1 > scriptEditor.SourceData.Size then break end replacementText = replacementText .. "\n" .. (lines[i] or "") end newCode = replacementText end return newCode end local function declareCurrentTextSize(scriptEditor, textWidth) local codeField = scriptEditor.Background.CodeField local richOverlayContainer = scriptEditor.Background.RichOverlayContainer local textHeight = textWidth*2 local genericSize = codeField.Size local currentWidth = codeField.AbsoluteSize.X local currentHeight = codeField.AbsoluteSize.Y local newSize = UDim2.new(1, genericSize.X.Offset - currentWidth%textWidth + 1, 1, -10 - currentHeight%textHeight) codeField.Size = newSize richOverlayContainer.Size = newSize local originalSize = scriptEditor.VisibleLines recountVisibleLines(scriptEditor) codeField.Text = fixCodeFieldLines(scriptEditor, originalSize) end local function moveShiftContainer(scriptEditor) local background = scriptEditor.Background local codeField = background.CodeField local shiftContainer = background.RichOverlayContainer.ShiftContainer scriptEditor.ScrollingShift = GetTextBoxScrolling.UpdateShift(codeField, codeField.CursorPosition, scriptEditor.ScrollingShift) shiftContainer.Position = UDim2.new(0, -scriptEditor.ScrollingShift, 0, 0) end local function updateLines(scriptEditor) local lineNumbers = {} local visibleCodeLines = {} table.move(scriptEditor.SourceData.Code, scriptEditor.LineFocused, scriptEditor.LineFocused + scriptEditor.VisibleLines, 1, visibleCodeLines) for i = 1, scriptEditor.VisibleLines + 1 do local line = scriptEditor.SourceData.Code[scriptEditor.LineFocused + i - 1] if not line then break end table.insert(lineNumbers, i + scriptEditor.LineFocused - 1) end local visibleCodeSegment = table.concat(visibleCodeLines, "\n") scriptEditor.Background.LineNumberContainer.LineNumber.Text = table.concat(lineNumbers, "\n") scriptEditor.Background.RichOverlayContainer.ShiftContainer.RichOverlayLabel.Text = colorify(tabsToSpaces(visibleCodeSegment), scriptEditor.Theme) end local function onCodeFieldEdit(scriptEditor) if not registerEditEvent then registerEditEvent = true return end local background = scriptEditor.Background local codeField = background.CodeField local newText = codeField.Text local lines = string.split(newText, "\n") local finalRawCode = newText task.defer(function() local lineNumberWidth = 6*math.ceil(math.log10(#scriptEditor.SourceData.Code + .1)) background.LineNumberContainer.Size = UDim2.new(0, lineNumberWidth + 3, 1, -10) background.LineNumberBackground.Size = UDim2.new(0, lineNumberWidth + 6, 1, 0) codeField.Position = UDim2.new(0, lineNumberWidth + 9, 0, 5) codeField.Size = UDim2.new(1, -(lineNumberWidth + 9 + 5), 1, -10) background.RichOverlayContainer.Position = UDim2.new(0, lineNumberWidth + 9, 0, 5) background.RichOverlayContainer.Size = UDim2.new(1, -(lineNumberWidth + 9 + 5), 1, -10) declareCurrentTextSize(scriptEditor, 7) end) local luaArray = string.split(newText, "\n") scriptEditor.SourceData:Write(scriptEditor.LineFocused, scriptEditor.VisibleLines, luaArray) scriptEditor.RawSource = scriptEditor.SourceData:ToString() updateLines(scriptEditor) local originalSize = scriptEditor.VisibleLines finalRawCode = fixCodeFieldLines(scriptEditor, #lines) local data = { Code = finalRawCode, Gain = finalRawCode:len() - scriptEditor.DisplayCode:len(), Cursor = 0, Selection = 0, } AutoIndent.OnScriptChange(scriptEditor, data) AutoWrap.OnScriptChange(scriptEditor, data) finalRawCode = data.Code if finalRawCode ~= newText then local prevPosition = codeField.CursorPosition codeField.CursorPosition = -1 codeField.Text = finalRawCode codeField.CursorPosition = prevPosition + data.Cursor else scriptEditor.OnEdit:Fire(scriptEditor.RawSource) end scriptEditor.DisplayCode = finalRawCode end local function repack(t, separator) local str = t[1] for i = 2, #t do if not t[i] then end str = str .. separator .. t[i] end return str end function recountVisibleLines(scriptEditor) local visibleLines = math.floor(scriptEditor.Background.CodeField.AbsoluteSize.Y/14) scriptEditor.VisibleLines = visibleLines end function getLastSelectedString(scriptEditor) local codeField = scriptEditor.Background.CodeField scriptEditor._LastCursorPosition = codeField.CursorPosition scriptEditor._LastSelectionStart = codeField.SelectionStart local start = math.min(scriptEditor._LastCursorPosition, scriptEditor._LastSelectionStart) local finish = math.max(scriptEditor._LastCursorPosition, scriptEditor._LastSelectionStart, 1) - 1 if start == -1 then start = finish + 1 end scriptEditor._LastSelectedString = string.sub(codeField.Text, start, finish) scriptEditor._LastPreviousChar = string.sub(codeField.Text, start - 1, start - 1) end function ScriptEditor:ApplyTheme(ThemeData) ThemeData = ThemeData or self.Theme self.Theme = ThemeData local background = self.Background if #background:GetChildren() == 0 then return end background.BackgroundTransparency = 1 background.LineNumberBackground.BackgroundTransparency = 1 background.CodeField.TextColor3 = ThemeData.text Storage.LineNumber.TextColor3 = ThemeData.text Storage.RichOverlayLabel.TextColor3 = ThemeData.text for _, lineLabel: Instance in pairs(background.LineNumberContainer:GetChildren()) do if lineLabel:IsA("TextLabel") then lineLabel.TextColor3 = ThemeData.text end end for _, lineLabel: Instance in pairs(background.RichOverlayContainer.ShiftContainer:GetChildren()) do if lineLabel:IsA("TextLabel") then lineLabel.TextColor3 = ThemeData.text end end self:JumpTo() end function ScriptEditor:ApplyStudioTheme(studio: Studio) local theme = { background = studio["Background Color"], text = studio["Text Color"], sidebar = studio["Script Editor Scrollbar Background Color"], } for ThemePropertyName, highlightData in HighlightColorsInfo do theme[ThemePropertyName] = { color = studio[highlightData[1]], isBold = highlightData[2] } end self:ApplyTheme(theme) end function ScriptEditor:JumpTo(lineNumber: number) lineNumber = lineNumber or self.LineFocused self.LineFocused = lineNumber local lines = string.split(self.RawSource, "\n") local displayCode = "" for i = lineNumber, lineNumber + self.VisibleLines - 1 do local line = lines[i] if line then displayCode = displayCode .. line .. "\n" end end displayCode = string.sub(displayCode, 1, displayCode:len() - 1) displayCode = displayCode if self.Background.CodeField.Text == displayCode then onCodeFieldEdit(self) else self.Background.CodeField.Text = displayCode end end function ScriptEditor:SetStringAsync(str: string, lineNumber: number) self.Background.CodeField.Visible = false self.Background.RichOverlayContainer.Visible = false self.Background.LineNumberContainer.Visible = false task.delay(1/30, function() recountVisibleLines(self) self.RawSource = str self.SourceData = LuaTable.FromString(str) self.Background.CodeField.Visible = true self.Background.RichOverlayContainer.Visible = true self.Background.LineNumberContainer.Visible = true self:JumpTo(lineNumber or 1) end) end function ScriptEditor:SetScriptAsync(scriptObject: LuaSourceContainer, lineNumber: number) self:SetStringAsync(scriptObject.Source, lineNumber) end function ScriptEditor:Unload() self:SetStringAsync("") end function ScriptEditor:ReadOnly(allowEditing: boolean?) self.Background.CodeField.TextEditable = allowEditing or false end function ScriptEditor.Embed(frame: GuiBase2d) local background = InitGui.init() background.Parent = frame local scriptEditor = { LineFocused = 1, RawSource = "", SourceData = LuaTable.FromString(""), DisplayCode = "", Background = background, VisibleLines = 1, OutputScript = nil, AutoWrapEnabled = false, ScrollingShift = 0, Destroyed = false, OnEdit = SignalModule.new(), Theme = DefaultTheme, } Storage.LineNumber:Clone().Parent = background.LineNumberContainer Storage.RichOverlayLabel:Clone().Parent = background.RichOverlayContainer.ShiftContainer setmetatable(scriptEditor, ScriptEditor) local codeField = background.CodeField codeField:GetPropertyChangedSignal("Text"):Connect(function() onCodeFieldEdit(scriptEditor) end) codeField:GetPropertyChangedSignal("CursorPosition"):Connect(function() task.defer(moveShiftContainer, scriptEditor) end) background:GetPropertyChangedSignal("AbsoluteSize"):Connect(function() local originalSize = scriptEditor.VisibleLines recountVisibleLines(scriptEditor) codeField.Text = fixCodeFieldLines(scriptEditor, originalSize) updateLines(scriptEditor) task.defer(moveShiftContainer, scriptEditor) end) codeField.InputChanged:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseWheel then local roundZ = math.round(input.Position.Z) local newLineFocused = math.clamp(scriptEditor.LineFocused - roundZ*3, 1, #string.split(scriptEditor.RawSource, "\n")) codeField:ReleaseFocus() scriptEditor:JumpTo(newLineFocused) end end) AutoWrap.OnScriptEditorInstantiation(scriptEditor) scriptEditor:SetStringAsync("") return scriptEditor end function ScriptEditor:Relocate(newFrame: GuiBase2d) local background = self.Background background.Parent = newFrame end function ScriptEditor:Destroy() if self.Destroyed then return end self.Destroyed = true self.Background:Destroy() self.OnEdit:DisconnectAll() end local HttpService: HttpService = game:GetService("HttpService") local ImGui = {} :: ImGui ImGui.TemplateConfig = { colorDark = { -- Dear, ImGui default dark TextColor = Color3.fromRGB(255, 255, 255), TextTransparency = 0, TextDisabledColor = Color3.fromRGB(128, 128, 128), TextDisabledTransparency = 0, -- Dear ImGui uses 110, 110, 125 -- The Roblox window selection highlight is 67, 191, 254 BorderColor = Color3.fromRGB(110, 110, 125), BorderActiveColor = Color3.fromRGB(160, 160, 175), -- does not exist in Dear ImGui -- BorderTransparency will be problematic for non UIStroke border implimentations -- is not implimented because of this BorderTransparency = 0.5, BorderActiveTransparency = 0.3, WindowBgColor = Color3.fromRGB(15, 15, 15), WindowBgTransparency = 0.06, PopupBgColor = Color3.fromRGB(20, 20, 20), PopupBgTransparency = 0.06, ScrollbarGrabColor = Color3.fromRGB(128, 128, 128), ScrollbarGrabTransparency = 0, TitleBgColor = Color3.fromRGB(10, 10, 10), TitleBgTransparency = 0, TitleBgActiveColor = Color3.fromRGB(41, 74, 122), TitleBgActiveTransparency = 0, TitleBgCollapsedColor = Color3.fromRGB(0, 0, 0), TitleBgCollapsedTransparency = 0.5, MenubarBgColor = Color3.fromRGB(36, 36, 36), MenubarBgTransparency = 0, FrameBgColor = Color3.fromRGB(41, 74, 122), FrameBgTransparency = 0.46, FrameBgHoveredColor = Color3.fromRGB(66, 150, 250), FrameBgHoveredTransparency = 0.46, FrameBgActiveColor = Color3.fromRGB(66, 150, 250), FrameBgActiveTransparency = 0.33, ButtonColor = Color3.fromRGB(66, 150, 250), ButtonTransparency = 0.6, ButtonHoveredColor = Color3.fromRGB(66, 150, 250), ButtonHoveredTransparency = 0, ButtonActiveColor = Color3.fromRGB(15, 135, 250), ButtonActiveTransparency = 0, ImageColor = Color3.fromRGB(255, 255, 255), ImageTransparency = 0, SliderGrabColor = Color3.fromRGB(66, 150, 250), SliderGrabTransparency = 0, SliderGrabActiveColor = Color3.fromRGB(117, 138, 204), SliderGrabActiveTransparency = 0, HeaderColor = Color3.fromRGB(66, 150, 250), HeaderTransparency = 0.69, HeaderHoveredColor = Color3.fromRGB(66, 150, 250), HeaderHoveredTransparency = 0.2, HeaderActiveColor = Color3.fromRGB(66, 150, 250), HeaderActiveTransparency = 0, TabColor = Color3.fromRGB(46, 89, 148), TabTransparency = 0.14, TabHoveredColor = Color3.fromRGB(66, 150, 250), TabHoveredTransparency = 0.2, TabActiveColor = Color3.fromRGB(51, 105, 173), TabActiveTransparency = 0, SelectionImageObjectColor = Color3.fromRGB(255, 255, 255), SelectionImageObjectTransparency = 0.8, SelectionImageObjectBorderColor = Color3.fromRGB(255, 255, 255), SelectionImageObjectBorderTransparency = 0, TableBorderStrongColor = Color3.fromRGB(79, 79, 89), TableBorderStrongTransparency = 0, TableBorderLightColor = Color3.fromRGB(59, 59, 64), TableBorderLightTransparency = 0, TableRowBgColor = Color3.fromRGB(0, 0, 0), TableRowBgTransparency = 1, TableRowBgAltColor = Color3.fromRGB(255, 255, 255), TableRowBgAltTransparency = 0.94, NavWindowingHighlightColor = Color3.fromRGB(255, 255, 255), NavWindowingHighlightTransparency = 0.3, NavWindowingDimBgColor = Color3.fromRGB(204, 204, 204), NavWindowingDimBgTransparency = 0.65, SeparatorColor = Color3.fromRGB(110, 110, 128), SeparatorTransparency = 0.5, CheckMarkColor = Color3.fromRGB(66, 150, 250), CheckMarkTransparency = 0, PlotHistogramColor = Color3.fromRGB(230, 179, 0), PlotHistogramTransparency = 0, PlotHistogramHoveredColor = Color3.fromRGB(255, 153, 0), PlotHistogramHoveredTransparency = 0, }, colorLight = { -- Dear, ImGui default light TextColor = Color3.fromRGB(0, 0, 0), TextTransparency = 0, TextDisabledColor = Color3.fromRGB(153, 153, 153), TextDisabledTransparency = 0, BorderColor = Color3.fromRGB(64, 64, 64), BorderActiveColor = Color3.fromRGB(64, 64, 64), BorderTransparency = 0.5, BorderActiveTransparency = 0.2, WindowBgColor = Color3.fromRGB(240, 240, 240), WindowBgTransparency = 0, PopupBgColor = Color3.fromRGB(255, 255, 255), PopupBgTransparency = 0.02, TitleBgColor = Color3.fromRGB(245, 245, 245), TitleBgTransparency = 0, TitleBgActiveColor = Color3.fromRGB(209, 209, 209), TitleBgActiveTransparency = 0, TitleBgCollapsedColor = Color3.fromRGB(255, 255, 255), TitleBgCollapsedTransparency = 0.5, MenubarBgColor = Color3.fromRGB(219, 219, 219), MenubarBgTransparency = 0, ScrollbarGrabColor = Color3.fromRGB(96, 96, 96), ScrollbarGrabTransparency = 0, FrameBgColor = Color3.fromRGB(255, 255, 255), FrameBgTransparency = 0.6, FrameBgHoveredColor = Color3.fromRGB(66, 150, 250), FrameBgHoveredTransparency = 0.6, FrameBgActiveColor = Color3.fromRGB(66, 150, 250), FrameBgActiveTransparency = 0.33, ButtonColor = Color3.fromRGB(66, 150, 250), ButtonTransparency = 0.6, ButtonHoveredColor = Color3.fromRGB(66, 150, 250), ButtonHoveredTransparency = 0, ButtonActiveColor = Color3.fromRGB(15, 135, 250), ButtonActiveTransparency = 0, ImageColor = Color3.fromRGB(255, 255, 255), ImageTransparency = 0, HeaderColor = Color3.fromRGB(66, 150, 250), HeaderTransparency = 0.31, HeaderHoveredColor = Color3.fromRGB(66, 150, 250), HeaderHoveredTransparency = 0.2, HeaderActiveColor = Color3.fromRGB(66, 150, 250), HeaderActiveTransparency = 0, TabColor = Color3.fromRGB(195, 203, 213), TabTransparency = 0.07, TabHoveredColor = Color3.fromRGB(66, 150, 250), TabHoveredTransparency = 0.2, TabActiveColor = Color3.fromRGB(152, 186, 255), TabActiveTransparency = 0, SliderGrabColor = Color3.fromRGB(61, 133, 224), SliderGrabTransparency = 0, SliderGrabActiveColor = Color3.fromRGB(66, 150, 250), SliderGrabActiveTransparency = 0, SelectionImageObjectColor = Color3.fromRGB(0, 0, 0), SelectionImageObjectTransparency = 0.8, SelectionImageObjectBorderColor = Color3.fromRGB(0, 0, 0), SelectionImageObjectBorderTransparency = 0, TableBorderStrongColor = Color3.fromRGB(145, 145, 163), TableBorderStrongTransparency = 0, TableBorderLightColor = Color3.fromRGB(173, 173, 189), TableBorderLightTransparency = 0, TableRowBgColor = Color3.fromRGB(0, 0, 0), TableRowBgTransparency = 1, TableRowBgAltColor = Color3.fromRGB(77, 77, 77), TableRowBgAltTransparency = 0.91, NavWindowingHighlightColor = Color3.fromRGB(179, 179, 179), NavWindowingHighlightTransparency = 0.3, NavWindowingDimBgColor = Color3.fromRGB(51, 51, 51), NavWindowingDimBgTransparency = 0.8, SeparatorColor = Color3.fromRGB(99, 99, 99), SeparatorTransparency = 0.38, CheckMarkColor = Color3.fromRGB(66, 150, 250), CheckMarkTransparency = 0, PlotHistogramColor = Color3.fromRGB(230, 179, 0), PlotHistogramTransparency = 0, PlotHistogramHoveredColor = Color3.fromRGB(255, 153, 0), PlotHistogramHoveredTransparency = 0, }, sizeDefault = { -- Dear, ImGui default ItemWidth = UDim.new(1, 0), ContentWidth = UDim.new(0.65, 0), ContentHeight = UDim.new(0, 0), WindowPadding = Vector2.new(8, 8), WindowResizePadding = Vector2.new(6, 6), FramePadding = Vector2.new(4, 3), ItemSpacing = Vector2.new(8, 4), ItemInnerSpacing = Vector2.new(4, 4), CellPadding = Vector2.new(4, 2), DisplaySafeAreaPadding = Vector2.new(0, 0), SeparatorTextPadding = Vector2.new(20, 3), IndentSpacing = 21, TextFont = Font.fromEnum(Enum.Font.Code), TextSize = 13, FrameBorderSize = 0, FrameRounding = 0, GrabRounding = 0, WindowRounding = 0, -- these don't actually work but it's nice to have them. WindowBorderSize = 1, WindowTitleAlign = Enum.LeftRight.Left, PopupBorderSize = 1, PopupRounding = 0, ScrollbarSize = 7, GrabMinSize = 10, SeparatorTextBorderSize = 3, ImageBorderSize = 2, }, sizeClear = { -- easier to read and manuveure ItemWidth = UDim.new(1, 0), ContentWidth = UDim.new(0.65, 0), ContentHeight = UDim.new(0, 0), WindowPadding = Vector2.new(12, 8), WindowResizePadding = Vector2.new(8, 8), FramePadding = Vector2.new(6, 4), ItemSpacing = Vector2.new(8, 8), ItemInnerSpacing = Vector2.new(8, 8), CellPadding = Vector2.new(4, 4), DisplaySafeAreaPadding = Vector2.new(8, 8), SeparatorTextPadding = Vector2.new(24, 6), IndentSpacing = 25, TextFont = Font.fromEnum(Enum.Font.Ubuntu), TextSize = 15, FrameBorderSize = 1, FrameRounding = 4, GrabRounding = 4, WindowRounding = 4, WindowBorderSize = 1, WindowTitleAlign = Enum.LeftRight.Center, PopupBorderSize = 1, PopupRounding = 4, ScrollbarSize = 9, GrabMinSize = 14, SeparatorTextBorderSize = 4, ImageBorderSize = 4, }, utilityDefault = { UseScreenGUIs = true, IgnoreGuiInset = false, Parent = nil, RichText = false, TextWrapped = false, DisplayOrderOffset = 127, ZIndexOffset = 0, MouseDoubleClickTime = 0.30, -- Time for a double-click, in seconds. MouseDoubleClickMaxDist = 6.0, -- Distance threshold to stay in to validate a double-click, in pixels. HoverColor = Color3.fromRGB(255, 255, 0), HoverTransparency = 0.1, }, } for i, v in ImGui.TemplateConfig.colorDark do if typeof(v) == "Color3" then local h, s, v = Color3.toHSV(v) ImGui.TemplateConfig.colorDark[i] = Color3.fromHSV(1, s, v) --print(Color3.fromHSV(0.139222, 1, 1)) end end local function Internal(ImGui: ImGui): Internal local Internal = {} :: Internal Internal._version = [[ 2.3.1 ]] Internal._started = false Internal._shutdown = false Internal._cycleTick = 0 Internal._globalRefreshRequested = false Internal._localRefreshActive = false Internal._widgets = {} Internal._stackIndex = 1 Internal._rootInstance = nil Internal._rootWidget = { ID = "R", type = "Root", Instance = Internal._rootInstance, ZIndex = 0, ZOffset = 0, } Internal._lastWidget = Internal._rootWidget Internal._rootConfig = {} Internal._config = Internal._rootConfig Internal._IDStack = { "R" } Internal._usedIDs = {} Internal._pushedId = nil Internal._nextWidgetId = nil Internal._states = {} Internal._postCycleCallbacks = {} Internal._connectedFunctions = {} Internal._connections = {} Internal._initFunctions = {} Internal._fullErrorTracebacks = game:GetService("RunService"):IsStudio() Internal._cycleCoroutine = coroutine.create(function() while Internal._started do for _, callback: () -> string in Internal._connectedFunctions do --debug.profilebegin("ImGui/Connection") local status: boolean, _error: string = pcall(callback) --debug.profileend() if not status then Internal._stackIndex = 1 coroutine.yield(false, _error) end end coroutine.yield(true) end end) local StateClass = {} StateClass.__index = StateClass function StateClass:get(): T return self.value end function StateClass:set(newValue: T): T if newValue == self.value then return self.value end self.value = newValue for _, thisWidget: Widget in self.ConnectedWidgets do Internal._widgets[thisWidget.type].UpdateState(thisWidget) end for _, callback in self.ConnectedFunctions do callback(newValue) end return self.value end function StateClass:onChange(callback: (newValue: T) -> ()): () -> () local connectionIndex: number = #self.ConnectedFunctions + 1 self.ConnectedFunctions[connectionIndex] = callback return function() self.ConnectedFunctions[connectionIndex] = nil end end Internal.StateClass = StateClass function Internal._cycle() if ImGui.Disabled then return end Internal._rootWidget.lastCycleTick = Internal._cycleTick if Internal._rootInstance == nil or Internal._rootInstance.Parent == nil then ImGui.ForceRefresh() end for _, widget: Widget in Internal._lastVDOM do if widget.lastCycleTick ~= Internal._cycleTick and (widget.lastCycleTick ~= -1) then Internal._DiscardWidget(widget) end end setmetatable(Internal._lastVDOM, { __mode = "kv" }) Internal._lastVDOM = Internal._VDOM Internal._VDOM = Internal._generateEmptyVDOM() task.spawn(function() for _, callback: () -> () in Internal._postCycleCallbacks do callback() end end) if Internal._globalRefreshRequested then Internal._generateSelectionImageObject() Internal._globalRefreshRequested = false for _, widget: Widget in Internal._lastVDOM do Internal._DiscardWidget(widget) end Internal._generateRootInstance() Internal._lastVDOM = Internal._generateEmptyVDOM() end Internal._cycleTick += 1 table.clear(Internal._usedIDs) local compatibleParent: boolean = (Internal.parentInstance:IsA("GuiBase2d") or Internal.parentInstance:IsA("CoreGui") or Internal.parentInstance:IsA("PluginGui") or Internal.parentInstance:IsA("PlayerGui")) if compatibleParent == false then error("ImGui Parent Instance cant contain GUI") end if Internal._fullErrorTracebacks then for _, callback: () -> () in Internal._connectedFunctions do callback() end else local coroutineStatus = coroutine.status(Internal._cycleCoroutine) if coroutineStatus == "suspended" then local _, success, result = coroutine.resume(Internal._cycleCoroutine) if success == false then error(result, 0) end elseif coroutineStatus == "running" then error("ImGui cycleCoroutine took to long to yield. Connected functions should not yield.") else error("unrecoverable state") end end if Internal._stackIndex ~= 1 then Internal._stackIndex = 1 --error("Callback has too few calls to ImGui.End()", 0) end end function Internal._NoOp() end function Internal.WidgetConstructor(type: string, widgetClass: WidgetClass) local Fields: { [string]: { [string]: { string } } } = { All = { Required = { "Generate", "Discard", "Update", "Args", "Events", "hasChildren", "hasState", }, Optional = {}, }, IfState = { Required = { "GenerateState", "UpdateState", }, Optional = {}, }, IfChildren = { Required = { "ChildAdded", }, Optional = { "ChildDiscarded", }, }, } local thisWidget = {} :: WidgetClass for _, field: string in Fields.All.Required do assert(widgetClass[field] ~= nil, `field {field} is missing from widget {type}, it is required for all widgets`) thisWidget[field] = widgetClass[field] end for _, field: string in Fields.All.Optional do if widgetClass[field] == nil then thisWidget[field] = Internal._NoOp else thisWidget[field] = widgetClass[field] end end if widgetClass.hasState then for _, field: string in Fields.IfState.Required do assert(widgetClass[field] ~= nil, `field {field} is missing from widget {type}, it is required for all widgets with state`) thisWidget[field] = widgetClass[field] end for _, field: string in Fields.IfState.Optional do if widgetClass[field] == nil then thisWidget[field] = Internal._NoOp else thisWidget[field] = widgetClass[field] end end end if widgetClass.hasChildren then for _, field: string in Fields.IfChildren.Required do assert(widgetClass[field] ~= nil, `field {field} is missing from widget {type}, it is required for all widgets with children`) thisWidget[field] = widgetClass[field] end for _, field: string in Fields.IfChildren.Optional do if widgetClass[field] == nil then thisWidget[field] = Internal._NoOp else thisWidget[field] = widgetClass[field] end end end Internal._widgets[type] = thisWidget ImGui.Args[type] = thisWidget.Args local ArgNames: { [number]: string } = {} for index: string, argument: number in thisWidget.Args do ArgNames[argument] = index end thisWidget.ArgNames = ArgNames for index: string, _ in thisWidget.Events do if ImGui.Events[index] == nil then ImGui.Events[index] = function() return Internal._EventCall(Internal._lastWidget, index) end end end end function Internal._Insert(widgetType: string, args: WidgetArguments?, states: WidgetStates?): Widget local ID: ID = Internal._getID(3) local thisWidgetClass: WidgetClass = Internal._widgets[widgetType] if Internal._VDOM[ID] then return Internal._ContinueWidget(ID, widgetType) end local arguments: Arguments = {} :: Arguments if args ~= nil then if type(args) ~= "table" then args = { args } end for index: number, argument: Argument in args do arguments[thisWidgetClass.ArgNames[index]] = argument end end table.freeze(arguments) local lastWidget: Widget? = Internal._lastVDOM[ID] if lastWidget and widgetType == lastWidget.type then if Internal._localRefreshActive then Internal._DiscardWidget(lastWidget) lastWidget = nil end end local thisWidget: Widget = if lastWidget == nil then Internal._GenNewWidget(widgetType, arguments, states, ID) else lastWidget local parentWidget: ParentWidget = thisWidget.parentWidget if thisWidget.type ~= "Window" and thisWidget.type ~= "Tooltip" then if thisWidget.ZIndex ~= parentWidget.ZOffset then parentWidget.ZUpdate = true end if parentWidget.ZUpdate then thisWidget.ZIndex = parentWidget.ZOffset if thisWidget.Instance then thisWidget.Instance.ZIndex = thisWidget.ZIndex thisWidget.Instance.LayoutOrder = thisWidget.ZIndex end end end if Internal._deepCompare(thisWidget.providedArguments, arguments) == false then thisWidget.arguments = Internal._deepCopy(arguments) thisWidget.providedArguments = arguments thisWidgetClass.Update(thisWidget) end thisWidget.lastCycleTick = Internal._cycleTick parentWidget.ZOffset += 1 if thisWidgetClass.hasChildren then local thisParent = thisWidget :: ParentWidget thisParent.ZOffset = 0 thisParent.ZUpdate = false Internal._stackIndex += 1 Internal._IDStack[Internal._stackIndex] = thisWidget.ID end Internal._VDOM[ID] = thisWidget Internal._lastWidget = thisWidget return thisWidget end function Internal._GenNewWidget(widgetType: string, arguments: Arguments, states: WidgetStates?, ID: ID): Widget local parentId: ID = Internal._IDStack[Internal._stackIndex] local parentWidget: ParentWidget = Internal._VDOM[parentId] local thisWidgetClass: WidgetClass = Internal._widgets[widgetType] local thisWidget = {} :: Widget setmetatable(thisWidget, thisWidget) thisWidget.ID = ID thisWidget.type = widgetType thisWidget.parentWidget = parentWidget thisWidget.trackedEvents = {} thisWidget.UID = HttpService:GenerateGUID(false):sub(0, 8) thisWidget.ZIndex = parentWidget.ZOffset thisWidget.Instance = thisWidgetClass.Generate(thisWidget) parentWidget = thisWidget.parentWidget if Internal._config.Parent then thisWidget.Instance.Parent = Internal._config.Parent else thisWidget.Instance.Parent = Internal._widgets[parentWidget.type].ChildAdded(parentWidget, thisWidget) end thisWidget.providedArguments = arguments thisWidget.arguments = Internal._deepCopy(arguments) thisWidgetClass.Update(thisWidget) local eventMTParent if thisWidgetClass.hasState then local stateWidget = thisWidget :: StateWidget if states then for index: string, state: State in states do if not (type(state) == "table" and getmetatable(state :: any) == Internal.StateClass) then states[index] = Internal._widgetState(stateWidget, index, state) end end stateWidget.state = states for _, state: State in states do state.ConnectedWidgets[stateWidget.ID] = stateWidget end else stateWidget.state = {} end thisWidgetClass.GenerateState(stateWidget) thisWidgetClass.UpdateState(stateWidget) stateWidget.stateMT = {} setmetatable(stateWidget.state, stateWidget.stateMT) stateWidget.__index = stateWidget.state eventMTParent = stateWidget.stateMT else eventMTParent = thisWidget end eventMTParent.__index = function(_, eventName: string) return function() return Internal._EventCall(thisWidget, eventName) end end return thisWidget end function Internal._ContinueWidget(ID: ID, widgetType: string): Widget local thisWidgetClass: WidgetClass = Internal._widgets[widgetType] local thisWidget: Widget = Internal._VDOM[ID] if thisWidgetClass.hasChildren then Internal._stackIndex += 1 Internal._IDStack[Internal._stackIndex] = thisWidget.ID end Internal._lastWidget = thisWidget return thisWidget end function Internal._DiscardWidget(widgetToDiscard: Widget) local widgetParent = widgetToDiscard.parentWidget if widgetParent then Internal._widgets[widgetParent.type].ChildDiscarded(widgetParent, widgetToDiscard) end Internal._widgets[widgetToDiscard.type].Discard(widgetToDiscard) widgetToDiscard.lastCycleTick = -1 end function Internal._widgetState(thisWidget: StateWidget, stateName: string, initialValue: any): State local ID: ID = thisWidget.ID .. stateName if Internal._states[ID] then Internal._states[ID].ConnectedWidgets[thisWidget.ID] = thisWidget return Internal._states[ID] else Internal._states[ID] = { ID = ID, value = initialValue, ConnectedWidgets = { [thisWidget.ID] = thisWidget }, ConnectedFunctions = {}, } setmetatable(Internal._states[ID], Internal.StateClass) return Internal._states[ID] end end function Internal._EventCall(thisWidget: Widget, eventName: string): boolean local Events: Events = Internal._widgets[thisWidget.type].Events local Event: Event = Events[eventName] assert(Event ~= nil, `widget {thisWidget.type} has no event of name {eventName}`) if thisWidget.trackedEvents[eventName] == nil then Event.Init(thisWidget) thisWidget.trackedEvents[eventName] = true end return Event.Get(thisWidget) end function Internal._GetParentWidget(): ParentWidget return Internal._VDOM[Internal._IDStack[Internal._stackIndex]] end function Internal._generateEmptyVDOM(): { [ID]: Widget } return { ["R"] = Internal._rootWidget, } end function Internal._generateRootInstance() Internal._rootInstance = Internal._widgets["Root"].Generate(Internal._widgets["Root"]) Internal._rootInstance.Parent = Internal.parentInstance Internal._rootWidget.Instance = Internal._rootInstance end function Internal._generateSelectionImageObject() if Internal.SelectionImageObject then Internal.SelectionImageObject:Destroy() end local SelectionImageObject: Frame = Instance.new("Frame") SelectionImageObject.Position = UDim2.fromOffset(-1, -1) SelectionImageObject.Size = UDim2.new(1, 2, 1, 2) SelectionImageObject.BackgroundColor3 = Internal._config.SelectionImageObjectColor SelectionImageObject.BackgroundTransparency = Internal._config.SelectionImageObjectTransparency SelectionImageObject.BorderSizePixel = 0 local UIStroke: UIStroke = Instance.new("UIStroke") UIStroke.Thickness = 1 UIStroke.Color = Internal._config.SelectionImageObjectBorderColor UIStroke.Transparency = Internal._config.SelectionImageObjectBorderTransparency UIStroke.LineJoinMode = Enum.LineJoinMode.Round UIStroke.ApplyStrokeMode = Enum.ApplyStrokeMode.Border UIStroke.Parent = SelectionImageObject local Rounding: UICorner = Instance.new("UICorner") Rounding.CornerRadius = UDim.new(0, 2) Rounding.Parent = SelectionImageObject Internal.SelectionImageObject = SelectionImageObject end function Internal._getID(levelsToIgnore: number): ID if Internal._nextWidgetId then local ID: ID = Internal._nextWidgetId Internal._nextWidgetId = nil return ID end local i: number = 1 + (levelsToIgnore or 1) local ID: ID = "" local levelInfo: number = debug.info(i, "l") while levelInfo ~= -1 and levelInfo ~= nil do ID ..= "+" .. levelInfo i += 1 levelInfo = debug.info(i, "l") end if Internal._usedIDs[ID] then Internal._usedIDs[ID] += 1 else Internal._usedIDs[ID] = 1 end local discriminator = if Internal._pushedId then Internal._pushedId else Internal._usedIDs[ID] return ID .. ":" .. discriminator end function Internal._deepCompare(t1: {}, t2: {}): boolean for i, v1 in t1 do local v2 = t2[i] if type(v1) == "table" then if v2 and type(v2) == "table" then if Internal._deepCompare(v1, v2) == false then return false end else return false end else if type(v1) ~= type(v2) or v1 ~= v2 then return false end end end return true end function Internal._deepCopy(t: {}): {} local copy: {} = table.clone(t) for k: any, v: any in pairs(t) do if type(v) == "table" then copy[k] = Internal._deepCopy(v) end end return copy end Internal._lastVDOM = Internal._generateEmptyVDOM() Internal._VDOM = Internal._generateEmptyVDOM() ImGui.Internal = Internal ImGui._config = Internal._config return Internal end local Internal: Internal = Internal(ImGui) ImGui.Disabled = false ImGui.Args = {} ImGui.Events = {} function ImGui.Init(parentInstance: Instance?, eventConnection: (RBXScriptSignal | () -> () | false)?): ImGui assert(Internal._started == false, "ImGui.Init can only be called once.") assert(Internal._shutdown == false, "ImGui.Init cannot be called once shutdown.") if parentInstance == nil then parentInstance = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui") end if eventConnection == nil then eventConnection = game:GetService("RunService").Heartbeat end Internal.parentInstance = parentInstance :: Instance Internal._started = true Internal._generateRootInstance() Internal._generateSelectionImageObject() for _, callback: () -> () in Internal._initFunctions do callback() end task.spawn(function() if typeof(eventConnection) == "function" then while Internal._started do eventConnection() Internal._cycle() end elseif eventConnection ~= nil and eventConnection ~= false then Internal._eventConnection = eventConnection:Connect(function() Internal._cycle() end) end end) return ImGui end function ImGui.Shutdown() Internal._started = false Internal._shutdown = true if Internal._eventConnection then Internal._eventConnection:Disconnect() end Internal._eventConnection = nil if Internal._rootWidget then if Internal._rootWidget.Instance then Internal._widgets["Root"].Discard(Internal._rootWidget) end Internal._rootInstance = nil end if Internal.SelectionImageObject then Internal.SelectionImageObject:Destroy() end for _, connection: RBXScriptConnection in Internal._connections do connection:Disconnect() end end function ImGui:Connect(callback: () -> ()): () -> () if Internal._started == false then warn("ImGui:Connect() was called before calling ImGui.Init(), the connected function will never run") end local connectionIndex: number = #Internal._connectedFunctions + 1 Internal._connectedFunctions[connectionIndex] = callback return function() Internal._connectedFunctions[connectionIndex] = nil end end function ImGui.Append(userInstance: GuiObject) local parentWidget: ParentWidget = Internal._GetParentWidget() local widgetInstanceParent: GuiObject if Internal._config.Parent then widgetInstanceParent = Internal._config.Parent :: any else widgetInstanceParent = Internal._widgets[parentWidget.type].ChildAdded(parentWidget, { type = "userInstance" } :: Widget) end userInstance.Parent = widgetInstanceParent end function ImGui.End() if Internal._stackIndex == 1 then error("Callback has too many calls to ImGui.End()", 2) end Internal._IDStack[Internal._stackIndex] = nil Internal._stackIndex -= 1 end function ImGui.ForceRefresh() Internal._globalRefreshRequested = true end function ImGui.UpdateGlobalConfig(deltaStyle: { [string]: any }) for index, style in deltaStyle do Internal._rootConfig[index] = style end ImGui.ForceRefresh() end function ImGui.PushConfig(deltaStyle: { [string]: any }) local ID = ImGui.State(-1) if ID.value == -1 then ID:set(deltaStyle) else if Internal._deepCompare(ID:get(), deltaStyle) == false then Internal._localRefreshActive = true ID:set(deltaStyle) end end Internal._config = setmetatable(deltaStyle, { __index = Internal._config, }) :: any end function ImGui.PopConfig() Internal._localRefreshActive = false Internal._config = getmetatable(Internal._config :: any).__index end ImGui.UpdateGlobalConfig(ImGui.TemplateConfig.colorDark) ImGui.UpdateGlobalConfig(ImGui.TemplateConfig.sizeDefault) ImGui.UpdateGlobalConfig(ImGui.TemplateConfig.utilityDefault) Internal._globalRefreshRequested = false function ImGui.PushId(ID: ID) assert(typeof(ID) == "string", "ImGui expected ImGui.PushId id to PushId to be a string.") Internal._pushedId = tostring(ID) end function ImGui.PopId() Internal._pushedId = nil end function ImGui.SetNextWidgetID(ID: ID) Internal._nextWidgetId = ID end function ImGui.State(initialValue: T): State local ID: ID = Internal._getID(2) if Internal._states[ID] then return Internal._states[ID] end Internal._states[ID] = { ID = ID, value = initialValue, ConnectedWidgets = {}, ConnectedFunctions = {}, } :: any setmetatable(Internal._states[ID], Internal.StateClass) return Internal._states[ID] end function ImGui.WeakState(initialValue: T): State local ID: ID = Internal._getID(2) if Internal._states[ID] then if next(Internal._states[ID].ConnectedWidgets) == nil then Internal._states[ID] = nil else return Internal._states[ID] end end Internal._states[ID] = { ID = ID, value = initialValue, ConnectedWidgets = {}, ConnectedFunctions = {}, } :: any setmetatable(Internal._states[ID], Internal.StateClass) return Internal._states[ID] end function ImGui.VariableState(variable: T, callback: (T) -> ()): State local ID: ID = Internal._getID(2) local state: State? = Internal._states[ID] if state then if variable ~= state.value then state:set(variable) end return state end local newState = { ID = ID, value = variable, ConnectedWidgets = {}, ConnectedFunctions = {}, } :: State setmetatable(newState, Internal.StateClass) Internal._states[ID] = newState newState:onChange(callback) return newState end function ImGui.TableState(tab: { [K]: V }, key: K, callback: ((newValue: V) -> false?)?): State local value: V = tab[key] local ID: ID = Internal._getID(2) local state: State? = Internal._states[ID] if state then if value ~= state.value then state:set(value) end return state end local newState = { ID = ID, value = value, ConnectedWidgets = {}, ConnectedFunctions = {}, } :: State setmetatable(newState, Internal.StateClass) Internal._states[ID] = newState newState:onChange(function() if callback ~= nil then if callback(newState.value) then tab[key] = newState.value end else tab[key] = newState.value end end) return newState end function ImGui.ComputedState(firstState: State, onChangeCallback: (firstValue: T) -> U): State local ID: ID = Internal._getID(2) if Internal._states[ID] then return Internal._states[ID] else Internal._states[ID] = { ID = ID, value = onChangeCallback(firstState.value), ConnectedWidgets = {}, ConnectedFunctions = {}, } :: State firstState:onChange(function(newValue: T) Internal._states[ID]:set(onChangeCallback(newValue)) end) setmetatable(Internal._states[ID], Internal.StateClass) return Internal._states[ID] end end ImGui.ShowDemoWindow = require(game.ReplicatedStorage.ImGui.demoWindow)(ImGui) local widgets = {} :: WidgetUtility local function Widgets(ImGui: Internal) widgets.GuiService = game:GetService("GuiService") widgets.RunService = game:GetService("RunService") widgets.UserInputService = game:GetService("UserInputService") widgets.ContextActionService = game:GetService("ContextActionService") widgets.TextService = game:GetService("TextService") widgets.ICONS = { RIGHT_POINTING_TRIANGLE = "rbxasset://textures/DeveloperFramework/button_arrow_right.png", DOWN_POINTING_TRIANGLE = "rbxasset://textures/DeveloperFramework/button_arrow_down.png", MULTIPLICATION_SIGN = "rbxasset://textures/AnimationEditor/icon_close.png", BOTTOM_RIGHT_CORNER = "rbxasset://textures/ui/InspectMenu/gr-item-selector-triangle.png", CHECK_MARK = "rbxasset://textures/AnimationEditor/icon_checkmark.png", ALPHA_BACKGROUND_TEXTURE = "rbxasset://textures/meshPartFallback.png", UNKNOWN_TEXTURE = "rbxasset://textures/ui/GuiImagePlaceholder.png", } widgets.IS_STUDIO = widgets.RunService:IsStudio() function widgets.getTime() if widgets.IS_STUDIO then return os.clock() else return time() end end widgets.GuiOffset = Vector2.zero widgets.MouseOffset = if ImGui._config.IgnoreGuiInset then Vector2.zero else widgets.GuiService:GetGuiInset() local connection: RBXScriptConnection = widgets.GuiService:GetPropertyChangedSignal("TopbarInset"):Once(function() widgets.MouseOffset = if ImGui._config.IgnoreGuiInset then Vector2.zero else widgets.GuiService:GetGuiInset() end) task.delay(3, function() connection:Disconnect() end) do function widgets.getMouseLocation(): Vector2 return widgets.UserInputService:GetMouseLocation() - widgets.MouseOffset end function widgets.isPosInsideRect(pos: Vector2, rectMin: Vector2, rectMax: Vector2): boolean return pos.X > rectMin.X and pos.X < rectMax.X and pos.Y > rectMin.Y and pos.Y < rectMax.Y end function widgets.findBestWindowPosForPopup(refPos: Vector2, size: Vector2, outerMin: Vector2, outerMax: Vector2): Vector2 local CURSOR_OFFSET_DIST: number = 20 if refPos.X + size.X + CURSOR_OFFSET_DIST > outerMax.X then if refPos.Y + size.Y + CURSOR_OFFSET_DIST > outerMax.Y then refPos += Vector2.new(0, -(CURSOR_OFFSET_DIST + size.Y)) else refPos += Vector2.new(0, CURSOR_OFFSET_DIST) end else refPos += Vector2.new(CURSOR_OFFSET_DIST) end local clampedPos: Vector2 = Vector2.new(math.max(math.min(refPos.X + size.X, outerMax.X) - size.X, outerMin.X), math.max(math.min(refPos.Y + size.Y, outerMax.Y) - size.Y, outerMin.Y)) return clampedPos end function widgets.getScreenSizeForWindow(thisWidget: Widget): Vector2 if thisWidget.Instance:IsA("GuiBase2d") then return thisWidget.Instance.AbsoluteSize else local rootParent = thisWidget.Instance.Parent if rootParent:IsA("GuiBase2d") then return rootParent.AbsoluteSize else if rootParent.Parent:IsA("GuiBase2d") then return rootParent.AbsoluteSize else return workspace.CurrentCamera.ViewportSize end end end end function widgets.extend(superClass: WidgetClass, subClass: WidgetClass): WidgetClass local newClass: WidgetClass = table.clone(superClass) for index: unknown, value: any in subClass do newClass[index] = value end return newClass end function widgets.UIPadding(Parent: GuiObject, PxPadding: Vector2): UIPadding local UIPaddingInstance: UIPadding = Instance.new("UIPadding") UIPaddingInstance.PaddingLeft = UDim.new(0, PxPadding.X) UIPaddingInstance.PaddingRight = UDim.new(0, PxPadding.X) UIPaddingInstance.PaddingTop = UDim.new(0, PxPadding.Y) UIPaddingInstance.PaddingBottom = UDim.new(0, PxPadding.Y) UIPaddingInstance.Parent = Parent return UIPaddingInstance end function widgets.UIListLayout(Parent: GuiObject, FillDirection: Enum.FillDirection, Padding: UDim): UIListLayout local UIListLayoutInstance: UIListLayout = Instance.new("UIListLayout") UIListLayoutInstance.SortOrder = Enum.SortOrder.LayoutOrder UIListLayoutInstance.Padding = Padding UIListLayoutInstance.FillDirection = FillDirection UIListLayoutInstance.Parent = Parent return UIListLayoutInstance end function widgets.UIStroke(Parent: GuiObject, Thickness: number, Color: Color3, Transparency: number): UIStroke local UIStrokeInstance: UIStroke = Instance.new("UIStroke") UIStrokeInstance.Thickness = Thickness UIStrokeInstance.Color = Color UIStrokeInstance.Transparency = Transparency UIStrokeInstance.ApplyStrokeMode = Enum.ApplyStrokeMode.Border UIStrokeInstance.LineJoinMode = Enum.LineJoinMode.Round UIStrokeInstance.Parent = Parent return UIStrokeInstance end function widgets.UICorner(Parent: GuiObject, PxRounding: number?): UICorner local UICornerInstance: UICorner = Instance.new("UICorner") UICornerInstance.CornerRadius = UDim.new(PxRounding and 0 or 1, PxRounding or 0) UICornerInstance.Parent = Parent return UICornerInstance end function widgets.UISizeConstraint(Parent: GuiObject, MinSize: Vector2?, MaxSize: Vector2?): UISizeConstraint local UISizeConstraintInstance: UISizeConstraint = Instance.new("UISizeConstraint") UISizeConstraintInstance.MinSize = MinSize or UISizeConstraintInstance.MinSize UISizeConstraintInstance.MaxSize = MaxSize or UISizeConstraintInstance.MaxSize UISizeConstraintInstance.Parent = Parent return UISizeConstraintInstance end function widgets.applyTextStyle(thisInstance: TextLabel & TextButton & TextBox) thisInstance.FontFace = ImGui._config.TextFont thisInstance.TextSize = ImGui._config.TextSize thisInstance.TextColor3 = ImGui._config.TextColor thisInstance.TextTransparency = ImGui._config.TextTransparency thisInstance.TextXAlignment = Enum.TextXAlignment.Left thisInstance.TextYAlignment = Enum.TextYAlignment.Center thisInstance.RichText = ImGui._config.RichText thisInstance.TextWrapped = ImGui._config.TextWrapped thisInstance.AutoLocalize = false end function widgets.applyInteractionHighlights(Property: string, Button: GuiButton, Highlightee: GuiObject, Colors: { [string]: any }) local exitedButton: boolean = false widgets.applyMouseEnter(Button, function() Highlightee[Property .. "Color3"] = Colors.HoveredColor Highlightee[Property .. "Transparency"] = Colors.HoveredTransparency exitedButton = false end) widgets.applyMouseLeave(Button, function() Highlightee[Property .. "Color3"] = Colors.Color Highlightee[Property .. "Transparency"] = Colors.Transparency exitedButton = true end) widgets.applyInputBegan(Button, function(input: InputObject) if not (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Gamepad1) then return end Highlightee[Property .. "Color3"] = Colors.ActiveColor Highlightee[Property .. "Transparency"] = Colors.ActiveTransparency end) widgets.applyInputEnded(Button, function(input: InputObject) if not (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Gamepad1) or exitedButton then return end if input.UserInputType == Enum.UserInputType.MouseButton1 then Highlightee[Property .. "Color3"] = Colors.HoveredColor Highlightee[Property .. "Transparency"] = Colors.HoveredTransparency end if input.UserInputType == Enum.UserInputType.Gamepad1 then Highlightee[Property .. "Color3"] = Colors.Color Highlightee[Property .. "Transparency"] = Colors.Transparency end end) Button.SelectionImageObject = ImGui.SelectionImageObject end function widgets.applyInteractionHighlightsWithMultiHighlightee(Property: string, Button: GuiButton, Highlightees: { { GuiObject | { [string]: Color3 | number } } }) local exitedButton: boolean = false widgets.applyMouseEnter(Button, function() for _, Highlightee in Highlightees do Highlightee[1][Property .. "Color3"] = Highlightee[2].HoveredColor Highlightee[1][Property .. "Transparency"] = Highlightee[2].HoveredTransparency exitedButton = false end end) widgets.applyMouseLeave(Button, function() for _, Highlightee in Highlightees do Highlightee[1][Property .. "Color3"] = Highlightee[2].Color Highlightee[1][Property .. "Transparency"] = Highlightee[2].Transparency exitedButton = true end end) widgets.applyInputBegan(Button, function(input: InputObject) if not (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Gamepad1) then return end for _, Highlightee in Highlightees do Highlightee[1][Property .. "Color3"] = Highlightee[2].ActiveColor Highlightee[1][Property .. "Transparency"] = Highlightee[2].ActiveTransparency end end) widgets.applyInputEnded(Button, function(input: InputObject) if not (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Gamepad1) or exitedButton then return end for _, Highlightee in Highlightees do if input.UserInputType == Enum.UserInputType.MouseButton1 then Highlightee[1][Property .. "Color3"] = Highlightee[2].HoveredColor Highlightee[1][Property .. "Transparency"] = Highlightee[2].HoveredTransparency end if input.UserInputType == Enum.UserInputType.Gamepad1 then Highlightee[1][Property .. "Color3"] = Highlightee[2].Color Highlightee[1][Property .. "Transparency"] = Highlightee[2].Transparency end end end) Button.SelectionImageObject = ImGui.SelectionImageObject end function widgets.applyFrameStyle(thisInstance: GuiObject, noPadding: boolean?, noCorner: boolean?) local FrameBorderSize: number = ImGui._config.FrameBorderSize local FrameRounding: number = ImGui._config.FrameRounding thisInstance.BorderSizePixel = 0 if FrameBorderSize > 0 then widgets.UIStroke(thisInstance, FrameBorderSize, ImGui._config.BorderColor, ImGui._config.BorderTransparency) end if FrameRounding > 0 and not noCorner then widgets.UICorner(thisInstance, FrameRounding) end if not noPadding then widgets.UIPadding(thisInstance, ImGui._config.FramePadding) end end function widgets.applyButtonClick(thisInstance: GuiButton, callback: () -> ()) thisInstance.MouseButton1Click:Connect(function() callback() end) end function widgets.applyButtonDown(thisInstance: GuiButton, callback: (x: number, y: number) -> ()) thisInstance.MouseButton1Down:Connect(function(...) callback(...) end) end function widgets.applyMouseEnter(thisInstance: GuiObject, callback: () -> ()) thisInstance.MouseEnter:Connect(function(...) callback(...) end) end function widgets.applyMouseLeave(thisInstance: GuiObject, callback: () -> ()) thisInstance.MouseLeave:Connect(function(...) callback(...) end) end function widgets.applyInputBegan(thisInstance: GuiButton, callback: (input: InputObject) -> ()) thisInstance.InputBegan:Connect(function(...) callback(...) end) end function widgets.applyInputEnded(thisInstance: GuiButton, callback: (input: InputObject) -> ()) thisInstance.InputEnded:Connect(function(...) callback(...) end) end function widgets.discardState(thisWidget: StateWidget) for _, state: State in thisWidget.state do state.ConnectedWidgets[thisWidget.ID] = nil end end function widgets.registerEvent(event: string, callback: (...any) -> ()) table.insert(ImGui._initFunctions, function() table.insert(ImGui._connections, widgets.UserInputService[event]:Connect(callback)) end) end end widgets.EVENTS = { hover = function(pathToHovered: (thisWidget: Widget) -> GuiObject) return { ["Init"] = function(thisWidget: Widget & Hovered) local hoveredGuiObject: GuiObject = pathToHovered(thisWidget) widgets.applyMouseEnter(hoveredGuiObject, function() thisWidget.isHoveredEvent = true end) widgets.applyMouseLeave(hoveredGuiObject, function() thisWidget.isHoveredEvent = false end) thisWidget.isHoveredEvent = false end, ["Get"] = function(thisWidget: Widget & Hovered): boolean return thisWidget.isHoveredEvent end, } end, click = function(pathToClicked: (thisWidget: Widget) -> GuiButton) return { ["Init"] = function(thisWidget: Widget & Clicked) local clickedGuiObject: GuiButton = pathToClicked(thisWidget) thisWidget.lastClickedTick = -1 widgets.applyButtonClick(clickedGuiObject, function() thisWidget.lastClickedTick = ImGui._cycleTick + 1 end) end, ["Get"] = function(thisWidget: Widget & Clicked): boolean return thisWidget.lastClickedTick == ImGui._cycleTick end, } end, rightClick = function(pathToClicked: (thisWidget: Widget) -> GuiButton) return { ["Init"] = function(thisWidget: Widget & RightClicked) local clickedGuiObject: GuiButton = pathToClicked(thisWidget) thisWidget.lastRightClickedTick = -1 clickedGuiObject.MouseButton2Click:Connect(function() thisWidget.lastRightClickedTick = ImGui._cycleTick + 1 end) end, ["Get"] = function(thisWidget: Widget & RightClicked): boolean return thisWidget.lastRightClickedTick == ImGui._cycleTick end, } end, doubleClick = function(pathToClicked: (thisWidget: Widget) -> GuiButton) return { ["Init"] = function(thisWidget: Widget & DoubleClicked) local clickedGuiObject: GuiButton = pathToClicked(thisWidget) thisWidget.lastClickedTime = -1 thisWidget.lastClickedPosition = Vector2.zero thisWidget.lastDoubleClickedTick = -1 widgets.applyButtonDown(clickedGuiObject, function(x: number, y: number) local currentTime: number = widgets.getTime() local isTimeValid: boolean = currentTime - thisWidget.lastClickedTime < ImGui._config.MouseDoubleClickTime if isTimeValid and (Vector2.new(x, y) - thisWidget.lastClickedPosition).Magnitude < ImGui._config.MouseDoubleClickMaxDist then thisWidget.lastDoubleClickedTick = ImGui._cycleTick + 1 else thisWidget.lastClickedTime = currentTime thisWidget.lastClickedPosition = Vector2.new(x, y) end end) end, ["Get"] = function(thisWidget: Widget & DoubleClicked): boolean return thisWidget.lastDoubleClickedTick == ImGui._cycleTick end, } end, ctrlClick = function(pathToClicked: (thisWidget: Widget) -> GuiButton) return { ["Init"] = function(thisWidget: Widget & CtrlClicked) local clickedGuiObject: GuiButton = pathToClicked(thisWidget) thisWidget.lastCtrlClickedTick = -1 widgets.applyButtonClick(clickedGuiObject, function() if widgets.UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or widgets.UserInputService:IsKeyDown(Enum.KeyCode.RightControl) then thisWidget.lastCtrlClickedTick = ImGui._cycleTick + 1 end end) end, ["Get"] = function(thisWidget: Widget & CtrlClicked): boolean return thisWidget.lastCtrlClickedTick == ImGui._cycleTick end, } end, } ImGui._utility = widgets widgets.ELEMENTS = { Root = function(ImGui: Internal, widgets: WidgetUtility) local NumNonWindowChildren: number = 0 --stylua: ignore ImGui.WidgetConstructor("Root", { hasState = false, hasChildren = true, Args = {}, Events = {}, Generate = function(_thisWidget: Root) local Root: Folder = Instance.new("Folder") Root.Name = "ImGui_Root" local PseudoWindowScreenGui if ImGui._config.UseScreenGUIs then PseudoWindowScreenGui = Instance.new("ScreenGui") PseudoWindowScreenGui.ResetOnSpawn = false PseudoWindowScreenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling PseudoWindowScreenGui.DisplayOrder = ImGui._config.DisplayOrderOffset PseudoWindowScreenGui.IgnoreGuiInset = ImGui._config.IgnoreGuiInset else PseudoWindowScreenGui = Instance.new("Frame") PseudoWindowScreenGui.AnchorPoint = Vector2.new(0.5, 0.5) PseudoWindowScreenGui.Position = UDim2.new(0.5, 0, 0.5, 0) PseudoWindowScreenGui.Size = UDim2.new(1, 0, 1, 0) PseudoWindowScreenGui.BackgroundTransparency = 1 PseudoWindowScreenGui.ZIndex = ImGui._config.DisplayOrderOffset end PseudoWindowScreenGui.Name = "PseudoWindowScreenGui" PseudoWindowScreenGui.Parent = Root local PopupScreenGui if ImGui._config.UseScreenGUIs then PopupScreenGui = Instance.new("ScreenGui") PopupScreenGui.ResetOnSpawn = false PopupScreenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling PopupScreenGui.DisplayOrder = ImGui._config.DisplayOrderOffset + 1024 -- room for 1024 regular windows before overlap PopupScreenGui.IgnoreGuiInset = ImGui._config.IgnoreGuiInset else PopupScreenGui = Instance.new("Frame") PopupScreenGui.AnchorPoint = Vector2.new(0.5, 0.5) PopupScreenGui.Position = UDim2.new(0.5, 0, 0.5, 0) PopupScreenGui.Size = UDim2.new(1, 0, 1, 0) PopupScreenGui.BackgroundTransparency = 1 PopupScreenGui.ZIndex = ImGui._config.DisplayOrderOffset + 1024 end PopupScreenGui.Name = "PopupScreenGui" PopupScreenGui.Parent = Root local TooltipContainer: Frame = Instance.new("Frame") TooltipContainer.Name = "TooltipContainer" TooltipContainer.AutomaticSize = Enum.AutomaticSize.XY TooltipContainer.Size = UDim2.fromOffset(0, 0) TooltipContainer.BackgroundTransparency = 1 TooltipContainer.BorderSizePixel = 0 widgets.UIListLayout(TooltipContainer, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.PopupBorderSize)) TooltipContainer.Parent = PopupScreenGui local MenuBarContainer: Frame = Instance.new("Frame") MenuBarContainer.Name = "MenuBarContainer" MenuBarContainer.AutomaticSize = Enum.AutomaticSize.Y MenuBarContainer.Size = UDim2.fromScale(1, 0) MenuBarContainer.BackgroundTransparency = 1 MenuBarContainer.BorderSizePixel = 0 MenuBarContainer.Parent = PopupScreenGui local PseudoWindow: Frame = Instance.new("Frame") PseudoWindow.Name = "PseudoWindow" PseudoWindow.Size = UDim2.new(0, 0, 0, 0) PseudoWindow.Position = UDim2.fromOffset(0, 22) PseudoWindow.AutomaticSize = Enum.AutomaticSize.XY PseudoWindow.BackgroundTransparency = ImGui._config.WindowBgTransparency PseudoWindow.BackgroundColor3 = ImGui._config.WindowBgColor PseudoWindow.BorderSizePixel = ImGui._config.WindowBorderSize PseudoWindow.BorderColor3 = ImGui._config.BorderColor PseudoWindow.Selectable = false PseudoWindow.SelectionGroup = true PseudoWindow.SelectionBehaviorUp = Enum.SelectionBehavior.Stop PseudoWindow.SelectionBehaviorDown = Enum.SelectionBehavior.Stop PseudoWindow.SelectionBehaviorLeft = Enum.SelectionBehavior.Stop PseudoWindow.SelectionBehaviorRight = Enum.SelectionBehavior.Stop PseudoWindow.Visible = false widgets.UIPadding(PseudoWindow, ImGui._config.WindowPadding) widgets.UIListLayout(PseudoWindow, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) PseudoWindow.Parent = PseudoWindowScreenGui return Root end, Update = function(thisWidget: Root) if NumNonWindowChildren > 0 then local Root = thisWidget.Instance :: any local PseudoWindowScreenGui = Root.PseudoWindowScreenGui :: any local PseudoWindow: Frame = PseudoWindowScreenGui.PseudoWindow PseudoWindow.Visible = true end end, Discard = function(thisWidget: Root) NumNonWindowChildren = 0 thisWidget.Instance:Destroy() end, ChildAdded = function(thisWidget: Root, thisChild: Widget) local Root = thisWidget.Instance :: any if thisChild.type == "Window" then return thisWidget.Instance elseif thisChild.type == "Tooltip" then return Root.PopupScreenGui.TooltipContainer elseif thisChild.type == "MenuBar" then return Root.PopupScreenGui.MenuBarContainer else local PseudoWindowScreenGui = Root.PseudoWindowScreenGui :: any local PseudoWindow: Frame = PseudoWindowScreenGui.PseudoWindow NumNonWindowChildren += 1 PseudoWindow.Visible = true return PseudoWindow end end, ChildDiscarded = function(thisWidget: Root, thisChild: Widget) if thisChild.type ~= "Window" and thisChild.type ~= "Tooltip" and thisChild.type ~= "MenuBar" then NumNonWindowChildren -= 1 if NumNonWindowChildren == 0 then local Root = thisWidget.Instance :: any local PseudoWindowScreenGui = Root.PseudoWindowScreenGui :: any local PseudoWindow: Frame = PseudoWindowScreenGui.PseudoWindow PseudoWindow.Visible = false end end end, } :: WidgetClass) end, Window = function(ImGui: Internal, widgets: WidgetUtility) local function relocateTooltips() if ImGui._rootInstance == nil then return end local PopupScreenGui = ImGui._rootInstance:FindFirstChild("PopupScreenGui") local TooltipContainer = PopupScreenGui.TooltipContainer local mouseLocation: Vector2 = widgets.getMouseLocation() local newPosition: Vector2 = widgets.findBestWindowPosForPopup(mouseLocation, TooltipContainer.AbsoluteSize, ImGui._config.DisplaySafeAreaPadding, PopupScreenGui.AbsoluteSize) TooltipContainer.Position = UDim2.fromOffset(newPosition.X, newPosition.Y) end widgets.registerEvent("InputChanged", function() if not ImGui._started then return end relocateTooltips() end) --stylua: ignore ImGui.WidgetConstructor("Tooltip", { hasState = false, hasChildren = false, Args = { ["Text"] = 1, }, Events = {}, Generate = function(thisWidget: Tooltip) thisWidget.parentWidget = ImGui._rootWidget -- only allow root as parent local Tooltip: Frame = Instance.new("Frame") Tooltip.Name = "ImGui_Tooltip" Tooltip.Size = UDim2.new(ImGui._config.ContentWidth, UDim.new(0, 0)) Tooltip.AutomaticSize = Enum.AutomaticSize.Y Tooltip.BorderSizePixel = 0 Tooltip.BackgroundTransparency = 1 Tooltip.ZIndex = thisWidget.ZIndex + 1 Tooltip.LayoutOrder = thisWidget.ZIndex + 1 local TooltipText: TextLabel = Instance.new("TextLabel") TooltipText.Name = "TooltipText" TooltipText.Size = UDim2.fromOffset(0, 0) TooltipText.AutomaticSize = Enum.AutomaticSize.XY TooltipText.BackgroundColor3 = ImGui._config.PopupBgColor TooltipText.BackgroundTransparency = ImGui._config.PopupBgTransparency TooltipText.BorderSizePixel = ImGui._config.PopupBorderSize TooltipText.TextWrapped = ImGui._config.TextWrapped widgets.applyTextStyle(TooltipText) widgets.UIStroke(TooltipText, ImGui._config.WindowBorderSize, ImGui._config.BorderActiveColor, ImGui._config.BorderActiveTransparency) widgets.UIPadding(TooltipText, ImGui._config.WindowPadding) if ImGui._config.PopupRounding > 0 then widgets.UICorner(TooltipText, ImGui._config.PopupRounding) end TooltipText.Parent = Tooltip return Tooltip end, Update = function(thisWidget: Tooltip) local Tooltip = thisWidget.Instance :: Frame local TooltipText: TextLabel = Tooltip.TooltipText if thisWidget.arguments.Text == nil then error("ImGui.Text Text Argument is required", 5) end TooltipText.Text = thisWidget.arguments.Text relocateTooltips() end, Discard = function(thisWidget: Tooltip) thisWidget.Instance:Destroy() end, } :: WidgetClass) local windowDisplayOrder: number = 0 -- incremental count which is used for determining focused windows ZIndex local dragWindow: Window? -- window being dragged, may be nil local isDragging: boolean = false local moveDeltaCursorPosition: Vector2 -- cursor offset from drag origin (top left of window) local resizeWindow: Window? -- window being resized, may be nil local isResizing = false local isInsideResize = false -- is cursor inside of the focused window resize outer padding local isInsideWindow = false -- is cursor inside of the focused window local resizeFromTopBottom: Enum.TopBottom = Enum.TopBottom.Top local resizeFromLeftRight: Enum.LeftRight = Enum.LeftRight.Left local lastCursorPosition: Vector2 local focusedWindow: Window? -- window with focus, may be nil local anyFocusedWindow: boolean = false -- is there any focused window? local windowWidgets: { [ID]: Window } = {} -- array of widget objects of type window local function quickSwapWindows() -- ctrl + tab swapping functionality if ImGui._config.UseScreenGUIs == false then return end local lowest: number = 0xFFFF local lowestWidget: Window for _, widget: Window in windowWidgets do if widget.state.isOpened.value and not widget.arguments.NoNav then if widget.Instance:IsA("ScreenGui") then local value: number = widget.Instance.DisplayOrder if value < lowest then lowest = value lowestWidget = widget end end end end if not lowestWidget then return end if lowestWidget.state.isUncollapsed.value == false then lowestWidget.state.isUncollapsed:set(true) end ImGui.SetFocusedWindow(lowestWidget) end local function fitSizeToWindowBounds(thisWidget: Window, intentedSize: Vector2): Vector2 local windowSize: Vector2 = Vector2.new(thisWidget.state.position.value.X, thisWidget.state.position.value.Y) local minWindowSize: number = (ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y) * 2 local usableSize: Vector2 = widgets.getScreenSizeForWindow(thisWidget) local safeAreaPadding: Vector2 = Vector2.new(ImGui._config.WindowBorderSize + ImGui._config.DisplaySafeAreaPadding.X, ImGui._config.WindowBorderSize + ImGui._config.DisplaySafeAreaPadding.Y) local maxWindowSize: Vector2 = (usableSize - windowSize - safeAreaPadding) return Vector2.new(math.clamp(intentedSize.X, minWindowSize, math.max(maxWindowSize.X, minWindowSize)), math.clamp(intentedSize.Y, minWindowSize, math.max(maxWindowSize.Y, minWindowSize))) end local function fitPositionToWindowBounds(thisWidget: Window, intendedPosition: Vector2): Vector2 local thisWidgetInstance = thisWidget.Instance local usableSize: Vector2 = widgets.getScreenSizeForWindow(thisWidget) local safeAreaPadding: Vector2 = Vector2.new(ImGui._config.WindowBorderSize + ImGui._config.DisplaySafeAreaPadding.X, ImGui._config.WindowBorderSize + ImGui._config.DisplaySafeAreaPadding.Y) return Vector2.new( math.clamp(intendedPosition.X, safeAreaPadding.X, math.max(safeAreaPadding.X, usableSize.X - thisWidgetInstance.WindowButton.AbsoluteSize.X - safeAreaPadding.X)), math.clamp(intendedPosition.Y, safeAreaPadding.Y, math.max(safeAreaPadding.Y, usableSize.Y - thisWidgetInstance.WindowButton.AbsoluteSize.Y - safeAreaPadding.Y)) ) end ImGui.SetFocusedWindow = function(thisWidget: Window?) if focusedWindow == thisWidget then return end if anyFocusedWindow and focusedWindow ~= nil then if windowWidgets[focusedWindow.ID] then local Window = focusedWindow.Instance :: Frame local WindowButton = Window.WindowButton :: TextButton local Content = WindowButton.Content :: Frame local TitleBar: Frame = Content.TitleBar -- update appearance to unfocus if focusedWindow.state.isUncollapsed.value then TitleBar.BackgroundColor3 = ImGui._config.TitleBgColor TitleBar.BackgroundTransparency = ImGui._config.TitleBgTransparency else TitleBar.BackgroundColor3 = ImGui._config.TitleBgCollapsedColor TitleBar.BackgroundTransparency = ImGui._config.TitleBgCollapsedTransparency end WindowButton.UIStroke.Color = ImGui._config.BorderColor end anyFocusedWindow = false focusedWindow = nil end if thisWidget ~= nil then -- update appearance to focus anyFocusedWindow = true focusedWindow = thisWidget local Window = thisWidget.Instance :: Frame local WindowButton = Window.WindowButton :: TextButton local Content = WindowButton.Content :: Frame local TitleBar: Frame = Content.TitleBar TitleBar.BackgroundColor3 = ImGui._config.TitleBgActiveColor TitleBar.BackgroundTransparency = ImGui._config.TitleBgActiveTransparency WindowButton.UIStroke.Color = ImGui._config.BorderActiveColor windowDisplayOrder += 1 if thisWidget.usesScreenGuis then Window.DisplayOrder = windowDisplayOrder + ImGui._config.DisplayOrderOffset else Window.ZIndex = windowDisplayOrder + ImGui._config.DisplayOrderOffset end if thisWidget.state.isUncollapsed.value == false then thisWidget.state.isUncollapsed:set(true) end local firstSelectedObject: GuiObject? = widgets.GuiService.SelectedObject if firstSelectedObject then if TitleBar.Visible then widgets.GuiService:Select(TitleBar) else widgets.GuiService:Select(thisWidget.ChildContainer) end end end end widgets.registerEvent("InputBegan", function(input: InputObject) if not ImGui._started then return end if input.UserInputType == Enum.UserInputType.MouseButton1 then local inWindow: boolean = false local position: Vector2 = widgets.getMouseLocation() for _, window: Window in windowWidgets do local Window = window.Instance :: Instance if not Window then continue end local WindowButton = Window.WindowButton :: TextButton local ResizeBorder: TextButton = WindowButton.ResizeBorder if ResizeBorder and widgets.isPosInsideRect(position, ResizeBorder.AbsolutePosition, ResizeBorder.AbsolutePosition + ResizeBorder.AbsoluteSize) then inWindow = true break end end if not inWindow then ImGui.SetFocusedWindow(nil) end end if input.KeyCode == Enum.KeyCode.Tab and (widgets.UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or widgets.UserInputService:IsKeyDown(Enum.KeyCode.RightControl)) then quickSwapWindows() end if input.UserInputType == Enum.UserInputType.MouseButton1 and isInsideResize and not isInsideWindow and anyFocusedWindow and focusedWindow then local midWindow: Vector2 = focusedWindow.state.position.value + (focusedWindow.state.size.value / 2) local cursorPosition: Vector2 = widgets.getMouseLocation() - midWindow -- check which axis its closest to, then check which side is closest with math.sign if math.abs(cursorPosition.X) * focusedWindow.state.size.value.Y >= math.abs(cursorPosition.Y) * focusedWindow.state.size.value.X then resizeFromTopBottom = Enum.TopBottom.Center resizeFromLeftRight = if math.sign(cursorPosition.X) == -1 then Enum.LeftRight.Left else Enum.LeftRight.Right else resizeFromLeftRight = Enum.LeftRight.Center resizeFromTopBottom = if math.sign(cursorPosition.Y) == -1 then Enum.TopBottom.Top else Enum.TopBottom.Bottom end isResizing = true resizeWindow = focusedWindow end end) widgets.registerEvent("TouchTapInWorld", function(_, gameProcessedEvent: boolean) if not ImGui._started then return end if not gameProcessedEvent then ImGui.SetFocusedWindow(nil) end end) widgets.registerEvent("InputChanged", function(input: InputObject) if not ImGui._started then return end if isDragging and dragWindow then local mouseLocation: Vector2 if input.UserInputType == Enum.UserInputType.Touch then local location: Vector3 = input.Position mouseLocation = Vector2.new(location.X, location.Y) else mouseLocation = widgets.getMouseLocation() end local Window = dragWindow.Instance :: Frame local dragInstance: TextButton = Window.WindowButton local intendedPosition: Vector2 = mouseLocation - moveDeltaCursorPosition local newPos: Vector2 = fitPositionToWindowBounds(dragWindow, intendedPosition) -- state shouldnt be used like this, but calling :set would run the entire UpdateState function for the window, which is slow. dragInstance.Position = UDim2.fromOffset(newPos.X, newPos.Y) dragWindow.state.position.value = newPos end if isResizing and resizeWindow and resizeWindow.arguments.NoResize ~= true then local Window = resizeWindow.Instance :: Frame local resizeInstance: TextButton = Window.WindowButton local windowPosition: Vector2 = Vector2.new(resizeInstance.Position.X.Offset, resizeInstance.Position.Y.Offset) local windowSize: Vector2 = Vector2.new(resizeInstance.Size.X.Offset, resizeInstance.Size.Y.Offset) local mouseDelta: Vector2 | Vector3 if input.UserInputType == Enum.UserInputType.Touch then mouseDelta = input.Delta else mouseDelta = widgets.getMouseLocation() - lastCursorPosition end local intendedPosition: Vector2 = windowPosition + Vector2.new(if resizeFromLeftRight == Enum.LeftRight.Left then mouseDelta.X else 0, if resizeFromTopBottom == Enum.TopBottom.Top then mouseDelta.Y else 0) local intendedSize: Vector2 = windowSize + Vector2.new( if resizeFromLeftRight == Enum.LeftRight.Left then -mouseDelta.X elseif resizeFromLeftRight == Enum.LeftRight.Right then mouseDelta.X else 0, if resizeFromTopBottom == Enum.TopBottom.Top then -mouseDelta.Y elseif resizeFromTopBottom == Enum.TopBottom.Bottom then mouseDelta.Y else 0 ) local newSize: Vector2 = fitSizeToWindowBounds(resizeWindow, intendedSize) local newPosition: Vector2 = fitPositionToWindowBounds(resizeWindow, intendedPosition) resizeInstance.Size = UDim2.fromOffset(newSize.X, newSize.Y) resizeWindow.state.size.value = newSize resizeInstance.Position = UDim2.fromOffset(newPosition.X, newPosition.Y) resizeWindow.state.position.value = newPosition end lastCursorPosition = widgets.getMouseLocation() end) widgets.registerEvent("InputEnded", function(input, _) if not ImGui._started then return end if (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) and isDragging and dragWindow then local Window = dragWindow.Instance :: Frame local dragInstance: TextButton = Window.WindowButton isDragging = false dragWindow.state.position:set(Vector2.new(dragInstance.Position.X.Offset, dragInstance.Position.Y.Offset)) end if (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) and isResizing and resizeWindow then local Window = resizeWindow.Instance :: Instance isResizing = false resizeWindow.state.size:set(Window.WindowButton.AbsoluteSize) end if input.KeyCode == Enum.KeyCode.ButtonX then quickSwapWindows() end end) --stylua: ignore ImGui.WidgetConstructor("Window", { hasState = true, hasChildren = true, Args = { ["Title"] = 1, ["NoTitleBar"] = 2, ["NoBackground"] = 3, ["NoCollapse"] = 4, ["NoClose"] = 5, ["NoMove"] = 6, ["NoScrollbar"] = 7, ["NoResize"] = 8, ["NoNav"] = 9, ["NoMenu"] = 10, }, Events = { ["closed"] = { ["Init"] = function(_thisWidget: Window) end, ["Get"] = function(thisWidget: Window) return thisWidget.lastClosedTick == ImGui._cycleTick end, }, ["opened"] = { ["Init"] = function(_thisWidget: Window) end, ["Get"] = function(thisWidget: Window) return thisWidget.lastOpenedTick == ImGui._cycleTick end, }, ["collapsed"] = { ["Init"] = function(_thisWidget: Window) end, ["Get"] = function(thisWidget: Window) return thisWidget.lastCollapsedTick == ImGui._cycleTick end, }, ["uncollapsed"] = { ["Init"] = function(_thisWidget: Window) end, ["Get"] = function(thisWidget: Window) return thisWidget.lastUncollapsedTick == ImGui._cycleTick end, }, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) local Window = thisWidget.Instance :: Frame return Window.WindowButton end), }, Generate = function(thisWidget: Window) thisWidget.parentWidget = ImGui._rootWidget -- only allow root as parent thisWidget.usesScreenGuis = ImGui._config.UseScreenGUIs windowWidgets[thisWidget.ID] = thisWidget local Window if thisWidget.usesScreenGuis then Window = Instance.new("ScreenGui") Window.ResetOnSpawn = false Window.ZIndexBehavior = Enum.ZIndexBehavior.Sibling Window.DisplayOrder = ImGui._config.DisplayOrderOffset Window.IgnoreGuiInset = ImGui._config.IgnoreGuiInset else Window = Instance.new("Frame") Window.AnchorPoint = Vector2.new(0.5, 0.5) Window.Position = UDim2.new(0.5, 0, 0.5, 0) Window.Size = UDim2.new(1, 0, 1, 0) Window.BackgroundTransparency = 1 Window.ZIndex = ImGui._config.DisplayOrderOffset end Window.Name = "ImGui_Window" local WindowButton: TextButton = Instance.new("TextButton") WindowButton.Name = "WindowButton" WindowButton.Size = UDim2.fromOffset(0, 0) WindowButton.BackgroundTransparency = 1 WindowButton.BorderSizePixel = 0 WindowButton.Text = "" WindowButton.ClipsDescendants = false WindowButton.AutoButtonColor = false WindowButton.Selectable = false WindowButton.SelectionImageObject = ImGui.SelectionImageObject WindowButton.SelectionGroup = true WindowButton.SelectionBehaviorUp = Enum.SelectionBehavior.Stop WindowButton.SelectionBehaviorDown = Enum.SelectionBehavior.Stop WindowButton.SelectionBehaviorLeft = Enum.SelectionBehavior.Stop WindowButton.SelectionBehaviorRight = Enum.SelectionBehavior.Stop widgets.UIStroke(WindowButton, ImGui._config.WindowBorderSize, ImGui._config.BorderColor, ImGui._config.BorderTransparency) WindowButton.Parent = Window widgets.applyInputBegan(WindowButton, function(input: InputObject) if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Keyboard then return end if thisWidget.state.isUncollapsed.value then ImGui.SetFocusedWindow(thisWidget) end if not thisWidget.arguments.NoMove and input.UserInputType == Enum.UserInputType.MouseButton1 then dragWindow = thisWidget isDragging = true moveDeltaCursorPosition = widgets.getMouseLocation() - thisWidget.state.position.value end end) local Content: Frame = Instance.new("Frame") Content.Name = "Content" Content.AnchorPoint = Vector2.new(0.5, 0.5) Content.Position = UDim2.fromScale(0.5, 0.5) Content.Size = UDim2.fromScale(1, 1) Content.BackgroundTransparency = 1 Content.ClipsDescendants = true Content.Parent = WindowButton local UIListLayout: UIListLayout = widgets.UIListLayout(Content, Enum.FillDirection.Vertical, UDim.new(0, 0)) UIListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Top local ChildContainer: ScrollingFrame = Instance.new("ScrollingFrame") ChildContainer.Name = "WindowContainer" ChildContainer.Size = UDim2.fromScale(1, 1) ChildContainer.BackgroundColor3 = ImGui._config.WindowBgColor ChildContainer.BackgroundTransparency = ImGui._config.WindowBgTransparency ChildContainer.BorderSizePixel = 0 ChildContainer.AutomaticCanvasSize = Enum.AutomaticSize.Y ChildContainer.ScrollBarImageTransparency = ImGui._config.ScrollbarGrabTransparency ChildContainer.ScrollBarImageColor3 = ImGui._config.ScrollbarGrabColor ChildContainer.CanvasSize = UDim2.fromScale(0, 0) ChildContainer.VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar ChildContainer.LayoutOrder = thisWidget.ZIndex + 0xFFFF ChildContainer.ClipsDescendants = true widgets.UIPadding(ChildContainer, ImGui._config.WindowPadding) ChildContainer.Parent = Content local UIFlexItem: UIFlexItem = Instance.new("UIFlexItem") UIFlexItem.FlexMode = Enum.UIFlexMode.Fill UIFlexItem.ItemLineAlignment = Enum.ItemLineAlignment.End UIFlexItem.Parent = ChildContainer ChildContainer:GetPropertyChangedSignal("CanvasPosition"):Connect(function() -- "wrong" use of state here, for optimization thisWidget.state.scrollDistance.value = ChildContainer.CanvasPosition.Y end) widgets.applyInputBegan(ChildContainer, function(input: InputObject) if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Keyboard then return end if thisWidget.state.isUncollapsed.value then ImGui.SetFocusedWindow(thisWidget) end end) local TerminatingFrame: Frame = Instance.new("Frame") TerminatingFrame.Name = "TerminatingFrame" TerminatingFrame.Size = UDim2.fromOffset(0, ImGui._config.WindowPadding.Y + ImGui._config.FramePadding.Y) TerminatingFrame.BackgroundTransparency = 1 TerminatingFrame.BorderSizePixel = 0 TerminatingFrame.LayoutOrder = 0x7FFFFFF0 local ChildContainerUIListLayout: UIListLayout = widgets.UIListLayout(ChildContainer, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) ChildContainerUIListLayout.VerticalAlignment = Enum.VerticalAlignment.Top TerminatingFrame.Parent = ChildContainer local TitleBar: Frame = Instance.new("Frame") TitleBar.Name = "TitleBar" TitleBar.AutomaticSize = Enum.AutomaticSize.Y TitleBar.Size = UDim2.fromScale(1, 0) TitleBar.BorderSizePixel = 0 TitleBar.ClipsDescendants = true TitleBar.Parent = Content widgets.UIPadding(TitleBar, Vector2.new(ImGui._config.FramePadding.X)) widgets.UIListLayout(TitleBar, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)).VerticalAlignment = Enum.VerticalAlignment.Center widgets.applyInputBegan(TitleBar, function(input: InputObject) if input.UserInputType == Enum.UserInputType.Touch then if not thisWidget.arguments.NoMove then dragWindow = thisWidget isDragging = true local location: Vector3 = input.Position moveDeltaCursorPosition = Vector2.new(location.X, location.Y) - thisWidget.state.position.value end end end) local TitleButtonSize: number = ImGui._config.TextSize + ((ImGui._config.FramePadding.Y - 1) * 2) local CollapseButton: TextButton = Instance.new("TextButton") CollapseButton.Name = "CollapseButton" CollapseButton.AnchorPoint = Vector2.new(0, 0.5) CollapseButton.Size = UDim2.fromOffset(TitleButtonSize, TitleButtonSize) CollapseButton.Position = UDim2.new(0, 0, 0.5, 0) CollapseButton.AutomaticSize = Enum.AutomaticSize.None CollapseButton.BackgroundTransparency = 1 CollapseButton.BorderSizePixel = 0 CollapseButton.AutoButtonColor = false CollapseButton.Text = "" widgets.UICorner(CollapseButton) CollapseButton.Parent = TitleBar widgets.applyButtonClick(CollapseButton, function() thisWidget.state.isUncollapsed:set(not thisWidget.state.isUncollapsed.value) end) widgets.applyInteractionHighlights("Background", CollapseButton, CollapseButton, { Color = ImGui._config.ButtonColor, Transparency = 1, HoveredColor = ImGui._config.ButtonHoveredColor, HoveredTransparency = ImGui._config.ButtonHoveredTransparency, ActiveColor = ImGui._config.ButtonActiveColor, ActiveTransparency = ImGui._config.ButtonActiveTransparency, }) local CollapseArrow: ImageLabel = Instance.new("ImageLabel") CollapseArrow.Name = "Arrow" CollapseArrow.AnchorPoint = Vector2.new(0.5, 0.5) CollapseArrow.Size = UDim2.fromOffset(math.floor(0.7 * TitleButtonSize), math.floor(0.7 * TitleButtonSize)) CollapseArrow.Position = UDim2.fromScale(0.5, 0.5) CollapseArrow.BackgroundTransparency = 1 CollapseArrow.BorderSizePixel = 0 CollapseArrow.Image = widgets.ICONS.MULTIPLICATION_SIGN CollapseArrow.ImageColor3 = ImGui._config.TextColor CollapseArrow.ImageTransparency = ImGui._config.TextTransparency CollapseArrow.Parent = CollapseButton local CloseButton: TextButton = Instance.new("TextButton") CloseButton.Name = "CloseButton" CloseButton.AnchorPoint = Vector2.new(1, 0.5) CloseButton.Size = UDim2.fromOffset(TitleButtonSize, TitleButtonSize) CloseButton.Position = UDim2.new(1, 0, 0.5, 0) CloseButton.AutomaticSize = Enum.AutomaticSize.None CloseButton.BackgroundTransparency = 1 CloseButton.BorderSizePixel = 0 CloseButton.Text = "" CloseButton.LayoutOrder = 2 CloseButton.AutoButtonColor = false widgets.UICorner(CloseButton) widgets.applyButtonClick(CloseButton, function() thisWidget.state.isOpened:set(false) end) widgets.applyInteractionHighlights("Background", CloseButton, CloseButton, { Color = ImGui._config.ButtonColor, Transparency = 1, HoveredColor = ImGui._config.ButtonHoveredColor, HoveredTransparency = ImGui._config.ButtonHoveredTransparency, ActiveColor = ImGui._config.ButtonActiveColor, ActiveTransparency = ImGui._config.ButtonActiveTransparency, }) CloseButton.Parent = TitleBar local CloseIcon: ImageLabel = Instance.new("ImageLabel") CloseIcon.Name = "Icon" CloseIcon.AnchorPoint = Vector2.new(0.5, 0.5) CloseIcon.Size = UDim2.fromOffset(math.floor(0.7 * TitleButtonSize), math.floor(0.7 * TitleButtonSize)) CloseIcon.Position = UDim2.fromScale(0.5, 0.5) CloseIcon.BackgroundTransparency = 1 CloseIcon.BorderSizePixel = 0 CloseIcon.Image = widgets.ICONS.MULTIPLICATION_SIGN CloseIcon.ImageColor3 = ImGui._config.TextColor CloseIcon.ImageTransparency = ImGui._config.TextTransparency CloseIcon.Parent = CloseButton -- allowing fractional titlebar title location dosent seem useful, as opposed to Enum.LeftRight. local Title: TextLabel = Instance.new("TextLabel") Title.Name = "Title" Title.AutomaticSize = Enum.AutomaticSize.XY Title.BorderSizePixel = 0 Title.BackgroundTransparency = 1 Title.LayoutOrder = 1 Title.ClipsDescendants = true widgets.UIPadding(Title, Vector2.new(0, ImGui._config.FramePadding.Y)) widgets.applyTextStyle(Title) Title.TextXAlignment = Enum.TextXAlignment[ImGui._config.WindowTitleAlign.Name] :: Enum.TextXAlignment local TitleFlexItem: UIFlexItem = Instance.new("UIFlexItem") TitleFlexItem.FlexMode = Enum.UIFlexMode.Fill TitleFlexItem.ItemLineAlignment = Enum.ItemLineAlignment.Center TitleFlexItem.Parent = Title Title.Parent = TitleBar local ResizeButtonSize: number = ImGui._config.TextSize + ImGui._config.FramePadding.X local ResizeGrip = Instance.new("ImageButton") ResizeGrip.Name = "ResizeGrip" ResizeGrip.AnchorPoint = Vector2.one ResizeGrip.Size = UDim2.fromOffset(ResizeButtonSize, ResizeButtonSize) ResizeGrip.Position = UDim2.fromScale(1, 1) ResizeGrip.Rotation = 90 ResizeGrip.AutoButtonColor = false ResizeGrip.BorderSizePixel = 0 ResizeGrip.BackgroundTransparency = 1 ResizeGrip.Image = widgets.ICONS.BOTTOM_RIGHT_CORNER ResizeGrip.ImageColor3 = ImGui._config.ButtonColor ResizeGrip.ImageTransparency = ImGui._config.ButtonTransparency ResizeGrip.Selectable = false ResizeGrip.ZIndex = 3 ResizeGrip.Parent = WindowButton widgets.applyInteractionHighlights("Image", ResizeGrip, ResizeGrip, { Color = ImGui._config.ButtonColor, Transparency = ImGui._config.ButtonTransparency, HoveredColor = ImGui._config.ButtonHoveredColor, HoveredTransparency = ImGui._config.ButtonHoveredTransparency, ActiveColor = ImGui._config.ButtonActiveColor, ActiveTransparency = ImGui._config.ButtonActiveTransparency, }) widgets.applyButtonDown(ResizeGrip, function() if not anyFocusedWindow or not (focusedWindow == thisWidget) then ImGui.SetFocusedWindow(thisWidget) -- mitigating wrong focus when clicking on buttons inside of a window without clicking the window itself end isResizing = true resizeFromTopBottom = Enum.TopBottom.Bottom resizeFromLeftRight = Enum.LeftRight.Right resizeWindow = thisWidget end) local ResizeBorder: Frame = Instance.new("Frame") ResizeBorder.Name = "ResizeBorder" ResizeBorder.Size = UDim2.new(1, ImGui._config.WindowResizePadding.X * 2, 1, ImGui._config.WindowResizePadding.Y * 2) ResizeBorder.Position = UDim2.fromOffset(-ImGui._config.WindowResizePadding.X, -ImGui._config.WindowResizePadding.Y) ResizeBorder.BackgroundTransparency = 1 ResizeBorder.BorderSizePixel = 0 ResizeBorder.Active = true ResizeBorder.Selectable = false ResizeBorder.ClipsDescendants = false ResizeBorder.Parent = WindowButton widgets.applyMouseEnter(ResizeBorder, function() if focusedWindow == thisWidget then isInsideResize = true end end) widgets.applyMouseLeave(ResizeBorder, function() if focusedWindow == thisWidget then isInsideResize = false end end) widgets.applyMouseEnter(WindowButton, function() if focusedWindow == thisWidget then isInsideWindow = true end end) widgets.applyMouseLeave(WindowButton, function() if focusedWindow == thisWidget then isInsideWindow = false end end) thisWidget.ChildContainer = ChildContainer return Window end, Update = function(thisWidget: Window) local Window = thisWidget.Instance :: GuiObject local ChildContainer = thisWidget.ChildContainer :: ScrollingFrame local WindowButton = Window.WindowButton :: TextButton local Content = WindowButton.Content :: Frame local TitleBar = Content.TitleBar :: Frame local Title: TextLabel = TitleBar.Title local MenuBar: Frame? = Content:FindFirstChild("MenuBar") local ResizeGrip: TextButton = WindowButton.ResizeGrip if thisWidget.arguments.NoResize ~= true then ResizeGrip.Visible = true else ResizeGrip.Visible = false end if thisWidget.arguments.NoScrollbar then ChildContainer.ScrollBarThickness = 0 else ChildContainer.ScrollBarThickness = ImGui._config.ScrollbarSize end if thisWidget.arguments.NoTitleBar then TitleBar.Visible = false else TitleBar.Visible = true end if MenuBar then if thisWidget.arguments.NoMenu then MenuBar.Visible = false else MenuBar.Visible = true end end if thisWidget.arguments.NoBackground then ChildContainer.BackgroundTransparency = 1 else ChildContainer.BackgroundTransparency = ImGui._config.WindowBgTransparency end -- TitleBar buttons if thisWidget.arguments.NoCollapse then TitleBar.CollapseButton.Visible = false else TitleBar.CollapseButton.Visible = true end if thisWidget.arguments.NoClose then TitleBar.CloseButton.Visible = false else TitleBar.CloseButton.Visible = true end Title.Text = thisWidget.arguments.Title or "" end, Discard = function(thisWidget: Window) if focusedWindow == thisWidget then focusedWindow = nil anyFocusedWindow = false end if dragWindow == thisWidget then dragWindow = nil isDragging = false end if resizeWindow == thisWidget then resizeWindow = nil isResizing = false end windowWidgets[thisWidget.ID] = nil thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, ChildAdded = function(thisWidget: Window, thisChid: Widget) local Window = thisWidget.Instance :: Frame local WindowButton = Window.WindowButton :: TextButton local Content = WindowButton.Content :: Frame if thisChid.type == "MenuBar" then local ChildContainer = thisWidget.ChildContainer :: ScrollingFrame thisChid.Instance.ZIndex = ChildContainer.ZIndex + 1 thisChid.Instance.LayoutOrder = ChildContainer.LayoutOrder - 1 return Content end return thisWidget.ChildContainer end, UpdateState = function(thisWidget: Window) local stateSize: Vector2 = thisWidget.state.size.value local statePosition: Vector2 = thisWidget.state.position.value local stateIsUncollapsed: boolean = thisWidget.state.isUncollapsed.value local stateIsOpened: boolean = thisWidget.state.isOpened.value local stateScrollDistance: number = thisWidget.state.scrollDistance.value local Window = thisWidget.Instance :: Frame local ChildContainer = thisWidget.ChildContainer :: ScrollingFrame local WindowButton = Window.WindowButton :: TextButton local Content = WindowButton.Content :: Frame local TitleBar = Content.TitleBar :: Frame local MenuBar: Frame? = Content:FindFirstChild("MenuBar") local ResizeGrip: TextButton = WindowButton.ResizeGrip WindowButton.Size = UDim2.fromOffset(stateSize.X, stateSize.Y) WindowButton.Position = UDim2.fromOffset(statePosition.X, statePosition.Y) if stateIsOpened then if thisWidget.usesScreenGuis then Window.Enabled = true WindowButton.Visible = true else Window.Visible = true WindowButton.Visible = true end thisWidget.lastOpenedTick = ImGui._cycleTick + 1 else if thisWidget.usesScreenGuis then Window.Enabled = false WindowButton.Visible = false else Window.Visible = false WindowButton.Visible = false end thisWidget.lastClosedTick = ImGui._cycleTick + 1 end if stateIsUncollapsed then TitleBar.CollapseButton.Arrow.Image = widgets.ICONS.DOWN_POINTING_TRIANGLE if MenuBar then MenuBar.Visible = not thisWidget.arguments.NoMenu end ChildContainer.Visible = true if thisWidget.arguments.NoResize ~= true then ResizeGrip.Visible = true end WindowButton.AutomaticSize = Enum.AutomaticSize.None thisWidget.lastUncollapsedTick = ImGui._cycleTick + 1 else local collapsedHeight: number = TitleBar.AbsoluteSize.Y -- ImGui._config.TextSize + ImGui._config.FramePadding.Y * 2 TitleBar.CollapseButton.Arrow.Image = widgets.ICONS.RIGHT_POINTING_TRIANGLE if MenuBar then MenuBar.Visible = false end ChildContainer.Visible = false ResizeGrip.Visible = false WindowButton.Size = UDim2.fromOffset(stateSize.X, collapsedHeight) thisWidget.lastCollapsedTick = ImGui._cycleTick + 1 end if stateIsOpened and stateIsUncollapsed then ImGui.SetFocusedWindow(thisWidget) else TitleBar.BackgroundColor3 = ImGui._config.TitleBgCollapsedColor TitleBar.BackgroundTransparency = ImGui._config.TitleBgCollapsedTransparency WindowButton.UIStroke.Color = ImGui._config.BorderColor ImGui.SetFocusedWindow(nil) end -- cant update canvasPosition in this cycle because scrollingframe isint ready to be changed if stateScrollDistance and stateScrollDistance ~= 0 then local callbackIndex: number = #ImGui._postCycleCallbacks + 1 local desiredCycleTick: number = ImGui._cycleTick + 1 ImGui._postCycleCallbacks[callbackIndex] = function() if ImGui._cycleTick >= desiredCycleTick then if thisWidget.lastCycleTick ~= -1 then ChildContainer.CanvasPosition = Vector2.new(0, stateScrollDistance) end ImGui._postCycleCallbacks[callbackIndex] = nil end end end end, GenerateState = function(thisWidget: Window) if thisWidget.state.size == nil then thisWidget.state.size = ImGui._widgetState(thisWidget, "size", Vector2.new(400, 300)) end if thisWidget.state.position == nil then thisWidget.state.position = ImGui._widgetState(thisWidget, "position", if anyFocusedWindow and focusedWindow then focusedWindow.state.position.value + Vector2.new(15, 45) else Vector2.new(150, 250)) end thisWidget.state.position.value = fitPositionToWindowBounds(thisWidget, thisWidget.state.position.value) thisWidget.state.size.value = fitSizeToWindowBounds(thisWidget, thisWidget.state.size.value) if thisWidget.state.isUncollapsed == nil then thisWidget.state.isUncollapsed = ImGui._widgetState(thisWidget, "isUncollapsed", true) end if thisWidget.state.isOpened == nil then thisWidget.state.isOpened = ImGui._widgetState(thisWidget, "isOpened", true) end if thisWidget.state.scrollDistance == nil then thisWidget.state.scrollDistance = ImGui._widgetState(thisWidget, "scrollDistance", 0) end end, } :: WidgetClass) end, Menu = function(ImGui: Internal, widgets: WidgetUtility) local AnyMenuOpen: boolean = false local ActiveMenu: Menu? = nil local MenuStack: { Menu } = {} local function EmptyMenuStack(menuIndex: number?) for index = #MenuStack, menuIndex and menuIndex + 1 or 1, -1 do local widget: Menu = MenuStack[index] widget.state.isOpened:set(false) widget.Instance.BackgroundColor3 = ImGui._config.HeaderColor widget.Instance.BackgroundTransparency = 1 table.remove(MenuStack, index) end if #MenuStack == 0 then AnyMenuOpen = false ActiveMenu = nil end end local function UpdateChildContainerTransform(thisWidget: Menu) local submenu: boolean = thisWidget.parentWidget.type == "Menu" local Menu = thisWidget.Instance :: Frame local ChildContainer = thisWidget.ChildContainer :: ScrollingFrame ChildContainer.Size = UDim2.fromOffset(Menu.AbsoluteSize.X, 0) if ChildContainer.Parent == nil then return end local menuPosition: Vector2 = Menu.AbsolutePosition - widgets.GuiOffset local menuSize: Vector2 = Menu.AbsoluteSize local containerSize: Vector2 = ChildContainer.AbsoluteSize local borderSize: number = ImGui._config.PopupBorderSize local screenSize: Vector2 = ChildContainer.Parent.AbsoluteSize local x: number = menuPosition.X local y: number local anchor: Vector2 = Vector2.zero if submenu then if menuPosition.X + containerSize.X > screenSize.X then anchor = Vector2.xAxis else x = menuPosition.X + menuSize.X end end if menuPosition.Y + containerSize.Y > screenSize.Y then -- too low. y = menuPosition.Y - borderSize + (submenu and menuSize.Y or 0) anchor += Vector2.yAxis else y = menuPosition.Y + borderSize + (submenu and 0 or menuSize.Y) end ChildContainer.Position = UDim2.fromOffset(x, y) ChildContainer.AnchorPoint = anchor end widgets.registerEvent("InputBegan", function(inputObject: InputObject) if not ImGui._started then return end if inputObject.UserInputType ~= Enum.UserInputType.MouseButton1 and inputObject.UserInputType ~= Enum.UserInputType.MouseButton2 then return end if AnyMenuOpen == false then return end if ActiveMenu == nil then return end -- this only checks if we clicked outside all the menus. If we clicked in any menu, then the hover function handles this. local isInMenu: boolean = false local MouseLocation: Vector2 = widgets.getMouseLocation() for _, menu: Menu in MenuStack do for _, container: GuiObject in { menu.ChildContainer, menu.Instance } do local rectMin: Vector2 = container.AbsolutePosition - widgets.GuiOffset local rectMax: Vector2 = rectMin + container.AbsoluteSize if widgets.isPosInsideRect(MouseLocation, rectMin, rectMax) then isInMenu = true break end end if isInMenu then break end end if not isInMenu then EmptyMenuStack() end end) --stylua: ignore ImGui.WidgetConstructor("MenuBar", { hasState = false, hasChildren = true, Args = {}, Events = {}, Generate = function(thisWidget: MenuBar) local MenuBar: Frame = Instance.new("Frame") MenuBar.Name = "MenuBar" MenuBar.Size = UDim2.fromScale(1, 0) MenuBar.AutomaticSize = Enum.AutomaticSize.Y MenuBar.BackgroundColor3 = ImGui._config.MenubarBgColor MenuBar.BackgroundTransparency = ImGui._config.MenubarBgTransparency MenuBar.BorderSizePixel = 0 MenuBar.LayoutOrder = thisWidget.ZIndex MenuBar.ClipsDescendants = true widgets.UIPadding(MenuBar, Vector2.new(ImGui._config.WindowPadding.X, 1)) widgets.UIListLayout(MenuBar, Enum.FillDirection.Horizontal, UDim.new()).VerticalAlignment = Enum.VerticalAlignment.Center widgets.applyFrameStyle(MenuBar, true, true) return MenuBar end, Update = function() end, ChildAdded = function(thisWidget: MenuBar, _thisChild: Widget) return thisWidget.Instance end, Discard = function(thisWidget: MenuBar) thisWidget.Instance:Destroy() end, } :: WidgetClass) --stylua: ignore ImGui.WidgetConstructor("Menu", { hasState = true, hasChildren = true, Args = { ["Text"] = 1, }, Events = { ["clicked"] = widgets.EVENTS.click(function(thisWidget: Widget) return thisWidget.Instance end), ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), ["opened"] = { ["Init"] = function(_thisWidget: Menu) end, ["Get"] = function(thisWidget: Menu) return thisWidget.lastOpenedTick == ImGui._cycleTick end, }, ["closed"] = { ["Init"] = function(_thisWidget: Menu) end, ["Get"] = function(thisWidget: Menu) return thisWidget.lastClosedTick == ImGui._cycleTick end, }, }, Generate = function(thisWidget: Menu) local Menu: TextButton thisWidget.ButtonColors = { Color = ImGui._config.HeaderColor, Transparency = 1, HoveredColor = ImGui._config.HeaderHoveredColor, HoveredTransparency = ImGui._config.HeaderHoveredTransparency, ActiveColor = ImGui._config.HeaderHoveredColor, ActiveTransparency = ImGui._config.HeaderHoveredTransparency, } if thisWidget.parentWidget.type == "Menu" then -- this Menu is a sub-Menu Menu = Instance.new("TextButton") Menu.Name = "Menu" Menu.BackgroundColor3 = ImGui._config.HeaderColor Menu.BackgroundTransparency = 1 Menu.BorderSizePixel = 0 Menu.Size = UDim2.fromScale(1, 0) Menu.Text = "" Menu.AutomaticSize = Enum.AutomaticSize.Y Menu.LayoutOrder = thisWidget.ZIndex Menu.AutoButtonColor = false local UIPadding = widgets.UIPadding(Menu, ImGui._config.FramePadding) UIPadding.PaddingTop = UIPadding.PaddingTop - UDim.new(0, 1) widgets.UIListLayout(Menu, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)).VerticalAlignment = Enum.VerticalAlignment.Center local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(TextLabel) TextLabel.Parent = Menu local frameSize: number = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y local padding: number = math.round(0.2 * frameSize) local iconSize: number = frameSize - 2 * padding local Icon: ImageLabel = Instance.new("ImageLabel") Icon.Name = "Icon" Icon.Size = UDim2.fromOffset(iconSize, iconSize) Icon.BackgroundTransparency = 1 Icon.BorderSizePixel = 0 Icon.ImageColor3 = ImGui._config.TextColor Icon.ImageTransparency = ImGui._config.TextTransparency Icon.Image = widgets.ICONS.RIGHT_POINTING_TRIANGLE Icon.LayoutOrder = 1 Icon.Parent = Menu else Menu = Instance.new("TextButton") Menu.Name = "Menu" Menu.AutomaticSize = Enum.AutomaticSize.XY Menu.Size = UDim2.fromScale(0, 0) Menu.BackgroundColor3 = ImGui._config.HeaderColor Menu.BackgroundTransparency = 1 Menu.BorderSizePixel = 0 Menu.Text = "" Menu.LayoutOrder = thisWidget.ZIndex Menu.AutoButtonColor = false Menu.ClipsDescendants = true widgets.applyTextStyle(Menu) widgets.UIPadding(Menu, Vector2.new(ImGui._config.ItemSpacing.X, ImGui._config.FramePadding.Y)) end widgets.applyInteractionHighlights("Background", Menu, Menu, thisWidget.ButtonColors) widgets.applyButtonClick(Menu, function() local openMenu: boolean = if #MenuStack <= 1 then not thisWidget.state.isOpened.value else true thisWidget.state.isOpened:set(openMenu) AnyMenuOpen = openMenu ActiveMenu = openMenu and thisWidget or nil -- the hovering should handle all of the menus after the first one. if #MenuStack <= 1 then if openMenu then table.insert(MenuStack, thisWidget) else table.remove(MenuStack) end end end) widgets.applyMouseEnter(Menu, function() if AnyMenuOpen and ActiveMenu and ActiveMenu ~= thisWidget then local parentMenu = thisWidget.parentWidget :: Menu local parentIndex: number? = table.find(MenuStack, parentMenu) EmptyMenuStack(parentIndex) thisWidget.state.isOpened:set(true) ActiveMenu = thisWidget AnyMenuOpen = true table.insert(MenuStack, thisWidget) end end) local ChildContainer: ScrollingFrame = Instance.new("ScrollingFrame") ChildContainer.Name = "MenuContainer" ChildContainer.BackgroundColor3 = ImGui._config.PopupBgColor ChildContainer.BackgroundTransparency = ImGui._config.PopupBgTransparency ChildContainer.BorderSizePixel = 0 ChildContainer.Size = UDim2.fromOffset(0, 0) ChildContainer.AutomaticSize = Enum.AutomaticSize.XY ChildContainer.AutomaticCanvasSize = Enum.AutomaticSize.Y ChildContainer.ScrollBarImageTransparency = ImGui._config.ScrollbarGrabTransparency ChildContainer.ScrollBarImageColor3 = ImGui._config.ScrollbarGrabColor ChildContainer.ScrollBarThickness = ImGui._config.ScrollbarSize ChildContainer.CanvasSize = UDim2.fromScale(0, 0) ChildContainer.VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar ChildContainer.ZIndex = 6 ChildContainer.LayoutOrder = 6 ChildContainer.ClipsDescendants = true -- Unfortunatley, ScrollingFrame does not work with UICorner -- if ImGui._config.PopupRounding > 0 then -- widgets.UICorner(ChildContainer, ImGui._config.PopupRounding) -- end widgets.UIStroke(ChildContainer, ImGui._config.WindowBorderSize, ImGui._config.BorderColor, ImGui._config.BorderTransparency) widgets.UIPadding(ChildContainer, Vector2.new(2, ImGui._config.WindowPadding.Y - ImGui._config.ItemSpacing.Y)) local ChildContainerUIListLayout: UIListLayout = widgets.UIListLayout(ChildContainer, Enum.FillDirection.Vertical, UDim.new(0, 1)) ChildContainerUIListLayout.VerticalAlignment = Enum.VerticalAlignment.Top local RootPopupScreenGui = ImGui._rootInstance and ImGui._rootInstance:FindFirstChild("PopupScreenGui") :: GuiObject ChildContainer.Parent = RootPopupScreenGui thisWidget.ChildContainer = ChildContainer return Menu end, Update = function(thisWidget: Menu) local Menu = thisWidget.Instance :: TextButton local TextLabel: TextLabel if thisWidget.parentWidget.type == "Menu" then TextLabel = Menu.TextLabel else TextLabel = Menu end TextLabel.Text = thisWidget.arguments.Text or "Menu" end, ChildAdded = function(thisWidget: Menu, _thisChild: Widget) UpdateChildContainerTransform(thisWidget) return thisWidget.ChildContainer end, ChildDiscarded = function(thisWidget: Menu, _thisChild: Widget) UpdateChildContainerTransform(thisWidget) end, GenerateState = function(thisWidget: Menu) if thisWidget.state.isOpened == nil then thisWidget.state.isOpened = ImGui._widgetState(thisWidget, "isOpened", false) end end, UpdateState = function(thisWidget: Menu) local ChildContainer = thisWidget.ChildContainer :: ScrollingFrame if thisWidget.state.isOpened.value then thisWidget.lastOpenedTick = ImGui._cycleTick + 1 thisWidget.ButtonColors.Transparency = ImGui._config.HeaderTransparency ChildContainer.Visible = true UpdateChildContainerTransform(thisWidget) else thisWidget.lastClosedTick = ImGui._cycleTick + 1 thisWidget.ButtonColors.Transparency = 1 ChildContainer.Visible = false end end, Discard = function(thisWidget: Menu) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, } :: WidgetClass) --stylua: ignore ImGui.WidgetConstructor("MenuItem", { hasState = false, hasChildren = false, Args = { Text = 1, KeyCode = 2, ModifierKey = 3, }, Events = { ["clicked"] = widgets.EVENTS.click(function(thisWidget: Widget) return thisWidget.Instance end), ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: MenuItem) local MenuItem: TextButton = Instance.new("TextButton") MenuItem.Name = "MenuItem" MenuItem.BackgroundTransparency = 1 MenuItem.BorderSizePixel = 0 MenuItem.Size = UDim2.fromScale(1, 0) MenuItem.Text = "" MenuItem.AutomaticSize = Enum.AutomaticSize.Y MenuItem.LayoutOrder = thisWidget.ZIndex MenuItem.AutoButtonColor = false local UIPadding = widgets.UIPadding(MenuItem, ImGui._config.FramePadding) UIPadding.PaddingTop = UIPadding.PaddingTop - UDim.new(0, 1) widgets.UIListLayout(MenuItem, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) widgets.applyInteractionHighlights("Background", MenuItem, MenuItem, { Color = ImGui._config.HeaderColor, Transparency = 1, HoveredColor = ImGui._config.HeaderHoveredColor, HoveredTransparency = ImGui._config.HeaderHoveredTransparency, ActiveColor = ImGui._config.HeaderHoveredColor, ActiveTransparency = ImGui._config.HeaderHoveredTransparency, }) widgets.applyButtonClick(MenuItem, function() EmptyMenuStack() end) widgets.applyMouseEnter(MenuItem, function() local parentMenu = thisWidget.parentWidget :: Menu if AnyMenuOpen and ActiveMenu and ActiveMenu ~= parentMenu then local parentIndex: number? = table.find(MenuStack, parentMenu) EmptyMenuStack(parentIndex) ActiveMenu = parentMenu AnyMenuOpen = true end end) local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(TextLabel) TextLabel.Parent = MenuItem local Shortcut: TextLabel = Instance.new("TextLabel") Shortcut.Name = "Shortcut" Shortcut.BackgroundTransparency = 1 Shortcut.BorderSizePixel = 0 Shortcut.LayoutOrder = 1 Shortcut.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(Shortcut) Shortcut.Text = "" Shortcut.TextColor3 = ImGui._config.TextDisabledColor Shortcut.TextTransparency = ImGui._config.TextDisabledTransparency Shortcut.Parent = MenuItem return MenuItem end, Update = function(thisWidget: MenuItem) local MenuItem = thisWidget.Instance :: TextButton local TextLabel: TextLabel = MenuItem.TextLabel local Shortcut: TextLabel = MenuItem.Shortcut TextLabel.Text = thisWidget.arguments.Text if thisWidget.arguments.KeyCode then if thisWidget.arguments.ModifierKey then Shortcut.Text = thisWidget.arguments.ModifierKey.Name .. " + " .. thisWidget.arguments.KeyCode.Name else Shortcut.Text = thisWidget.arguments.KeyCode.Name end end end, Discard = function(thisWidget: MenuItem) thisWidget.Instance:Destroy() end, } :: WidgetClass) --stylua: ignore ImGui.WidgetConstructor("MenuToggle", { hasState = true, hasChildren = false, Args = { Text = 1, KeyCode = 2, ModifierKey = 3, }, Events = { ["checked"] = { ["Init"] = function(_thisWidget: MenuToggle) end, ["Get"] = function(thisWidget: MenuToggle): boolean return thisWidget.lastCheckedTick == ImGui._cycleTick end, }, ["unchecked"] = { ["Init"] = function(_thisWidget: MenuToggle) end, ["Get"] = function(thisWidget: MenuToggle): boolean return thisWidget.lastUncheckedTick == ImGui._cycleTick end, }, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: MenuToggle) local MenuItem: TextButton = Instance.new("TextButton") MenuItem.Name = "MenuItem" MenuItem.BackgroundTransparency = 1 MenuItem.BorderSizePixel = 0 MenuItem.Size = UDim2.fromScale(1, 0) MenuItem.Text = "" MenuItem.AutomaticSize = Enum.AutomaticSize.Y MenuItem.LayoutOrder = thisWidget.ZIndex MenuItem.AutoButtonColor = false local UIPadding = widgets.UIPadding(MenuItem, ImGui._config.FramePadding) UIPadding.PaddingTop = UIPadding.PaddingTop - UDim.new(0, 1) widgets.UIListLayout(MenuItem, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)).VerticalAlignment = Enum.VerticalAlignment.Center widgets.applyInteractionHighlights("Background", MenuItem, MenuItem, { Color = ImGui._config.HeaderColor, Transparency = 1, HoveredColor = ImGui._config.HeaderHoveredColor, HoveredTransparency = ImGui._config.HeaderHoveredTransparency, ActiveColor = ImGui._config.HeaderHoveredColor, ActiveTransparency = ImGui._config.HeaderHoveredTransparency, }) widgets.applyButtonClick(MenuItem, function() local wasChecked: boolean = thisWidget.state.isChecked.value thisWidget.state.isChecked:set(not wasChecked) EmptyMenuStack() end) widgets.applyMouseEnter(MenuItem, function() local parentMenu = thisWidget.parentWidget :: Menu if AnyMenuOpen and ActiveMenu and ActiveMenu ~= parentMenu then local parentIndex: number? = table.find(MenuStack, parentMenu) EmptyMenuStack(parentIndex) ActiveMenu = parentMenu AnyMenuOpen = true end end) local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(TextLabel) TextLabel.Parent = MenuItem local Shortcut: TextLabel = Instance.new("TextLabel") Shortcut.Name = "Shortcut" Shortcut.BackgroundTransparency = 1 Shortcut.BorderSizePixel = 0 Shortcut.LayoutOrder = 1 Shortcut.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(Shortcut) Shortcut.Text = "" Shortcut.TextColor3 = ImGui._config.TextDisabledColor Shortcut.TextTransparency = ImGui._config.TextDisabledTransparency Shortcut.Parent = MenuItem local frameSize: number = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y local padding: number = math.round(0.2 * frameSize) local iconSize: number = frameSize - 2 * padding local Icon: ImageLabel = Instance.new("ImageLabel") Icon.Name = "Icon" Icon.Size = UDim2.fromOffset(iconSize, iconSize) Icon.BackgroundTransparency = 1 Icon.BorderSizePixel = 0 Icon.ImageColor3 = ImGui._config.TextColor Icon.ImageTransparency = ImGui._config.TextTransparency Icon.Image = widgets.ICONS.CHECK_MARK Icon.LayoutOrder = 2 Icon.Parent = MenuItem return MenuItem end, GenerateState = function(thisWidget: MenuToggle) if thisWidget.state.isChecked == nil then thisWidget.state.isChecked = ImGui._widgetState(thisWidget, "isChecked", false) end end, Update = function(thisWidget: MenuToggle) local MenuItem = thisWidget.Instance :: TextButton local TextLabel: TextLabel = MenuItem.TextLabel local Shortcut: TextLabel = MenuItem.Shortcut TextLabel.Text = thisWidget.arguments.Text if thisWidget.arguments.KeyCode then if thisWidget.arguments.ModifierKey then Shortcut.Text = thisWidget.arguments.ModifierKey.Name .. " + " .. thisWidget.arguments.KeyCode.Name else Shortcut.Text = thisWidget.arguments.KeyCode.Name end end end, UpdateState = function(thisWidget: MenuToggle) local MenuItem = thisWidget.Instance :: TextButton local Icon: ImageLabel = MenuItem.Icon if thisWidget.state.isChecked.value then Icon.Image = widgets.ICONS.CHECK_MARK thisWidget.lastCheckedTick = ImGui._cycleTick + 1 else Icon.Image = "" thisWidget.lastUncheckedTick = ImGui._cycleTick + 1 end end, Discard = function(thisWidget: MenuToggle) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, } :: WidgetClass) end, Format = function(ImGui: Internal, widgets: WidgetUtility) ImGui.WidgetConstructor("Separator", { hasState = false, hasChildren = false, Args = {}, Events = {}, Generate = function(thisWidget: Separator) local Separator: Frame = Instance.new("Frame") Separator.Name = "ImGui_Separator" Separator.BackgroundColor3 = ImGui._config.SeparatorColor Separator.BackgroundTransparency = ImGui._config.SeparatorTransparency Separator.BorderSizePixel = 0 if thisWidget.parentWidget.type == "SameLine" then Separator.Size = UDim2.new(0, 1, 1, 0) else Separator.Size = UDim2.new(1, 0, 0, 1) end Separator.LayoutOrder = thisWidget.ZIndex widgets.UIListLayout(Separator, Enum.FillDirection.Vertical, UDim.new(0, 0)) return Separator end, Update = function(_thisWidget: Separator) end, Discard = function(thisWidget: Separator) thisWidget.Instance:Destroy() end, } :: WidgetClass) ImGui.WidgetConstructor("Indent", { hasState = false, hasChildren = true, Args = { ["Width"] = 1, }, Events = {}, Generate = function(thisWidget: Indent) local Indent: Frame = Instance.new("Frame") Indent.Name = "ImGui_Indent" Indent.BackgroundTransparency = 1 Indent.BorderSizePixel = 0 Indent.Size = UDim2.fromScale(1, 0) Indent.AutomaticSize = Enum.AutomaticSize.Y Indent.LayoutOrder = thisWidget.ZIndex widgets.UIListLayout(Indent, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) widgets.UIPadding(Indent, Vector2.zero) return Indent end, Update = function(thisWidget: Indent) local Indent = thisWidget.Instance :: Frame local indentWidth: number if thisWidget.arguments.Width then indentWidth = thisWidget.arguments.Width else indentWidth = ImGui._config.IndentSpacing end Indent.UIPadding.PaddingLeft = UDim.new(0, indentWidth) end, Discard = function(thisWidget: Indent) thisWidget.Instance:Destroy() end, ChildAdded = function(thisWidget: Indent, _thisChild: Widget) return thisWidget.Instance end, } :: WidgetClass) ImGui.WidgetConstructor("SameLine", { hasState = false, hasChildren = true, Args = { ["Width"] = 1, ["VerticalAlignment"] = 2, ["HorizontalAlignment"] = 3, }, Events = {}, Generate = function(thisWidget: SameLine) local SameLine: Frame = Instance.new("Frame") SameLine.Name = "ImGui_SameLine" SameLine.BackgroundTransparency = 1 SameLine.BorderSizePixel = 0 SameLine.Size = UDim2.fromScale(1, 0) SameLine.AutomaticSize = Enum.AutomaticSize.Y SameLine.LayoutOrder = thisWidget.ZIndex widgets.UIListLayout(SameLine, Enum.FillDirection.Horizontal, UDim.new(0, 0)) return SameLine end, Update = function(thisWidget: SameLine) local Sameline = thisWidget.Instance :: Frame local uiListLayout: UIListLayout = Sameline.UIListLayout local itemWidth: number if thisWidget.arguments.Width then itemWidth = thisWidget.arguments.Width else itemWidth = ImGui._config.ItemSpacing.X end uiListLayout.Padding = UDim.new(0, itemWidth) if thisWidget.arguments.VerticalAlignment then uiListLayout.VerticalAlignment = thisWidget.arguments.VerticalAlignment else uiListLayout.VerticalAlignment = Enum.VerticalAlignment.Top end if thisWidget.arguments.HorizontalAlignment then uiListLayout.HorizontalAlignment = thisWidget.arguments.HorizontalAlignment else uiListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Left end end, Discard = function(thisWidget: SameLine) thisWidget.Instance:Destroy() end, ChildAdded = function(thisWidget: SameLine, _thisChild: Widget) return thisWidget.Instance end, } :: WidgetClass) ImGui.WidgetConstructor("Group", { hasState = false, hasChildren = true, Args = {}, Events = {}, Generate = function(thisWidget: Group) local Group: Frame = Instance.new("Frame") Group.Name = "ImGui_Group" Group.AutomaticSize = Enum.AutomaticSize.XY Group.Size = UDim2.fromOffset(0, 0) Group.BackgroundTransparency = 1 Group.BorderSizePixel = 0 Group.LayoutOrder = thisWidget.ZIndex Group.ClipsDescendants = false widgets.UIListLayout(Group, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) return Group end, Update = function(_thisWidget: Group) end, Discard = function(thisWidget: Group) thisWidget.Instance:Destroy() end, ChildAdded = function(thisWidget: Group, _thisChild: Widget) return thisWidget.Instance end, } :: WidgetClass) end, Text = function(ImGui: Internal, widgets: WidgetUtility) --stylua: ignore ImGui.WidgetConstructor("Text", { hasState = false, hasChildren = false, Args = { ["Text"] = 1, ["Wrapped"] = 2, ["Color"] = 3, ["RichText"] = 4, }, Events = { ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Text) local Text: TextLabel = Instance.new("TextLabel") Text.Name = "ImGui_Text" Text.Size = UDim2.fromOffset(0, 0) Text.BackgroundTransparency = 1 Text.BorderSizePixel = 0 Text.LayoutOrder = thisWidget.ZIndex Text.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(Text) widgets.UIPadding(Text, Vector2.new(0, 2)) return Text end, Update = function(thisWidget: Text) local Text = thisWidget.Instance :: TextLabel if thisWidget.arguments.Text == nil then error("ImGui.Text Text Argument is required", 5) end if thisWidget.arguments.Wrapped ~= nil then Text.TextWrapped = thisWidget.arguments.Wrapped else Text.TextWrapped = ImGui._config.TextWrapped end if thisWidget.arguments.Color then Text.TextColor3 = thisWidget.arguments.Color else Text.TextColor3 = ImGui._config.TextColor end if thisWidget.arguments.RichText ~= nil then Text.RichText = thisWidget.arguments.RichText else Text.RichText = ImGui._config.RichText end Text.Text = thisWidget.arguments.Text end, Discard = function(thisWidget: Text) thisWidget.Instance:Destroy() end, } :: WidgetClass) --stylua: ignore ImGui.WidgetConstructor("SeparatorText", { hasState = false, hasChildren = false, Args = { ["Text"] = 1, }, Events = { ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: SeparatorText) local SeparatorText = Instance.new("Frame") SeparatorText.Name = "ImGui_SeparatorText" SeparatorText.Size = UDim2.fromScale(1, 0) SeparatorText.BackgroundTransparency = 1 SeparatorText.BorderSizePixel = 0 SeparatorText.AutomaticSize = Enum.AutomaticSize.Y SeparatorText.LayoutOrder = thisWidget.ZIndex SeparatorText.ClipsDescendants = true widgets.UIPadding(SeparatorText, Vector2.new(0, ImGui._config.SeparatorTextPadding.Y)) widgets.UIListLayout(SeparatorText, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemSpacing.X)) SeparatorText.UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.AutomaticSize = Enum.AutomaticSize.XY TextLabel.LayoutOrder = 1 widgets.applyTextStyle(TextLabel) TextLabel.Parent = SeparatorText local Left: Frame = Instance.new("Frame") Left.Name = "Left" Left.AnchorPoint = Vector2.new(1, 0.5) Left.BackgroundColor3 = ImGui._config.SeparatorColor Left.BackgroundTransparency = ImGui._config.SeparatorTransparency Left.BorderSizePixel = 0 Left.Size = UDim2.fromOffset(ImGui._config.SeparatorTextPadding.X - ImGui._config.ItemSpacing.X, ImGui._config.SeparatorTextBorderSize) Left.Parent = SeparatorText local Right: Frame = Instance.new("Frame") Right.Name = "Right" Right.AnchorPoint = Vector2.new(1, 0.5) Right.BackgroundColor3 = ImGui._config.SeparatorColor Right.BackgroundTransparency = ImGui._config.SeparatorTransparency Right.BorderSizePixel = 0 Right.Size = UDim2.new(1, 0, 0, ImGui._config.SeparatorTextBorderSize) Right.LayoutOrder = 2 Right.Parent = SeparatorText return SeparatorText end, Update = function(thisWidget: SeparatorText) local SeparatorText = thisWidget.Instance :: Frame local TextLabel: TextLabel = SeparatorText.TextLabel if thisWidget.arguments.Text == nil then error("ImGui.Text Text Argument is required", 5) end TextLabel.Text = thisWidget.arguments.Text end, Discard = function(thisWidget: SeparatorText) thisWidget.Instance:Destroy() end, } :: WidgetClass) end, Button = function(ImGui: Internal, widgets: WidgetUtility) local abstractButton = { hasState = false, hasChildren = false, Args = { ["Text"] = 1, ["Size"] = 2, }, Events = { ["clicked"] = widgets.EVENTS.click(function(thisWidget: Widget) return thisWidget.Instance end), ["rightClicked"] = widgets.EVENTS.rightClick(function(thisWidget: Widget) return thisWidget.Instance end), ["doubleClicked"] = widgets.EVENTS.doubleClick(function(thisWidget: Widget) return thisWidget.Instance end), ["ctrlClicked"] = widgets.EVENTS.ctrlClick(function(thisWidget: Widget) return thisWidget.Instance end), ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Button) local Button: TextButton = Instance.new("TextButton") Button.Size = UDim2.fromOffset(0, 0) Button.BackgroundColor3 = ImGui._config.ButtonColor Button.BackgroundTransparency = ImGui._config.ButtonTransparency Button.AutoButtonColor = false Button.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(Button) Button.TextXAlignment = Enum.TextXAlignment.Center widgets.applyFrameStyle(Button) widgets.applyInteractionHighlights("Background", Button, Button, { Color = ImGui._config.ButtonColor, Transparency = ImGui._config.ButtonTransparency, HoveredColor = ImGui._config.ButtonHoveredColor, HoveredTransparency = ImGui._config.ButtonHoveredTransparency, ActiveColor = ImGui._config.ButtonActiveColor, ActiveTransparency = ImGui._config.ButtonActiveTransparency, }) Button.ZIndex = thisWidget.ZIndex Button.LayoutOrder = thisWidget.ZIndex return Button end, Update = function(thisWidget: Button) local Button = thisWidget.Instance :: TextButton Button.Text = thisWidget.arguments.Text or "Button" Button.Size = thisWidget.arguments.Size or UDim2.fromOffset(0, 0) end, Discard = function(thisWidget: Button) thisWidget.Instance:Destroy() end, } :: WidgetClass widgets.abstractButton = abstractButton ImGui.WidgetConstructor("Button", widgets.extend(abstractButton, { Generate = function(thisWidget: Button) local Button: TextButton = abstractButton.Generate(thisWidget) Button.Name = "ImGui_Button" return Button end, } :: WidgetClass) ) ImGui.WidgetConstructor("SmallButton", widgets.extend(abstractButton, { Generate = function(thisWidget: Button) local SmallButton = abstractButton.Generate(thisWidget) :: TextButton SmallButton.Name = "ImGui_SmallButton" local uiPadding: UIPadding = SmallButton.UIPadding uiPadding.PaddingLeft = UDim.new(0, 2) uiPadding.PaddingRight = UDim.new(0, 2) uiPadding.PaddingTop = UDim.new(0, 0) uiPadding.PaddingBottom = UDim.new(0, 0) return SmallButton end, } :: WidgetClass) ) end, Checkbox = function(ImGui: Internal, widgets: WidgetUtility) ImGui.WidgetConstructor("Checkbox", { hasState = true, hasChildren = false, Args = { ["Text"] = 1, }, Events = { ["checked"] = { ["Init"] = function(_thisWidget: Checkbox) end, ["Get"] = function(thisWidget: Checkbox): boolean return thisWidget.lastCheckedTick == ImGui._cycleTick end, }, ["unchecked"] = { ["Init"] = function(_thisWidget: Checkbox) end, ["Get"] = function(thisWidget: Checkbox): boolean return thisWidget.lastUncheckedTick == ImGui._cycleTick end, }, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Checkbox) local Checkbox: TextButton = Instance.new("TextButton") Checkbox.Name = "ImGui_Checkbox" Checkbox.AutomaticSize = Enum.AutomaticSize.XY Checkbox.Size = UDim2.fromOffset(0, 0) Checkbox.BackgroundTransparency = 1 Checkbox.BorderSizePixel = 0 Checkbox.Text = "" Checkbox.AutoButtonColor = false Checkbox.ZIndex = thisWidget.ZIndex Checkbox.LayoutOrder = thisWidget.ZIndex local UIListLayout: UIListLayout = widgets.UIListLayout(Checkbox, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center local checkboxSize: number = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y local Box: Frame = Instance.new("Frame") Box.Name = "Box" Box.Size = UDim2.fromOffset(checkboxSize, checkboxSize) Box.BackgroundColor3 = ImGui._config.FrameBgColor Box.BackgroundTransparency = ImGui._config.FrameBgTransparency widgets.applyFrameStyle(Box, true) widgets.UIPadding(Box, Vector2.new(math.floor(checkboxSize / 10), math.floor(checkboxSize / 10))) widgets.applyInteractionHighlights("Background", Checkbox, Box, { Color = ImGui._config.FrameBgColor, Transparency = ImGui._config.FrameBgTransparency, HoveredColor = ImGui._config.FrameBgHoveredColor, HoveredTransparency = ImGui._config.FrameBgHoveredTransparency, ActiveColor = ImGui._config.FrameBgActiveColor, ActiveTransparency = ImGui._config.FrameBgActiveTransparency, }) Box.Parent = Checkbox local Checkmark: ImageLabel = Instance.new("ImageLabel") Checkmark.Name = "Checkmark" Checkmark.Size = UDim2.fromScale(1, 1) Checkmark.BackgroundTransparency = 1 Checkmark.ImageColor3 = ImGui._config.CheckMarkColor Checkmark.ImageTransparency = ImGui._config.CheckMarkTransparency Checkmark.ScaleType = Enum.ScaleType.Fit Checkmark.Parent = Box widgets.applyButtonClick(Checkbox, function() local wasChecked: boolean = thisWidget.state.isChecked.value thisWidget.state.isChecked:set(not wasChecked) end) local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.AutomaticSize = Enum.AutomaticSize.XY TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.LayoutOrder = 1 widgets.applyTextStyle(TextLabel) TextLabel.Parent = Checkbox return Checkbox end, Update = function(thisWidget: Checkbox) local Checkbox = thisWidget.Instance :: TextButton Checkbox.TextLabel.Text = thisWidget.arguments.Text or "Checkbox" end, Discard = function(thisWidget: Checkbox) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, GenerateState = function(thisWidget: Checkbox) if thisWidget.state.isChecked == nil then thisWidget.state.isChecked = ImGui._widgetState(thisWidget, "checked", false) end end, UpdateState = function(thisWidget: Checkbox) local Checkbox = thisWidget.Instance :: TextButton local Box = Checkbox.Box :: Frame local Checkmark: ImageLabel = Box.Checkmark if thisWidget.state.isChecked.value then Checkmark.Image = widgets.ICONS.CHECK_MARK thisWidget.lastCheckedTick = ImGui._cycleTick + 1 else Checkmark.Image = "" thisWidget.lastUncheckedTick = ImGui._cycleTick + 1 end end, } :: WidgetClass) end, RadioButton = function(ImGui: Internal, widgets: WidgetUtility) --stylua: ignore ImGui.WidgetConstructor("RadioButton", { hasState = true, hasChildren = false, Args = { ["Text"] = 1, ["Index"] = 2, }, Events = { ["selected"] = { ["Init"] = function(_thisWidget: RadioButton) end, ["Get"] = function(thisWidget: RadioButton) return thisWidget.lastSelectedTick == ImGui._cycleTick end, }, ["unselected"] = { ["Init"] = function(_thisWidget: RadioButton) end, ["Get"] = function(thisWidget: RadioButton) return thisWidget.lastUnselectedTick == ImGui._cycleTick end, }, ["active"] = { ["Init"] = function(_thisWidget: RadioButton) end, ["Get"] = function(thisWidget: RadioButton) return thisWidget.state.index.value == thisWidget.arguments.Index end, }, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: RadioButton) local RadioButton: TextButton = Instance.new("TextButton") RadioButton.Name = "ImGui_RadioButton" RadioButton.AutomaticSize = Enum.AutomaticSize.XY RadioButton.Size = UDim2.fromOffset(0, 0) RadioButton.BackgroundTransparency = 1 RadioButton.BorderSizePixel = 0 RadioButton.Text = "" RadioButton.LayoutOrder = thisWidget.ZIndex RadioButton.AutoButtonColor = false RadioButton.ZIndex = thisWidget.ZIndex RadioButton.LayoutOrder = thisWidget.ZIndex local UIListLayout: UIListLayout = widgets.UIListLayout(RadioButton, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center local buttonSize: number = ImGui._config.TextSize + 2 * (ImGui._config.FramePadding.Y - 1) local Button: Frame = Instance.new("Frame") Button.Name = "Button" Button.Size = UDim2.fromOffset(buttonSize, buttonSize) Button.Parent = RadioButton Button.BackgroundColor3 = ImGui._config.FrameBgColor Button.BackgroundTransparency = ImGui._config.FrameBgTransparency widgets.UICorner(Button) widgets.UIPadding(Button, Vector2.new(math.max(1, math.floor(buttonSize / 5)), math.max(1, math.floor(buttonSize / 5)))) local Circle: Frame = Instance.new("Frame") Circle.Name = "Circle" Circle.Size = UDim2.fromScale(1, 1) Circle.Parent = Button Circle.BackgroundColor3 = ImGui._config.CheckMarkColor Circle.BackgroundTransparency = ImGui._config.CheckMarkTransparency widgets.UICorner(Circle) widgets.applyInteractionHighlights("Background", RadioButton, Button, { Color = ImGui._config.FrameBgColor, Transparency = ImGui._config.FrameBgTransparency, HoveredColor = ImGui._config.FrameBgHoveredColor, HoveredTransparency = ImGui._config.FrameBgHoveredTransparency, ActiveColor = ImGui._config.FrameBgActiveColor, ActiveTransparency = ImGui._config.FrameBgActiveTransparency, }) widgets.applyButtonClick(RadioButton, function() thisWidget.state.index:set(thisWidget.arguments.Index) end) local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.AutomaticSize = Enum.AutomaticSize.XY TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.LayoutOrder = 1 widgets.applyTextStyle(TextLabel) TextLabel.Parent = RadioButton return RadioButton end, Update = function(thisWidget: RadioButton) local RadioButton = thisWidget.Instance :: TextButton local TextLabel: TextLabel = RadioButton.TextLabel TextLabel.Text = thisWidget.arguments.Text or "Radio Button" if thisWidget.state then ImGui._widgets[thisWidget.type].UpdateState(thisWidget) end end, Discard = function(thisWidget: RadioButton) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, GenerateState = function(thisWidget: RadioButton) if thisWidget.state.index == nil then thisWidget.state.index = ImGui._widgetState(thisWidget, "index", thisWidget.arguments.Index) end end, UpdateState = function(thisWidget: RadioButton) local RadioButton = thisWidget.Instance :: TextButton local Button = RadioButton.Button :: Frame local Circle: Frame = Button.Circle if thisWidget.state.index.value == thisWidget.arguments.Index then -- only need to hide the circle Circle.BackgroundTransparency = ImGui._config.CheckMarkTransparency thisWidget.lastSelectedTick = ImGui._cycleTick + 1 else Circle.BackgroundTransparency = 1 thisWidget.lastUnselectedTick = ImGui._cycleTick + 1 end end, } :: WidgetClass) end, Image = function(ImGui: Internal, widgets: WidgetUtility) local abstractImage = { hasState = false, hasChildren = false, Args = { ["Image"] = 1, ["Size"] = 2, ["Rect"] = 3, ["ScaleType"] = 4, ["ResampleMode"] = 5, ["TileSize"] = 6, ["SliceCenter"] = 7, ["SliceScale"] = 8, }, Discard = function(thisWidget: Image) thisWidget.Instance:Destroy() end, } :: WidgetClass ImGui.WidgetConstructor("Image", widgets.extend(abstractImage, { Events = { ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Image) local Image: ImageLabel = Instance.new("ImageLabel") Image.Name = "ImGui_Image" Image.BackgroundTransparency = 1 Image.BorderSizePixel = 0 Image.ImageColor3 = ImGui._config.ImageColor Image.ImageTransparency = ImGui._config.ImageTransparency Image.LayoutOrder = thisWidget.ZIndex widgets.applyFrameStyle(Image, true) return Image end, Update = function(thisWidget: Image) local Image = thisWidget.Instance :: ImageLabel Image.Image = thisWidget.arguments.Image or widgets.ICONS.UNKNOWN_TEXTURE Image.Size = thisWidget.arguments.Size if thisWidget.arguments.ScaleType then Image.ScaleType = thisWidget.arguments.ScaleType if thisWidget.arguments.ScaleType == Enum.ScaleType.Tile and thisWidget.arguments.TileSize then Image.TileSize = thisWidget.arguments.TileSize elseif thisWidget.arguments.ScaleType == Enum.ScaleType.Slice then if thisWidget.arguments.SliceCenter then Image.SliceCenter = thisWidget.arguments.SliceCenter end if thisWidget.arguments.SliceScale then Image.SliceScale = thisWidget.arguments.SliceScale end end end if thisWidget.arguments.Rect then Image.ImageRectOffset = thisWidget.arguments.Rect.Min Image.ImageRectSize = Vector2.new(thisWidget.arguments.Rect.Width, thisWidget.arguments.Rect.Height) end if thisWidget.arguments.ResampleMode then Image.ResampleMode = thisWidget.arguments.ResampleMode end end, } :: WidgetClass) ) ImGui.WidgetConstructor("ImageButton", widgets.extend(abstractImage, { Events = { ["clicked"] = widgets.EVENTS.click(function(thisWidget: Widget) return thisWidget.Instance end), ["rightClicked"] = widgets.EVENTS.rightClick(function(thisWidget: Widget) return thisWidget.Instance end), ["doubleClicked"] = widgets.EVENTS.doubleClick(function(thisWidget: Widget) return thisWidget.Instance end), ["ctrlClicked"] = widgets.EVENTS.ctrlClick(function(thisWidget: Widget) return thisWidget.Instance end), ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: ImageButton) local Button: ImageButton = Instance.new("ImageButton") Button.Name = "ImGui_ImageButton" Button.AutomaticSize = Enum.AutomaticSize.XY Button.BackgroundColor3 = ImGui._config.FrameBgColor Button.BackgroundTransparency = ImGui._config.FrameBgTransparency Button.BorderSizePixel = 0 Button.Image = "" Button.ImageTransparency = 1 Button.LayoutOrder = thisWidget.ZIndex Button.AutoButtonColor = false widgets.applyFrameStyle(Button, true) widgets.UIPadding(Button, Vector2.new(ImGui._config.ImageBorderSize, ImGui._config.ImageBorderSize)) local Image: ImageLabel = Instance.new("ImageLabel") Image.Name = "ImageLabel" Image.BackgroundTransparency = 1 Image.BorderSizePixel = 0 Image.ImageColor3 = ImGui._config.ImageColor Image.ImageTransparency = ImGui._config.ImageTransparency Image.Parent = Button widgets.applyInteractionHighlights("Background", Button, Button, { Color = ImGui._config.FrameBgColor, Transparency = ImGui._config.FrameBgTransparency, HoveredColor = ImGui._config.FrameBgHoveredColor, HoveredTransparency = ImGui._config.FrameBgHoveredTransparency, ActiveColor = ImGui._config.FrameBgActiveColor, ActiveTransparency = ImGui._config.FrameBgActiveTransparency, }) return Button end, Update = function(thisWidget: ImageButton) local Button = thisWidget.Instance :: TextButton local Image: ImageLabel = Button.ImageLabel Image.Image = thisWidget.arguments.Image or widgets.ICONS.UNKNOWN_TEXTURE Image.Size = thisWidget.arguments.Size if thisWidget.arguments.ScaleType then Image.ScaleType = thisWidget.arguments.ScaleType if thisWidget.arguments.ScaleType == Enum.ScaleType.Tile and thisWidget.arguments.TileSize then Image.TileSize = thisWidget.arguments.TileSize elseif thisWidget.arguments.ScaleType == Enum.ScaleType.Slice then if thisWidget.arguments.SliceCenter then Image.SliceCenter = thisWidget.arguments.SliceCenter end if thisWidget.arguments.SliceScale then Image.SliceScale = thisWidget.arguments.SliceScale end end end if thisWidget.arguments.Rect then Image.ImageRectOffset = thisWidget.arguments.Rect.Min Image.ImageRectSize = Vector2.new(thisWidget.arguments.Rect.Width, thisWidget.arguments.Rect.Height) end if thisWidget.arguments.ResampleMode then Image.ResampleMode = thisWidget.arguments.ResampleMode end end, } :: WidgetClass) ) end, Tree = function(ImGui: Internal, widgets: WidgetUtility) local abstractTree = { hasState = true, hasChildren = true, Events = { ["collapsed"] = { ["Init"] = function(_thisWidget: CollapsingHeader) end, ["Get"] = function(thisWidget: CollapsingHeader) return thisWidget.lastCollapsedTick == ImGui._cycleTick end, }, ["uncollapsed"] = { ["Init"] = function(_thisWidget: CollapsingHeader) end, ["Get"] = function(thisWidget: CollapsingHeader) return thisWidget.lastUncollapsedTick == ImGui._cycleTick end, }, ["hovered"] = widgets.EVENTS.hover(function(thisWidget) return thisWidget.Instance end), }, Discard = function(thisWidget: CollapsingHeader) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, ChildAdded = function(thisWidget: CollapsingHeader, _thisChild: Widget) local ChildContainer: Frame = thisWidget.ChildContainer :: Frame ChildContainer.Visible = thisWidget.state.isUncollapsed.value return ChildContainer end, UpdateState = function(thisWidget: CollapsingHeader) local isUncollapsed: boolean = thisWidget.state.isUncollapsed.value local Tree = thisWidget.Instance :: Frame local ChildContainer = thisWidget.ChildContainer :: Frame local Header = Tree.Header :: Frame local Button = Header.Button :: TextButton local Arrow: ImageLabel = Button.Arrow Arrow.Image = (isUncollapsed and widgets.ICONS.DOWN_POINTING_TRIANGLE or widgets.ICONS.RIGHT_POINTING_TRIANGLE) if isUncollapsed then thisWidget.lastUncollapsedTick = ImGui._cycleTick + 1 else thisWidget.lastCollapsedTick = ImGui._cycleTick + 1 end ChildContainer.Visible = isUncollapsed end, GenerateState = function(thisWidget: CollapsingHeader) if thisWidget.state.isUncollapsed == nil then thisWidget.state.isUncollapsed = ImGui._widgetState(thisWidget, "isUncollapsed", false) end end, } :: WidgetClass --stylua: ignore ImGui.WidgetConstructor( "Tree", widgets.extend(abstractTree, { Args = { ["Text"] = 1, ["SpanAvailWidth"] = 2, ["NoIndent"] = 3, }, Generate = function(thisWidget: Tree) local Tree: Frame = Instance.new("Frame") Tree.Name = "ImGui_Tree" Tree.Size = UDim2.new(ImGui._config.ItemWidth, UDim.new(0, 0)) Tree.AutomaticSize = Enum.AutomaticSize.Y Tree.BackgroundTransparency = 1 Tree.BorderSizePixel = 0 Tree.LayoutOrder = thisWidget.ZIndex widgets.UIListLayout(Tree, Enum.FillDirection.Vertical, UDim.new(0, 0)) local ChildContainer: Frame = Instance.new("Frame") ChildContainer.Name = "TreeContainer" ChildContainer.Size = UDim2.fromScale(1, 0) ChildContainer.AutomaticSize = Enum.AutomaticSize.Y ChildContainer.BackgroundTransparency = 1 ChildContainer.BorderSizePixel = 0 ChildContainer.LayoutOrder = 1 ChildContainer.Visible = false -- ChildContainer.ClipsDescendants = true widgets.UIListLayout(ChildContainer, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) local ChildContainerPadding: UIPadding = widgets.UIPadding(ChildContainer, Vector2.zero) ChildContainerPadding.PaddingTop = UDim.new(0, ImGui._config.ItemSpacing.Y) ChildContainer.Parent = Tree local Header: Frame = Instance.new("Frame") Header.Name = "Header" Header.Size = UDim2.fromScale(1, 0) Header.AutomaticSize = Enum.AutomaticSize.Y Header.BackgroundTransparency = 1 Header.BorderSizePixel = 0 Header.Parent = Tree local Button: TextButton = Instance.new("TextButton") Button.Name = "Button" Button.BackgroundTransparency = 1 Button.BorderSizePixel = 0 Button.Text = "" Button.AutoButtonColor = false widgets.applyInteractionHighlights("Background", Button, Header, { Color = Color3.fromRGB(0, 0, 0), Transparency = 1, HoveredColor = ImGui._config.HeaderHoveredColor, HoveredTransparency = ImGui._config.HeaderHoveredTransparency, ActiveColor = ImGui._config.HeaderActiveColor, ActiveTransparency = ImGui._config.HeaderActiveTransparency, }) local ButtonPadding: UIPadding = widgets.UIPadding(Button, Vector2.zero) ButtonPadding.PaddingLeft = UDim.new(0, ImGui._config.FramePadding.X) local ButtonUIListLayout: UIListLayout = widgets.UIListLayout(Button, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.FramePadding.X)) ButtonUIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center Button.Parent = Header local Arrow: ImageLabel = Instance.new("ImageLabel") Arrow.Name = "Arrow" Arrow.Size = UDim2.fromOffset(ImGui._config.TextSize, math.floor(ImGui._config.TextSize * 0.7)) Arrow.BackgroundTransparency = 1 Arrow.BorderSizePixel = 0 Arrow.ImageColor3 = ImGui._config.TextColor Arrow.ImageTransparency = ImGui._config.TextTransparency Arrow.ScaleType = Enum.ScaleType.Fit Arrow.Parent = Button local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.Size = UDim2.fromOffset(0, 0) TextLabel.AutomaticSize = Enum.AutomaticSize.XY TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 local TextPadding: UIPadding = widgets.UIPadding(TextLabel, Vector2.zero) TextPadding.PaddingRight = UDim.new(0, 21) widgets.applyTextStyle(TextLabel) TextLabel.Parent = Button widgets.applyButtonClick(Button, function() thisWidget.state.isUncollapsed:set(not thisWidget.state.isUncollapsed.value) end) thisWidget.ChildContainer = ChildContainer return Tree end, Update = function(thisWidget: Tree) local Tree = thisWidget.Instance :: Frame local ChildContainer = thisWidget.ChildContainer :: Frame local Header = Tree.Header :: Frame local Button = Header.Button :: TextButton local TextLabel: TextLabel = Button.TextLabel local Padding: UIPadding = ChildContainer.UIPadding TextLabel.Text = thisWidget.arguments.Text or "Tree" if thisWidget.arguments.SpanAvailWidth then Button.AutomaticSize = Enum.AutomaticSize.Y Button.Size = UDim2.fromScale(1, 0) else Button.AutomaticSize = Enum.AutomaticSize.XY Button.Size = UDim2.fromScale(0, 0) end if thisWidget.arguments.NoIndent then Padding.PaddingLeft = UDim.new(0, 0) else Padding.PaddingLeft = UDim.new(0, ImGui._config.IndentSpacing) end end, }) ) --stylua: ignore ImGui.WidgetConstructor( "CollapsingHeader", widgets.extend(abstractTree, { Args = { ["Text"] = 1, }, Generate = function(thisWidget: CollapsingHeader) local CollapsingHeader: Frame = Instance.new("Frame") CollapsingHeader.Name = "ImGui_CollapsingHeader" CollapsingHeader.Size = UDim2.new(ImGui._config.ItemWidth, UDim.new(0, 0)) CollapsingHeader.AutomaticSize = Enum.AutomaticSize.Y CollapsingHeader.BackgroundTransparency = 1 CollapsingHeader.BorderSizePixel = 0 CollapsingHeader.LayoutOrder = thisWidget.ZIndex widgets.UIListLayout(CollapsingHeader, Enum.FillDirection.Vertical, UDim.new(0, 0)) local ChildContainer: Frame = Instance.new("Frame") ChildContainer.Name = "CollapsingHeaderContainer" ChildContainer.Size = UDim2.fromScale(1, 0) ChildContainer.AutomaticSize = Enum.AutomaticSize.Y ChildContainer.BackgroundTransparency = 1 ChildContainer.BorderSizePixel = 0 ChildContainer.LayoutOrder = 1 ChildContainer.Visible = false -- ChildContainer.ClipsDescendants = true widgets.UIListLayout(ChildContainer, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) local ChildContainerPadding: UIPadding = widgets.UIPadding(ChildContainer, Vector2.zero) ChildContainerPadding.PaddingTop = UDim.new(0, ImGui._config.ItemSpacing.Y) ChildContainer.Parent = CollapsingHeader local Header: Frame = Instance.new("Frame") Header.Name = "Header" Header.Size = UDim2.fromScale(1, 0) Header.AutomaticSize = Enum.AutomaticSize.Y Header.BackgroundTransparency = 1 Header.BorderSizePixel = 0 Header.Parent = CollapsingHeader local Button = Instance.new("TextButton") Button.Name = "Button" Button.Size = UDim2.new(1, 0, 0, 0) Button.AutomaticSize = Enum.AutomaticSize.Y Button.BackgroundColor3 = ImGui._config.HeaderColor Button.BackgroundTransparency = ImGui._config.HeaderTransparency Button.BorderSizePixel = 0 Button.Text = "" Button.AutoButtonColor = false Button.ClipsDescendants = true widgets.UIPadding(Button, ImGui._config.FramePadding) -- we add a custom padding because it extends on both sides widgets.applyFrameStyle(Button, true) local ButtonUIListLayout: UIListLayout = widgets.UIListLayout(Button, Enum.FillDirection.Horizontal, UDim.new(0, 2 * ImGui._config.FramePadding.X)) ButtonUIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center widgets.applyInteractionHighlights("Background", Button, Button, { Color = ImGui._config.HeaderColor, Transparency = ImGui._config.HeaderTransparency, HoveredColor = ImGui._config.HeaderHoveredColor, HoveredTransparency = ImGui._config.HeaderHoveredTransparency, ActiveColor = ImGui._config.HeaderActiveColor, ActiveTransparency = ImGui._config.HeaderActiveTransparency, }) Button.Parent = Header local Arrow: ImageLabel = Instance.new("ImageLabel") Arrow.Name = "Arrow" Arrow.Size = UDim2.fromOffset(ImGui._config.TextSize, math.ceil(ImGui._config.TextSize * 0.8)) Arrow.AutomaticSize = Enum.AutomaticSize.Y Arrow.BackgroundTransparency = 1 Arrow.BorderSizePixel = 0 Arrow.ImageColor3 = ImGui._config.TextColor Arrow.ImageTransparency = ImGui._config.TextTransparency Arrow.ScaleType = Enum.ScaleType.Fit Arrow.Parent = Button local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.Size = UDim2.fromOffset(0, 0) TextLabel.AutomaticSize = Enum.AutomaticSize.XY TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 local TextPadding: UIPadding = widgets.UIPadding(TextLabel, Vector2.zero) TextPadding.PaddingRight = UDim.new(0, 21) widgets.applyTextStyle(TextLabel) TextLabel.Parent = Button widgets.applyButtonClick(Button, function() thisWidget.state.isUncollapsed:set(not thisWidget.state.isUncollapsed.value) end) thisWidget.ChildContainer = ChildContainer return CollapsingHeader end, Update = function(thisWidget: CollapsingHeader) local Tree = thisWidget.Instance :: Frame local Header = Tree.Header :: Frame local Button = Header.Button :: TextButton local TextLabel: TextLabel = Button.TextLabel TextLabel.Text = thisWidget.arguments.Text or "Collapsing Header" end, }) ) end, Input = function(ImGui: Internal, widgets: WidgetUtility) local numberChanged = { ["Init"] = function(_thisWidget: Widget) end, ["Get"] = function(thisWidget: Input) return thisWidget.lastNumberChangedTick == ImGui._cycleTick end, } local function getValueByIndex(value: T, index: number, arguments: any): number local t: string = typeof(value) local v = value :: any if t == "number" then return v elseif t == "Vector2" then if index == 1 then return v.X elseif index == 2 then return v.Y end elseif t == "Vector3" then if index == 1 then return v.X elseif index == 2 then return v.Y elseif index == 3 then return v.Z end elseif t == "UDim" then if index == 1 then return v.Scale elseif index == 2 then return v.Offset end elseif t == "UDim2" then if index == 1 then return v.X.Scale elseif index == 2 then return v.X.Offset elseif index == 3 then return v.Y.Scale elseif index == 4 then return v.Y.Offset end elseif t == "Color3" then local color: { number } = arguments.UseHSV and { v:ToHSV() } or { v.R, v.G, v.B } if index == 1 then return color[1] elseif index == 2 then return color[2] elseif index == 3 then return color[3] end elseif t == "Rect" then if index == 1 then return v.Min.X elseif index == 2 then return v.Min.Y elseif index == 3 then return v.Max.X elseif index == 4 then return v.Max.Y end elseif t == "table" then return v[index] end error(`Incorrect datatype or value: {value} {typeof(value)} {index}`) end local function updateValueByIndex(value: T, index: number, newValue: number, arguments: Arguments): T if typeof(value) == "number" then return newValue :: any elseif typeof(value) == "Vector2" then if index == 1 then return Vector2.new(newValue, value.Y) :: any elseif index == 2 then return Vector2.new(value.X, newValue) :: any end elseif typeof(value) == "Vector3" then if index == 1 then return Vector3.new(newValue, value.Y, value.Z) :: any elseif index == 2 then return Vector3.new(value.X, newValue, value.Z) :: any elseif index == 3 then return Vector3.new(value.X, value.Y, newValue) :: any end elseif typeof(value) == "UDim" then if index == 1 then return UDim.new(newValue, value.Offset) :: any elseif index == 2 then return UDim.new(value.Scale, newValue) :: any end elseif typeof(value) == "UDim2" then if index == 1 then return UDim2.new(UDim.new(newValue, value.X.Offset), value.Y) :: any elseif index == 2 then return UDim2.new(UDim.new(value.X.Scale, newValue), value.Y) :: any elseif index == 3 then return UDim2.new(value.X, UDim.new(newValue, value.Y.Offset)) :: any elseif index == 4 then return UDim2.new(value.X, UDim.new(value.Y.Scale, newValue)) :: any end elseif typeof(value) == "Rect" then if index == 1 then return Rect.new(Vector2.new(newValue, value.Min.Y), value.Max) :: any elseif index == 2 then return Rect.new(Vector2.new(value.Min.X, newValue), value.Max) :: any elseif index == 3 then return Rect.new(value.Min, Vector2.new(newValue, value.Max.Y)) :: any elseif index == 4 then return Rect.new(value.Min, Vector2.new(value.Max.X, newValue)) :: any end elseif typeof(value) == "Color3" then if arguments.UseHSV then local h: number, s: number, v: number = value:ToHSV() if index == 1 then return Color3.fromHSV(newValue, s, v) :: any elseif index == 2 then return Color3.fromHSV(h, newValue, v) :: any elseif index == 3 then return Color3.fromHSV(h, s, newValue) :: any end end if index == 1 then return Color3.new(newValue, value.G, value.B) :: any elseif index == 2 then return Color3.new(value.R, newValue, value.B) :: any elseif index == 3 then return Color3.new(value.R, value.G, newValue) :: any end end error(`Incorrect datatype or value {value} {typeof(value)} {index}`) end local defaultIncrements: { [InputDataTypes]: { number } } = { Num = { 1 }, Vector2 = { 1, 1 }, Vector3 = { 1, 1, 1 }, UDim = { 0.01, 1 }, UDim2 = { 0.01, 1, 0.01, 1 }, Color3 = { 1, 1, 1 }, Color4 = { 1, 1, 1, 1 }, Rect = { 1, 1, 1, 1 }, } local defaultMin: { [InputDataTypes]: { number } } = { Num = { 0 }, Vector2 = { 0, 0 }, Vector3 = { 0, 0, 0 }, UDim = { 0, 0 }, UDim2 = { 0, 0, 0, 0 }, Rect = { 0, 0, 0, 0 }, } local defaultMax: { [InputDataTypes]: { number } } = { Num = { 100 }, Vector2 = { 100, 100 }, Vector3 = { 100, 100, 100 }, UDim = { 1, 960 }, UDim2 = { 1, 960, 1, 960 }, Rect = { 960, 960, 960, 960 }, } local defaultPrefx: { [InputDataTypes]: { string } } = { Num = { "" }, Vector2 = { "X: ", "Y: " }, Vector3 = { "X: ", "Y: ", "Z: " }, UDim = { "", "" }, UDim2 = { "", "", "", "" }, Color3_RGB = { "R: ", "G: ", "B: " }, Color3_HSV = { "H: ", "S: ", "V: " }, Color4_RGB = { "R: ", "G: ", "B: ", "T: " }, Color4_HSV = { "H: ", "S: ", "V: ", "T: " }, Rect = { "X: ", "Y: ", "X: ", "Y: " }, } local defaultSigFigs: { [InputDataTypes]: { number } } = { Num = { 0 }, Vector2 = { 0, 0 }, Vector3 = { 0, 0, 0 }, UDim = { 3, 0 }, UDim2 = { 3, 0, 3, 0 }, Color3 = { 0, 0, 0 }, Color4 = { 0, 0, 0, 0 }, Rect = { 0, 0, 0, 0 }, } --[[ Input ]] local generateInputScalar: (dataType: InputDataTypes, components: number, defaultValue: any) -> WidgetClass do local function generateButtons(thisWidget: Input, parent: GuiObject, rightPadding: number, textHeight: number) rightPadding += 2 * ImGui._config.ItemInnerSpacing.X + 2 * textHeight local SubButton = widgets.abstractButton.Generate(thisWidget) :: TextButton SubButton.Name = "SubButton" SubButton.ZIndex = 5 SubButton.LayoutOrder = 5 SubButton.TextXAlignment = Enum.TextXAlignment.Center SubButton.Text = "-" SubButton.Size = UDim2.fromOffset(ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y, ImGui._config.TextSize) SubButton.Parent = parent widgets.applyButtonClick(SubButton, function() local isCtrlHeld: boolean = widgets.UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or widgets.UserInputService:IsKeyDown(Enum.KeyCode.RightControl) local changeValue: number = (thisWidget.arguments.Increment and getValueByIndex(thisWidget.arguments.Increment, 1, thisWidget.arguments :: Argument) or 1) * (isCtrlHeld and 100 or 1) local newValue: number = thisWidget.state.number.value - changeValue if thisWidget.arguments.Min ~= nil then newValue = math.max(newValue, getValueByIndex(thisWidget.arguments.Min, 1, thisWidget.arguments :: Argument)) end if thisWidget.arguments.Max ~= nil then newValue = math.min(newValue, getValueByIndex(thisWidget.arguments.Max, 1, thisWidget.arguments :: Argument)) end thisWidget.state.number:set(newValue) thisWidget.lastNumberChangedTick = ImGui._cycleTick + 1 end) local AddButton = widgets.abstractButton.Generate(thisWidget) :: TextButton AddButton.Name = "AddButton" AddButton.ZIndex = 6 AddButton.LayoutOrder = 6 AddButton.TextXAlignment = Enum.TextXAlignment.Center AddButton.Text = "+" AddButton.Size = UDim2.fromOffset(ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y, ImGui._config.TextSize) AddButton.Parent = parent widgets.applyButtonClick(AddButton, function() local isCtrlHeld: boolean = widgets.UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or widgets.UserInputService:IsKeyDown(Enum.KeyCode.RightControl) local changeValue: number = (thisWidget.arguments.Increment and getValueByIndex(thisWidget.arguments.Increment, 1, thisWidget.arguments :: Argument) or 1) * (isCtrlHeld and 100 or 1) local newValue: number = thisWidget.state.number.value + changeValue if thisWidget.arguments.Min ~= nil then newValue = math.max(newValue, getValueByIndex(thisWidget.arguments.Min, 1, thisWidget.arguments :: Argument)) end if thisWidget.arguments.Max ~= nil then newValue = math.min(newValue, getValueByIndex(thisWidget.arguments.Max, 1, thisWidget.arguments :: Argument)) end thisWidget.state.number:set(newValue) thisWidget.lastNumberChangedTick = ImGui._cycleTick + 1 end) return rightPadding end function generateInputScalar(dataType: InputDataTypes, components: number, defaultValue: any) return { hasState = true, hasChildren = false, Args = { ["Text"] = 1, ["Increment"] = 2, ["Min"] = 3, ["Max"] = 4, ["Format"] = 5, }, Events = { ["numberChanged"] = numberChanged, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Input) local Input: Frame = Instance.new("Frame") Input.Name = "ImGui_Input" .. dataType Input.Size = UDim2.fromScale(1, 0) Input.BackgroundTransparency = 1 Input.BorderSizePixel = 0 Input.LayoutOrder = thisWidget.ZIndex Input.AutomaticSize = Enum.AutomaticSize.Y local UIListLayout: UIListLayout = widgets.UIListLayout(Input, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center -- we add plus and minus buttons if there is only one box. This can be disabled through the argument. local rightPadding: number = 0 local textHeight: number = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y if components == 1 then rightPadding = generateButtons(thisWidget :: any, Input, rightPadding, textHeight) end -- we divide the total area evenly between each field. This includes accounting for any additional boxes and the offset. -- for the final field, we make sure it's flush by calculating the space avaiable for it. This only makes the Vector2 box -- 4 pixels shorter, all for the sake of flush. local componentWidth: UDim = UDim.new(ImGui._config.ContentWidth.Scale / components, (ImGui._config.ContentWidth.Offset - (ImGui._config.ItemInnerSpacing.X * (components - 1)) - rightPadding) / components) local totalWidth: UDim = UDim.new(componentWidth.Scale * (components - 1), (componentWidth.Offset * (components - 1)) + (ImGui._config.ItemInnerSpacing.X * (components - 1)) + rightPadding) local lastComponentWidth: UDim = ImGui._config.ContentWidth - totalWidth -- we handle each component individually since they don't need to interact with each other. for index = 1, components do local InputField: TextBox = Instance.new("TextBox") InputField.Name = "InputField" .. tostring(index) InputField.LayoutOrder = index if index == components then InputField.Size = UDim2.new(lastComponentWidth, ImGui._config.ContentHeight) else InputField.Size = UDim2.new(componentWidth, ImGui._config.ContentHeight) end InputField.AutomaticSize = Enum.AutomaticSize.Y InputField.BackgroundColor3 = ImGui._config.FrameBgColor InputField.BackgroundTransparency = ImGui._config.FrameBgTransparency InputField.ClearTextOnFocus = false InputField.TextTruncate = Enum.TextTruncate.AtEnd InputField.ClipsDescendants = true widgets.applyFrameStyle(InputField) widgets.applyTextStyle(InputField) widgets.UISizeConstraint(InputField, Vector2.xAxis) InputField.Parent = Input InputField.FocusLost:Connect(function() local newValue: number? = tonumber(InputField.Text:match("-?%d*%.?%d*")) if newValue ~= nil then if thisWidget.arguments.Min ~= nil then newValue = math.max(newValue, getValueByIndex(thisWidget.arguments.Min, index, thisWidget.arguments)) end if thisWidget.arguments.Max ~= nil then newValue = math.min(newValue, getValueByIndex(thisWidget.arguments.Max, index, thisWidget.arguments :: any)) end if thisWidget.arguments.Increment then newValue = math.round(newValue / getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments)) * getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments) end thisWidget.state.number:set(updateValueByIndex(thisWidget.state.number.value, index, newValue, thisWidget.arguments :: any)) thisWidget.lastNumberChangedTick = ImGui._cycleTick + 1 end local format: string = thisWidget.arguments.Format[index] or thisWidget.arguments.Format[1] if thisWidget.arguments.Prefix then format = thisWidget.arguments.Prefix[index] .. format end InputField.Text = string.format(format, getValueByIndex(thisWidget.state.number.value, index, thisWidget.arguments)) thisWidget.state.editingText:set(0) end) InputField.Focused:Connect(function() -- this highlights the entire field InputField.CursorPosition = #InputField.Text + 1 InputField.SelectionStart = 1 thisWidget.state.editingText:set(index) end) end local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.LayoutOrder = 7 TextLabel.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(TextLabel) TextLabel.Parent = Input return Input end, Update = function(thisWidget: Input) local Input = thisWidget.Instance :: GuiObject local TextLabel: TextLabel = Input.TextLabel TextLabel.Text = thisWidget.arguments.Text or `Input {dataType}` if components == 1 then Input.SubButton.Visible = not thisWidget.arguments.NoButtons Input.AddButton.Visible = not thisWidget.arguments.NoButtons end if thisWidget.arguments.Format and typeof(thisWidget.arguments.Format) ~= "table" then thisWidget.arguments.Format = { thisWidget.arguments.Format } elseif not thisWidget.arguments.Format then -- we calculate the format for the s.f. using the max, min and increment arguments. local format: { string } = {} for index = 1, components do local sigfigs: number = defaultSigFigs[dataType][index] if thisWidget.arguments.Increment then local value: number = getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if thisWidget.arguments.Max then local value: number = getValueByIndex(thisWidget.arguments.Max, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if thisWidget.arguments.Min then local value: number = getValueByIndex(thisWidget.arguments.Min, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if sigfigs > 0 then -- we know it's a float. format[index] = `%.{sigfigs}f` else format[index] = "%d" end end thisWidget.arguments.Format = format thisWidget.arguments.Prefix = defaultPrefx[dataType] end end, Discard = function(thisWidget: Input) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, GenerateState = function(thisWidget: Input) if thisWidget.state.number == nil then thisWidget.state.number = ImGui._widgetState(thisWidget, "number", defaultValue) end if thisWidget.state.editingText == nil then thisWidget.state.editingText = ImGui._widgetState(thisWidget, "editingText", 0) end end, UpdateState = function(thisWidget: Input) local Input = thisWidget.Instance :: GuiObject for index = 1, components do local InputField: TextBox = Input:FindFirstChild("InputField" .. tostring(index)) local format: string = thisWidget.arguments.Format[index] or thisWidget.arguments.Format[1] if thisWidget.arguments.Prefix then format = thisWidget.arguments.Prefix[index] .. format end InputField.Text = string.format(format, getValueByIndex(thisWidget.state.number.value, index, thisWidget.arguments)) end end, } end end --[[ Drag ]] local generateDragScalar: (dataType: InputDataTypes, components: number, defaultValue: any) -> WidgetClass local generateColorDragScalar: (dataType: InputDataTypes, ...any) -> WidgetClass do local PreviouseMouseXPosition: number = 0 local AnyActiveDrag: boolean = false local ActiveDrag: Input? = nil local ActiveIndex: number = 0 local ActiveDataType: InputDataTypes | "" = "" local function updateActiveDrag() local currentMouseX: number = widgets.getMouseLocation().X local mouseXDelta: number = currentMouseX - PreviouseMouseXPosition PreviouseMouseXPosition = currentMouseX if AnyActiveDrag == false then return end if ActiveDrag == nil then return end local state: State = ActiveDrag.state.number if ActiveDataType == "Color3" or ActiveDataType == "Color4" then local Drag = ActiveDrag :: any state = Drag.state.color if ActiveIndex == 4 then state = Drag.state.transparency end end local increment: number = ActiveDrag.arguments.Increment and getValueByIndex(ActiveDrag.arguments.Increment, ActiveIndex, ActiveDrag.arguments) or defaultIncrements[ActiveDataType][ActiveIndex] increment *= (widgets.UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) or widgets.UserInputService:IsKeyDown(Enum.KeyCode.RightShift)) and 10 or 1 increment *= (widgets.UserInputService:IsKeyDown(Enum.KeyCode.LeftAlt) or widgets.UserInputService:IsKeyDown(Enum.KeyCode.RightAlt)) and 0.1 or 1 -- we increase the speed for Color3 and Color4 since it's too slow because the increment argument needs to be low. increment *= (ActiveDataType == "Color3" or ActiveDataType == "Color4") and 5 or 1 local value: number = getValueByIndex(state.value, ActiveIndex, ActiveDrag.arguments) local newValue: number = value + (mouseXDelta * increment) if ActiveDrag.arguments.Min ~= nil then newValue = math.max(newValue, getValueByIndex(ActiveDrag.arguments.Min, ActiveIndex, ActiveDrag.arguments)) end if ActiveDrag.arguments.Max ~= nil then newValue = math.min(newValue, getValueByIndex(ActiveDrag.arguments.Max, ActiveIndex, ActiveDrag.arguments)) end state:set(updateValueByIndex(state.value, ActiveIndex, newValue, ActiveDrag.arguments :: any)) ActiveDrag.lastNumberChangedTick = ImGui._cycleTick + 1 end local function DragMouseDown(thisWidget: Input, dataTypes: InputDataTypes, index: number, x: number, y: number) local currentTime: number = widgets.getTime() local isTimeValid: boolean = currentTime - thisWidget.lastClickedTime < ImGui._config.MouseDoubleClickTime local isCtrlHeld: boolean = widgets.UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or widgets.UserInputService:IsKeyDown(Enum.KeyCode.RightControl) if (isTimeValid and (Vector2.new(x, y) - thisWidget.lastClickedPosition).Magnitude < ImGui._config.MouseDoubleClickMaxDist) or isCtrlHeld then thisWidget.state.editingText:set(index) else thisWidget.lastClickedTime = currentTime thisWidget.lastClickedPosition = Vector2.new(x, y) AnyActiveDrag = true ActiveDrag = thisWidget ActiveIndex = index ActiveDataType = dataTypes updateActiveDrag() end end widgets.registerEvent("InputChanged", function() if not ImGui._started then return end updateActiveDrag() end) widgets.registerEvent("InputEnded", function(inputObject: InputObject) if not ImGui._started then return end if inputObject.UserInputType == Enum.UserInputType.MouseButton1 and AnyActiveDrag then AnyActiveDrag = false ActiveDrag = nil ActiveIndex = 0 end end) function generateDragScalar(dataType: InputDataTypes, components: number, defaultValue: any) return { hasState = true, hasChildren = false, Args = { ["Text"] = 1, ["Increment"] = 2, ["Min"] = 3, ["Max"] = 4, ["Format"] = 5, }, Events = { ["numberChanged"] = numberChanged, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Input) thisWidget.lastClickedTime = -1 thisWidget.lastClickedPosition = Vector2.zero local Drag: Frame = Instance.new("Frame") Drag.Name = "ImGui_Drag" .. dataType Drag.Size = UDim2.fromScale(1, 0) Drag.BackgroundTransparency = 1 Drag.BorderSizePixel = 0 Drag.LayoutOrder = thisWidget.ZIndex Drag.AutomaticSize = Enum.AutomaticSize.Y local UIListLayout: UIListLayout = widgets.UIListLayout(Drag, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center -- we add a color box if it is Color3 or Color4. local rightPadding: number = 0 local textHeight: number = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y if dataType == "Color3" or dataType == "Color4" then rightPadding += ImGui._config.ItemInnerSpacing.X + textHeight local ColorBox: ImageLabel = Instance.new("ImageLabel") ColorBox.Name = "ColorBox" ColorBox.BorderSizePixel = 0 ColorBox.Size = UDim2.fromOffset(textHeight, textHeight) ColorBox.LayoutOrder = 5 ColorBox.Image = widgets.ICONS.ALPHA_BACKGROUND_TEXTURE ColorBox.ImageTransparency = 1 widgets.applyFrameStyle(ColorBox, true) ColorBox.Parent = Drag end -- we divide the total area evenly between each field. This includes accounting for any additional boxes and the offset. -- for the final field, we make sure it's flush by calculating the space avaiable for it. This only makes the Vector2 box -- 4 pixels shorter, all for the sake of flush. local componentWidth: UDim = UDim.new(ImGui._config.ContentWidth.Scale / components, (ImGui._config.ContentWidth.Offset - (ImGui._config.ItemInnerSpacing.X * (components - 1)) - rightPadding) / components) local totalWidth: UDim = UDim.new(componentWidth.Scale * (components - 1), (componentWidth.Offset * (components - 1)) + (ImGui._config.ItemInnerSpacing.X * (components - 1)) + rightPadding) local lastComponentWidth: UDim = ImGui._config.ContentWidth - totalWidth for index = 1, components do local DragField: TextButton = Instance.new("TextButton") DragField.Name = "DragField" .. tostring(index) DragField.LayoutOrder = index if index == components then DragField.Size = UDim2.new(lastComponentWidth, ImGui._config.ContentHeight) else DragField.Size = UDim2.new(componentWidth, ImGui._config.ContentHeight) end DragField.AutomaticSize = Enum.AutomaticSize.Y DragField.BackgroundColor3 = ImGui._config.FrameBgColor DragField.BackgroundTransparency = ImGui._config.FrameBgTransparency DragField.AutoButtonColor = false DragField.Text = "" DragField.ClipsDescendants = true widgets.applyFrameStyle(DragField) widgets.applyTextStyle(DragField) widgets.UISizeConstraint(DragField, Vector2.xAxis) DragField.TextXAlignment = Enum.TextXAlignment.Center DragField.Parent = Drag widgets.applyInteractionHighlights("Background", DragField, DragField, { Color = ImGui._config.FrameBgColor, Transparency = ImGui._config.FrameBgTransparency, HoveredColor = ImGui._config.FrameBgHoveredColor, HoveredTransparency = ImGui._config.FrameBgHoveredTransparency, ActiveColor = ImGui._config.FrameBgActiveColor, ActiveTransparency = ImGui._config.FrameBgActiveTransparency, }) local InputField: TextBox = Instance.new("TextBox") InputField.Name = "InputField" InputField.Size = UDim2.new(1, 0, 1, 0) InputField.BackgroundTransparency = 1 InputField.ClearTextOnFocus = false InputField.TextTruncate = Enum.TextTruncate.AtEnd InputField.ClipsDescendants = true InputField.Visible = false widgets.applyFrameStyle(InputField, true) widgets.applyTextStyle(InputField) InputField.Parent = DragField InputField.FocusLost:Connect(function() local newValue: number? = tonumber(InputField.Text:match("-?%d*%.?%d*")) local state: State = thisWidget.state.number local widget = thisWidget :: any if dataType == "Color4" and index == 4 then state = widget.state.transparency elseif dataType == "Color3" or dataType == "Color4" then state = widget.state.color end if newValue ~= nil then if dataType == "Color3" or dataType == "Color4" and not widget.arguments.UseFloats then newValue = newValue / 255 end if thisWidget.arguments.Min ~= nil then newValue = math.max(newValue, getValueByIndex(thisWidget.arguments.Min, index, thisWidget.arguments)) end if thisWidget.arguments.Max ~= nil then newValue = math.min(newValue, getValueByIndex(thisWidget.arguments.Max, index, thisWidget.arguments)) end if thisWidget.arguments.Increment then newValue = math.round(newValue / getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments)) * getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments) end state:set(updateValueByIndex(state.value, index, newValue, thisWidget.arguments :: any)) thisWidget.lastNumberChangedTick = ImGui._cycleTick + 1 end local value: number = getValueByIndex(state.value, index, thisWidget.arguments) if dataType == "Color3" or dataType == "Color4" and not widget.arguments.UseFloats then value = math.round(value * 255) end local format: string = thisWidget.arguments.Format[index] or thisWidget.arguments.Format[1] if thisWidget.arguments.Prefix then format = thisWidget.arguments.Prefix[index] .. format end InputField.Text = string.format(format, value) thisWidget.state.editingText:set(0) InputField:ReleaseFocus(true) end) InputField.Focused:Connect(function() -- this highlights the entire field InputField.CursorPosition = #InputField.Text + 1 InputField.SelectionStart = 1 thisWidget.state.editingText:set(index) end) widgets.applyButtonDown(DragField, function(x: number, y: number) DragMouseDown(thisWidget :: any, dataType, index, x, y) end) end local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.LayoutOrder = 6 TextLabel.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(TextLabel) TextLabel.Parent = Drag return Drag end, Update = function(thisWidget: Input) local Input = thisWidget.Instance :: GuiObject local TextLabel: TextLabel = Input.TextLabel TextLabel.Text = thisWidget.arguments.Text or `Drag {dataType}` if thisWidget.arguments.Format and typeof(thisWidget.arguments.Format) ~= "table" then thisWidget.arguments.Format = { thisWidget.arguments.Format } elseif not thisWidget.arguments.Format then -- we calculate the format for the s.f. using the max, min and increment arguments. local format: { string } = {} for index = 1, components do local sigfigs: number = defaultSigFigs[dataType][index] if thisWidget.arguments.Increment then local value: number = getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if thisWidget.arguments.Max then local value: number = getValueByIndex(thisWidget.arguments.Max, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if thisWidget.arguments.Min then local value: number = getValueByIndex(thisWidget.arguments.Min, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if sigfigs > 0 then -- we know it's a float. format[index] = `%.{sigfigs}f` else format[index] = "%d" end end thisWidget.arguments.Format = format thisWidget.arguments.Prefix = defaultPrefx[dataType] end end, Discard = function(thisWidget: Input) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, GenerateState = function(thisWidget: Input) if thisWidget.state.number == nil then thisWidget.state.number = ImGui._widgetState(thisWidget, "number", defaultValue) end if thisWidget.state.editingText == nil then thisWidget.state.editingText = ImGui._widgetState(thisWidget, "editingText", false) end end, UpdateState = function(thisWidget: Input) local Drag = thisWidget.Instance :: Frame local widget = thisWidget :: any for index = 1, components do local state: State = thisWidget.state.number if dataType == "Color3" or dataType == "Color4" then state = widget.state.color if index == 4 then state = widget.state.transparency end end local DragField = Drag:FindFirstChild("DragField" .. tostring(index)) :: TextButton local InputField: TextBox = DragField.InputField local value: number = getValueByIndex(state.value, index, thisWidget.arguments) if (dataType == "Color3" or dataType == "Color4") and not widget.arguments.UseFloats then value = math.round(value * 255) end local format: string = thisWidget.arguments.Format[index] or thisWidget.arguments.Format[1] if thisWidget.arguments.Prefix then format = thisWidget.arguments.Prefix[index] .. format end DragField.Text = string.format(format, value) InputField.Text = tostring(value) if thisWidget.state.editingText.value == index then InputField.Visible = true InputField:CaptureFocus() DragField.TextTransparency = 1 else InputField.Visible = false DragField.TextTransparency = ImGui._config.TextTransparency end end if dataType == "Color3" or dataType == "Color4" then local ColorBox: ImageLabel = Drag.ColorBox ColorBox.BackgroundColor3 = widget.state.color.value if dataType == "Color4" then ColorBox.ImageTransparency = 1 - widget.state.transparency.value end end end, } end function generateColorDragScalar(dataType: InputDataTypes, ...: any) local defaultValues: { any } = { ... } local input: WidgetClass = generateDragScalar(dataType, dataType == "Color4" and 4 or 3, defaultValues[1]) return widgets.extend(input, { Args = { ["Text"] = 1, ["UseFloats"] = 2, ["UseHSV"] = 3, ["Format"] = 4, }, Update = function(thisWidget: InputColor4) local Input = thisWidget.Instance :: GuiObject local TextLabel: TextLabel = Input.TextLabel TextLabel.Text = thisWidget.arguments.Text or `Drag {dataType}` if thisWidget.arguments.Format and typeof(thisWidget.arguments.Format) ~= "table" then thisWidget.arguments.Format = { thisWidget.arguments.Format } elseif not thisWidget.arguments.Format then if thisWidget.arguments.UseFloats then thisWidget.arguments.Format = { "%.3f" } else thisWidget.arguments.Format = { "%d" } end thisWidget.arguments.Prefix = defaultPrefx[dataType .. if thisWidget.arguments.UseHSV then "_HSV" else "_RGB"] end thisWidget.arguments.Min = { 0, 0, 0, 0 } thisWidget.arguments.Max = { 1, 1, 1, 1 } thisWidget.arguments.Increment = { 0.001, 0.001, 0.001, 0.001 } -- since the state values have changed display, we call an update. The check is because state is not -- initialised on creation, so it would error otherwise. if thisWidget.state then ImGui._widgets[thisWidget.type].UpdateState(thisWidget) end end, GenerateState = function(thisWidget: InputColor4) if thisWidget.state.color == nil then thisWidget.state.color = ImGui._widgetState(thisWidget, "color", defaultValues[1]) end if dataType == "Color4" then if thisWidget.state.transparency == nil then thisWidget.state.transparency = ImGui._widgetState(thisWidget, "transparency", defaultValues[2]) end end if thisWidget.state.editingText == nil then thisWidget.state.editingText = ImGui._widgetState(thisWidget, "editingText", false) end end, }) end end --[[ Slider ]] local generateSliderScalar: (dataType: InputDataTypes, components: number, defaultValue: any) -> WidgetClass local generateEnumSliderScalar: (enum: Enum, item: EnumItem) -> WidgetClass do local AnyActiveSlider: boolean = false local ActiveSlider: Input? = nil local ActiveIndex: number = 0 local ActiveDataType: InputDataTypes | "" = "" local function updateActiveSlider() if AnyActiveSlider == false then return end if ActiveSlider == nil then return end local Slider = ActiveSlider.Instance :: Frame local SliderField = Slider:FindFirstChild("SliderField" .. tostring(ActiveIndex)) :: TextButton local GrabBar: Frame = SliderField.GrabBar local increment: number = ActiveSlider.arguments.Increment and getValueByIndex(ActiveSlider.arguments.Increment, ActiveIndex, ActiveSlider.arguments) or defaultIncrements[ActiveDataType][ActiveIndex] local min: number = ActiveSlider.arguments.Min and getValueByIndex(ActiveSlider.arguments.Min, ActiveIndex, ActiveSlider.arguments) or defaultMin[ActiveDataType][ActiveIndex] local max: number = ActiveSlider.arguments.Max and getValueByIndex(ActiveSlider.arguments.Max, ActiveIndex, ActiveSlider.arguments) or defaultMax[ActiveDataType][ActiveIndex] local GrabWidth: number = GrabBar.AbsoluteSize.X local Offset: number = widgets.getMouseLocation().X - (SliderField.AbsolutePosition.X - widgets.GuiOffset.X + GrabWidth / 2) local Ratio: number = Offset / (SliderField.AbsoluteSize.X - GrabWidth) local Positions: number = math.floor((max - min) / increment) local newValue: number = math.clamp(math.round(Ratio * Positions) * increment + min, min, max) ActiveSlider.state.number:set(updateValueByIndex(ActiveSlider.state.number.value, ActiveIndex, newValue, ActiveSlider.arguments :: any)) ActiveSlider.lastNumberChangedTick = ImGui._cycleTick + 1 end local function SliderMouseDown(thisWidget: Input, dataType: InputDataTypes, index: number) local isCtrlHeld: boolean = widgets.UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or widgets.UserInputService:IsKeyDown(Enum.KeyCode.RightControl) if isCtrlHeld then thisWidget.state.editingText:set(index) else AnyActiveSlider = true ActiveSlider = thisWidget ActiveIndex = index ActiveDataType = dataType updateActiveSlider() end end widgets.registerEvent("InputChanged", function() if not ImGui._started then return end updateActiveSlider() end) widgets.registerEvent("InputEnded", function(inputObject: InputObject) if not ImGui._started then return end if inputObject.UserInputType == Enum.UserInputType.MouseButton1 and AnyActiveSlider then AnyActiveSlider = false ActiveSlider = nil ActiveIndex = 0 ActiveDataType = "" end end) function generateSliderScalar(dataType: InputDataTypes, components: number, defaultValue: any) return { hasState = true, hasChildren = false, Args = { ["Text"] = 1, ["Increment"] = 2, ["Min"] = 3, ["Max"] = 4, ["Format"] = 5, }, Events = { ["numberChanged"] = numberChanged, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Input) local Slider: Frame = Instance.new("Frame") Slider.Name = "ImGui_Slider" .. dataType Slider.Size = UDim2.fromScale(1, 0) Slider.BackgroundTransparency = 1 Slider.BorderSizePixel = 0 Slider.LayoutOrder = thisWidget.ZIndex Slider.AutomaticSize = Enum.AutomaticSize.Y local UIListLayout: UIListLayout = widgets.UIListLayout(Slider, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center -- we divide the total area evenly between each field. This includes accounting for any additional boxes and the offset. -- for the final field, we make sure it's flush by calculating the space avaiable for it. This only makes the Vector2 box -- 4 pixels shorter, all for the sake of flush. local componentWidth: UDim = UDim.new(ImGui._config.ContentWidth.Scale / components, (ImGui._config.ContentWidth.Offset - (ImGui._config.ItemInnerSpacing.X * (components - 1))) / components) local totalWidth: UDim = UDim.new(componentWidth.Scale * (components - 1), (componentWidth.Offset * (components - 1)) + (ImGui._config.ItemInnerSpacing.X * (components - 1))) local lastComponentWidth: UDim = ImGui._config.ContentWidth - totalWidth for index = 1, components do local SliderField: TextButton = Instance.new("TextButton") SliderField.Name = "SliderField" .. tostring(index) SliderField.LayoutOrder = index if index == components then SliderField.Size = UDim2.new(lastComponentWidth, ImGui._config.ContentHeight) else SliderField.Size = UDim2.new(componentWidth, ImGui._config.ContentHeight) end SliderField.AutomaticSize = Enum.AutomaticSize.Y SliderField.BackgroundColor3 = ImGui._config.FrameBgColor SliderField.BackgroundTransparency = ImGui._config.FrameBgTransparency SliderField.AutoButtonColor = false SliderField.Text = "" SliderField.ClipsDescendants = true widgets.applyFrameStyle(SliderField) widgets.applyTextStyle(SliderField) widgets.UISizeConstraint(SliderField, Vector2.xAxis) SliderField.Parent = Slider local OverlayText = Instance.new("TextLabel") OverlayText.Name = "OverlayText" OverlayText.Size = UDim2.fromScale(1, 1) OverlayText.BackgroundTransparency = 1 OverlayText.BorderSizePixel = 0 OverlayText.ZIndex = 10 OverlayText.ClipsDescendants = true widgets.applyTextStyle(OverlayText) OverlayText.TextXAlignment = Enum.TextXAlignment.Center OverlayText.Parent = SliderField widgets.applyInteractionHighlights("Background", SliderField, SliderField, { Color = ImGui._config.FrameBgColor, Transparency = ImGui._config.FrameBgTransparency, HoveredColor = ImGui._config.FrameBgHoveredColor, HoveredTransparency = ImGui._config.FrameBgHoveredTransparency, ActiveColor = ImGui._config.FrameBgActiveColor, ActiveTransparency = ImGui._config.FrameBgActiveTransparency, }) local InputField: TextBox = Instance.new("TextBox") InputField.Name = "InputField" InputField.Size = UDim2.new(1, 0, 1, 0) InputField.BackgroundTransparency = 1 InputField.ClearTextOnFocus = false InputField.TextTruncate = Enum.TextTruncate.AtEnd InputField.ClipsDescendants = true InputField.Visible = false widgets.applyFrameStyle(InputField, true) widgets.applyTextStyle(InputField) InputField.Parent = SliderField InputField.FocusLost:Connect(function() local newValue: number? = tonumber(InputField.Text:match("-?%d*%.?%d*")) if newValue ~= nil then if thisWidget.arguments.Min ~= nil then newValue = math.max(newValue, getValueByIndex(thisWidget.arguments.Min, index, thisWidget.arguments)) end if thisWidget.arguments.Max ~= nil then newValue = math.min(newValue, getValueByIndex(thisWidget.arguments.Max, index, thisWidget.arguments)) end if thisWidget.arguments.Increment then newValue = math.round(newValue / getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments)) * getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments) end thisWidget.state.number:set(updateValueByIndex(thisWidget.state.number.value, index, newValue, thisWidget.arguments :: any)) thisWidget.lastNumberChangedTick = ImGui._cycleTick + 1 end local format: string = thisWidget.arguments.Format[index] or thisWidget.arguments.Format[1] if thisWidget.arguments.Prefix then format = thisWidget.arguments.Prefix[index] .. format end InputField.Text = string.format(format, getValueByIndex(thisWidget.state.number.value, index, thisWidget.arguments)) thisWidget.state.editingText:set(0) InputField:ReleaseFocus(true) end) InputField.Focused:Connect(function() -- this highlights the entire field InputField.CursorPosition = #InputField.Text + 1 InputField.SelectionStart = 1 thisWidget.state.editingText:set(index) end) widgets.applyButtonDown(SliderField, function() SliderMouseDown(thisWidget :: any, dataType, index) end) local GrabBar: Frame = Instance.new("Frame") GrabBar.Name = "GrabBar" GrabBar.ZIndex = 5 GrabBar.AnchorPoint = Vector2.new(0.5, 0.5) GrabBar.Position = UDim2.new(0, 0, 0.5, 0) GrabBar.BorderSizePixel = 0 GrabBar.BackgroundColor3 = ImGui._config.SliderGrabColor GrabBar.Transparency = ImGui._config.SliderGrabTransparency if ImGui._config.GrabRounding > 0 then widgets.UICorner(GrabBar, ImGui._config.GrabRounding) end widgets.UISizeConstraint(GrabBar, Vector2.new(ImGui._config.GrabMinSize, 0)) GrabBar.Parent = SliderField end local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.LayoutOrder = 5 TextLabel.AutomaticSize = Enum.AutomaticSize.XY widgets.applyTextStyle(TextLabel) TextLabel.Parent = Slider return Slider end, Update = function(thisWidget: Input) local Input = thisWidget.Instance :: GuiObject local TextLabel: TextLabel = Input.TextLabel TextLabel.Text = thisWidget.arguments.Text or `Slider {dataType}` if thisWidget.arguments.Format and typeof(thisWidget.arguments.Format) ~= "table" then thisWidget.arguments.Format = { thisWidget.arguments.Format } elseif not thisWidget.arguments.Format then -- we calculate the format for the s.f. using the max, min and increment arguments. local format: { string } = {} for index = 1, components do local sigfigs: number = defaultSigFigs[dataType][index] if thisWidget.arguments.Increment then local value: number = getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if thisWidget.arguments.Max then local value: number = getValueByIndex(thisWidget.arguments.Max, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if thisWidget.arguments.Min then local value: number = getValueByIndex(thisWidget.arguments.Min, index, thisWidget.arguments) sigfigs = math.max(sigfigs, math.ceil(-math.log10(value == 0 and 1 or value)), sigfigs) end if sigfigs > 0 then -- we know it's a float. format[index] = `%.{sigfigs}f` else format[index] = "%d" end end thisWidget.arguments.Format = format thisWidget.arguments.Prefix = defaultPrefx[dataType] end for index = 1, components do local SliderField = Input:FindFirstChild("SliderField" .. tostring(index)) :: TextButton local GrabBar: Frame = SliderField.GrabBar local increment: number = thisWidget.arguments.Increment and getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments) or defaultIncrements[dataType][index] local min: number = thisWidget.arguments.Min and getValueByIndex(thisWidget.arguments.Min, index, thisWidget.arguments) or defaultMin[dataType][index] local max: number = thisWidget.arguments.Max and getValueByIndex(thisWidget.arguments.Max, index, thisWidget.arguments) or defaultMax[dataType][index] local grabScaleSize: number = 1 / math.floor((1 + max - min) / increment) GrabBar.Size = UDim2.new(grabScaleSize, 0, 1, 0) end local callbackIndex: number = #ImGui._postCycleCallbacks + 1 local desiredCycleTick: number = ImGui._cycleTick + 1 ImGui._postCycleCallbacks[callbackIndex] = function() if ImGui._cycleTick >= desiredCycleTick then if thisWidget.lastCycleTick ~= -1 then ImGui._widgets[`Slider{dataType}`].UpdateState(thisWidget) end ImGui._postCycleCallbacks[callbackIndex] = nil end end end, Discard = function(thisWidget: Input) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, GenerateState = function(thisWidget: Input) if thisWidget.state.number == nil then thisWidget.state.number = ImGui._widgetState(thisWidget, "number", defaultValue) end if thisWidget.state.editingText == nil then thisWidget.state.editingText = ImGui._widgetState(thisWidget, "editingText", false) end end, UpdateState = function(thisWidget: Input) local Slider = thisWidget.Instance :: Frame for index = 1, components do local SliderField = Slider:FindFirstChild("SliderField" .. tostring(index)) :: TextButton local InputField: TextBox = SliderField.InputField local OverlayText: TextLabel = SliderField.OverlayText local GrabBar: Frame = SliderField.GrabBar local value: number = getValueByIndex(thisWidget.state.number.value, index, thisWidget.arguments) local format: string = thisWidget.arguments.Format[index] or thisWidget.arguments.Format[1] if thisWidget.arguments.Prefix then format = thisWidget.arguments.Prefix[index] .. format end OverlayText.Text = string.format(format, value) InputField.Text = tostring(value) local increment: number = thisWidget.arguments.Increment and getValueByIndex(thisWidget.arguments.Increment, index, thisWidget.arguments) or defaultIncrements[dataType][index] local min: number = thisWidget.arguments.Min and getValueByIndex(thisWidget.arguments.Min, index, thisWidget.arguments) or defaultMin[dataType][index] local max: number = thisWidget.arguments.Max and getValueByIndex(thisWidget.arguments.Max, index, thisWidget.arguments) or defaultMax[dataType][index] local SliderWidth: number = SliderField.AbsoluteSize.X local PaddedWidth: number = SliderWidth - GrabBar.AbsoluteSize.X local Ratio: number = (value - min) / (max - min) local Positions: number = math.floor((max - min) / increment) local ClampedRatio: number = math.clamp(math.floor((Ratio * Positions)) / Positions, 0, 1) local PaddedRatio: number = ((PaddedWidth / SliderWidth) * ClampedRatio) + ((1 - (PaddedWidth / SliderWidth)) / 2) GrabBar.Position = UDim2.new(PaddedRatio, 0, 0.5, 0) if thisWidget.state.editingText.value == index then InputField.Visible = true OverlayText.Visible = false GrabBar.Visible = false InputField:CaptureFocus() else InputField.Visible = false OverlayText.Visible = true GrabBar.Visible = true end end end, } end function generateEnumSliderScalar(enum: Enum, item: EnumItem) local input: WidgetClass = generateSliderScalar("Enum", 1, item.Value) local valueToName = { string } for _, enumItem: EnumItem in enum:GetEnumItems() do valueToName[enumItem.Value] = enumItem.Name end return widgets.extend(input, { Args = { ["Text"] = 1, }, Update = function(thisWidget: InputEnum) local Input = thisWidget.Instance :: GuiObject local TextLabel: TextLabel = Input.TextLabel TextLabel.Text = thisWidget.arguments.Text or "Input Enum" thisWidget.arguments.Increment = 1 thisWidget.arguments.Min = 0 thisWidget.arguments.Max = #enum:GetEnumItems() - 1 local SliderField = Input:FindFirstChild("SliderField1") :: TextButton local GrabBar: Frame = SliderField.GrabBar local grabScaleSize: number = 1 / math.floor(#enum:GetEnumItems()) GrabBar.Size = UDim2.new(grabScaleSize, 0, 1, 0) end, GenerateState = function(thisWidget: InputEnum) if thisWidget.state.number == nil then thisWidget.state.number = ImGui._widgetState(thisWidget, "number", item.Value) end if thisWidget.state.enumItem == nil then thisWidget.state.enumItem = ImGui._widgetState(thisWidget, "enumItem", item) end if thisWidget.state.editingText == nil then thisWidget.state.editingText = ImGui._widgetState(thisWidget, "editingText", false) end end, }) end end do local inputNum: WidgetClass = generateInputScalar("Num", 1, 0) inputNum.Args["NoButtons"] = 6 ImGui.WidgetConstructor("InputNum", inputNum) end ImGui.WidgetConstructor("InputVector2", generateInputScalar("Vector2", 2, Vector2.zero)) ImGui.WidgetConstructor("InputVector3", generateInputScalar("Vector3", 3, Vector3.zero)) ImGui.WidgetConstructor("InputUDim", generateInputScalar("UDim", 2, UDim.new())) ImGui.WidgetConstructor("InputUDim2", generateInputScalar("UDim2", 4, UDim2.new())) ImGui.WidgetConstructor("InputRect", generateInputScalar("Rect", 4, Rect.new(0, 0, 0, 0))) ImGui.WidgetConstructor("DragNum", generateDragScalar("Num", 1, 0)) ImGui.WidgetConstructor("DragVector2", generateDragScalar("Vector2", 2, Vector2.zero)) ImGui.WidgetConstructor("DragVector3", generateDragScalar("Vector3", 3, Vector3.zero)) ImGui.WidgetConstructor("DragUDim", generateDragScalar("UDim", 2, UDim.new())) ImGui.WidgetConstructor("DragUDim2", generateDragScalar("UDim2", 4, UDim2.new())) ImGui.WidgetConstructor("DragRect", generateDragScalar("Rect", 4, Rect.new(0, 0, 0, 0))) ImGui.WidgetConstructor("InputColor3", generateColorDragScalar("Color3", Color3.fromRGB(0, 0, 0))) ImGui.WidgetConstructor("InputColor4", generateColorDragScalar("Color4", Color3.fromRGB(0, 0, 0), 0)) ImGui.WidgetConstructor("SliderNum", generateSliderScalar("Num", 1, 0)) ImGui.WidgetConstructor("SliderVector2", generateSliderScalar("Vector2", 2, Vector2.zero)) ImGui.WidgetConstructor("SliderVector3", generateSliderScalar("Vector3", 3, Vector3.zero)) ImGui.WidgetConstructor("SliderUDim", generateSliderScalar("UDim", 2, UDim.new())) ImGui.WidgetConstructor("SliderUDim2", generateSliderScalar("UDim2", 4, UDim2.new())) ImGui.WidgetConstructor("SliderRect", generateSliderScalar("Rect", 4, Rect.new(0, 0, 0, 0))) -- ImGui.WidgetConstructor("SliderEnum", generateSliderScalar("Enum", 4, 0)) -- stylua: ignore ImGui.WidgetConstructor("InputText", { hasState = true, hasChildren = false, Args = { ["Text"] = 1, ["TextHint"] = 2, ["ReadOnly"] = 3, ["MultiLine"] = 4, }, Events = { ["textChanged"] = { ["Init"] = function(thisWidget: InputText) thisWidget.lastTextChangedTick = 0 end, ["Get"] = function(thisWidget: InputText) return thisWidget.lastTextChangedTick == ImGui._cycleTick end, }, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: InputText) local InputText: Frame = Instance.new("Frame") InputText.Name = "ImGui_InputText" InputText.AutomaticSize = Enum.AutomaticSize.Y InputText.Size = UDim2.fromScale(1, 0) InputText.BackgroundTransparency = 1 InputText.BorderSizePixel = 0 InputText.ZIndex = thisWidget.ZIndex InputText.LayoutOrder = thisWidget.ZIndex local UIListLayout: UIListLayout = widgets.UIListLayout(InputText, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center local InputField: TextBox = Instance.new("TextBox") InputField.Name = "InputField" InputField.Size = UDim2.new(ImGui._config.ContentWidth, ImGui._config.ContentHeight) InputField.AutomaticSize = Enum.AutomaticSize.Y InputField.BackgroundColor3 = ImGui._config.FrameBgColor InputField.BackgroundTransparency = ImGui._config.FrameBgTransparency InputField.Text = "" InputField.TextYAlignment = Enum.TextYAlignment.Top InputField.PlaceholderColor3 = ImGui._config.TextDisabledColor InputField.ClearTextOnFocus = false InputField.ClipsDescendants = true widgets.applyFrameStyle(InputField) widgets.applyTextStyle(InputField) widgets.UISizeConstraint(InputField, Vector2.xAxis) -- prevents sizes beaking when getting too small. -- InputField.UIPadding.PaddingLeft = UDim.new(0, ImGui._config.ItemInnerSpacing.X) -- InputField.UIPadding.PaddingRight = UDim.new(0, 0) InputField.Parent = InputText InputField.FocusLost:Connect(function() thisWidget.state.text:set(InputField.Text) thisWidget.lastTextChangedTick = ImGui._cycleTick + 1 end) local frameHeight: number = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.Size = UDim2.fromOffset(0, frameHeight) TextLabel.AutomaticSize = Enum.AutomaticSize.X TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.LayoutOrder = 1 widgets.applyTextStyle(TextLabel) TextLabel.Parent = InputText return InputText end, Update = function(thisWidget: InputText) local InputText = thisWidget.Instance :: Frame local TextLabel: TextLabel = InputText.TextLabel local InputField: TextBox = InputText.InputField TextLabel.Text = thisWidget.arguments.Text or "Input Text" InputField.PlaceholderText = thisWidget.arguments.TextHint or "" InputField.TextEditable = not thisWidget.arguments.ReadOnly InputField.MultiLine = thisWidget.arguments.MultiLine or false end, Discard = function(thisWidget: InputText) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, GenerateState = function(thisWidget: InputText) if thisWidget.state.text == nil then thisWidget.state.text = ImGui._widgetState(thisWidget, "text", "") end end, UpdateState = function(thisWidget: InputText) local InputText = thisWidget.Instance :: Frame local InputField: TextBox = InputText.InputField InputField.Text = thisWidget.state.text.value end, } :: WidgetClass) -- blah blah blah here ImGui.WidgetConstructor("ScriptEditor", { hasState = true, hasChildren = false, Args = { ["Text"] = 1, ["TextHint"] = 2, ["ReadOnly"] = 3, ["MultiLine"] = 4, }, Events = { ["textChanged"] = { ["Init"] = function(thisWidget: InputText) thisWidget.lastTextChangedTick = 0 end, ["Get"] = function(thisWidget: InputText) return thisWidget.lastTextChangedTick == ImGui._cycleTick end, }, ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: InputText) local InputText: Frame = Instance.new("Frame") InputText.Name = "ImGui_InputText" InputText.AutomaticSize = Enum.AutomaticSize.Y InputText.Size = UDim2.fromScale(1, 0) InputText.BackgroundTransparency = 1 InputText.BorderSizePixel = 0 InputText.ZIndex = thisWidget.ZIndex InputText.LayoutOrder = thisWidget.ZIndex local UIListLayout: UIListLayout = widgets.UIListLayout(InputText, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center local Editor = ScriptEditor.Embed(InputText) Editor.AutoWrapEnabled = true Editor:SetStringAsync('print("Hello, World!")\n -- still need to hook this up to state managment', 1) --[[ InputField.FocusLost:Connect(function() thisWidget.state.text:set(InputField.Text) thisWidget.lastTextChangedTick = ImGui._cycleTick + 1 end) ]]-- local frameHeight: number = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.Size = UDim2.fromOffset(0, frameHeight) TextLabel.AutomaticSize = Enum.AutomaticSize.X TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.LayoutOrder = 1 widgets.applyTextStyle(TextLabel) TextLabel.Parent = InputText return InputText end, Update = function(thisWidget: InputText) local InputText = thisWidget.Instance :: Frame local TextLabel: TextLabel = InputText.TextLabel TextLabel.Text = thisWidget.arguments.Text or "Input Text" end, Discard = function(thisWidget: InputText) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, GenerateState = function(thisWidget: InputText) if thisWidget.state.text == nil then thisWidget.state.text = ImGui._widgetState(thisWidget, "text", "") end end, UpdateState = function(thisWidget: InputText) local InputText = thisWidget.Instance :: Frame end, } :: WidgetClass) end, Combo = function(ImGui: Internal, widgets: WidgetUtility) ImGui.WidgetConstructor("Selectable", { hasState = true, hasChildren = false, Args = { ["Text"] = 1, ["Index"] = 2, ["NoClick"] = 3, }, Events = { ["selected"] = { ["Init"] = function(_thisWidget: Selectable) end, ["Get"] = function(thisWidget: Selectable) return thisWidget.lastSelectedTick == ImGui._cycleTick end, }, ["unselected"] = { ["Init"] = function(_thisWidget: Selectable) end, ["Get"] = function(thisWidget: Selectable) return thisWidget.lastUnselectedTick == ImGui._cycleTick end, }, ["active"] = { ["Init"] = function(_thisWidget: Selectable) end, ["Get"] = function(thisWidget: Selectable) return thisWidget.state.index.value == thisWidget.arguments.Index end, }, ["clicked"] = widgets.EVENTS.click(function(thisWidget: Widget) local Selectable = thisWidget.Instance :: Frame return Selectable.SelectableButton end), ["rightClicked"] = widgets.EVENTS.rightClick(function(thisWidget: Widget) local Selectable = thisWidget.Instance :: Frame return Selectable.SelectableButton end), ["doubleClicked"] = widgets.EVENTS.doubleClick(function(thisWidget: Widget) local Selectable = thisWidget.Instance :: Frame return Selectable.SelectableButton end), ["ctrlClicked"] = widgets.EVENTS.ctrlClick(function(thisWidget: Widget) local Selectable = thisWidget.Instance :: Frame return Selectable.SelectableButton end), ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) local Selectable = thisWidget.Instance :: Frame return Selectable.SelectableButton end), }, Generate = function(thisWidget: Selectable) local Selectable: Frame = Instance.new("Frame") Selectable.Name = "ImGui_Selectable" Selectable.Size = UDim2.new(ImGui._config.ItemWidth, UDim.new(0, ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y - ImGui._config.ItemSpacing.Y)) Selectable.BackgroundTransparency = 1 Selectable.BorderSizePixel = 0 Selectable.ZIndex = 0 Selectable.LayoutOrder = thisWidget.ZIndex local SelectableButton: TextButton = Instance.new("TextButton") SelectableButton.Name = "SelectableButton" SelectableButton.Size = UDim2.new(1, 0, 0, ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y) SelectableButton.Position = UDim2.fromOffset(0, -bit32.rshift(ImGui._config.ItemSpacing.Y, 1)) SelectableButton.BackgroundColor3 = ImGui._config.HeaderColor SelectableButton.ClipsDescendants = true widgets.applyFrameStyle(SelectableButton) widgets.applyTextStyle(SelectableButton) widgets.UISizeConstraint(SelectableButton, Vector2.xAxis) thisWidget.ButtonColors = { Color = ImGui._config.HeaderColor, Transparency = 1, HoveredColor = ImGui._config.HeaderHoveredColor, HoveredTransparency = ImGui._config.HeaderHoveredTransparency, ActiveColor = ImGui._config.HeaderActiveColor, ActiveTransparency = ImGui._config.HeaderActiveTransparency, } widgets.applyInteractionHighlights("Background", SelectableButton, SelectableButton, thisWidget.ButtonColors) widgets.applyButtonClick(SelectableButton, function() if thisWidget.arguments.NoClick ~= true then if type(thisWidget.state.index.value) == "boolean" then thisWidget.state.index:set(not thisWidget.state.index.value) else thisWidget.state.index:set(thisWidget.arguments.Index) end end end) SelectableButton.Parent = Selectable return Selectable end, Update = function(thisWidget: Selectable) local Selectable = thisWidget.Instance :: Frame local SelectableButton: TextButton = Selectable.SelectableButton SelectableButton.Text = thisWidget.arguments.Text or "Selectable" end, Discard = function(thisWidget: Selectable) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, GenerateState = function(thisWidget: Selectable) if thisWidget.state.index == nil then if thisWidget.arguments.Index ~= nil then error("a shared state index is required for Selectables with an Index argument", 5) end thisWidget.state.index = ImGui._widgetState(thisWidget, "index", false) end end, UpdateState = function(thisWidget: Selectable) local Selectable = thisWidget.Instance :: Frame local SelectableButton: TextButton = Selectable.SelectableButton if thisWidget.state.index.value == (thisWidget.arguments.Index or true) then thisWidget.ButtonColors.Transparency = ImGui._config.HeaderTransparency SelectableButton.BackgroundTransparency = ImGui._config.HeaderTransparency thisWidget.lastSelectedTick = ImGui._cycleTick + 1 else thisWidget.ButtonColors.Transparency = 1 SelectableButton.BackgroundTransparency = 1 thisWidget.lastUnselectedTick = ImGui._cycleTick + 1 end end, } :: WidgetClass) local AnyOpenedCombo: boolean = false local ComboOpenedTick: number = -1 local OpenedCombo: Combo? = nil local function UpdateChildContainerTransform(thisWidget: Combo) local Combo = thisWidget.Instance :: Frame local PreviewContainer = Combo.PreviewContainer :: TextButton local ChildContainer = thisWidget.ChildContainer :: ScrollingFrame ChildContainer.Size = UDim2.fromOffset(PreviewContainer.AbsoluteSize.X, 0) local previewPosition: Vector2 = PreviewContainer.AbsolutePosition - widgets.GuiOffset local previewSize: Vector2 = PreviewContainer.AbsoluteSize local containerSize: Vector2 = ChildContainer.AbsoluteSize local borderSize: number = ImGui._config.PopupBorderSize local screenSize: Vector2 = ChildContainer.Parent.AbsoluteSize local x: number = previewPosition.X local y: number local anchor: Vector2 = Vector2.zero if previewPosition.Y + containerSize.Y > screenSize.Y then y = previewPosition.Y - borderSize anchor = Vector2.yAxis else y = previewPosition.Y + previewSize.Y + borderSize end ChildContainer.AnchorPoint = anchor ChildContainer.Position = UDim2.fromOffset(x, y) end widgets.registerEvent("InputBegan", function(inputObject: InputObject) if not ImGui._started then return end if inputObject.UserInputType ~= Enum.UserInputType.MouseButton1 and inputObject.UserInputType ~= Enum.UserInputType.MouseButton2 and inputObject.UserInputType ~= Enum.UserInputType.Touch then return end if AnyOpenedCombo == false or not OpenedCombo then return end if ComboOpenedTick == ImGui._cycleTick then return end local MouseLocation: Vector2 = widgets.getMouseLocation() local Combo = OpenedCombo.Instance :: Frame local PreviewContainer: TextButton = Combo.PreviewContainer local ChildContainer = OpenedCombo.ChildContainer local rectMin: Vector2 = PreviewContainer.AbsolutePosition - widgets.GuiOffset local rectMax: Vector2 = PreviewContainer.AbsolutePosition - widgets.GuiOffset + PreviewContainer.AbsoluteSize if widgets.isPosInsideRect(MouseLocation, rectMin, rectMax) then return end rectMin = ChildContainer.AbsolutePosition - widgets.GuiOffset rectMax = ChildContainer.AbsolutePosition - widgets.GuiOffset + ChildContainer.AbsoluteSize if widgets.isPosInsideRect(MouseLocation, rectMin, rectMax) then return end OpenedCombo.state.isOpened:set(false) end) ImGui.WidgetConstructor("Combo", { hasState = true, hasChildren = true, Args = { ["Text"] = 1, ["NoButton"] = 2, ["NoPreview"] = 3, }, Events = { ["opened"] = { ["Init"] = function(_thisWidget: Combo) end, ["Get"] = function(thisWidget: Combo) return thisWidget.lastOpenedTick == ImGui._cycleTick end, }, ["closed"] = { ["Init"] = function(_thisWidget: Combo) end, ["Get"] = function(thisWidget: Combo) return thisWidget.lastClosedTick == ImGui._cycleTick end, }, ["clicked"] = widgets.EVENTS.click(function(thisWidget: Widget) return thisWidget.Instance end), ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Combo) local frameHeight: number = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y local Combo: Frame = Instance.new("Frame") Combo.Name = "ImGui_Combo" Combo.Size = UDim2.fromScale(1, 0) Combo.AutomaticSize = Enum.AutomaticSize.Y Combo.BackgroundTransparency = 1 Combo.BorderSizePixel = 0 Combo.LayoutOrder = thisWidget.ZIndex local UIListLayout: UIListLayout = widgets.UIListLayout(Combo, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center local PreviewContainer: TextButton = Instance.new("TextButton") PreviewContainer.Name = "PreviewContainer" PreviewContainer.Size = UDim2.new(ImGui._config.ContentWidth, UDim.new(0, 0)) PreviewContainer.AutomaticSize = Enum.AutomaticSize.Y PreviewContainer.BackgroundTransparency = 1 PreviewContainer.Text = "" PreviewContainer.ZIndex = thisWidget.ZIndex + 2 PreviewContainer.AutoButtonColor = false widgets.applyFrameStyle(PreviewContainer, true) widgets.UIListLayout(PreviewContainer, Enum.FillDirection.Horizontal, UDim.new(0, 0)) widgets.UISizeConstraint(PreviewContainer, Vector2.new(frameHeight + 1)) PreviewContainer.Parent = Combo local PreviewLabel: TextLabel = Instance.new("TextLabel") PreviewLabel.Name = "PreviewLabel" PreviewLabel.Size = UDim2.new(UDim.new(1, 0), ImGui._config.ContentHeight) PreviewLabel.AutomaticSize = Enum.AutomaticSize.Y PreviewLabel.BackgroundColor3 = ImGui._config.FrameBgColor PreviewLabel.BackgroundTransparency = ImGui._config.FrameBgTransparency PreviewLabel.BorderSizePixel = 0 PreviewLabel.ClipsDescendants = true widgets.applyTextStyle(PreviewLabel) widgets.UIPadding(PreviewLabel, ImGui._config.FramePadding) PreviewLabel.Parent = PreviewContainer local DropdownButton: TextLabel = Instance.new("TextLabel") DropdownButton.Name = "DropdownButton" DropdownButton.Size = UDim2.new(0, frameHeight, ImGui._config.ContentHeight.Scale, math.max(ImGui._config.ContentHeight.Offset, frameHeight)) DropdownButton.BorderSizePixel = 0 DropdownButton.BackgroundColor3 = ImGui._config.ButtonColor DropdownButton.BackgroundTransparency = ImGui._config.ButtonTransparency DropdownButton.Text = "" local padding: number = math.round(frameHeight * 0.2) local dropdownSize: number = frameHeight - 2 * padding local Dropdown: ImageLabel = Instance.new("ImageLabel") Dropdown.Name = "Dropdown" Dropdown.AnchorPoint = Vector2.new(0.5, 0.5) Dropdown.Size = UDim2.fromOffset(dropdownSize, dropdownSize) Dropdown.Position = UDim2.fromScale(0.5, 0.5) Dropdown.BackgroundTransparency = 1 Dropdown.BorderSizePixel = 0 Dropdown.ImageColor3 = ImGui._config.TextColor Dropdown.ImageTransparency = ImGui._config.TextTransparency Dropdown.Parent = DropdownButton DropdownButton.Parent = PreviewContainer widgets.applyInteractionHighlightsWithMultiHighlightee("Background", PreviewContainer, { { PreviewLabel, { Color = ImGui._config.FrameBgColor, Transparency = ImGui._config.FrameBgTransparency, HoveredColor = ImGui._config.FrameBgHoveredColor, HoveredTransparency = ImGui._config.FrameBgHoveredTransparency, ActiveColor = ImGui._config.FrameBgActiveColor, ActiveTransparency = ImGui._config.FrameBgActiveTransparency, }, }, { DropdownButton, { Color = ImGui._config.ButtonColor, Transparency = ImGui._config.ButtonTransparency, HoveredColor = ImGui._config.ButtonHoveredColor, HoveredTransparency = ImGui._config.ButtonHoveredTransparency, ActiveColor = ImGui._config.ButtonHoveredColor, ActiveTransparency = ImGui._config.ButtonHoveredTransparency, }, }, }) widgets.applyButtonClick(PreviewContainer, function() if AnyOpenedCombo and OpenedCombo ~= thisWidget then return end thisWidget.state.isOpened:set(not thisWidget.state.isOpened.value) end) local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.Size = UDim2.fromOffset(0, frameHeight) TextLabel.AutomaticSize = Enum.AutomaticSize.X TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 widgets.applyTextStyle(TextLabel) TextLabel.Parent = Combo local ChildContainer: ScrollingFrame = Instance.new("ScrollingFrame") ChildContainer.Name = "ComboContainer" ChildContainer.AutomaticSize = Enum.AutomaticSize.Y ChildContainer.BackgroundColor3 = ImGui._config.PopupBgColor ChildContainer.BackgroundTransparency = ImGui._config.PopupBgTransparency ChildContainer.BorderSizePixel = 0 ChildContainer.AutomaticCanvasSize = Enum.AutomaticSize.Y ChildContainer.ScrollBarImageTransparency = ImGui._config.ScrollbarGrabTransparency ChildContainer.ScrollBarImageColor3 = ImGui._config.ScrollbarGrabColor ChildContainer.ScrollBarThickness = ImGui._config.ScrollbarSize ChildContainer.CanvasSize = UDim2.fromScale(0, 0) ChildContainer.VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar ChildContainer.ClipsDescendants = true widgets.UIStroke(ChildContainer, ImGui._config.WindowBorderSize, ImGui._config.BorderColor, ImGui._config.BorderTransparency) widgets.UIPadding(ChildContainer, Vector2.new(2, ImGui._config.WindowPadding.Y)) widgets.UISizeConstraint(ChildContainer, Vector2.new(100)) local ChildContainerUIListLayout: UIListLayout = widgets.UIListLayout(ChildContainer, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) ChildContainerUIListLayout.VerticalAlignment = Enum.VerticalAlignment.Top local RootPopupScreenGui = ImGui._rootInstance and ImGui._rootInstance:WaitForChild("PopupScreenGui") :: GuiObject ChildContainer.Parent = RootPopupScreenGui thisWidget.ChildContainer = ChildContainer return Combo end, Update = function(thisWidget: Combo) local ImGui_Combo = thisWidget.Instance :: Frame local PreviewContainer = ImGui_Combo.PreviewContainer :: TextButton local PreviewLabel: TextLabel = PreviewContainer.PreviewLabel local DropdownButton: TextLabel = PreviewContainer.DropdownButton local TextLabel: TextLabel = ImGui_Combo.TextLabel TextLabel.Text = thisWidget.arguments.Text or "Combo" if thisWidget.arguments.NoButton then DropdownButton.Visible = false PreviewLabel.Size = UDim2.new(UDim.new(1, 0), PreviewLabel.Size.Height) else DropdownButton.Visible = true local DropdownButtonSize = ImGui._config.TextSize + 2 * ImGui._config.FramePadding.Y PreviewLabel.Size = UDim2.new(UDim.new(1, -DropdownButtonSize), PreviewLabel.Size.Height) end if thisWidget.arguments.NoPreview then PreviewLabel.Visible = false PreviewContainer.Size = UDim2.new(0, 0, 0, 0) PreviewContainer.AutomaticSize = Enum.AutomaticSize.XY else PreviewLabel.Visible = true PreviewContainer.Size = UDim2.new(ImGui._config.ContentWidth, ImGui._config.ContentHeight) PreviewContainer.AutomaticSize = Enum.AutomaticSize.Y end end, ChildAdded = function(thisWidget: Combo, _thisChild: Widget) UpdateChildContainerTransform(thisWidget) return thisWidget.ChildContainer end, GenerateState = function(thisWidget: Combo) if thisWidget.state.index == nil then thisWidget.state.index = ImGui._widgetState(thisWidget, "index", "No Selection") end thisWidget.state.index:onChange(function() if thisWidget.state.isOpened.value then thisWidget.state.isOpened:set(false) end end) if thisWidget.state.isOpened == nil then thisWidget.state.isOpened = ImGui._widgetState(thisWidget, "isOpened", false) end end, UpdateState = function(thisWidget: Combo) local Combo = thisWidget.Instance :: Frame local ChildContainer = thisWidget.ChildContainer :: ScrollingFrame local PreviewContainer = Combo.PreviewContainer :: TextButton local PreviewLabel: TextLabel = PreviewContainer.PreviewLabel local DropdownButton = PreviewContainer.DropdownButton :: TextLabel local Dropdown: ImageLabel = DropdownButton.Dropdown if thisWidget.state.isOpened.value then AnyOpenedCombo = true OpenedCombo = thisWidget ComboOpenedTick = ImGui._cycleTick thisWidget.lastOpenedTick = ImGui._cycleTick + 1 Dropdown.Image = widgets.ICONS.RIGHT_POINTING_TRIANGLE ChildContainer.Visible = true UpdateChildContainerTransform(thisWidget) else if AnyOpenedCombo then AnyOpenedCombo = false OpenedCombo = nil thisWidget.lastClosedTick = ImGui._cycleTick + 1 end Dropdown.Image = widgets.ICONS.DOWN_POINTING_TRIANGLE ChildContainer.Visible = false end local stateIndex: any = thisWidget.state.index.value PreviewLabel.Text = if typeof(stateIndex) == "EnumItem" then stateIndex.Name else tostring(stateIndex) end, Discard = function(thisWidget: Combo) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, } :: WidgetClass) end, Plot = function(ImGui: Internal, widgets: WidgetUtility) ImGui.WidgetConstructor( "ProgressBar", { hasState = true, hasChildren = false, Args = { ["Text"] = 1, ["Format"] = 2, }, Events = { ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), ["changed"] = { ["Init"] = function(_thisWidget: ProgressBar) end, ["Get"] = function(thisWidget: ProgressBar) return thisWidget.lastChangedTick == ImGui._cycleTick end, }, }, Generate = function(thisWidget: ProgressBar) local ProgressBar: Frame = Instance.new("Frame") ProgressBar.Name = "ImGui_ProgressBar" ProgressBar.Size = UDim2.new(ImGui._config.ItemWidth, UDim.new()) ProgressBar.BackgroundTransparency = 1 ProgressBar.AutomaticSize = Enum.AutomaticSize.Y ProgressBar.LayoutOrder = thisWidget.ZIndex local UIListLayout: UIListLayout = widgets.UIListLayout(ProgressBar, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) UIListLayout.VerticalAlignment = Enum.VerticalAlignment.Center local Bar: Frame = Instance.new("Frame") Bar.Name = "Bar" Bar.Size = UDim2.new(ImGui._config.ContentWidth, ImGui._config.ContentHeight) Bar.BackgroundColor3 = ImGui._config.FrameBgColor Bar.BackgroundTransparency = ImGui._config.FrameBgTransparency Bar.BorderSizePixel = 0 Bar.AutomaticSize = Enum.AutomaticSize.Y Bar.ClipsDescendants = true widgets.applyFrameStyle(Bar, true) Bar.Parent = ProgressBar local Progress: TextLabel = Instance.new("TextLabel") Progress.Name = "Progress" Progress.AutomaticSize = Enum.AutomaticSize.Y Progress.Size = UDim2.new(UDim.new(0, 0), ImGui._config.ContentHeight) Progress.BackgroundColor3 = ImGui._config.PlotHistogramColor Progress.BackgroundTransparency = ImGui._config.PlotHistogramTransparency Progress.BorderSizePixel = 0 widgets.applyTextStyle(Progress) widgets.UIPadding(Progress, ImGui._config.FramePadding) widgets.UICorner(Progress, ImGui._config.FrameRounding) Progress.Text = "" Progress.Parent = Bar local Value: TextLabel = Instance.new("TextLabel") Value.Name = "Value" Value.AutomaticSize = Enum.AutomaticSize.XY Value.Size = UDim2.new(UDim.new(0, 0), ImGui._config.ContentHeight) Value.BackgroundTransparency = 1 Value.BorderSizePixel = 0 Value.ZIndex = 1 widgets.applyTextStyle(Value) widgets.UIPadding(Value, ImGui._config.FramePadding) Value.Parent = Bar local TextLabel: TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.AutomaticSize = Enum.AutomaticSize.XY TextLabel.AnchorPoint = Vector2.new(0, 0.5) TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 TextLabel.LayoutOrder = 1 widgets.applyTextStyle(TextLabel) widgets.UIPadding(Value, ImGui._config.FramePadding) TextLabel.Parent = ProgressBar return ProgressBar end, GenerateState = function(thisWidget: ProgressBar) if thisWidget.state.progress == nil then thisWidget.state.progress = ImGui._widgetState(thisWidget, "Progress", 0) end end, Update = function(thisWidget: ProgressBar) local Progress = thisWidget.Instance :: Frame local TextLabel: TextLabel = Progress.TextLabel local Bar = Progress.Bar :: Frame local Value: TextLabel = Bar.Value if thisWidget.arguments.Format ~= nil and typeof(thisWidget.arguments.Format) == "string" then Value.Text = thisWidget.arguments.Format end TextLabel.Text = thisWidget.arguments.Text or "Progress Bar" end, UpdateState = function(thisWidget: ProgressBar) local ProgressBar = thisWidget.Instance :: Frame local Bar = ProgressBar.Bar :: Frame local Progress: TextLabel = Bar.Progress local Value: TextLabel = Bar.Value local progress: number = thisWidget.state.progress.value progress = math.clamp(progress, 0, 1) local totalWidth: number = Bar.AbsoluteSize.X local textWidth: number = Value.AbsoluteSize.X if totalWidth * (1 - progress) < textWidth then Value.AnchorPoint = Vector2.xAxis Value.Position = UDim2.fromScale(1, 0) else Value.AnchorPoint = Vector2.zero Value.Position = UDim2.new(progress, 0, 0, 0) end Progress.Size = UDim2.new(UDim.new(progress, 0), Progress.Size.Height) if thisWidget.arguments.Format ~= nil and typeof(thisWidget.arguments.Format) == "string" then Value.Text = thisWidget.arguments.Format else Value.Text = string.format("%d%%", progress * 100) end thisWidget.lastChangedTick = ImGui._cycleTick + 1 end, Discard = function(thisWidget: ProgressBar) thisWidget.Instance:Destroy() widgets.discardState(thisWidget) end, } :: WidgetClass ) end, Table = function(ImGui: Internal, widgets: WidgetUtility) local tableWidgets: { [ID]: Table } = {} -- reset the cell index every frame. table.insert(ImGui._postCycleCallbacks, function() for _, thisWidget: Table in tableWidgets do thisWidget.RowColumnIndex = 0 end end) --stylua: ignore ImGui.WidgetConstructor("Table", { hasState = false, hasChildren = true, Args = { ["NumColumns"] = 1, ["RowBg"] = 2, ["BordersOuter"] = 3, ["BordersInner"] = 4, }, Events = { ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Widget) return thisWidget.Instance end), }, Generate = function(thisWidget: Table) tableWidgets[thisWidget.ID] = thisWidget thisWidget.InitialNumColumns = -1 thisWidget.RowColumnIndex = 0 -- reference to these is stored as an optimization thisWidget.ColumnInstances = {} thisWidget.CellInstances = {} local Table: Frame = Instance.new("Frame") Table.Name = "ImGui_Table" Table.Size = UDim2.new(ImGui._config.ItemWidth, UDim.new(0, 0)) Table.AutomaticSize = Enum.AutomaticSize.Y Table.BackgroundTransparency = 1 Table.BorderSizePixel = 0 Table.ZIndex = thisWidget.ZIndex + 1024 -- allocate room for 1024 cells, because Table UIStroke has to appear above cell UIStroke Table.LayoutOrder = thisWidget.ZIndex Table.ClipsDescendants = true widgets.UIListLayout(Table, Enum.FillDirection.Horizontal, UDim.new(0, 0)) widgets.UIStroke(Table, 1, ImGui._config.TableBorderStrongColor, ImGui._config.TableBorderStrongTransparency) return Table end, Update = function(thisWidget: Table) local Table = thisWidget.Instance :: Frame if thisWidget.arguments.BordersOuter == false then Table.UIStroke.Thickness = 0 else Table.UIStroke.Thickness = 1 end if thisWidget.InitialNumColumns == -1 then if thisWidget.arguments.NumColumns == nil then error("ImGui.Table NumColumns argument is required", 5) end thisWidget.InitialNumColumns = thisWidget.arguments.NumColumns for index = 1, thisWidget.InitialNumColumns do local zindex: number = thisWidget.ZIndex + 1 + index local Column: Frame = Instance.new("Frame") Column.Name = `Column_{index}` Column.Size = UDim2.new(1 / thisWidget.InitialNumColumns, 0, 0, 0) Column.AutomaticSize = Enum.AutomaticSize.Y Column.BackgroundTransparency = 1 Column.BorderSizePixel = 0 Column.ZIndex = zindex Column.LayoutOrder = zindex Column.ClipsDescendants = true widgets.UIListLayout(Column, Enum.FillDirection.Vertical, UDim.new(0, 0)) thisWidget.ColumnInstances[index] = Column Column.Parent = Table end elseif thisWidget.arguments.NumColumns ~= thisWidget.InitialNumColumns then -- its possible to make it so that the NumColumns can increase, -- but decreasing it would interfere with child widget instances error("ImGui.Table NumColumns Argument must be static") end if thisWidget.arguments.RowBg == false then for _, Cell: Frame in thisWidget.CellInstances do Cell.BackgroundTransparency = 1 end else for index: number, Cell: Frame in thisWidget.CellInstances do local currentRow: number = math.ceil(index / thisWidget.InitialNumColumns) Cell.BackgroundTransparency = if currentRow % 2 == 0 then ImGui._config.TableRowBgAltTransparency else ImGui._config.TableRowBgTransparency end end -- wooo, I love lua Especially on an object and child based system like Roblox! I never have to do anything -- annoying or dumb to make it like me! if thisWidget.arguments.BordersInner == false then for _, Cell: Frame & { UIStroke: UIStroke } in thisWidget.CellInstances :: any do Cell.UIStroke.Thickness = 0 end else for _, Cell: Frame & { UIStroke: UIStroke } in thisWidget.CellInstances :: any do Cell.UIStroke.Thickness = 0.5 end end end, Discard = function(thisWidget: Table) tableWidgets[thisWidget.ID] = nil thisWidget.Instance:Destroy() end, ChildAdded = function(thisWidget: Table, _thisChild: Widget) if thisWidget.RowColumnIndex == 0 then thisWidget.RowColumnIndex = 1 end local potentialCellParent: Frame = thisWidget.CellInstances[thisWidget.RowColumnIndex] if potentialCellParent then return potentialCellParent end local selectedParent: Frame = thisWidget.ColumnInstances[((thisWidget.RowColumnIndex - 1) % thisWidget.InitialNumColumns) + 1] local zindex: number = selectedParent.ZIndex + thisWidget.RowColumnIndex local Cell: Frame = Instance.new("Frame") Cell.Name = `Cell_{thisWidget.RowColumnIndex}` Cell.Size = UDim2.new(1, 0, 0, 0) Cell.AutomaticSize = Enum.AutomaticSize.Y Cell.BackgroundTransparency = 1 Cell.BorderSizePixel = 0 Cell.ZIndex = zindex Cell.LayoutOrder = zindex Cell.ClipsDescendants = true widgets.UIPadding(Cell, ImGui._config.CellPadding) widgets.UIListLayout(Cell, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) if thisWidget.arguments.BordersInner == false then widgets.UIStroke(Cell, 0, ImGui._config.TableBorderLightColor, ImGui._config.TableBorderLightTransparency) else widgets.UIStroke(Cell, 0.5, ImGui._config.TableBorderLightColor, ImGui._config.TableBorderLightTransparency) -- this takes advantage of unintended behavior when UIStroke is set to 0.5 to render cell borders, -- at 0.5, only the top and left side of the cell will be rendered with a border. end if thisWidget.arguments.RowBg ~= false then local currentRow: number = math.ceil(thisWidget.RowColumnIndex / thisWidget.InitialNumColumns) local color: Color3 = if currentRow % 2 == 0 then ImGui._config.TableRowBgAltColor else ImGui._config.TableRowBgColor local transparency: number = if currentRow % 2 == 0 then ImGui._config.TableRowBgAltTransparency else ImGui._config.TableRowBgTransparency Cell.BackgroundColor3 = color Cell.BackgroundTransparency = transparency end thisWidget.CellInstances[thisWidget.RowColumnIndex] = Cell Cell.Parent = selectedParent return Cell end, } :: WidgetClass) end, Tab = function(ImGui: Types.Internal, widgets: Types.WidgetUtility) local function openTab(TabBar: Types.TabBar, Index: number) if TabBar.state.index.value > 0 then return end TabBar.state.index:set(Index) end local function closeTab(TabBar: Types.TabBar, Index: number) -- search left for open tabs for i = Index - 1, 1, -1 do if TabBar.Tabs[i].state.isOpened.value == true then TabBar.state.index:set(i) return end end -- search right for open tabs for i = Index, #TabBar.Tabs do if TabBar.Tabs[i].state.isOpened.value == true then TabBar.state.index:set(i) return end end -- no open tabs, so wait for one TabBar.state.index:set(0) end --stylua: ignore ImGui.WidgetConstructor("TabBar", { hasState = true, hasChildren = true, Args = {}, Events = {}, Generate = function(thisWidget: Types.TabBar) local TabBar: Frame = Instance.new("Frame") TabBar.Name = "ImGui_TabBar" TabBar.AutomaticSize = Enum.AutomaticSize.Y TabBar.Size = UDim2.fromScale(1, 0) TabBar.BackgroundTransparency = 1 TabBar.BorderSizePixel = 0 TabBar.LayoutOrder = thisWidget.ZIndex widgets.UIListLayout(TabBar, Enum.FillDirection.Vertical, UDim.new()).VerticalAlignment = Enum.VerticalAlignment.Bottom local Bar: Frame = Instance.new("Frame") Bar.Name = "Bar" Bar.AutomaticSize = Enum.AutomaticSize.Y Bar.Size = UDim2.fromScale(1, 0) Bar.BackgroundTransparency = 1 Bar.BorderSizePixel = 0 widgets.UIListLayout(Bar, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)) Bar.Parent = TabBar local Underline: Frame = Instance.new("Frame") Underline.Name = "Underline" Underline.Size = UDim2.new(1, 0, 0, 1) Underline.BackgroundColor3 = ImGui._config.TabActiveColor Underline.BackgroundTransparency = ImGui._config.TabActiveTransparency Underline.BorderSizePixel = 0 Underline.LayoutOrder = 1 Underline.Parent = TabBar local ChildContainer: Frame = Instance.new("Frame") ChildContainer.Name = "TabContainer" ChildContainer.AutomaticSize = Enum.AutomaticSize.Y ChildContainer.Size = UDim2.fromScale(1, 0) ChildContainer.BackgroundTransparency = 1 ChildContainer.BorderSizePixel = 0 ChildContainer.LayoutOrder = 2 ChildContainer.ClipsDescendants = true ChildContainer.Parent = TabBar thisWidget.ChildContainer = ChildContainer thisWidget.Tabs = {} return TabBar end, Update = function(_thisWidget: Types.TabBar) end, ChildAdded = function(thisWidget: Types.TabBar, thisChild: Types.Tab) assert(thisChild.type == "Tab", "Only ImGui.Tab can be parented to ImGui.TabBar.") local TabBar = thisWidget.Instance :: Frame thisChild.ChildContainer.Parent = thisWidget.ChildContainer thisChild.Index = #thisWidget.Tabs + 1 thisWidget.state.index.ConnectedWidgets[thisChild.ID] = thisChild table.insert(thisWidget.Tabs, thisChild) return TabBar.Bar end, ChildDiscarded = function(thisWidget: Types.TabBar, thisChild: Types.Tab) local Index: number = thisChild.Index table.remove(thisWidget.Tabs, Index) for i = Index, #thisWidget.Tabs do thisWidget.Tabs[i].Index = i end closeTab(thisWidget, Index) end, GenerateState = function(thisWidget: Types.Tab) if thisWidget.state.index == nil then thisWidget.state.index = ImGui._widgetState(thisWidget, "index", 1) end end, UpdateState = function(_thisWidget: Types.Tab) end, Discard = function(thisWidget: Types.TabBar) thisWidget.Instance:Destroy() end, } :: Types.WidgetClass) --stylua: ignore ImGui.WidgetConstructor("Tab", { hasState = true, hasChildren = true, Args = { ["Text"] = 1, ["Hideable"] = 2, }, Events = { ["clicked"] = widgets.EVENTS.click(function(thisWidget: Types.Widget) return thisWidget.Instance end), ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Types.Widget) return thisWidget.Instance end), ["selected"] = { ["Init"] = function(_thisWidget: Types.Tab) end, ["Get"] = function(thisWidget: Types.Tab) return thisWidget.lastSelectedTick == ImGui._cycleTick end, }, ["unselected"] = { ["Init"] = function(_thisWidget: Types.Tab) end, ["Get"] = function(thisWidget: Types.Tab) return thisWidget.lastUnselectedTick == ImGui._cycleTick end, }, ["active"] = { ["Init"] = function(_thisWidget: Types.Tab) end, ["Get"] = function(thisWidget: Types.Tab) return thisWidget.state.index.value == thisWidget.Index end, }, ["opened"] = { ["Init"] = function(_thisWidget: Types.Tab) end, ["Get"] = function(thisWidget: Types.Tab) return thisWidget.lastOpenedTick == ImGui._cycleTick end, }, ["closed"] = { ["Init"] = function(_thisWidget: Types.Tab) end, ["Get"] = function(thisWidget: Types.Tab) return thisWidget.lastClosedTick == ImGui._cycleTick end, }, }, Generate = function(thisWidget: Types.Tab) local Tab = Instance.new("TextButton") Tab.Name = "ImGui_Tab" Tab.AutomaticSize = Enum.AutomaticSize.XY Tab.BackgroundColor3 = ImGui._config.TabColor Tab.BackgroundTransparency = ImGui._config.TabTransparency Tab.BorderSizePixel = 0 Tab.Text = "" Tab.AutoButtonColor = false thisWidget.ButtonColors = { Color = ImGui._config.TabColor, Transparency = ImGui._config.TabTransparency, HoveredColor = ImGui._config.TabHoveredColor, HoveredTransparency = ImGui._config.TabHoveredTransparency, ActiveColor = ImGui._config.TabActiveColor, ActiveTransparency = ImGui._config.TabActiveTransparency, } widgets.UIPadding(Tab, Vector2.new(ImGui._config.FramePadding.X, 0)) widgets.applyFrameStyle(Tab, true, true) widgets.UIListLayout(Tab, Enum.FillDirection.Horizontal, UDim.new(0, ImGui._config.ItemInnerSpacing.X)).VerticalAlignment = Enum.VerticalAlignment.Center widgets.applyInteractionHighlights("Background", Tab, Tab, thisWidget.ButtonColors) widgets.applyButtonClick(Tab, function() thisWidget.state.index:set(thisWidget.Index) end) local TextLabel = Instance.new("TextLabel") TextLabel.Name = "TextLabel" TextLabel.AutomaticSize = Enum.AutomaticSize.XY TextLabel.BackgroundTransparency = 1 TextLabel.BorderSizePixel = 0 widgets.applyTextStyle(TextLabel) widgets.UIPadding(TextLabel, Vector2.new(0, ImGui._config.FramePadding.Y)) TextLabel.Parent = Tab local ButtonSize: number = ImGui._config.TextSize + ((ImGui._config.FramePadding.Y - 1) * 2) local CloseButton = Instance.new("TextButton") CloseButton.Name = "CloseButton" CloseButton.BackgroundTransparency = 1 CloseButton.BorderSizePixel = 0 CloseButton.LayoutOrder = 1 CloseButton.Size = UDim2.fromOffset(ButtonSize, ButtonSize) CloseButton.Text = "" CloseButton.AutoButtonColor = false widgets.UICorner(CloseButton) widgets.applyButtonClick(CloseButton, function() thisWidget.state.isOpened:set(false) closeTab(thisWidget.parentWidget, thisWidget.Index) end) widgets.applyInteractionHighlights("Background", CloseButton, CloseButton, { Color = ImGui._config.TabColor, Transparency = 1, HoveredColor = ImGui._config.ButtonHoveredColor, HoveredTransparency = ImGui._config.ButtonHoveredTransparency, ActiveColor = ImGui._config.ButtonActiveColor, ActiveTransparency = ImGui._config.ButtonActiveTransparency, }) CloseButton.Parent = Tab local Icon = Instance.new("ImageLabel") Icon.Name = "Icon" Icon.AnchorPoint = Vector2.new(0.5, 0.5) Icon.BackgroundTransparency = 1 Icon.BorderSizePixel = 0 Icon.Image = widgets.ICONS.MULTIPLICATION_SIGN Icon.ImageTransparency = 1 Icon.Position = UDim2.fromScale(0.5, 0.5) Icon.Size = UDim2.fromOffset(math.floor(0.7 * ButtonSize), math.floor(0.7 * ButtonSize)) widgets.applyInteractionHighlights("Image", Tab, Icon, { Color = ImGui._config.TextColor, Transparency = 1, HoveredColor = ImGui._config.TextColor, HoveredTransparency = ImGui._config.TextTransparency, ActiveColor = ImGui._config.TextColor, ActiveTransparency = ImGui._config.TextTransparency, }) Icon.Parent = CloseButton local ChildContainer: Frame = Instance.new("Frame") ChildContainer.Name = "TabContainer" ChildContainer.AutomaticSize = Enum.AutomaticSize.Y ChildContainer.Size = UDim2.fromScale(1, 0) ChildContainer.BackgroundTransparency = 1 ChildContainer.BorderSizePixel = 0 ChildContainer.ClipsDescendants = true widgets.UIListLayout(ChildContainer, Enum.FillDirection.Vertical, UDim.new(0, ImGui._config.ItemSpacing.Y)) widgets.UIPadding(ChildContainer, Vector2.new(0, ImGui._config.ItemSpacing.Y)).PaddingBottom = UDim.new() thisWidget.ChildContainer = ChildContainer return Tab end, Update = function(thisWidget: Types.Tab) local Tab = thisWidget.Instance :: TextButton local TextLabel: TextLabel = Tab.TextLabel local CloseButton: TextButton = Tab.CloseButton TextLabel.Text = thisWidget.arguments.Text CloseButton.Visible = if thisWidget.arguments.Hideable == true then true else false end, ChildAdded = function(thisWidget: Types.Tab, _thisChild: Types.Widget) return thisWidget.ChildContainer end, GenerateState = function(thisWidget: Types.Tab) thisWidget.state.index = thisWidget.parentWidget.state.index thisWidget.state.index.ConnectedWidgets[thisWidget.ID] = thisWidget if thisWidget.state.isOpened == nil then thisWidget.state.isOpened = ImGui._widgetState(thisWidget, "isOpened", true) end end, UpdateState = function(thisWidget: Types.Tab) local Tab = thisWidget.Instance :: TextButton local Container = thisWidget.ChildContainer :: Frame if thisWidget.state.isOpened.value == true then thisWidget.lastOpenedTick = ImGui._cycleTick + 1 openTab(thisWidget.parentWidget, thisWidget.Index) Tab.Visible = true else thisWidget.lastClosedTick = ImGui._cycleTick + 1 closeTab(thisWidget.parentWidget, thisWidget.Index) Tab.Visible = false end if thisWidget.state.index.value == thisWidget.Index then thisWidget.ButtonColors.Color = ImGui._config.TabActiveColor thisWidget.ButtonColors.Transparency = ImGui._config.TabActiveTransparency Tab.BackgroundColor3 = ImGui._config.TabActiveColor Tab.BackgroundTransparency = ImGui._config.TabActiveTransparency Container.Visible = true thisWidget.lastSelectedTick = ImGui._cycleTick + 1 else thisWidget.ButtonColors.Color = ImGui._config.TabColor thisWidget.ButtonColors.Transparency = ImGui._config.TabTransparency Tab.BackgroundColor3 = ImGui._config.TabColor Tab.BackgroundTransparency = ImGui._config.TabTransparency Container.Visible = false thisWidget.lastUnselectedTick = ImGui._cycleTick + 1 end end, Discard = function(thisWidget: Types.Tab) thisWidget.Instance:Destroy() end } :: Types.WidgetClass) end } for _, Element in widgets.ELEMENTS do Element(ImGui, widgets) end end Widgets(Internal) local function API(ImGui: ImGui) local function wrapper(name: string) return function(arguments: WidgetArguments?, states: WidgetStates?): Widget return ImGui.Internal._Insert(name, arguments, states) end end ImGui.Window = wrapper("Window") ImGui.SetFocusedWindow = ImGui.Internal.SetFocusedWindow ImGui.Tooltip = wrapper("Tooltip") ImGui.MenuBar = wrapper("MenuBar") ImGui.Menu = wrapper("Menu") ImGui.MenuItem = wrapper("MenuItem") ImGui.MenuToggle = wrapper("MenuToggle") ImGui.Separator = wrapper("Separator") ImGui.Indent = wrapper("Indent") ImGui.SameLine = wrapper("SameLine") ImGui.Group = wrapper("Group") ImGui.Text = wrapper("Text") ImGui.TabBar = wrapper("TabBar") ImGui.Tab = wrapper("Tab") ImGui.ScriptEditor = wrapper("ScriptEditor") ImGui.TextWrapped = function(arguments: WidgetArguments): Text arguments[2] = true return ImGui.Internal._Insert("Text", arguments) :: Text end ImGui.TextColored = function(arguments: WidgetArguments): Text arguments[3] = arguments[2] arguments[2] = nil return ImGui.Internal._Insert("Text", arguments) :: Text end ImGui.SeparatorText = wrapper("SeparatorText") ImGui.InputText = wrapper("InputText") ImGui.Button = wrapper("Button") ImGui.SmallButton = wrapper("SmallButton") ImGui.Checkbox = wrapper("Checkbox") ImGui.RadioButton = wrapper("RadioButton") ImGui.Image = wrapper("Image") ImGui.ImageButton = wrapper("ImageButton") ImGui.Tree = wrapper("Tree") ImGui.CollapsingHeader = wrapper("CollapsingHeader") ImGui.InputNum = wrapper("InputNum") ImGui.InputVector2 = wrapper("InputVector2") ImGui.InputVector3 = wrapper("InputVector3") ImGui.InputUDim = wrapper("InputUDim") ImGui.InputUDim2 = wrapper("InputUDim2") ImGui.InputRect = wrapper("InputRect") ImGui.DragNum = wrapper("DragNum") ImGui.DragVector2 = wrapper("DragVector2") ImGui.DragVector3 = wrapper("DragVector3") ImGui.DragUDim = wrapper("DragUDim") ImGui.DragUDim2 = wrapper("DragUDim2") ImGui.DragRect = wrapper("DragRect") ImGui.InputColor3 = wrapper("InputColor3") ImGui.InputColor4 = wrapper("InputColor4") ImGui.SliderNum = wrapper("SliderNum") ImGui.SliderVector2 = wrapper("SliderVector2") ImGui.SliderVector3 = wrapper("SliderVector3") ImGui.SliderUDim = wrapper("SliderUDim") ImGui.SliderUDim2 = wrapper("SliderUDim2") ImGui.SliderRect = wrapper("SliderRect") ImGui.Selectable = wrapper("Selectable") ImGui.Combo = wrapper("Combo") ImGui.ComboArray = function(arguments: WidgetArguments, states: WidgetStates?, selectionArray: { T }) local defaultState if states == nil then defaultState = ImGui.State(selectionArray[1]) else defaultState = states end local thisWidget = ImGui.Internal._Insert("Combo", arguments, defaultState) local sharedIndex: State = thisWidget.state.index for _, Selection in selectionArray do ImGui.Internal._Insert("Selectable", { Selection, Selection }, { index = sharedIndex } :: States) end ImGui.End() return thisWidget end ImGui.ComboEnum = function(arguments: WidgetArguments, states: WidgetStates?, enumType: Enum) local defaultState if states == nil then defaultState = ImGui.State(enumType:GetEnumItems()[1]) else defaultState = states end local thisWidget = ImGui.Internal._Insert("Combo", arguments, defaultState) local sharedIndex = thisWidget.state.index for _, Selection in enumType:GetEnumItems() do ImGui.Internal._Insert("Selectable", { Selection.Name, Selection }, { index = sharedIndex } :: States) end ImGui.End() return thisWidget end ImGui.InputEnum = ImGui.ComboEnum ImGui.ProgressBar = wrapper("ProgressBar") ImGui.Table = wrapper("Table") ImGui.NextColumn = function() local parentWidget = ImGui.Internal._GetParentWidget() :: Table assert(parentWidget.type == "Table", "ImGui.NextColumn can only be called within a table.") parentWidget.RowColumnIndex += 1 end ImGui.SetColumnIndex = function(columnIndex: number) local parentWidget = ImGui.Internal._GetParentWidget() :: Table assert(parentWidget.type == "Table", "ImGui.SetColumnIndex can only be called within a table.") assert(columnIndex >= parentWidget.InitialNumColumns, "ImGui.SetColumnIndex Argument must be in column range") parentWidget.RowColumnIndex = math.floor(parentWidget.RowColumnIndex / parentWidget.InitialNumColumns) + (columnIndex - 1) end ImGui.NextRow = function() local parentWidget = ImGui.Internal._GetParentWidget() :: Table assert(parentWidget.type == "Table", "ImGui.NextColumn can only be called within a table.") local InitialNumColumns: number = parentWidget.InitialNumColumns local nextRow: number = math.floor((parentWidget.RowColumnIndex + 1) / InitialNumColumns) * InitialNumColumns parentWidget.RowColumnIndex = nextRow end ImGui.RenderMarkup = function(element) local elementType = element[1] local args = element[2] or {} local children = element[3] or {} local events = element[4] or {} local Element = ImGui[elementType](unpack(args)) if #children > 0 then for _, child in ipairs(children) do if type(child) == "table" then ImGui.RenderMarkup(child) end end ImGui.End() end for event, callback in pairs(events) do if Element[event]() then callback() end end end end API(ImGui) return ImGui.Init()