--[[ Filename: Utility.lua Written by: jeditkacheff Version 1.0 Description: Utility module for CoreScripts --]] ------------------ CONSTANTS -------------------- local SELECTED_COLOR = Color3.fromRGB(0,162,255) local NON_SELECTED_COLOR = Color3.fromRGB(78,84,96) local ARROW_COLOR = Color3.fromRGB(204, 204, 204) local ARROW_COLOR_HOVER = Color3.fromRGB(255, 255, 255) local ARROW_COLOR_TOUCH = ARROW_COLOR_HOVER local SELECTED_LEFT_IMAGE = "rbxasset://textures/ui/Settings/Slider/SelectedBarLeft.png" local NON_SELECTED_LEFT_IMAGE = "rbxasset://textures/ui/Settings/Slider/BarLeft.png" local SELECTED_RIGHT_IMAGE = "rbxasset://textures/ui/Settings/Slider/SelectedBarRight.png" local NON_SELECTED_RIGHT_IMAGE= "rbxasset://textures/ui/Settings/Slider/BarRight.png" local CONTROLLER_SCROLL_DELTA = 0.2 local CONTROLLER_THUMBSTICK_DEADZONE = 0.8 local DROPDOWN_BG_TRANSPARENCY = 0.2 ------------- SERVICES ---------------- local HttpService = game:GetService("HttpService") local UserInputService = game:GetService("UserInputService") local GuiService = game:GetService("GuiService") local RunService = game:GetService("RunService") local CoreGui = game:GetService("CoreGui") local RobloxGui = CoreGui:FindFirstChild("RobloxGui") local ContextActionService = game:GetService("ContextActionService") local VRService = game:GetService("VRService") --------------- FLAGS ---------------- -- Enable the old Utility.lua if the EnablePortraitMode flag is off local enablePortraitModeSuccess, enablePortraitModeValue = pcall(function() return settings():GetFFlag("EnablePortraitMode") end) local enablePortraitMode = enablePortraitModeSuccess and enablePortraitModeValue local reportPlayerInMenuSuccess, reportPlayerInMenuValue = pcall(function() return settings():GetFFlag("CoreScriptReportPlayerInMenu") end) local enableReportPlayer = reportPlayerInMenuSuccess and reportPlayerInMenuValue local fixSettingsMenuDropdownsSuccess, fixSettingsMenuDropdownsValue = pcall(function() return settings():GetFFlag("FixSettingsMenuDropdowns") end) local fixSettingsMenuDropdowns = fixSettingsMenuDropdownsSuccess and fixSettingsMenuDropdownsValue if not enablePortraitMode then return require(RobloxGui.Modules.Settings:WaitForChild("UtilityOld")) end ------------------ VARIABLES -------------------- local tenFootInterfaceEnabled = require(RobloxGui.Modules:WaitForChild("TenFootInterface")):IsEnabled() ----------- UTILITIES -------------- local Util = {} do function Util.Create(instanceType) return function(data) local obj = Instance.new(instanceType) local parent = nil for k, v in pairs(data) do if type(k) == 'number' then v.Parent = obj elseif k == 'Parent' then parent = v else obj[k] = v end end if parent then obj.Parent = parent end return obj end end end local onResizedCallbacks = {} setmetatable(onResizedCallbacks, { __mode = 'k' }) -- used by several guis to show no selection adorn local noSelectionObject = Util.Create'ImageLabel' { Image = "", BackgroundTransparency = 1 }; -- MATH -- function clamp(low, high, input) return math.max(low, math.min(high, input)) end function ClampVector2(low, high, input) return Vector2.new(clamp(low.x, high.x, input.x), clamp(low.y, high.y, input.y)) end ---- TWEENZ ---- local function Linear(t, b, c, d) if t >= d then return b + c end return c*t/d + b end local function EaseOutQuad(t, b, c, d) if t >= d then return b + c end t = t/d return b - c*t*(t - 2) end local function EaseInOutQuad(t, b, c, d) if t >= d then return b + c end t = t/d if t < 1/2 then return 2*c*t*t + b end return b + c*(2*(2 - t)*t - 1) end function PropertyTweener(instance, prop, start, final, duration, easingFunc, cbFunc) local this = {} this.StartTime = tick() this.EndTime = this.StartTime + duration this.Cancelled = false local finished = false local percentComplete = 0 local function finalize() if instance then instance[prop] = easingFunc(1, start, final - start, 1) end finished = true percentComplete = 1 if cbFunc then cbFunc() end end -- Initial set instance[prop] = easingFunc(0, start, final - start, duration) coroutine.wrap(function() local now = tick() while now < this.EndTime and instance do if this.Cancelled then return end instance[prop] = easingFunc(now - this.StartTime, start, final - start, duration) percentComplete = clamp(0, 1, (now - this.StartTime) / duration) RunService.RenderStepped:wait() now = tick() end if this.Cancelled == false and instance then finalize() end end)() function this:GetFinal() return final end function this:GetPercentComplete() return percentComplete end function this:IsFinished() return finished end function this:Finish() if not finished then self:Cancel() finalize() end end function this:Cancel() this.Cancelled = true end return this end ----------- CLASS DECLARATION -------------- local function CreateSignal() local sig = {} local mSignaler = Instance.new('BindableEvent') local mArgData = nil local mArgDataCount = nil function sig:fire(...) mArgData = {...} mArgDataCount = select('#', ...) mSignaler:Fire() end function sig:connect(f) if not f then error("connect(nil)", 2) end return mSignaler.Event:Connect(function() f(unpack(mArgData, 1, mArgDataCount)) end) end function sig:wait() mSignaler.Event:wait() if not mArgData then error("Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.") end return unpack(mArgData, 1, mArgDataCount) end return sig end local function getViewportSize() while not workspace.CurrentCamera do workspace.Changed:wait() end -- ViewportSize is initally set to 1, 1 in Camera.cpp constructor. -- Also check against 0, 0 incase this is changed in the future. while workspace.CurrentCamera.ViewportSize == Vector2.new(0,0) or workspace.CurrentCamera.ViewportSize == Vector2.new(1,1) do workspace.CurrentCamera.Changed:wait() end return workspace.CurrentCamera.ViewportSize end local function isSmallTouchScreen() local viewportSize = getViewportSize() return UserInputService.TouchEnabled and (viewportSize.Y < 500 or viewportSize.X < 700) end local function isPortrait() local viewport = getViewportSize() return viewport.Y > viewport.X end local function isTenFootInterface() return tenFootInterfaceEnabled end local function usesSelectedObject() --VR does not use selected objects (in the same way as gamepad) if VRService.VREnabled then return false end --Touch does not use selected objects unless there's also a gamepad if UserInputService.TouchEnabled and not UserInputService.GamepadEnabled then return false end --PC with gamepad, console... does use selected objects return true end local function isPosOverGui(pos, gui, debug) -- does not account for rotation local ax, ay = gui.AbsolutePosition.x, gui.AbsolutePosition.y local sx, sy = gui.AbsoluteSize.x, gui.AbsoluteSize.y local bx, by = ax+sx, ay+sy return pos.x > ax and pos.x < bx and pos.y > ay and pos.y < by end local function isPosOverGuiWithClipping(pos, gui) -- isPosOverGui, accounts for clipping and visibility, does not account for rotation if not isPosOverGui(pos, gui) then return false end local clipping = false local check = gui while true do if check == nil or (not check:IsA'GuiObject' and not check:IsA'LayerCollector') then clipping = true if check and check:IsA'CoreGui' then clipping = false end break end if check:IsA'GuiObject' and not check.Visible then clipping = true break end if check:IsA'LayerCollector' or check.ClipsDescendants then if not isPosOverGui(pos, check) then clipping = true break end end check = check.Parent end return not clipping end local function areGuisIntersecting(a, b) -- does not account for rotation local aax, aay = a.AbsolutePosition.x, a.AbsolutePosition.y local asx, asy = a.AbsoluteSize.x, a.AbsoluteSize.y local abx, aby = aax+asx, aay+asy local bax, bay = b.AbsolutePosition.x, b.AbsolutePosition.y local bsx, bsy = b.AbsoluteSize.x, b.AbsoluteSize.y local bbx, bby = bax+bsx, bay+bsy local intersectingX = aax < bbx and abx > bax local intersectingY = aay < bby and aby > bay local intersecting = intersectingX and intersectingY return intersecting end local function isGuiVisible(gui, debug) -- true if any part of the gui is visible on the screen, considers clipping, does not account for rotation local clipping = false local check = gui while true do if check == nil or not check:IsA'GuiObject' and not check:IsA'LayerCollector' then clipping = true if check and check:IsA'CoreGui' then clipping = false end break end if check:IsA'GuiObject' and not check.Visible then clipping = true break end if check:IsA'LayerCollector' or check.ClipsDescendants then if not areGuisIntersecting(check, gui) then clipping = true break end end check = check.Parent end return not clipping end local function addHoverState(button, instance, onNormalButtonState, onHoverButtonState) local function onNormalButtonStateCallback() onNormalButtonState(instance) end local function onHoverButtonStateCallback() onHoverButtonState(instance) end button.MouseEnter:Connect(onHoverButtonStateCallback) button.SelectionGained:Connect(onHoverButtonStateCallback) button.MouseLeave:Connect(onNormalButtonStateCallback) button.SelectionLost:Connect(onNormalButtonStateCallback) onNormalButtonState(instance) end local function addOnResizedCallback(key, callback) onResizedCallbacks[key] = callback callback(getViewportSize(), isPortrait()) end local gamepadSet = { [Enum.UserInputType.Gamepad1] = true; [Enum.UserInputType.Gamepad2] = true; [Enum.UserInputType.Gamepad3] = true; [Enum.UserInputType.Gamepad4] = true; [Enum.UserInputType.Gamepad5] = true; [Enum.UserInputType.Gamepad6] = true; [Enum.UserInputType.Gamepad7] = true; [Enum.UserInputType.Gamepad8] = true; } local function MakeDefaultButton(name, size, clickFunc, pageRef, hubRef) local SelectionOverrideObject = Util.Create'ImageLabel' { Image = "", BackgroundTransparency = 1, }; local button = Util.Create'ImageButton' { Name = name .. "Button", Image = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButton.png", ScaleType = Enum.ScaleType.Slice, SliceCenter = Rect.new(8,6,46,44), AutoButtonColor = false, BackgroundTransparency = 1, Size = size, ZIndex = 2, SelectionImageObject = SelectionOverrideObject }; if not enableReportPlayer then button.NextSelectionLeft = button button.NextSelectionRight = button end local enabled = Util.Create'BoolValue' { Name = 'Enabled', Parent = button, Value = true } if clickFunc then button.MouseButton1Click:Connect(function() clickFunc(gamepadSet[UserInputService:GetLastInputType()] or false) end) end local function isPointerInput(inputObject) return inputObject.UserInputType == Enum.UserInputType.MouseMovement or inputObject.UserInputType == Enum.UserInputType.Touch end local rowRef = nil local function setRowRef(ref) rowRef = ref end local function selectButton() local hub = hubRef if hub == nil then if pageRef then hub = pageRef.HubRef end end if (hub and hub.Active) or hub == nil then button.Image = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButtonSelected.png" local scrollTo = button if rowRef then scrollTo = rowRef end if hub then hub:ScrollToFrame(scrollTo) end end end local function deselectButton() button.Image = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButton.png" end button.InputBegan:Connect(function(inputObject) if button.Selectable and isPointerInput(inputObject) then selectButton() end end) button.InputEnded:Connect(function(inputObject) if button.Selectable and GuiService.SelectedCoreObject ~= button and isPointerInput(inputObject) then deselectButton() end end) button.SelectionGained:Connect(function() selectButton() end) button.SelectionLost:Connect(function() deselectButton() end) local guiServiceCon = GuiService.Changed:Connect(function(prop) if prop ~= "SelectedCoreObject" then return end if not usesSelectedObject() then return end if GuiService.SelectedCoreObject == nil or GuiService.SelectedCoreObject ~= button then deselectButton() return end if button.Selectable then selectButton() end end) return button, setRowRef end local function MakeButton(name, text, size, clickFunc, pageRef, hubRef) local button, setRowRef = MakeDefaultButton(name, size, clickFunc, pageRef, hubRef) local textLabel = Util.Create'TextLabel' { Name = name .. "TextLabel", BackgroundTransparency = 1, BorderSizePixel = 0, Size = UDim2.new(1, 0, 1, -8), Position = UDim2.new(0,0,0,0), TextColor3 = Color3.fromRGB(255,255,255), TextYAlignment = Enum.TextYAlignment.Center, Font = Enum.Font.SourceSansBold, TextSize = 24, Text = text, TextScaled = true, TextWrapped = true, ZIndex = 2, Parent = button }; local constraint = Instance.new("UITextSizeConstraint",textLabel) if isSmallTouchScreen() then textLabel.TextSize = 18 elseif isTenFootInterface() then textLabel.TextSize = 36 end constraint.MaxTextSize = textLabel.TextSize return button, textLabel, setRowRef end local function MakeImageButton(name, image, size, imageSize, clickFunc, pageRef, hubRef) local button, setRowRef = MakeDefaultButton(name, size, clickFunc, pageRef, hubRef) local imageLabel = Util.Create'ImageLabel' { Name = name .. "ImageLabel", BackgroundTransparency = 1, BorderSizePixel = 0, Size = imageSize, Position = UDim2.new(0.5, 0, 0.5, 0), AnchorPoint = Vector2.new(0.5, 0.5), Image = image, ZIndex = 2, Parent = button }; return button, imageLabel, setRowRef end local function AddButtonRow(pageToAddTo, name, text, size, clickFunc, hubRef) local button, textLabel, setRowRef = MakeButton(name, text, size, clickFunc, pageToAddTo, hubRef) local row = Util.Create'Frame' { Name = name .. "Row", BackgroundTransparency = 1, Size = UDim2.new(1, 0, size.Y.Scale, size.Y.Offset), Parent = pageToAddTo.Page } button.Parent = row button.AnchorPoint = Vector2.new(1, 0) button.Position = UDim2.new(1, -20, 0, 0) return row, button, textLabel, setRowRef end local function CreateDropDown(dropDownStringTable, startPosition, settingsHub) -------------------- CONSTANTS ------------------------ local DEFAULT_DROPDOWN_TEXT = "Choose One" local SCROLLING_FRAME_PIXEL_OFFSET = 25 local SELECTION_TEXT_COLOR_NORMAL = Color3.fromRGB(178,178,178) local SELECTION_TEXT_COLOR_NORMAL_VR = Color3.fromRGB(229,229,229) local SELECTION_TEXT_COLOR_HIGHLIGHTED = Color3.fromRGB(255,255,255) -------------------- VARIABLES ------------------------ local lastSelectedCoreObject = nil -------------------- SETUP ------------------------ local this = {} this.CurrentIndex = nil local indexChangedEvent = Instance.new("BindableEvent") indexChangedEvent.Name = "IndexChanged" if type(dropDownStringTable) ~= "table" then error("CreateDropDown dropDownStringTable (first arg) is not a table", 2) return this end local indexChangedEvent = Instance.new("BindableEvent") indexChangedEvent.Name = "IndexChanged" local interactable = true local guid = HttpService:GenerateGUID(false) local dropDownButtonEnabled local lastStringTable = dropDownStringTable this.CurrentIndex = 0 ----------------- GUI SETUP ------------------------ local DropDownFullscreenFrame = Util.Create'ImageButton' { Name = "DropDownFullscreenFrame", BackgroundTransparency = DROPDOWN_BG_TRANSPARENCY, BorderSizePixel = 0, Size = UDim2.new(1, 0, 1, 0), BackgroundColor3 = Color3.fromRGB(0,0,0), ZIndex = 10, Active = true, Visible = false, Selectable = false, AutoButtonColor = false, Parent = CoreGui.RobloxGui }; local function onVREnabled(prop) if prop ~= "VREnabled" then return end if VRService.VREnabled then local Panel3D = require(CoreGui.RobloxGui.Modules.VR.Panel3D) DropDownFullscreenFrame.Parent = Panel3D.Get("SettingsMenu"):GetGUI() DropDownFullscreenFrame.BackgroundTransparency = 1 else DropDownFullscreenFrame.Parent = CoreGui.RobloxGui DropDownFullscreenFrame.BackgroundTransparency = DROPDOWN_BG_TRANSPARENCY end --Force the gui to update, but only if onVREnabled is fired later on if this.UpdateDropDownList then this:UpdateDropDownList(lastStringTable) end end VRService.Changed:Connect(onVREnabled) onVREnabled("VREnabled") local DropDownSelectionFrame = Util.Create'ImageLabel' { Name = "DropDownSelectionFrame", Image = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButton.png", ScaleType = Enum.ScaleType.Slice, SliceCenter = Rect.new(8,6,46,44), BackgroundTransparency = 1, Size = UDim2.new(0.6, 0, 0.9, 0), Position = UDim2.new(0.5, 0, 0.5, 0), AnchorPoint = Vector2.new(0.5, 0.5), ZIndex = 10, Parent = DropDownFullscreenFrame }; local DropDownScrollingFrame = Util.Create'ScrollingFrame' { Name = "DropDownScrollingFrame", BackgroundTransparency = 1, BorderSizePixel = 0, Size = UDim2.new(1, -20, 1, -SCROLLING_FRAME_PIXEL_OFFSET), Position = UDim2.new(0, 10, 0, 10), ZIndex = 10, Parent = DropDownSelectionFrame }; local guiServiceChangeCon = nil local active = false local hideDropDownSelection = function(name, inputState) if name ~= nil and inputState ~= Enum.UserInputState.Begin then return end this.DropDownFrame.Selectable = interactable if fixSettingsMenuDropdowns then --Make sure to set the hub to Active again so selecting the --dropdown button will highlight it settingsHub:SetActive(true) end if DropDownFullscreenFrame.Visible and usesSelectedObject() then GuiService.SelectedCoreObject = lastSelectedCoreObject end DropDownFullscreenFrame.Visible = false if guiServiceChangeCon then guiServiceChangeCon:Disconnect() end ContextActionService:UnbindCoreAction(guid .. "Action") ContextActionService:UnbindCoreAction(guid .. "FreezeAction") if not fixSettingsMenuDropdowns then --Setting this late will cause the dropdown button to not be highlighted correctly --so we're going to move this upward settingsHub:SetActive(true) end dropDownButtonEnabled.Value = interactable active = false if VRService.VREnabled then local Panel3D = require(CoreGui.RobloxGui.Modules.VR.Panel3D) Panel3D.Get("SettingsMenu"):SetSubpanelDepth(DropDownFullscreenFrame, 0) end end local noOpFunc = function() end local DropDownFrameClicked = function() if not interactable then return end this.DropDownFrame.Selectable = false active = true DropDownFullscreenFrame.Visible = true if VRService.VREnabled then local Panel3D = require(CoreGui.RobloxGui.Modules.VR.Panel3D) Panel3D.Get("SettingsMenu"):SetSubpanelDepth(DropDownFullscreenFrame, 0.5) end if not this.CurrentIndex then this.CurrentIndex = 1 end if this.CurrentIndex <= 0 then this.CurrentIndex = 1 end lastSelectedCoreObject = this.DropDownFrame GuiService.SelectedCoreObject = this.Selections[this.CurrentIndex] guiServiceChangeCon = GuiService:GetPropertyChangedSignal("SelectedCoreObject"):Connect(function() for i = 1, #this.Selections do if GuiService.SelectedCoreObject == this.Selections[i] then this.Selections[i].TextColor3 = SELECTION_TEXT_COLOR_HIGHLIGHTED else this.Selections[i].TextColor3 = VRService.VREnabled and SELECTION_TEXT_COLOR_NORMAL_VR or SELECTION_TEXT_COLOR_NORMAL end end end) ContextActionService:BindCoreAction(guid .. "FreezeAction", noOpFunc, false, Enum.UserInputType.Keyboard, Enum.UserInputType.Gamepad1) ContextActionService:BindCoreAction(guid .. "Action", hideDropDownSelection, false, Enum.KeyCode.ButtonB, Enum.KeyCode.Escape) settingsHub:SetActive(false) dropDownButtonEnabled.Value = false end local dropDownFrameSize = UDim2.new(0.6, 0, 0, 50) this.DropDownFrame = MakeButton("DropDownFrame", DEFAULT_DROPDOWN_TEXT, dropDownFrameSize, DropDownFrameClicked, nil, settingsHub) this.DropDownFrame.Position = UDim2.new(1, 0, 0.5, 0) this.DropDownFrame.AnchorPoint = Vector2.new(1, 0.5) dropDownButtonEnabled = this.DropDownFrame.Enabled local selectedTextLabel = this.DropDownFrame.DropDownFrameTextLabel selectedTextLabel.Position = UDim2.new(0, 15, 0, 0) selectedTextLabel.Size = UDim2.new(1, -50, 1, -8) selectedTextLabel.ClipsDescendants = true selectedTextLabel.TextXAlignment = Enum.TextXAlignment.Left local dropDownImage = Util.Create'ImageLabel' { Name = "DropDownImage", Image = "rbxasset://textures/ui/Settings/DropDown/DropDown.png", BackgroundTransparency = 1, AnchorPoint = Vector2.new(1, 0.5), Size = UDim2.new(0,15,0,10), Position = UDim2.new(1,-12,0.5,0), ZIndex = 2, Parent = this.DropDownFrame }; ---------------------- FUNCTIONS ----------------------------------- local function setSelection(index) local shouldFireChanged = false for i, selectionLabel in pairs(this.Selections) do if i == index then selectedTextLabel.Text = selectionLabel.Text this.CurrentIndex = i shouldFireChanged = true end end if shouldFireChanged then indexChangedEvent:Fire(index) end end local function setSelectionByValue(value) local shouldFireChanged = false for i, selectionLabel in pairs(this.Selections) do if selectionLabel.Text == value then selectedTextLabel.Text = selectionLabel.Text this.CurrentIndex = i shouldFireChanged = true end end if shouldFireChanged then indexChangedEvent:Fire(this.CurrentIndex) end return shouldFireChanged end local enterIsDown = false local function processInput(input) if input.UserInputState == Enum.UserInputState.Begin then if input.KeyCode == Enum.KeyCode.Return then if GuiService.SelectedCoreObject == this.DropDownFrame or this.SelectionInfo and this.SelectionInfo[GuiService.SelectedCoreObject] then enterIsDown = true end end elseif input.UserInputState == Enum.UserInputState.End then if input.KeyCode == Enum.KeyCode.Return and enterIsDown then enterIsDown = false if GuiService.SelectedCoreObject == this.DropDownFrame then DropDownFrameClicked() elseif this.SelectionInfo and this.SelectionInfo[GuiService.SelectedCoreObject] then local info = this.SelectionInfo[GuiService.SelectedCoreObject] info.Clicked() end end end end --------------------- PUBLIC FACING FUNCTIONS ----------------------- this.IndexChanged = indexChangedEvent.Event function this:SetSelectionIndex(newIndex) setSelection(newIndex) end function this:SetSelectionByValue(value) return setSelectionByValue(value) end function this:ResetSelectionIndex() this.CurrentIndex = nil selectedTextLabel.Text = DEFAULT_DROPDOWN_TEXT hideDropDownSelection() end function this:GetSelectedIndex() return this.CurrentIndex end function this:SetZIndex(newZIndex) this.DropDownFrame.ZIndex = newZIndex dropDownImage.ZIndex = newZIndex selectedTextLabel.ZIndex = newZIndex end function this:SetInteractable(value) interactable = value this.DropDownFrame.Selectable = interactable if not interactable then hideDropDownSelection() this:SetZIndex(1) else this:SetZIndex(2) end dropDownButtonEnabled.Value = value and not active end function this:UpdateDropDownList(dropDownStringTable) lastStringTable = dropDownStringTable if this.Selections then for i = 1, #this.Selections do this.Selections[i]:Destroy() end end this.Selections = {} this.SelectionInfo = {} local vrEnabled = VRService.VREnabled local font = vrEnabled and Enum.Font.SourceSansBold or Enum.Font.SourceSans local textSize = vrEnabled and 36 or 24 local itemHeight = vrEnabled and 70 or 50 local itemSpacing = itemHeight + 1 local dropDownWidth = vrEnabled and 600 or 400 for i,v in pairs(dropDownStringTable) do local SelectionOverrideObject = Util.Create'Frame' { BackgroundTransparency = 0.7, BorderSizePixel = 0, Size = UDim2.new(1, 0, 1, 0) }; local nextSelection = Util.Create'TextButton' { Name = "Selection" .. tostring(i), BackgroundTransparency = 1, BorderSizePixel = 0, AutoButtonColor = false, Size = UDim2.new(1, -28, 0, itemHeight), Position = UDim2.new(0,14,0, (i - 1) * itemSpacing), TextColor3 = VRService.VREnabled and SELECTION_TEXT_COLOR_NORMAL_VR or SELECTION_TEXT_COLOR_NORMAL, Font = font, TextSize = textSize, Text = v, ZIndex = 10, SelectionImageObject = SelectionOverrideObject, Parent = DropDownScrollingFrame }; if i == startPosition then this.CurrentIndex = i selectedTextLabel.Text = v nextSelection.TextColor3 = SELECTION_TEXT_COLOR_HIGHLIGHTED elseif not startPosition and i == 1 then nextSelection.TextColor3 = SELECTION_TEXT_COLOR_HIGHLIGHTED end local clicked = function() selectedTextLabel.Text = nextSelection.Text hideDropDownSelection() this.CurrentIndex = i indexChangedEvent:Fire(i) end nextSelection.MouseButton1Click:Connect(clicked) nextSelection.MouseEnter:Connect(function() if usesSelectedObject() then GuiService.SelectedCoreObject = nextSelection end end) this.Selections[i] = nextSelection this.SelectionInfo[nextSelection] = {Clicked = clicked} end GuiService:RemoveSelectionGroup(guid) GuiService:AddSelectionTuple(guid, unpack(this.Selections)) DropDownScrollingFrame.CanvasSize = UDim2.new(1,-20,0,#dropDownStringTable * itemSpacing) local function updateDropDownSize() if DropDownScrollingFrame.CanvasSize.Y.Offset < (DropDownFullscreenFrame.AbsoluteSize.Y - 10) then DropDownSelectionFrame.Size = UDim2.new(0, dropDownWidth, 0,DropDownScrollingFrame.CanvasSize.Y.Offset + SCROLLING_FRAME_PIXEL_OFFSET) else DropDownSelectionFrame.Size = UDim2.new(0, dropDownWidth, 0.9, 0) end end DropDownFullscreenFrame.Changed:Connect(function(prop) if prop ~= "AbsoluteSize" then return end updateDropDownSize() end) updateDropDownSize() end ----------------------- CONNECTIONS/SETUP -------------------------------- this:UpdateDropDownList(dropDownStringTable) DropDownFullscreenFrame.MouseButton1Click:Connect(hideDropDownSelection) settingsHub.PoppedMenu:Connect(function(poppedMenu) if poppedMenu == DropDownFullscreenFrame then hideDropDownSelection() end end) if not fixSettingsMenuDropdowns then UserInputService.InputBegan:Connect(processInput) UserInputService.InputEnded:Connect(processInput) end return this end local function CreateSelector(selectionStringTable, startPosition) -------------------- VARIABLES ------------------------ local lastInputDirection = 0 local TweenTime = 0.15 -------------------- SETUP ------------------------ local this = {} this.HubRef = nil if type(selectionStringTable) ~= "table" then error("CreateSelector selectionStringTable (first arg) is not a table", 2) return this end local indexChangedEvent = Instance.new("BindableEvent") indexChangedEvent.Name = "IndexChanged" local interactable = true this.CurrentIndex = 0 ----------------- GUI SETUP ------------------------ this.SelectorFrame = Util.Create'ImageButton' { Name = "Selector", Image = "", AutoButtonColor = false, NextSelectionLeft = this.SelectorFrame, NextSelectionRight = this.SelectorFrame, BackgroundTransparency = 1, Size = UDim2.new(0.6,0,0,50), Position = UDim2.new(1, 0, 0.5, 0), AnchorPoint = Vector2.new(1, 0.5), ZIndex = 2, SelectionImageObject = noSelectionObject }; local leftButton = Util.Create'ImageButton' { Name = "LeftButton", BackgroundTransparency = 1, AnchorPoint = Vector2.new(0, 0.5), Position = UDim2.new(0,0,0.5,0), Size = UDim2.new(0,50,0,50), Image = "", ZIndex = 3, Selectable = false, SelectionImageObject = noSelectionObject, Parent = this.SelectorFrame }; local rightButton = Util.Create'ImageButton' { Name = "RightButton", BackgroundTransparency = 1, AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1,0,0.5,0), Size = UDim2.new(0,50,0,50), Image = "", ZIndex = 3, Selectable = false, SelectionImageObject = noSelectionObject, Parent = this.SelectorFrame }; local leftButtonImage = Util.Create'ImageLabel' { Name = "LeftButton", BackgroundTransparency = 1, AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.new(0.5,0,0.5,0), Size = UDim2.new(0,18,0,30), Image = "rbxasset://textures/ui/Settings/Slider/Left.png", ImageColor3 = ARROW_COLOR, ZIndex = 4, Parent = leftButton }; local rightButtonImage = Util.Create'ImageLabel' { Name = "RightButton", BackgroundTransparency = 1, AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.new(0.5,0,0.5,0), Size = UDim2.new(0,18,0,30), Image = "rbxasset://textures/ui/Settings/Slider/Right.png", ImageColor3 = ARROW_COLOR, ZIndex = 4, Parent = rightButton }; if not UserInputService.TouchEnabled then local applyNormal, applyHover = function(instance) instance.ImageColor3 = ARROW_COLOR end, function(instance) instance.ImageColor3 = ARROW_COLOR_HOVER end addHoverState(leftButton, leftButtonImage, applyNormal, applyHover) addHoverState(rightButton, rightButtonImage, applyNormal, applyHover) end this.Selections = {} local isSelectionLabelVisible = {} local isAutoSelectButton = {} for i,v in pairs(selectionStringTable) do local nextSelection = Util.Create'TextLabel' { Name = "Selection" .. tostring(i), BackgroundTransparency = 1, BorderSizePixel = 0, Size = UDim2.new(1,leftButton.Size.X.Offset * -2, 1, 0), Position = UDim2.new(1,0,0,0), TextColor3 = Color3.fromRGB(255, 255, 255), TextYAlignment = Enum.TextYAlignment.Center, TextTransparency = 0.5, Font = Enum.Font.SourceSans, TextSize = 24, TextSize = 16, Text = v, ZIndex = 2, Visible = false, Parent = this.SelectorFrame }; if isTenFootInterface() then nextSelection.TextSize = 36 end if i == startPosition then this.CurrentIndex = i nextSelection.Position = UDim2.new(0,leftButton.Size.X.Offset,0,0) nextSelection.Visible = true isSelectionLabelVisible[nextSelection] = true else isSelectionLabelVisible[nextSelection] = false end this.Selections[i] = nextSelection end local autoSelectButton = Util.Create'ImageButton'{ Name = 'AutoSelectButton', BackgroundTransparency = 1, Image = '', Position = UDim2.new(0, leftButton.Size.X.Offset, 0, 0), Size = UDim2.new(1, leftButton.Size.X.Offset * -2, 1, 0), Parent = this.SelectorFrame, ZIndex = 2, SelectionImageObject = noSelectionObject } autoSelectButton.MouseButton1Click:Connect(function() if not interactable then return end local newIndex = this.CurrentIndex + 1 if newIndex > #this.Selections then newIndex = 1 end this:SetSelectionIndex(newIndex) if usesSelectedObject() then GuiService.SelectedCoreObject = this.SelectorFrame end end) isAutoSelectButton[autoSelectButton] = true ---------------------- FUNCTIONS ----------------------------------- local function setSelection(index, direction) for i, selectionLabel in pairs(this.Selections) do local isSelected = (i == index) local leftButtonUDim = UDim2.new(0,leftButton.Size.X.Offset,0,0) local tweenPos = UDim2.new(0,leftButton.Size.X.Offset * direction * 3,0,0) if isSelectionLabelVisible[selectionLabel] then tweenPos = UDim2.new(0,leftButton.Size.X.Offset * -direction * 3,0,0) end if tweenPos.X.Offset < 0 then tweenPos = UDim2.new(0,tweenPos.X.Offset + (selectionLabel.AbsoluteSize.X/4),0,0) end if isSelected then isSelectionLabelVisible[selectionLabel] = true selectionLabel.Position = tweenPos selectionLabel.Visible = true PropertyTweener(selectionLabel, "TextTransparency", 1, 0, TweenTime * 1.1, EaseOutQuad) if selectionLabel:IsDescendantOf(game) then selectionLabel:TweenPosition(leftButtonUDim, Enum.EasingDirection.In, Enum.EasingStyle.Quad, TweenTime, true) else selectionLabel.Position = leftButtonUDim end this.CurrentIndex = i indexChangedEvent:Fire(index) elseif isSelectionLabelVisible[selectionLabel] then isSelectionLabelVisible[selectionLabel] = false PropertyTweener(selectionLabel, "TextTransparency", 0, 1, TweenTime * 1.1, EaseOutQuad) if selectionLabel:IsDescendantOf(game) then selectionLabel:TweenPosition(tweenPos, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, TweenTime * 0.9, true) else selectionLabel.Position = UDim2.new(tweenPos) end end end end local function stepFunc(inputObject, step) if not interactable then return end if inputObject ~= nil and inputObject.UserInputType ~= Enum.UserInputType.MouseButton1 and inputObject.UserInputType ~= Enum.UserInputType.Gamepad1 and inputObject.UserInputType ~= Enum.UserInputType.Gamepad2 and inputObject.UserInputType ~= Enum.UserInputType.Gamepad3 and inputObject.UserInputType ~= Enum.UserInputType.Gamepad4 and inputObject.UserInputType ~= Enum.UserInputType.Keyboard then return end if usesSelectedObject() then GuiService.SelectedCoreObject = this.SelectorFrame end local newIndex = step + this.CurrentIndex local direction = 0 if newIndex > this.CurrentIndex then direction = 1 else direction = -1 end if newIndex > #this.Selections then newIndex = 1 elseif newIndex < 1 then newIndex = #this.Selections end setSelection(newIndex, direction) end local guiServiceCon = nil local function connectToGuiService() guiServiceCon = GuiService.Changed:Connect(function(prop) if prop == "SelectedCoreObject" then if GuiService.SelectedCoreObject == this.SelectorFrame then this.Selections[this.CurrentIndex].TextTransparency = 0 else if GuiService.SelectedCoreObject ~= nil and isAutoSelectButton[GuiService.SelectedCoreObject] then if VRService.VREnabled then this.Selections[this.CurrentIndex].TextTransparency = 0 else GuiService.SelectedCoreObject = this.SelectorFrame end else this.Selections[this.CurrentIndex].TextTransparency = 0.5 end end end end) end --------------------- PUBLIC FACING FUNCTIONS ----------------------- this.IndexChanged = indexChangedEvent.Event function this:SetSelectionIndex(newIndex) setSelection(newIndex, 1) end function this:GetSelectedIndex() return this.CurrentIndex end function this:SetZIndex(newZIndex) leftButton.ZIndex = newZIndex rightButton.ZIndex = newZIndex leftButtonImage.ZIndex = newZIndex rightButtonImage.ZIndex = newZIndex for i = 1, #this.Selections do this.Selections[i].ZIndex = newZIndex end end function this:SetInteractable(value) interactable = value this.SelectorFrame.Selectable = interactable if not interactable then for i, selectionLabel in pairs(this.Selections) do selectionLabel.TextColor3 = Color3.fromRGB(49, 49, 49) end else for i, selectionLabel in pairs(this.Selections) do selectionLabel.TextColor3 = Color3.fromRGB(255, 255, 255) end end end --------------------- SETUP ----------------------- local function onVREnabled(prop) if prop ~= "VREnabled" then return end local vrEnabled = VRService.VREnabled leftButton.Selectable = vrEnabled rightButton.Selectable = vrEnabled autoSelectButton.Selectable = vrEnabled end VRService.Changed:Connect(onVREnabled) onVREnabled("VREnabled") leftButton.InputBegan:Connect(function(inputObject) if inputObject.UserInputType == Enum.UserInputType.Touch then stepFunc(nil, -1) end end) leftButton.MouseButton1Click:Connect(function() if not UserInputService.TouchEnabled then stepFunc(nil, -1) end end) rightButton.InputBegan:Connect(function(inputObject) if inputObject.UserInputType == Enum.UserInputType.Touch then stepFunc(nil, 1) end end) rightButton.MouseButton1Click:Connect(function() if not UserInputService.TouchEnabled then stepFunc(nil, 1) end end) local isInTree = true UserInputService.InputBegan:Connect(function(inputObject) if not interactable then return end if not isInTree then return end if inputObject.UserInputType ~= Enum.UserInputType.Gamepad1 and inputObject.UserInputType ~= Enum.UserInputType.Keyboard then return end if GuiService.SelectedCoreObject ~= this.SelectorFrame then return end if inputObject.KeyCode == Enum.KeyCode.DPadLeft or inputObject.KeyCode == Enum.KeyCode.Left or inputObject.KeyCode == Enum.KeyCode.A then stepFunc(inputObject, -1) elseif inputObject.KeyCode == Enum.KeyCode.DPadRight or inputObject.KeyCode == Enum.KeyCode.Right or inputObject.KeyCode == Enum.KeyCode.D then stepFunc(inputObject, 1) end end) UserInputService.InputChanged:Connect(function(inputObject) if not interactable then return end if not isInTree then lastInputDirection = 0 return end if inputObject.UserInputType ~= Enum.UserInputType.Gamepad1 then return end local selected = GuiService.SelectedCoreObject if not selected or not selected:IsDescendantOf(this.SelectorFrame.Parent) then return end if inputObject.KeyCode ~= Enum.KeyCode.Thumbstick1 then return end if inputObject.Position.X > CONTROLLER_THUMBSTICK_DEADZONE and inputObject.Delta.X > 0 and lastInputDirection ~= 1 then lastInputDirection = 1 stepFunc(inputObject, lastInputDirection) elseif inputObject.Position.X < -CONTROLLER_THUMBSTICK_DEADZONE and inputObject.Delta.X < 0 and lastInputDirection ~= -1 then lastInputDirection = -1 stepFunc(inputObject, lastInputDirection) elseif math.abs(inputObject.Position.X) < CONTROLLER_THUMBSTICK_DEADZONE then lastInputDirection = 0 end end) this.SelectorFrame.AncestryChanged:Connect(function(child, parent) isInTree = parent if not isInTree then if guiServiceCon then guiServiceCon:Disconnect() end else connectToGuiService() end end) local function onResized(viewportSize, portrait) local textSize = 0 if portrait then textSize = 16 else textSize = isTenFootInterface() and 36 or 24 end for i, selection in pairs(this.Selections) do selection.TextSize = textSize end end addOnResizedCallback(this.SelectorFrame, onResized) connectToGuiService() return this end local function ShowAlert(alertMessage, okButtonText, settingsHub, okPressedFunc, hasBackground) local parent = CoreGui.RobloxGui if parent:FindFirstChild("AlertViewFullScreen") then return end --Declare AlertViewBacking so onVREnabled can take it as an upvalue local AlertViewBacking = nil --Handle VR toggle while alert is open --Future consideration: maybe rebuild gui when VR toggles mid-game; right now only subpaneling is handled rather than visual style local function onVREnabled(prop) if prop ~= "VREnabled" then return end local Panel3D, settingsPanel = nil, nil if VRService.VREnabled then Panel3D = require(CoreGui.RobloxGui.Modules.VR.Panel3D) settingsPanel = Panel3D.Get("SettingsMenu") parent = settingsPanel:GetGUI() else parent = CoreGui.RobloxGui end if AlertViewBacking and AlertViewBacking.Parent ~= nil then AlertViewBacking.Parent = parent if VRService.VREnabled then settingsPanel:SetSubpanelDepth(AlertViewBacking, 0.5) end end end local vrEnabledConn = VRService.Changed:Connect(onVREnabled) local NON_SELECTED_TEXT_COLOR = Color3.fromRGB(59, 166, 241) local SELECTED_TEXT_COLOR = Color3.fromRGB(255, 255, 255) AlertViewBacking = Util.Create'ImageLabel' { Name = "AlertViewBacking", Image = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButton.png", ScaleType = Enum.ScaleType.Slice, SliceCenter = Rect.new(8,6,46,44), BackgroundTransparency = 1, ImageTransparency = 1, Size = UDim2.new(0, 400, 0, 350), Position = UDim2.new(0.5, -200, 0.5, -175), ZIndex = 9, Parent = parent }; onVREnabled("VREnabled") if hasBackground or VRService.VREnabled then AlertViewBacking.ImageTransparency = 0 else AlertViewBacking.Size = UDim2.new(0.8, 0, 0, 350) AlertViewBacking.Position = UDim2.new(0.1, 0, 0.1, 0) end if CoreGui.RobloxGui.AbsoluteSize.Y <= AlertViewBacking.Size.Y.Offset then AlertViewBacking.Size = UDim2.new(AlertViewBacking.Size.X.Scale, AlertViewBacking.Size.X.Offset, AlertViewBacking.Size.Y.Scale, CoreGui.RobloxGui.AbsoluteSize.Y) AlertViewBacking.Position = UDim2.new(AlertViewBacking.Position.X.Scale, -AlertViewBacking.Size.X.Offset/2, 0.5, -AlertViewBacking.Size.Y.Offset/2) end local AlertViewText = Util.Create'TextLabel' { Name = "AlertViewText", BackgroundTransparency = 1, Size = UDim2.new(0.95, 0, 0.6, 0), Position = UDim2.new(0.025, 0, 0.05, 0), Font = Enum.Font.SourceSansBold, TextSize = 36, Text = alertMessage, TextWrapped = true, TextColor3 = Color3.fromRGB(255, 255, 255), TextXAlignment = Enum.TextXAlignment.Center, TextYAlignment = Enum.TextYAlignment.Center, ZIndex = 10, Parent = AlertViewBacking }; local SelectionOverrideObject = Util.Create'ImageLabel' { Image = "", BackgroundTransparency = 1 }; local removeId = HttpService:GenerateGUID(false) local destroyAlert = function(actionName, inputState) if VRService.VREnabled and (inputState == Enum.UserInputState.Begin or inputState == Enum.UserInputState.Cancel) then return end if not AlertViewBacking then return end if VRService.VREnabled then local Panel3D = require(CoreGui.RobloxGui.Modules.VR.Panel3D) Panel3D.Get("SettingsMenu"):SetSubpanelDepth(AlertViewBacking, 0) end AlertViewBacking:Destroy() AlertViewBacking = nil if okPressedFunc then okPressedFunc() end ContextActionService:UnbindCoreAction(removeId) GuiService.SelectedCoreObject = nil if settingsHub then settingsHub:ShowBar() end if vrEnabledConn then vrEnabledConn:Disconnect() end end local AlertViewButtonSize = UDim2.new(1, -20, 0, 60) local AlertViewButtonPosition = UDim2.new(0, 10, 0.65, 0) if not hasBackground then AlertViewButtonSize = UDim2.new(0, 200, 0, 50) AlertViewButtonPosition = UDim2.new(0.5, -100, 0.65, 0) end local AlertViewButton, AlertViewText = MakeButton("AlertViewButton", okButtonText, AlertViewButtonSize, destroyAlert) AlertViewButton.Position = AlertViewButtonPosition AlertViewButton.NextSelectionLeft = AlertViewButton AlertViewButton.NextSelectionRight = AlertViewButton AlertViewButton.NextSelectionUp = AlertViewButton AlertViewButton.NextSelectionDown = AlertViewButton AlertViewButton.ZIndex = 9 AlertViewText.ZIndex = AlertViewButton.ZIndex AlertViewButton.Parent = AlertViewBacking if usesSelectedObject() then GuiService.SelectedCoreObject = AlertViewButton end GuiService.SelectedCoreObject = AlertViewButton ContextActionService:BindCoreAction(removeId, destroyAlert, false, Enum.KeyCode.Escape, Enum.KeyCode.ButtonB, Enum.KeyCode.ButtonA) if settingsHub and not VRService.VREnabled then settingsHub:HideBar() settingsHub.Pages.CurrentPage:Hide(1, 1) end end local function CreateNewSlider(numOfSteps, startStep, minStep) -------------------- SETUP ------------------------ local this = {} local spacing = 4 local initialSpacing = 8 local steps = tonumber(numOfSteps) local currentStep = startStep local lastInputDirection = 0 local timeAtLastInput = nil local interactable = true local renderStepBindName = HttpService:GenerateGUID(false) -- this is done to prevent using these values below (trying to keep the variables consistent) numOfSteps = "" startStep = "" if steps <= 0 then error("CreateNewSlider failed because numOfSteps (first arg) is 0 or negative, please supply a positive integer", 2) return end local valueChangedEvent = Instance.new("BindableEvent") valueChangedEvent.Name = "ValueChanged" ----------------- GUI SETUP ------------------------ this.SliderFrame = Util.Create'ImageButton' { Name = "Slider", Image = "", AutoButtonColor = false, NextSelectionLeft = this.SliderFrame, NextSelectionRight = this.SliderFrame, BackgroundTransparency = 1, Size = UDim2.new(0.6, 0, 0, 50), Position = UDim2.new(1, 0, 0.5, 0), AnchorPoint = Vector2.new(1, 0.5), SelectionImageObject = noSelectionObject, ZIndex = 2 }; this.StepsContainer = Util.Create "Frame" { Name = "StepsContainer", Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(1, -100, 1, 0), AnchorPoint = Vector2.new(0.5, 0.5), BackgroundTransparency = 1, Parent = this.SliderFrame, } local leftButton = Util.Create'ImageButton' { Name = "LeftButton", BackgroundTransparency = 1, AnchorPoint = Vector2.new(0, 0.5), Position = UDim2.new(0,0,0.5,0), Size = UDim2.new(0,50,0,50), Image = "", ZIndex = 3, Selectable = false, SelectionImageObject = noSelectionObject, Active = true, Parent = this.SliderFrame }; local rightButton = Util.Create'ImageButton' { Name = "RightButton", BackgroundTransparency = 1, AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1,0,0.5,0), Size = UDim2.new(0,50,0,50), Image = "", ZIndex = 3, Selectable = false, SelectionImageObject = noSelectionObject, Active = true, Parent = this.SliderFrame }; local leftButtonImage = Util.Create'ImageLabel' { Name = "LeftButton", BackgroundTransparency = 1, AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.new(0.5,0,0.5,0), Size = UDim2.new(0,30,0,30), Image = "rbxasset://textures/ui/Settings/Slider/Less.png", ZIndex = 4, Parent = leftButton, ImageColor3 = UserInputService.TouchEnabled and ARROW_COLOR_TOUCH or ARROW_COLOR }; local rightButtonImage = Util.Create'ImageLabel' { Name = "RightButton", BackgroundTransparency = 1, AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.new(0.5,0,0.5,0), Size = UDim2.new(0,30,0,30), Image = "rbxasset://textures/ui/Settings/Slider/More.png", ZIndex = 4, Parent = rightButton, ImageColor3 = UserInputService.TouchEnabled and ARROW_COLOR_TOUCH or ARROW_COLOR }; if not UserInputService.TouchEnabled then local onNormalButtonState, onHoverButtonState = function(instance) instance.ImageColor3 = ARROW_COLOR end, function(instance) instance.ImageColor3 = ARROW_COLOR_HOVER end addHoverState(leftButton, leftButtonImage, onNormalButtonState, onHoverButtonState) addHoverState(rightButton, rightButtonImage, onNormalButtonState, onHoverButtonState) end this.Steps = {} local stepXSize = 35 if isSmallTouchScreen() then stepXSize = 25 end local stepXScale = 1 / steps stepXSize = 0 for i = 1, steps do local nextStep = Util.Create'ImageButton' { Name = "Step" .. tostring(i), BackgroundColor3 = SELECTED_COLOR, BackgroundTransparency = 0.36, BorderSizePixel = 0, AutoButtonColor = false, Active = false, AnchorPoint = Vector2.new(0, 0.5), Position = UDim2.new((i - 1) * stepXScale, spacing / 2, 0.5, 0), Size = UDim2.new(stepXScale,-spacing, 24 / 50, 0), Image = "", ZIndex = 3, Selectable = false, ImageTransparency = 0.36, Parent = this.StepsContainer, SelectionImageObject = noSelectionObject }; if i > currentStep then nextStep.BackgroundColor3 = NON_SELECTED_COLOR end if i == 1 or i == steps then nextStep.BackgroundTransparency = 1 nextStep.ScaleType = Enum.ScaleType.Slice nextStep.SliceCenter = Rect.new(3,3,32,21) if i <= currentStep then if i == 1 then nextStep.Image = SELECTED_LEFT_IMAGE else nextStep.Image = SELECTED_RIGHT_IMAGE end else if i == 1 then nextStep.Image = NON_SELECTED_LEFT_IMAGE else nextStep.Image = NON_SELECTED_RIGHT_IMAGE end end end this.Steps[#this.Steps + 1] = nextStep end ------------------- FUNCTIONS --------------------- local function hideSelection() for i = 1, steps do this.Steps[i].BackgroundColor3 = NON_SELECTED_COLOR if i == 1 then this.Steps[i].Image = NON_SELECTED_LEFT_IMAGE elseif i == steps then this.Steps[i].Image = NON_SELECTED_RIGHT_IMAGE end end end local function showSelection() for i = 1, steps do if i > currentStep then break end this.Steps[i].BackgroundColor3 = SELECTED_COLOR if i == 1 then this.Steps[i].Image = SELECTED_LEFT_IMAGE elseif i == steps then this.Steps[i].Image = SELECTED_RIGHT_IMAGE end end end local function modifySelection(alpha) for i = 1, steps do if i == 1 or i == steps then this.Steps[i].ImageTransparency = alpha else this.Steps[i].BackgroundTransparency = alpha end end end local function setCurrentStep(newStepPosition) if not minStep then minStep = 0 end leftButton.Visible = true rightButton.Visible = true if newStepPosition <= minStep then newStepPosition = minStep leftButton.Visible = false end if newStepPosition >= steps then newStepPosition = steps rightButton.Visible = false end if currentStep == newStepPosition then return end currentStep = newStepPosition hideSelection() showSelection() timeAtLastInput = tick() valueChangedEvent:Fire(currentStep) end local function isActivateEvent(inputObject) if not inputObject then return false end return inputObject.UserInputType == Enum.UserInputType.MouseButton1 or inputObject.UserInputType == Enum.UserInputType.Touch or (inputObject.UserInputType == Enum.UserInputType.Gamepad1 and inputObject.KeyCode == Enum.KeyCode.ButtonA) end local function mouseDownFunc(inputObject, newStepPos, repeatAction) if not interactable then return end if inputObject == nil then return end if not isActivateEvent(inputObject) then return end if usesSelectedObject() and not VRService.VREnabled then GuiService.SelectedCoreObject = this.SliderFrame end if not VRService.VREnabled then if repeatAction then lastInputDirection = newStepPos - currentStep else lastInputDirection = 0 local mouseInputMovedCon = nil local mouseInputEndedCon = nil mouseInputMovedCon = UserInputService.InputChanged:Connect(function(inputObject) if inputObject.UserInputType ~= Enum.UserInputType.MouseMovement then return end local mousePos = inputObject.Position.X for i = 1, steps do local stepPosition = this.Steps[i].AbsolutePosition.X local stepSize = this.Steps[i].AbsoluteSize.X if mousePos >= stepPosition and mousePos <= stepPosition + stepSize then setCurrentStep(i) break elseif i == 1 and mousePos < stepPosition then setCurrentStep(0) break elseif i == steps and mousePos >= stepPosition then setCurrentStep(i) break end end end) mouseInputEndedCon = UserInputService.InputEnded:Connect(function(inputObject) if not isActivateEvent(inputObject) then return end lastInputDirection = 0 mouseInputEndedCon:Disconnect() mouseInputMovedCon:Disconnect() end) end else lastInputDirection = 0 end setCurrentStep(newStepPos) end local function mouseUpFunc(inputObject) if not interactable then return end if not isActivateEvent(inputObject) then return end lastInputDirection = 0 end local function touchClickFunc(inputObject, newStepPos, repeatAction) mouseDownFunc(inputObject, newStepPos, repeatAction) end --------------------- PUBLIC FACING FUNCTIONS ----------------------- this.ValueChanged = valueChangedEvent.Event function this:SetValue(newValue) setCurrentStep(newValue) end function this:GetValue() return currentStep end function this:SetInteractable(value) lastInputDirection = 0 interactable = value this.SliderFrame.Selectable = value if not interactable then hideSelection() else showSelection() end end function this:SetZIndex(newZIndex) leftButton.ZIndex = newZIndex rightButton.ZIndex = newZIndex leftButtonImage.ZIndex = newZIndex rightButtonImage.ZIndex = newZIndex for i = 1, #this.Steps do this.Steps[i].ZIndex = newZIndex end end function this:SetMinStep(newMinStep) if newMinStep >= 0 and newMinStep <= steps then minStep = newMinStep end if currentStep <= minStep then currentStep = minStep leftButton.Visible = false end if currentStep >= steps then currentStep = steps rightButton.Visible = false end end --------------------- SETUP ----------------------- leftButton.InputBegan:Connect(function(inputObject) mouseDownFunc(inputObject, currentStep - 1, true) end) leftButton.InputEnded:Connect(function(inputObject) mouseUpFunc(inputObject) end) rightButton.InputBegan:Connect(function(inputObject) mouseDownFunc(inputObject, currentStep + 1, true) end) rightButton.InputEnded:Connect(function(inputObject) mouseUpFunc(inputObject) end) local function onVREnabled(prop) if prop ~= "VREnabled" then return end if VRService.VREnabled then leftButton.Selectable = interactable rightButton.Selectable = interactable this.SliderFrame.Selectable = interactable for i = 1, steps do this.Steps[i].Selectable = interactable this.Steps[i].Active = interactable end else leftButton.Selectable = false rightButton.Selectable = false this.SliderFrame.Selectable = interactable for i = 1, steps do this.Steps[i].Selectable = false this.Steps[i].Active = false end end end VRService.Changed:Connect(onVREnabled) onVREnabled("VREnabled") for i = 1, steps do this.Steps[i].InputBegan:Connect(function(inputObject) mouseDownFunc(inputObject, i) end) this.Steps[i].InputEnded:Connect(function(inputObject) mouseUpFunc(inputObject) end) end this.SliderFrame.InputBegan:Connect(function(inputObject) if VRService.VREnabled then local selected = GuiService.SelectedCoreObject if not selected or not selected:IsDescendantOf(this.SliderFrame.Parent) then return end end mouseDownFunc(inputObject, currentStep) end) this.SliderFrame.InputEnded:Connect(function(inputObject) if VRService.VREnabled then local selected = GuiService.SelectedCoreObject if not selected or not selected:IsDescendantOf(this.SliderFrame.Parent) then return end end mouseUpFunc(inputObject) end) local stepSliderFunc = function() if timeAtLastInput == nil then return end local currentTime = tick() local timeSinceLastInput = currentTime - timeAtLastInput if timeSinceLastInput >= CONTROLLER_SCROLL_DELTA then setCurrentStep(currentStep + lastInputDirection) end end local isInTree = true local navigateLeft = -1 --these are just for differentiation, the actual value isn't important as long as they coerce to boolean true (all numbers do in Lua) local navigateRight = 1 local navigationKeyCodes = { [Enum.KeyCode.Thumbstick1] = true, --thumbstick can be either direction [Enum.KeyCode.DPadLeft] = navigateLeft, [Enum.KeyCode.DPadRight] = navigateRight, [Enum.KeyCode.Left] = navigateLeft, [Enum.KeyCode.Right] = navigateRight, [Enum.KeyCode.A] = navigateLeft, [Enum.KeyCode.D] = navigateRight, [Enum.KeyCode.ButtonA] = true --buttonA can be either direction } UserInputService.InputBegan:Connect(function(inputObject) if not interactable then return end if not isInTree then return end if inputObject.UserInputType ~= Enum.UserInputType.Gamepad1 and inputObject.UserInputType ~= Enum.UserInputType.Keyboard then return end local selected = GuiService.SelectedCoreObject if not selected or not selected:IsDescendantOf(this.SliderFrame.Parent) then return end if navigationKeyCodes[inputObject.KeyCode] == navigateLeft then lastInputDirection = -1 setCurrentStep(currentStep - 1) elseif navigationKeyCodes[inputObject.KeyCode] == navigateRight then lastInputDirection = 1 setCurrentStep(currentStep + 1) end end) UserInputService.InputEnded:Connect(function(inputObject) if not interactable then return end if inputObject.UserInputType ~= Enum.UserInputType.Gamepad1 and inputObject.UserInputType ~= Enum.UserInputType.Keyboard then return end local selected = GuiService.SelectedCoreObject if not selected or not selected:IsDescendantOf(this.SliderFrame.Parent) then return end if navigationKeyCodes[inputObject.KeyCode] then --detect any keycode considered a navigation key lastInputDirection = 0 end end) UserInputService.InputChanged:Connect(function(inputObject) if not interactable then lastInputDirection = 0 return end if not isInTree then lastInputDirection = 0 return end if inputObject.UserInputType ~= Enum.UserInputType.Gamepad1 then return end local selected = GuiService.SelectedCoreObject if not selected or not selected:IsDescendantOf(this.SliderFrame.Parent) then return end if inputObject.KeyCode ~= Enum.KeyCode.Thumbstick1 then return end if inputObject.Position.X > CONTROLLER_THUMBSTICK_DEADZONE and inputObject.Delta.X > 0 and lastInputDirection ~= 1 then lastInputDirection = 1 setCurrentStep(currentStep + 1) elseif inputObject.Position.X < -CONTROLLER_THUMBSTICK_DEADZONE and inputObject.Delta.X < 0 and lastInputDirection ~= -1 then lastInputDirection = -1 setCurrentStep(currentStep - 1) elseif math.abs(inputObject.Position.X) < CONTROLLER_THUMBSTICK_DEADZONE then lastInputDirection = 0 end end) local isBound = false GuiService.Changed:Connect(function(prop) if prop ~= "SelectedCoreObject" then return end local selected = GuiService.SelectedCoreObject local isThisSelected = selected and selected:IsDescendantOf(this.SliderFrame.Parent) if isThisSelected then modifySelection(0) if not isBound then isBound = true timeAtLastInput = tick() RunService:BindToRenderStep(renderStepBindName, Enum.RenderPriority.Input.Value + 1, stepSliderFunc) end else modifySelection(0.36) if isBound then isBound = false RunService:UnbindFromRenderStep(renderStepBindName) end end end) this.SliderFrame.AncestryChanged:Connect(function(child, parent) isInTree = parent end) setCurrentStep(currentStep) return this end local ROW_HEIGHT = 50 if isTenFootInterface() then ROW_HEIGHT = 90 end local nextPosTable = {} local function AddNewRow(pageToAddTo, rowDisplayName, selectionType, rowValues, rowDefault, extraSpacing) local nextRowPositionY = 0 local isARealRow = selectionType ~= 'TextBox' -- Textboxes are constructed in this function - they don't have an associated class. if nextPosTable[pageToAddTo] then nextRowPositionY = nextPosTable[pageToAddTo] end local RowFrame = nil RowFrame = Util.Create'ImageButton' { Name = rowDisplayName .. "Frame", BackgroundTransparency = 1, BorderSizePixel = 0, Image = "", Active = false, AutoButtonColor = false, Size = UDim2.new(1,0,0,ROW_HEIGHT), Position = UDim2.new(0,0,0,nextRowPositionY), ZIndex = 2, Selectable = false, SelectionImageObject = noSelectionObject, Parent = pageToAddTo.Page }; if RowFrame and extraSpacing then RowFrame.Position = UDim2.new(RowFrame.Position.X.Scale,RowFrame.Position.X.Offset, RowFrame.Position.Y.Scale,RowFrame.Position.Y.Offset + extraSpacing) end local RowLabel = nil RowLabel = Util.Create'TextLabel' { Name = rowDisplayName .. "Label", Text = rowDisplayName, Font = Enum.Font.SourceSansBold, TextSize = 16, TextColor3 = Color3.fromRGB(255,255,255), TextXAlignment = Enum.TextXAlignment.Left, BackgroundTransparency = 1, Size = UDim2.new(0,200,1,0), Position = UDim2.new(0,10,0,0), ZIndex = 2, Parent = RowFrame }; if not isARealRow then RowLabel.Text = '' end local function onResized(viewportSize, portrait) if portrait then RowLabel.TextSize = 16 else RowLabel.TextSize = isTenFootInterface() and 36 or 24 end end onResized(getViewportSize(), isPortrait()) addOnResizedCallback(RowFrame, onResized) local ValueChangerSelection = nil local ValueChangerInstance = nil if selectionType == "Slider" then ValueChangerInstance = CreateNewSlider(rowValues, rowDefault) ValueChangerInstance.SliderFrame.Parent = RowFrame ValueChangerSelection = ValueChangerInstance.SliderFrame elseif selectionType == "Selector" then ValueChangerInstance = CreateSelector(rowValues, rowDefault) ValueChangerInstance.SelectorFrame.Parent = RowFrame ValueChangerSelection = ValueChangerInstance.SelectorFrame elseif selectionType == "DropDown" then ValueChangerInstance = CreateDropDown(rowValues, rowDefault, pageToAddTo.HubRef) ValueChangerInstance.DropDownFrame.Parent = RowFrame ValueChangerSelection = ValueChangerInstance.DropDownFrame elseif selectionType == "TextBox" then local isMouseOverRow = false local forceReturnSelectionOnFocusLost = false local SelectionOverrideObject = Util.Create'ImageLabel' { Image = "", BackgroundTransparency = 1, }; ValueChangerInstance = {} ValueChangerInstance.HubRef = nil local box = Util.Create'TextBox' { AnchorPoint = Vector2.new(1, 0.5), Size = UDim2.new(0.6,0,1,0), Position = UDim2.new(1,0,0.5,0), Text = rowDisplayName, TextColor3 = Color3.fromRGB(49, 49, 49), BackgroundTransparency = 0.5, BorderSizePixel = 0, TextYAlignment = Enum.TextYAlignment.Top, TextXAlignment = Enum.TextXAlignment.Left, TextWrapped = true, Font = Enum.Font.SourceSans, TextSize = 24, ZIndex = 2, SelectionImageObject = SelectionOverrideObject, ClearTextOnFocus = false, Parent = RowFrame }; ValueChangerSelection = box box.Focused:Connect(function() if usesSelectedObject() then GuiService.SelectedCoreObject = box end if box.Text == rowDisplayName then box.Text = "" end end) box.FocusLost:Connect(function(enterPressed, inputObject) if GuiService.SelectedCoreObject == box and (not isMouseOverRow or forceReturnSelectionOnFocusLost) then GuiService.SelectedCoreObject = nil end forceReturnSelectionOnFocusLost = false end) if extraSpacing then box.Position = UDim2.new(box.Position.X.Scale,box.Position.X.Offset, box.Position.Y.Scale,box.Position.Y.Offset + extraSpacing) end ValueChangerSelection.SelectionGained:Connect(function() if usesSelectedObject() then box.BackgroundTransparency = 0.1 if ValueChangerInstance.HubRef then ValueChangerInstance.HubRef:ScrollToFrame(ValueChangerSelection) end end end) ValueChangerSelection.SelectionLost:Connect(function() if usesSelectedObject() then box.BackgroundTransparency = 0.5 end end) local setRowSelection = function() local fullscreenDropDown = CoreGui.RobloxGui:FindFirstChild("DropDownFullscreenFrame") if fullscreenDropDown and fullscreenDropDown.Visible then return end local valueFrame = ValueChangerSelection if valueFrame and valueFrame.Visible and valueFrame.ZIndex > 1 and usesSelectedObject() and pageToAddTo.Active then GuiService.SelectedCoreObject = valueFrame isMouseOverRow = true end end local function processInput(input) if input.UserInputState == Enum.UserInputState.Begin then if input.KeyCode == Enum.KeyCode.Return then if GuiService.SelectedCoreObject == ValueChangerSelection then forceReturnSelectionOnFocusLost = true box:CaptureFocus() end end end end box.MouseEnter:Connect(setRowSelection) UserInputService.InputBegan:Connect(processInput) elseif selectionType == "TextEntry" then local isMouseOverRow = false local forceReturnSelectionOnFocusLost = false local SelectionOverrideObject = Util.Create'ImageLabel' { Image = "", BackgroundTransparency = 1, }; ValueChangerInstance = {} ValueChangerInstance.HubRef = nil local box = Util.Create'TextBox' { AnchorPoint = Vector2.new(1, 0.5), Size = UDim2.new(0.4,-10,0,40), Position = UDim2.new(1,0,0.5,0), Text = rowDisplayName, TextColor3 = Color3.fromRGB(178, 178, 178), BackgroundTransparency = 1.0, BorderSizePixel = 0, TextYAlignment = Enum.TextYAlignment.Center, TextXAlignment = Enum.TextXAlignment.Center, TextWrapped = false, Font = Enum.Font.SourceSans, TextSize = 24, ZIndex = 2, SelectionImageObject = SelectionOverrideObject, ClearTextOnFocus = false, Parent = RowFrame }; ValueChangerSelection = box box.Focused:Connect(function() if usesSelectedObject() then GuiService.SelectedCoreObject = box end if box.Text == rowDisplayName then box.Text = "" end end) box.FocusLost:Connect(function(enterPressed, inputObject) if GuiService.SelectedCoreObject == box and (not isMouseOverRow or forceReturnSelectionOnFocusLost) then GuiService.SelectedCoreObject = nil end forceReturnSelectionOnFocusLost = false end) if extraSpacing then box.Position = UDim2.new(box.Position.X.Scale,box.Position.X.Offset, box.Position.Y.Scale,box.Position.Y.Offset + extraSpacing) end ValueChangerSelection.SelectionGained:Connect(function() if usesSelectedObject() then box.BackgroundTransparency = 0.8 if ValueChangerInstance.HubRef then ValueChangerInstance.HubRef:ScrollToFrame(ValueChangerSelection) end end end) ValueChangerSelection.SelectionLost:Connect(function() if usesSelectedObject() then box.BackgroundTransparency = 1.0 end end) local setRowSelection = function() local fullscreenDropDown = CoreGui.RobloxGui:FindFirstChild("DropDownFullscreenFrame") if fullscreenDropDown and fullscreenDropDown.Visible then return end local valueFrame = ValueChangerSelection if valueFrame and valueFrame.Visible and valueFrame.ZIndex > 1 and usesSelectedObject() and pageToAddTo.Active then GuiService.SelectedCoreObject = valueFrame isMouseOverRow = true end end local function processInput(input) if input.UserInputState == Enum.UserInputState.Begin then if input.KeyCode == Enum.KeyCode.Return then if GuiService.SelectedCoreObject == ValueChangerSelection then forceReturnSelectionOnFocusLost = true box:CaptureFocus() end end end end RowFrame.MouseEnter:Connect(setRowSelection) function ValueChangerInstance:SetZIndex(newZIndex) box.ZIndex = newZIndex end function ValueChangerInstance:SetInteractable(interactable) box.Selectable = interactable if not interactable then box.TextColor3 = Color3.fromRGB(49,49,49) box.ZIndex = 1 else box.TextColor3 = Color3.fromRGB(178,178,178) box.ZIndex = 2 end end function ValueChangerInstance:SetValue(value) -- should this do more? box.Text = value end local valueChangedEvent = Instance.new("BindableEvent") valueChangedEvent.Name = "ValueChanged" box.FocusLost:Connect(function() valueChangedEvent:Fire(box.Text) end) ValueChangerInstance.ValueChanged = valueChangedEvent.Event UserInputService.InputBegan:Connect(processInput) end ValueChangerInstance.Name = rowDisplayName .. "ValueChanger" nextRowPositionY = nextRowPositionY + ROW_HEIGHT if extraSpacing then nextRowPositionY = nextRowPositionY + extraSpacing end nextPosTable[pageToAddTo] = nextRowPositionY if isARealRow then local setRowSelection = function() local fullscreenDropDown = CoreGui.RobloxGui:FindFirstChild("DropDownFullscreenFrame") if fullscreenDropDown and fullscreenDropDown.Visible then return end local valueFrame = ValueChangerInstance.SliderFrame if not valueFrame then valueFrame = ValueChangerInstance.SliderFrame end if not valueFrame then valueFrame = ValueChangerInstance.DropDownFrame end if not valueFrame then valueFrame = ValueChangerInstance.SelectorFrame end if valueFrame and valueFrame.Visible and valueFrame.ZIndex > 1 and usesSelectedObject() and pageToAddTo.Active then GuiService.SelectedCoreObject = valueFrame end end RowFrame.MouseEnter:Connect(setRowSelection) --Could this be cleaned up even more? local function onVREnabled(prop) if prop == "VREnabled" then if VRService.VREnabled then RowFrame.Selectable = true RowFrame.Active = true ValueChangerSelection.Active = true GuiService.Changed:Connect(function(prop) if prop == "SelectedCoreObject" then local selected = GuiService.SelectedCoreObject if selected and (selected == RowFrame or selected:IsDescendantOf(RowFrame)) then RowFrame.BackgroundTransparency = 0.5 else RowFrame.BackgroundTransparency = 1 end end end) else RowFrame.Selectable = false RowFrame.Active = false end end end VRService.Changed:Connect(onVREnabled) onVREnabled("VREnabled") ValueChangerSelection.SelectionGained:Connect(function() if usesSelectedObject() then RowFrame.BackgroundTransparency = 0.5 if ValueChangerInstance.HubRef then ValueChangerInstance.HubRef:ScrollToFrame(RowFrame) end end end) ValueChangerSelection.SelectionLost:Connect(function() if usesSelectedObject() then RowFrame.BackgroundTransparency = 1 end end) end pageToAddTo:AddRow(RowFrame, RowLabel, ValueChangerInstance, extraSpacing, false) ValueChangerInstance.Selection = ValueChangerSelection return RowFrame, RowLabel, ValueChangerInstance end local function AddNewRowObject(pageToAddTo, rowDisplayName, rowObject, extraSpacing) local nextRowPositionY = 0 if nextPosTable[pageToAddTo] then nextRowPositionY = nextPosTable[pageToAddTo] end local RowFrame = Util.Create'ImageButton' { Name = rowDisplayName .. "Frame", BackgroundTransparency = 1, BorderSizePixel = 0, Image = "", Active = false, AutoButtonColor = false, Size = UDim2.new(1,0,0,ROW_HEIGHT), Position = UDim2.new(0,0,0,nextRowPositionY), ZIndex = 2, Selectable = not fixSettingsMenuDropdowns, --this should be false after removing FFlagFixSettingsMenuDropdowns SelectionImageObject = noSelectionObject, Parent = pageToAddTo.Page }; RowFrame.SelectionGained:Connect(function() RowFrame.BackgroundTransparency = 0.5 end) RowFrame.SelectionLost:Connect(function() RowFrame.BackgroundTransparency = 1 end) local RowLabel = Util.Create'TextLabel' { Name = rowDisplayName .. "Label", Text = rowDisplayName, Font = Enum.Font.SourceSansBold, TextSize = 16, TextColor3 = Color3.fromRGB(255,255,255), TextXAlignment = Enum.TextXAlignment.Left, BackgroundTransparency = 1, Size = UDim2.new(0,200,1,0), Position = UDim2.new(0,10,0,0), ZIndex = 2, Parent = RowFrame }; local function onResized(viewportSize, portrait) if portrait then RowLabel.TextSize = 16 else RowLabel.TextSize = isTenFootInterface() and 36 or 24 end end addOnResizedCallback(RowFrame, onResized) if extraSpacing then RowFrame.Position = UDim2.new(RowFrame.Position.X.Scale,RowFrame.Position.X.Offset, RowFrame.Position.Y.Scale,RowFrame.Position.Y.Offset + extraSpacing) end nextRowPositionY = nextRowPositionY + ROW_HEIGHT if extraSpacing then nextRowPositionY = nextRowPositionY + extraSpacing end nextPosTable[pageToAddTo] = nextRowPositionY local setRowSelection = function() if RowFrame.Visible then GuiService.SelectedCoreObject = RowFrame end end RowFrame.MouseEnter:Connect(setRowSelection) rowObject.SelectionImageObject = noSelectionObject rowObject.SelectionGained:Connect(function() RowFrame.BackgroundTransparency = 0.5 end) rowObject.SelectionLost:Connect(function() RowFrame.BackgroundTransparency = 1 end) rowObject.Parent = RowFrame pageToAddTo:AddRow(RowFrame, RowLabel, rowObject, extraSpacing, true) return RowFrame end -------- public facing API ---------------- local moduleApiTable = {} function moduleApiTable:Create(instanceType) return function(data) local obj = Instance.new(instanceType) local parent = nil for k, v in pairs(data) do if type(k) == 'number' then v.Parent = obj elseif k == 'Parent' then parent = v else obj[k] = v end end if parent then obj.Parent = parent end return obj end end -- RayPlaneIntersection (shortened) -- http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm function moduleApiTable:RayPlaneIntersection(ray, planeNormal, pointOnPlane) planeNormal = planeNormal.unit ray = ray.Unit local Vd = planeNormal:Dot(ray.Direction) if Vd == 0 then -- parallel, no intersection return nil end local V0 = planeNormal:Dot(pointOnPlane - ray.Origin) local t = V0 / Vd if t < 0 then --plane is behind ray origin, and thus there is no intersection return nil end return ray.Origin + ray.Direction * t end function moduleApiTable:GetEaseLinear() return Linear end function moduleApiTable:GetEaseOutQuad() return EaseOutQuad end function moduleApiTable:GetEaseInOutQuad() return EaseInOutQuad end function moduleApiTable:CreateNewSlider(numOfSteps, startStep, minStep) return CreateNewSlider(numOfSteps, startStep, minStep) end function moduleApiTable:CreateNewSelector(selectionStringTable, startPosition) return CreateSelector(selectionStringTable, startPosition) end function moduleApiTable:CreateNewDropDown(dropDownStringTable, startPosition) return CreateDropDown(dropDownStringTable, startPosition, nil) end function moduleApiTable:AddNewRow(pageToAddTo, rowDisplayName, selectionType, rowValues, rowDefault, extraSpacing) return AddNewRow(pageToAddTo, rowDisplayName, selectionType, rowValues, rowDefault, extraSpacing) end function moduleApiTable:AddNewRowObject(pageToAddTo, rowDisplayName, rowObject, extraSpacing) return AddNewRowObject(pageToAddTo, rowDisplayName, rowObject, extraSpacing) end function moduleApiTable:ShowAlert(alertMessage, okButtonText, settingsHub, okPressedFunc, hasBackground) ShowAlert(alertMessage, okButtonText, settingsHub, okPressedFunc, hasBackground) end function moduleApiTable:IsSmallTouchScreen() return isSmallTouchScreen() end function moduleApiTable:IsPortrait() return isPortrait() end function moduleApiTable:MakeStyledButton(name, text, size, clickFunc, pageRef, hubRef) return MakeButton(name, text, size, clickFunc, pageRef, hubRef) end function moduleApiTable:MakeStyledImageButton(name, image, size, imageSize, clickFunc, pageRef, hubRef) return MakeImageButton(name, image, size, imageSize, clickFunc, pageRef, hubRef) end function moduleApiTable:AddButtonRow(pageToAddTo, name, text, size, clickFunc, hubRef) return AddButtonRow(pageToAddTo, name, text, size, clickFunc, hubRef) end function moduleApiTable:CreateSignal() return CreateSignal() end function moduleApiTable:UsesSelectedObject() return usesSelectedObject() end function moduleApiTable:TweenProperty(instance, prop, start, final, duration, easingFunc, cbFunc) return PropertyTweener(instance, prop, start, final, duration, easingFunc, cbFunc) end function moduleApiTable:OnResized(key, callback) return addOnResizedCallback(key, callback) end function moduleApiTable:FireOnResized() local newSize = getViewportSize() local portrait = moduleApiTable:IsPortrait() for key, callback in pairs(onResizedCallbacks) do callback(newSize, portrait) end end return moduleApiTable