--!strict local TweenService = game:GetService("TweenService") type ShimeData = { PlaybackState: Enum.PlaybackState?, Host: GuiObject, Frame: Frame, Gradient: UIGradient, Corner: UICorner, Tween: Tween, Connections: { RBXScriptConnection }, Watchers: { RBXScriptConnection }, } local Shime = {} Shime.__index = Shime export type Shime = typeof(setmetatable({} :: ShimeData, Shime)) export type Preset = { Duration: number, Style: Enum.EasingStyle, Direction: Enum.EasingDirection, RepeatCount: number, Reverses: boolean, DelayTime: number, Rotation: number, Color: Color3, Spread: number, Peak: number, } export type PresetName = "Default" | "Soft" | "Fast" | "Slow" | "Subtle" | "Bright" | "Loading" | "PingPong" | "Once" | "Gold" | "Ice" const StartOffset = Vector2.new(-1, 0) const EndOffset = Vector2.new(1, 0) const NoRadius = UDim.new(0, 0) const CornerProperties = { "TopLeftRadius", "TopRightRadius", "BottomLeftRadius", "BottomRightRadius" } local function DetectIndividualCorners(): boolean local Probe = Instance.new("UICorner") local Supported = pcall(function() return (Probe :: any).TopLeftRadius end) Probe:Destroy() return Supported end const SupportsIndividualCorners = DetectIndividualCorners() local function Freeze(Value: T): T return (table.freeze :: any)(Value) end local function BandTransparency(Spread: number, Peak: number): NumberSequence local HalfWidth = math.clamp(Spread, 0.01, 0.49) return NumberSequence.new({ NumberSequenceKeypoint.new(0, 1), NumberSequenceKeypoint.new(0.5 - HalfWidth, 1), NumberSequenceKeypoint.new(0.5, math.clamp(Peak, 0, 1)), NumberSequenceKeypoint.new(0.5 + HalfWidth, 1), NumberSequenceKeypoint.new(1, 1), }) end local DefaultTransparency = BandTransparency(0.35, 0.55) local function CreateFrame(Host: GuiObject): (Frame, UIGradient, UICorner) local Frame = Instance.new("Frame") Frame.Name = "UIShimmer" Frame.BackgroundColor3 = Color3.new(1, 1, 1) Frame.BackgroundTransparency = 0 Frame.BorderSizePixel = 0 Frame.ClipsDescendants = true Frame.AnchorPoint = Vector2.new(0.5, 0.5) Frame.Position = UDim2.fromScale(0.5, 0.5) Frame.Size = UDim2.fromScale(1, 1) Frame.Visible = false local Gradient = Instance.new("UIGradient") Gradient.Rotation = 15 Gradient.Transparency = DefaultTransparency Gradient.Offset = StartOffset Gradient.Parent = Frame local Corner = Instance.new("UICorner") Corner.CornerRadius = NoRadius Corner.Parent = Frame Frame.Parent = Host return Frame, Gradient, Corner end local function GetAxis(StartPadding: UDim, EndPadding: UDim): (number, number, number, number) local TotalScale = StartPadding.Scale + EndPadding.Scale local TotalOffset = StartPadding.Offset + EndPadding.Offset local Factor = 1 / math.max(1 - TotalScale, 1e-3) local Shift = (StartPadding.Scale - EndPadding.Scale) * Factor / 2 return Factor, TotalOffset * Factor, 0.5 - Shift, (EndPadding.Offset - StartPadding.Offset) / 2 - Shift * TotalOffset end local function ApplyCorner(self: Shime) local HostCorner = self.Host:FindFirstChildOfClass("UICorner") if not HostCorner then self.Corner.CornerRadius = NoRadius return end if not SupportsIndividualCorners then self.Corner.CornerRadius = HostCorner.CornerRadius return end local Source = HostCorner :: any local Target = self.Corner :: any for _, Property in CornerProperties do Target[Property] = Source[Property] end end local function ApplyPadding(self: Shime) local Padding = self.Host:FindFirstChildOfClass("UIPadding") if not Padding then self.Frame.Size = UDim2.fromScale(1, 1) self.Frame.Position = UDim2.fromScale(0.5, 0.5) return end local WidthScale, WidthOffset, XScale, XOffset = GetAxis(Padding.PaddingLeft, Padding.PaddingRight) local HeightScale, HeightOffset, YScale, YOffset = GetAxis(Padding.PaddingTop, Padding.PaddingBottom) self.Frame.Size = UDim2.new(WidthScale, WidthOffset, HeightScale, HeightOffset) self.Frame.Position = UDim2.new(XScale, XOffset, YScale, YOffset) end local function DisconnectAll(Connections: { RBXScriptConnection }) for _, Connection in Connections do Connection:Disconnect() end table.clear(Connections) end local function Watch(self: Shime) DisconnectAll(self.Watchers) local HostCorner = self.Host:FindFirstChildOfClass("UICorner") local Padding = self.Host:FindFirstChildOfClass("UIPadding") if HostCorner then table.insert(self.Watchers, HostCorner.Changed:Connect(function() ApplyCorner(self) end)) end if Padding then table.insert(self.Watchers, Padding.Changed:Connect(function() ApplyPadding(self) end)) end ApplyCorner(self) ApplyPadding(self) end function Shime.new( Host: GuiObject, Duration: number?, Style: Enum.EasingStyle?, Direction: Enum.EasingDirection?, RepeatCount: number?, Reverses: boolean?, DelayTime: number? ): Shime assert(typeof(Host) == "Instance" and Host:IsA("GuiObject"), "Shime wants a GuiObject type host") local Frame, Gradient, Corner = CreateFrame(Host) local Info = TweenInfo.new( Duration or 1, Style or Enum.EasingStyle.Linear, Direction or Enum.EasingDirection.InOut, RepeatCount or -1, Reverses or false, DelayTime or 0 ) local self = setmetatable({ PlaybackState = nil, Host = Host, Frame = Frame, Gradient = Gradient, Corner = Corner, Tween = TweenService:Create(Gradient, Info, { Offset = EndOffset }), Connections = {}, Watchers = {}, } :: ShimeData, Shime) local function OnChildChanged(Child: Instance) if Child:IsA("UICorner") or Child:IsA("UIPadding") then Watch(self) end end table.insert(self.Connections, Host.ChildAdded:Connect(OnChildChanged)) table.insert(self.Connections, Host.ChildRemoved:Connect(OnChildChanged)) table.insert(self.Connections, Host.Destroying:Connect(function() self:Stop() end)) table.insert(self.Connections, self.Tween.Completed:Connect(function(State: Enum.PlaybackState) if State == Enum.PlaybackState.Completed then self.Frame.Visible = false self.PlaybackState = State end end)) Watch(self) return self end local function MakePreset( Duration: number, Style: Enum.EasingStyle, Direction: Enum.EasingDirection, RepeatCount: number, Reverses: boolean, DelayTime: number, Rotation: number, Color: Color3, Spread: number, Peak: number ): Preset local Result: Preset = { Duration = Duration, Style = Style, Direction = Direction, RepeatCount = RepeatCount, Reverses = Reverses, DelayTime = DelayTime, Rotation = Rotation, Color = Color, Spread = Spread, Peak = Peak, } return Freeze(Result) end const White = Color3.new(1, 1, 1) const Linear = Enum.EasingStyle.Linear const Sine = Enum.EasingStyle.Sine const Quad = Enum.EasingStyle.Quad const InOut = Enum.EasingDirection.InOut const Out = Enum.EasingDirection.Out const Presets: { [PresetName]: Preset } = { Default = MakePreset(1, Linear, InOut, -1, false, 0, 15, White, 0.35, 0.55), Soft = MakePreset(2, Sine, InOut, -1, false, 1, 15, White, 0.4, 0.75), Fast = MakePreset(0.5, Linear, InOut, -1, false, 0.5, 20, White, 0.25, 0.5), Slow = MakePreset(3, Sine, InOut, -1, false, 1.5, 15, White, 0.4, 0.6), Subtle = MakePreset(1.5, Linear, InOut, -1, false, 2, 15, White, 0.3, 0.85), Bright = MakePreset(1, Quad, InOut, -1, false, 1, 15, White, 0.3, 0.2), Loading = MakePreset(1.2, Linear, InOut, -1, false, 0, 0, White, 0.45, 0.7), PingPong = MakePreset(1.2, Sine, InOut, -1, true, 0.3, 15, White, 0.35, 0.55), Once = MakePreset(0.8, Quad, Out, 0, false, 0, 15, White, 0.3, 0.4), Gold = MakePreset(1.5, Sine, InOut, -1, false, 2, 25, Color3.fromRGB(255, 215, 120), 0.3, 0.35), Ice = MakePreset(1.8, Sine, InOut, -1, false, 1.5, 25, Color3.fromRGB(170, 225, 255), 0.35, 0.4), } table.freeze(Presets) Shime.Presets = Presets function Shime.FromPreset(Host: GuiObject, Preset: Preset | PresetName): Shime local Config = if type(Preset) == "string" then Presets[Preset] else Preset assert(Config, "Unknown Shime preset") local self = Shime.new( Host, Config.Duration, Config.Style, Config.Direction, Config.RepeatCount, Config.Reverses, Config.DelayTime ) self.Gradient.Rotation = Config.Rotation self.Gradient.Color = ColorSequence.new(Config.Color) self.Gradient.Transparency = BandTransparency(Config.Spread, Config.Peak) return self end function Shime.GetFrame(self: Shime): Frame return self.Frame end function Shime.GetGradient(self: Shime): UIGradient return self.Gradient end function Shime.GetCorner(self: Shime): UICorner return self.Corner end function Shime.Play(self: Shime) self.Frame.Visible = true self.Tween:Play() self.PlaybackState = Enum.PlaybackState.Playing end function Shime.Pause(self: Shime) if self.PlaybackState ~= Enum.PlaybackState.Playing then return end self.Tween:Pause() self.PlaybackState = Enum.PlaybackState.Paused end function Shime.Cancel(self: Shime) self.Tween:Cancel() self.Gradient.Offset = StartOffset self.Frame.Visible = false self.PlaybackState = Enum.PlaybackState.Cancelled end function Shime.Stop(self: Shime) self:Cancel() DisconnectAll(self.Connections) DisconnectAll(self.Watchers) self.Tween:Destroy() self.Frame:Destroy() end Shime.Destroy = Shime.Stop return Shime