Files
2025-09-18 17:55:52 -04:00

451 lines
14 KiB
Lua

-- Written by Kip Turner, Copyright ROBLOX 2015
local TextService = game:GetService('TextService')
local CoreGui = game:GetService("CoreGui")
local GuiService = game:GetService('GuiService')
local RunService = game:GetService('RunService')
local ContextActionService = game:GetService("ContextActionService")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local GameOptionsSettings = settings():FindFirstChild("Game Options")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local BACKGROUND_COLOR = Color3.new(3/255,3/255,3/255)
local START_EDGE_SIZE = UDim2.new(0.9,0,0.9,0)
local MIN_EDGE_SIZE = UDim2.new(0.85,0,0.85,0)
local MAX_EDGE_SIZE = UDim2.new(1,0,1,0)
local ZERO_VEC2 = Vector2.new(0,0)
local THUMBSTICK_BORDER_SENSITIVITY = 0.05
local MAX_STICK_ACCELERATION = 3
local ACCELERATION_RATE = 1
local DPAD_STEP_AMOUNT = 2
local DPAD_CODE_TO_EDGE_PUSH =
{
[Enum.KeyCode.DPadDown] = Vector2.new(0.0, DPAD_STEP_AMOUNT);
[Enum.KeyCode.DPadUp] = Vector2.new(0.0, -DPAD_STEP_AMOUNT);
[Enum.KeyCode.DPadLeft] = Vector2.new(-DPAD_STEP_AMOUNT, 0.0);
[Enum.KeyCode.DPadRight] = Vector2.new(DPAD_STEP_AMOUNT, 0.0);
}
local function CreateOverscanAdjustmentScreen(parent)
local this = {}
this.StickPosition = ZERO_VEC2
this.StickAcceleration = 1
local lastUpdate = nil
local lastParent = parent
local lastSavedOverscanPX = START_EDGE_SIZE.X.Scale
local lastSavedOverscanPY = START_EDGE_SIZE.Y.Scale
local function StorePreviousOverscanValues()
local success, errormsg = pcall(function()
lastSavedOverscanPX = GameOptionsSettings.OverscanPX
lastSavedOverscanPY = GameOptionsSettings.OverscanPY
end)
if not success then
print("Error, StorePreviousOverscanValues: OverscanPX and OverscanPY" , errormsg)
end
end
StorePreviousOverscanValues()
local MainContainer = Utility.Create'Frame'
{
Name = 'OverscanAdjustmentScreen';
Size = UDim2.new(1, 0, 1, 0);
BorderSizePixel = 1;
BackgroundTransparency = 0;
BackgroundColor3 = BACKGROUND_COLOR;
Visible = false;
}
local BackgroundImage = Utility.Create'ImageLabel'
{
Name = 'BackgroundImage';
Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/Background.png';
Size = UDim2.new(1,0,1,0);
BackgroundTransparency = 1;
Parent = MainContainer;
};
local Title = Utility.Create'TextLabel'
{
Name = 'Title';
Text = Strings:LocalizedString('ScreenSizeWord');
Position = UDim2.new(0, 230, 0, 175);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
Parent = MainContainer;
};
do
local titleTextSize = TextService:GetTextSize(Title.Text, Utility.ConvertFontSizeEnumToInt(Title.FontSize), Title.Font, Vector2.new())
Title.Size = UDim2.new(0, titleTextSize.X, 0, titleTextSize.Y)
end
local Prompt = Utility.Create'TextLabel'
{
Name = 'Prompt';
Text = Strings:LocalizedString('ResizeScreenPrompt');
Position = UDim2.new(0,230,0,225);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
Parent = MainContainer;
};
do
local ControllerImage = Utility.Create'ImageLabel'
{
Name = 'ControllerImage';
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/Controller@1080.png';
Size = UDim2.new(0,599,0,404);
Parent = MainContainer;
};
Utility.CalculateAnchor(ControllerImage, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
local Line = Utility.Create'Frame'
{
Name = 'Line';
Size = UDim2.new(0.4,0,0,1);
Position = UDim2.new(0.73, 0, 0.545, 0);
BackgroundColor3 = Color3.new(1,1,1);
BorderSizePixel = 0;
BackgroundTransparency = 0;
Parent = ControllerImage;
};
local InputHint = Utility.Create'TextLabel'
{
Name = 'InputHint';
Text = Strings:LocalizedString('ResizeScreenInputHint');
Size = UDim2.new(0,0,0,0);
Position = UDim2.new(1, 3, 0, -1);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
Parent = Line;
};
end
local AcceptButtonImage = Utility.Create'ImageLabel'
{
Name = 'AcceptButtonImage';
Size = UDim2.new(0,65,0,65);
Position = UDim2.new(0.5, 25, 0.75, 0);
Image = 'rbxasset://textures/ui/Shell/ButtonIcons/AButton.png';
BackgroundTransparency = 1;
Parent = MainContainer;
};
local AcceptHint = Utility.Create'TextLabel'
{
Name = 'AcceptHint';
Text = Strings:LocalizedString('AcceptWord'):upper();
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1, 5, 0, -3);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
Parent = AcceptButtonImage;
};
local ResetButtonImage = Utility.Create'ImageLabel'
{
Name = 'ResetButtonImage';
Size = UDim2.new(0,65,0,65);
Image = 'rbxasset://textures/ui/Shell/ButtonIcons/XButton.png';
BackgroundTransparency = 1;
Parent = MainContainer;
};
local ResetHint = Utility.Create'TextLabel'
{
Name = 'ResetHint';
Text = Strings:LocalizedString('ResetWord'):upper();
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1, 5, 0, -3);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
Parent = ResetButtonImage;
};
local Edges = Utility.Create'Frame'
{
Name = 'Edges';
Size = START_EDGE_SIZE;
BackgroundTransparency = 1;
Parent = MainContainer;
};
do
local edgesSelectionImage = Utility.Create'ImageLabel'
{
Name = 'EdgesSelectionImage';
Size = UDim2.new(1,2,1,2);
Position = UDim2.new(0,-1,0,-1);
Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/ScreenRangeOverlay.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(21,21,41,41);
BackgroundTransparency = 1;
};
edgesSelectionImage.Parent = Edges
end
for i = 0, 3 do
local CornerImage = Utility.Create'ImageLabel'
{
Name = 'CornerImage';
Size = UDim2.new(0, 95, 0, 95);
BackgroundTransparency = 1;
Rotation = 90 * i;
Image = "rbxasset://textures/ui/Shell/ScreenAdjustment/ScreenAdjustmentArrow.png";
Parent = Edges;
};
if i == 0 then
Utility.CalculateAnchor(CornerImage, UDim2.new(0,0,0,0), Utility.Enum.Anchor.TopLeft)
elseif i == 1 then
Utility.CalculateAnchor(CornerImage, UDim2.new(1, 0, 0, 0), Utility.Enum.Anchor.TopRight)
elseif i == 2 then
Utility.CalculateAnchor(CornerImage, UDim2.new(1, 0, 1, 0), Utility.Enum.Anchor.BottomRight)
elseif i == 3 then
Utility.CalculateAnchor(CornerImage, UDim2.new(0, 0, 1, 0), Utility.Enum.Anchor.BottomLeft)
end
end
local function RefreshLayout()
ResetButtonImage.Position = UDim2.new(0.5 - ResetButtonImage.Size.X.Scale, -ResetHint.TextBounds.X - 25 - ResetButtonImage.Size.X.Offset, 0.75, 0);
Prompt.Size = UDim2.new(0, Prompt.TextBounds.X, 0, Prompt.TextBounds.Y)
end
local closedEvent = Instance.new("BindableEvent")
closedEvent.Name = "ClosedEvent"
this.Closed = closedEvent.Event
local EdgePercent = Vector2.new(START_EDGE_SIZE.X, START_EDGE_SIZE.Y)
function this:GetAdjustmentEdgesPercent()
return EdgePercent
end
function this:SetAdjustmentEdges(newEdgeSize)
EdgePercent = Utility.ClampVector2(Vector2.new(MIN_EDGE_SIZE.X.Scale, MIN_EDGE_SIZE.Y.Scale), Vector2.new(1,1), newEdgeSize)
local guiSize = GuiRoot.AbsoluteSize
local absoluteEdgeSize = EdgePercent * guiSize
-- Round to nearest 2 so that we can evenly space above and below
local roundedAbsoluteEdgeSize = Vector2.new(Utility.Round(absoluteEdgeSize.X/2), Utility.Round(absoluteEdgeSize.Y/2)) * 2
roundedAbsoluteEdgeSize = Utility.ClampVector2(Vector2.new(), guiSize, roundedAbsoluteEdgeSize)
Edges.Size = UDim2.new(0, roundedAbsoluteEdgeSize.X, 0, roundedAbsoluteEdgeSize.Y)
Edges.Position = UDim2.new(0, (guiSize.X - roundedAbsoluteEdgeSize.X) / 2, 0, (guiSize.Y - roundedAbsoluteEdgeSize.Y) / 2)
end
function this:PushAdjustmentEdges(pushAmount)
self:SetAdjustmentEdges(self:GetAdjustmentEdgesPercent() + pushAmount)
end
function this:PushAdjustmentEdgesByPixels(pixelPushAmount)
local guiSize = GuiRoot.AbsoluteSize
local pushAmount = pixelPushAmount / guiSize
if Utility.IsFinite(pushAmount.X) and Utility.IsFinite(pushAmount.Y) then
self:SetAdjustmentEdges(self:GetAdjustmentEdgesPercent() + pushAmount)
end
end
function this:Update()
local now = tick()
if lastUpdate and self.StickPosition ~= ZERO_VEC2 then
local delta = now - lastUpdate
local transformedStick = Utility.GamepadLinearToCurve(self.StickPosition, 0.2)
self:PushAdjustmentEdges(Vector2.new(transformedStick.X, -transformedStick.Y) * self.StickAcceleration * delta * THUMBSTICK_BORDER_SENSITIVITY)
if transformedStick ~= ZERO_VEC2 then
self.StickAcceleration = math.min(self.StickAcceleration + delta * ACCELERATION_RATE, MAX_STICK_ACCELERATION)
else
self.StickAcceleration = 1
end
end
lastUpdate = now
end
function this:ResetEdges()
self:SetAdjustmentEdges(Vector2.new(START_EDGE_SIZE.X, START_EDGE_SIZE.Y))
end
local GuiRootChangedConn = nil
function this:Show()
MainContainer.Visible = true
MainContainer.Parent = lastParent
RefreshLayout()
self.StickAcceleration = 1
if not UserSettings().GameSettings:InStudioMode() and GameOptionsSettings.OverscanPX > 0 and GameOptionsSettings.OverscanPY > 0 then
this:SetAdjustmentEdges(Vector2.new(GameOptionsSettings.OverscanPX, GameOptionsSettings.OverscanPY))
else
self:ResetEdges()
end
local success, errormsg = pcall(function()
StorePreviousOverscanValues()
-- Need to do this step so that the player
-- can accurately estimate their TV's overscan
GameOptionsSettings.OverscanPX = 1
GameOptionsSettings.OverscanPY = 1
end)
if not success then
print("Error resetting Overscan Screen Resolution:" , errormsg)
end
Utility.DisconnectEvent(GuiRootChangedConn)
GuiRootChangedConn = GuiRoot.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
self:PushAdjustmentEdgesByPixels(ZERO_VEC2)
end
end)
self:PushAdjustmentEdgesByPixels(ZERO_VEC2)
end
function this:Hide()
MainContainer.Visible = false
MainContainer.Parent = nil
GuiRootChangedConn = Utility.DisconnectEvent(GuiRootChangedConn)
end
function this:Focus()
GuiService.SelectedCoreObject = nil
RefreshLayout()
self.StickPosition = ZERO_VEC2
self.StickAcceleration = 1
local beginSeen = false
ContextActionService:BindCoreAction("ResetAdjustmentScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
self:ResetEdges()
end
end,
false,
Enum.KeyCode.ButtonX)
ContextActionService:BindCoreAction("AcceptAdjustmentScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
beginSeen = true
elseif inputState == Enum.UserInputState.End and beginSeen then
local success, errormsg = pcall(function()
GameOptionsSettings.OverscanPX = math.min(1, self:GetAdjustmentEdgesPercent().X)
GameOptionsSettings.OverscanPY = math.min(1, self:GetAdjustmentEdgesPercent().Y)
StorePreviousOverscanValues()
end)
if not success then
print("Error setting Overscan Screen Resolution:" , errormsg)
end
if self == ScreenManager:GetTopScreen() then
ScreenManager:CloseCurrent()
closedEvent:Fire()
end
end
end,
false,
Enum.KeyCode.ButtonA)
ContextActionService:BindCoreAction("ThumbstickAdjustmentScreen",
function(actionName, inputState, inputObject)
self.StickPosition = Vector2.new(inputObject.Position.X, inputObject.Position.Y)
end,
false,
Enum.KeyCode.Thumbstick2)
ContextActionService:BindCoreAction("DPadAdjustmentScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
local pushAmount = DPAD_CODE_TO_EDGE_PUSH[inputObject.KeyCode]
if pushAmount then
self:PushAdjustmentEdgesByPixels(pushAmount)
end
end
end,
false,
Enum.KeyCode.DPadDown, Enum.KeyCode.DPadUp, Enum.KeyCode.DPadLeft, Enum.KeyCode.DPadRight)
RunService:BindToRenderStep("UpdateAdjustmentScreen", Enum.RenderPriority.Input.Value, function() self:Update() end)
end
function this:RemoveFocus()
self.StickPosition = ZERO_VEC2
lastUpdate = nil
ContextActionService:UnbindCoreAction("ResetAdjustmentScreen")
ContextActionService:UnbindCoreAction("AcceptAdjustmentScreen")
ContextActionService:UnbindCoreAction("ThumbstickAdjustmentScreen")
ContextActionService:UnbindCoreAction("DPadAdjustmentScreen")
RunService:UnbindFromRenderStep("UpdateAdjustmentScreen")
end
function this:SetPosition(newPosition)
MainContainer.Position = newPosition
end
function this:SetParent(newParent)
MainContainer.Parent = newParent
lastParent = newParent
end
function this:SetStyleForInGame()
BackgroundImage.Visible = false
MainContainer.BackgroundTransparency = 0.2
local function setZIndex(guiObject, newZIndex)
if not guiObject:IsA("GuiObject") then return end
guiObject.ZIndex = newZIndex
local children = guiObject:GetChildren()
for i = 1, #children do
setZIndex(children[i], newZIndex)
end
end
setZIndex(MainContainer, 9)
end
if not UserSettings().GameSettings:InStudioMode() then
pcall(function() PlatformService.Suspended:connect(function()
GameOptionsSettings.OverscanPX = lastSavedOverscanPX
GameOptionsSettings.OverscanPY = lastSavedOverscanPY
end)
end)
end
return this
end
return CreateOverscanAdjustmentScreen