add gs
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"lint": {
|
||||
"SameLineStatement": "fatal",
|
||||
"LocalShadow": "fatal",
|
||||
"FunctionUnused": "fatal",
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
--[[
|
||||
// FileName: ContextMenuGui.lua
|
||||
// Written by: TheGamer101
|
||||
// Description: Module for creating the context GUI.
|
||||
]]
|
||||
|
||||
-- CONSTANTS
|
||||
|
||||
local BOTTOM_SCREEN_PADDING_PERCENT = 0.02
|
||||
|
||||
-- SERVICES
|
||||
local CoreGuiService = game:GetService("CoreGui")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
|
||||
--- VARIABLES
|
||||
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
|
||||
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
|
||||
local SettingsModules = CoreGuiModules:WaitForChild("Settings")
|
||||
local AvatarMenuModules = CoreGuiModules:WaitForChild("AvatarContextMenu")
|
||||
local PlayerCarousel = nil
|
||||
local PlayerChangedEvent = Instance.new("BindableEvent")
|
||||
|
||||
--- Modules
|
||||
local ContextMenuUtil = require(AvatarMenuModules:WaitForChild("ContextMenuUtil"))
|
||||
local Utility = require(SettingsModules:WaitForChild("Utility"))
|
||||
|
||||
local ContextMenuGui = {}
|
||||
ContextMenuGui.__index = ContextMenuGui
|
||||
|
||||
-- PRIVATE METHODS
|
||||
|
||||
function ContextMenuGui:CreateContextMenuHolder(player)
|
||||
local contextMenuHolder = Instance.new("Frame")
|
||||
contextMenuHolder.Name = "AvatarContextMenu"
|
||||
contextMenuHolder.Position = UDim2.new(0, 0, 0, 0)
|
||||
contextMenuHolder.Size = UDim2.new(1, 0, 1, 0)
|
||||
contextMenuHolder.BackgroundTransparency = 1
|
||||
contextMenuHolder.Parent = RobloxGui
|
||||
contextMenuHolder.AutoLocalize = false
|
||||
return contextMenuHolder
|
||||
end
|
||||
|
||||
function ContextMenuGui:CreateLeaveMenuButton(frame, theme)
|
||||
local function closeMenu()
|
||||
self.CloseMenuFunc()
|
||||
end
|
||||
local closeMenuButton = Instance.new("ImageButton")
|
||||
closeMenuButton.Name = "CloseMenuButton"
|
||||
closeMenuButton.BackgroundTransparency = 1
|
||||
closeMenuButton.AnchorPoint = Vector2.new(1, 0)
|
||||
closeMenuButton.Position = UDim2.new(1, -10, 0, 10)
|
||||
closeMenuButton.Size = UDim2.new(0.05, 0, 0.1, 0)
|
||||
closeMenuButton.Image = theme.LeaveMenuImage
|
||||
closeMenuButton.Selectable = false
|
||||
closeMenuButton.Activated:Connect(closeMenu)
|
||||
|
||||
local aspectConstraint = Instance.new("UIAspectRatioConstraint")
|
||||
aspectConstraint.AspectType = Enum.AspectType.FitWithinMaxSize
|
||||
aspectConstraint.DominantAxis = Enum.DominantAxis.Height
|
||||
aspectConstraint.AspectRatio = 1
|
||||
aspectConstraint.Parent = closeMenuButton
|
||||
|
||||
closeMenuButton.Parent = frame
|
||||
|
||||
return closeMenuButton
|
||||
end
|
||||
|
||||
-- PUBLIC METHODS
|
||||
|
||||
local function listenToViewportChange(functionToFire)
|
||||
if functionToFire == nil then return end
|
||||
|
||||
local viewportChangedConnection = nil
|
||||
|
||||
local function updateCamera()
|
||||
local newCamera = workspace.CurrentCamera
|
||||
if viewportChangedConnection then
|
||||
viewportChangedConnection:Disconnect()
|
||||
end
|
||||
viewportChangedConnection = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(functionToFire)
|
||||
functionToFire()
|
||||
end
|
||||
|
||||
workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(updateCamera)
|
||||
updateCamera()
|
||||
end
|
||||
|
||||
function ContextMenuGui:CreateMenuFrame(theme)
|
||||
local contextMenuHolder = self:CreateContextMenuHolder()
|
||||
|
||||
local menu = Instance.new("ImageButton")
|
||||
menu.Name = "Menu"
|
||||
menu.AnchorPoint = theme.AnchorPoint
|
||||
menu.Size = theme.Size
|
||||
menu.Position = theme.OnScreenPosition
|
||||
menu.BackgroundTransparency = theme.BackgroundTransparency
|
||||
menu.BackgroundColor3 = theme.BackgroundColor
|
||||
menu.Image = theme.BackgroundImage
|
||||
menu.ScaleType = theme.BackgroundImageScaleType
|
||||
menu.SliceCenter = theme.BackgroundImageSliceCenter
|
||||
menu.AutoButtonColor = false
|
||||
menu.BorderSizePixel = 0
|
||||
menu.Selectable = false
|
||||
menu.Visible = false
|
||||
menu.Active = true
|
||||
menu.ClipsDescendants = true
|
||||
menu.Modal = true
|
||||
|
||||
GuiService:AddSelectionParent("AvatarContextMenuGroup", menu)
|
||||
|
||||
local aspectConstraint = Instance.new("UIAspectRatioConstraint")
|
||||
aspectConstraint.AspectType = Enum.AspectType.ScaleWithParentSize
|
||||
aspectConstraint.DominantAxis = Enum.DominantAxis.Height
|
||||
aspectConstraint.Name = "MenuAspectRatio"
|
||||
aspectConstraint.AspectRatio = theme.AspectRatio
|
||||
aspectConstraint.Parent = menu
|
||||
|
||||
local function updateAspectRatioForViewport()
|
||||
local viewportSize = workspace.CurrentCamera.ViewportSize
|
||||
if viewportSize.x < viewportSize.y then
|
||||
aspectConstraint.DominantAxis = Enum.DominantAxis.Width
|
||||
else
|
||||
aspectConstraint.DominantAxis = Enum.DominantAxis.Height
|
||||
end
|
||||
end
|
||||
listenToViewportChange(updateAspectRatioForViewport)
|
||||
|
||||
local sizeConstraint = Instance.new("UISizeConstraint")
|
||||
sizeConstraint.Name = "MenuSizeConstraint"
|
||||
sizeConstraint.MaxSize = theme.MaxSize
|
||||
sizeConstraint.MinSize = theme.MinSize
|
||||
sizeConstraint.Parent = menu
|
||||
|
||||
local contentFrame = Instance.new("Frame")
|
||||
contentFrame.Name = "Content"
|
||||
contentFrame.Size = UDim2.new(1,0,1,0)
|
||||
contentFrame.BackgroundTransparency = 1
|
||||
contentFrame.Parent = menu
|
||||
|
||||
local contentListLayout = Instance.new("UIListLayout")
|
||||
contentListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
|
||||
contentListLayout.VerticalAlignment = Enum.VerticalAlignment.Top
|
||||
contentListLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
contentListLayout.Parent = contentFrame
|
||||
|
||||
local contextActionList = Instance.new("ScrollingFrame")
|
||||
contextActionList.Name = "ContextActionList"
|
||||
contextActionList.AnchorPoint = Vector2.new(0.5,1)
|
||||
contextActionList.BackgroundColor3 = theme.ButtonFrameColor
|
||||
contextActionList.BackgroundTransparency = theme.ButtonFrameTransparency
|
||||
contextActionList.BorderSizePixel = 0
|
||||
contextActionList.LayoutOrder = 2
|
||||
contextActionList.Size = UDim2.new(1,-12,0.54,0)
|
||||
contextActionList.CanvasSize = UDim2.new(0,0,0,208)
|
||||
contextActionList.ScrollBarThickness = 4
|
||||
contextActionList.Selectable = false
|
||||
contextActionList.Parent = contentFrame
|
||||
|
||||
local contextActionListUIListLayout = Instance.new("UIListLayout")
|
||||
contextActionListUIListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
|
||||
contextActionListUIListLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
contextActionListUIListLayout.VerticalAlignment = Enum.VerticalAlignment.Top
|
||||
|
||||
contextActionListUIListLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
|
||||
contextActionList.CanvasSize = UDim2.new(0,0,0,contextActionListUIListLayout.AbsoluteContentSize.Y)
|
||||
end)
|
||||
|
||||
contextActionListUIListLayout.Parent = contextActionList
|
||||
|
||||
local nameTag = Instance.new("TextButton")
|
||||
nameTag.Name = "NameTag"
|
||||
nameTag.AnchorPoint = Vector2.new(0.5,1)
|
||||
nameTag.BackgroundColor3 = theme.NameTagColor
|
||||
nameTag.AutoButtonColor = false
|
||||
nameTag.BorderSizePixel = 0
|
||||
nameTag.LayoutOrder = 1
|
||||
nameTag.Size = UDim2.new(1,-12,0.16,0)
|
||||
nameTag.Font = theme.Font
|
||||
nameTag.TextColor3 = theme.TextColor
|
||||
nameTag.TextSize = 24 * theme.TextScale
|
||||
nameTag.Text = ""
|
||||
nameTag.TextXAlignment = Enum.TextXAlignment.Center
|
||||
nameTag.TextYAlignment = Enum.TextYAlignment.Center
|
||||
nameTag.Selectable = false
|
||||
nameTag.Parent = contentFrame
|
||||
|
||||
local underline = Instance.new("Frame")
|
||||
underline.Name = "Underline"
|
||||
underline.BackgroundColor3 = theme.NameUnderlineColor
|
||||
underline.AnchorPoint = Vector2.new(0,1)
|
||||
underline.BorderSizePixel = 0
|
||||
underline.Position = UDim2.new(0,0,1,0)
|
||||
underline.Size = UDim2.new(1,0,0,2)
|
||||
underline.Parent = nameTag
|
||||
|
||||
self:CreateLeaveMenuButton(menu, theme)
|
||||
|
||||
menu.Parent = contextMenuHolder
|
||||
self.ContextMenuFrame = menu
|
||||
|
||||
return menu
|
||||
end
|
||||
|
||||
function ContextMenuGui:UpdateGuiTheme(theme)
|
||||
self.ContextMenuFrame.Size = theme.Size
|
||||
self.ContextMenuFrame.AnchorPoint = theme.AnchorPoint
|
||||
|
||||
self.ContextMenuFrame.BackgroundTransparency = theme.BackgroundTransparency
|
||||
self.ContextMenuFrame.BackgroundColor3 = theme.BackgroundColor
|
||||
self.ContextMenuFrame.Image = theme.BackgroundImage
|
||||
self.ContextMenuFrame.ScaleType = theme.BackgroundImageScaleType
|
||||
self.ContextMenuFrame.SliceCenter = theme.BackgroundImageSliceCenter
|
||||
|
||||
self.ContextMenuFrame.CloseMenuButton.Image = theme.LeaveMenuImage
|
||||
|
||||
self.ContextMenuFrame.MenuSizeConstraint.MaxSize = theme.MaxSize
|
||||
self.ContextMenuFrame.MenuSizeConstraint.MinSize = theme.MinSize
|
||||
|
||||
self.ContextMenuFrame.MenuAspectRatio.AspectRatio = theme.AspectRatio
|
||||
|
||||
local contentFrame = self.ContextMenuFrame.Content
|
||||
contentFrame.ContextActionList.BackgroundColor3 = theme.ButtonFrameColor
|
||||
contentFrame.ContextActionList.BackgroundTransparency = theme.ButtonFrameTransparency
|
||||
|
||||
contentFrame.NameTag.BackgroundColor3 = theme.NameTagColor
|
||||
contentFrame.NameTag.Font = theme.Font
|
||||
contentFrame.NameTag.TextColor3 = theme.TextColor
|
||||
contentFrame.NameTag.TextSize = 24 * theme.TextScale
|
||||
|
||||
contentFrame.NameTag.Underline.BackgroundColor3 = theme.NameUnderlineColor
|
||||
|
||||
if PlayerCarousel then
|
||||
PlayerCarousel:UpdateGuiTheme(theme)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenuGui:BuildPlayerCarousel(playersByProximity, theme)
|
||||
if not PlayerCarousel then
|
||||
local playerCarouselModule = require(AvatarMenuModules.PlayerCarousel)
|
||||
PlayerCarousel = playerCarouselModule.new(theme)
|
||||
PlayerCarousel.rbxGui.Parent = self.ContextMenuFrame.Content
|
||||
end
|
||||
|
||||
PlayerCarousel:ClearPlayerEntries()
|
||||
|
||||
if self.PlayerChangedConnection then
|
||||
self.PlayerChangedConnection:Disconnect()
|
||||
end
|
||||
self.PlayerChangedConnection = PlayerCarousel.PlayerChanged:Connect(function(player)
|
||||
if player then
|
||||
self.ContextMenuFrame.Content.NameTag.Text = player.Name
|
||||
else
|
||||
self.ContextMenuFrame.Content.NameTag.Text = ""
|
||||
end
|
||||
PlayerChangedEvent:Fire(player)
|
||||
end)
|
||||
|
||||
for i = 1, #playersByProximity do
|
||||
PlayerCarousel:CreatePlayerEntry(playersByProximity[i][1], playersByProximity[i][2])
|
||||
end
|
||||
PlayerCarousel:FadeTowardsEdges()
|
||||
PlayerCarousel:AddCarouselDivider()
|
||||
end
|
||||
|
||||
function ContextMenuGui:RemovePlayerEntry(player)
|
||||
if not PlayerCarousel then return end
|
||||
|
||||
PlayerCarousel:RemovePlayerEntry(player)
|
||||
end
|
||||
|
||||
function ContextMenuGui:GetBottomScreenPaddingConstant()
|
||||
return BOTTOM_SCREEN_PADDING_PERCENT
|
||||
end
|
||||
|
||||
function ContextMenuGui:SetCloseMenuFunc(closeMenuFunc)
|
||||
self.CloseMenuFunc = closeMenuFunc
|
||||
end
|
||||
|
||||
function ContextMenuGui:SwitchToPlayerEntry(player, dontTween)
|
||||
if not PlayerCarousel then return end
|
||||
PlayerCarousel:SwitchToPlayerEntry(player, dontTween)
|
||||
end
|
||||
|
||||
function ContextMenuGui:OffsetPlayerEntry(offset)
|
||||
if not PlayerCarousel then return end
|
||||
PlayerCarousel:OffsetPlayerEntry(offset)
|
||||
end
|
||||
|
||||
function ContextMenuGui:GetSelectedPlayer()
|
||||
if not PlayerCarousel then return nil end
|
||||
return PlayerCarousel:GetSelectedPlayer()
|
||||
end
|
||||
|
||||
function ContextMenuGui.new()
|
||||
local obj = setmetatable({}, ContextMenuGui)
|
||||
|
||||
obj.CloseMenuFunc = nil
|
||||
|
||||
obj.ContextMenuFrame = nil
|
||||
obj.LastSetPlayerIcon = nil
|
||||
|
||||
obj.PlayerChangedConnection = nil
|
||||
|
||||
obj.SelectedPlayerChanged = PlayerChangedEvent.Event
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return ContextMenuGui.new()
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
--[[
|
||||
// FileName: ContextMenuItems.lua
|
||||
// Written by: TheGamer101
|
||||
// Description: Module for creating the context menu items for the menu and doing the actions when they are clicked.
|
||||
]]
|
||||
|
||||
--- SERVICES
|
||||
local PlayersService = game:GetService("Players")
|
||||
local CoreGuiService = game:GetService("CoreGui")
|
||||
local StarterGui = game:GetService("StarterGui")
|
||||
local Chat = game:GetService("Chat")
|
||||
local RunService = game:GetService("RunService")
|
||||
local AnalyticsService = game:GetService("RbxAnalyticsService")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
|
||||
-- MODULES
|
||||
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
|
||||
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
|
||||
local RobloxTranslator = require(RobloxGui.Modules.RobloxTranslator)
|
||||
local GameTranslator = require(RobloxGui.Modules.GameTranslator)
|
||||
local AvatarMenuModules = CoreGuiModules:WaitForChild("AvatarContextMenu")
|
||||
local ContextMenuUtil = require(AvatarMenuModules:WaitForChild("ContextMenuUtil"))
|
||||
local ThemeHandler = require(AvatarMenuModules.ThemeHandler)
|
||||
|
||||
local BlockingUtility = require(CoreGuiModules.BlockingUtility)
|
||||
local PolicyService = require(RobloxGui.Modules.Common.PolicyService)
|
||||
|
||||
-- FLAGS
|
||||
local FFlagInspectMenuSubjectToPolicy = require(CoreGuiModules.Flags.FFlagInspectMenuSubjectToPolicy)
|
||||
|
||||
-- VARIABLES
|
||||
|
||||
local LocalPlayer = PlayersService.LocalPlayer
|
||||
while not LocalPlayer do
|
||||
PlayersService.PlayerAdded:wait()
|
||||
LocalPlayer = PlayersService.LocalPlayer
|
||||
end
|
||||
|
||||
local EnabledContextMenuItems = {
|
||||
[Enum.AvatarContextMenuOption.Chat] = true,
|
||||
[Enum.AvatarContextMenuOption.Friend] = true,
|
||||
[Enum.AvatarContextMenuOption.Emote] = true,
|
||||
[Enum.AvatarContextMenuOption.InspectMenu] = true
|
||||
}
|
||||
local CustomContextMenuItems = {}
|
||||
local CustomItemAddedOrder = 0
|
||||
|
||||
-- CONSTANTS
|
||||
-- If Custom buttons exist these layout orders are offset by highest custom button layout order.
|
||||
local FRIEND_LAYOUT_ORDER = 1
|
||||
local CHAT_LAYOUT_ORDER = 3
|
||||
local INSPECT_AND_BUY_LAYOUT_ORDER = 4
|
||||
local WAVE_LAYOUT_ORDER = 5
|
||||
|
||||
local MENU_ITEM_SIZE_X = 0.96
|
||||
local MENU_ITEM_SIZE_Y = 0
|
||||
local MENU_ITEM_SIZE_Y_OFFSET = 52
|
||||
local VIEW_KEY = "InGame.InspectMenu.Action.View"
|
||||
|
||||
local ContextMenuItems = {}
|
||||
ContextMenuItems.__index = ContextMenuItems
|
||||
|
||||
-- PRIVATE METHODS
|
||||
function ContextMenuItems:UpdateInspectMenuEnabled()
|
||||
local enabled = GuiService:GetInspectMenuEnabled()
|
||||
if FFlagInspectMenuSubjectToPolicy then
|
||||
enabled = enabled and not PolicyService:IsSubjectToChinaPolicies()
|
||||
end
|
||||
|
||||
if enabled ~= EnabledContextMenuItems[Enum.AvatarContextMenuOption.InspectMenu] then
|
||||
EnabledContextMenuItems[Enum.AvatarContextMenuOption.InspectMenu] = enabled
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenuItems:ClearMenuItems()
|
||||
local children = self.MenuItemFrame:GetChildren()
|
||||
for i = 1, #children do
|
||||
if children[i]:IsA("GuiObject") then
|
||||
children[i]:Destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenuItems:AddCustomAvatarMenuItem(menuOption, bindableEvent)
|
||||
CustomItemAddedOrder = CustomItemAddedOrder + 1
|
||||
CustomContextMenuItems[menuOption] = {
|
||||
event = bindableEvent,
|
||||
layoutOrder = CustomItemAddedOrder,
|
||||
}
|
||||
end
|
||||
|
||||
function ContextMenuItems:RemoveCustomAvatarMenuItem(menuOption)
|
||||
CustomContextMenuItems[menuOption] = nil
|
||||
end
|
||||
|
||||
function ContextMenuItems:IsContextAvatarEnumItem(enumItem)
|
||||
local enumItems = Enum.AvatarContextMenuOption:GetEnumItems()
|
||||
for i = 1, #enumItems do
|
||||
if enumItem == enumItems[i] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ContextMenuItems:EnableDefaultMenuItem(menuOption)
|
||||
EnabledContextMenuItems[menuOption] = true
|
||||
end
|
||||
|
||||
function ContextMenuItems:RemoveDefaultMenuItem(menuOption)
|
||||
EnabledContextMenuItems[menuOption] = false
|
||||
end
|
||||
|
||||
function ContextMenuItems:RegisterCoreMethods()
|
||||
local function addMenuItemFunc(args) --[[ menuOption, bindableEvent]]
|
||||
if type(args) == "table" then
|
||||
local name = ""
|
||||
if args[1] and type(args[1]) == "string" then
|
||||
name = args[1]
|
||||
else
|
||||
error("AddAvatarContextMenuOption first argument must be a table or Enum.AvatarContextMenuOption")
|
||||
end
|
||||
|
||||
if args[2] and typeof(args[2]) == "Instance" and args[2].ClassName == "BindableEvent" then
|
||||
self:AddCustomAvatarMenuItem(name, args[2])
|
||||
else
|
||||
error("AddAvatarContextMenuOption second table entry must be a BindableEvent")
|
||||
end
|
||||
|
||||
elseif typeof(args) == "EnumItem" then
|
||||
if self:IsContextAvatarEnumItem(args) then
|
||||
self:EnableDefaultMenuItem(args)
|
||||
else
|
||||
error("AddAvatarContextMenuOption given EnumItem is not valid")
|
||||
end
|
||||
else
|
||||
error("AddAvatarContextMenuOption first argument must be a table or Enum.AvatarContextMenuOption")
|
||||
end
|
||||
end
|
||||
StarterGui:RegisterSetCore("AddAvatarContextMenuOption", addMenuItemFunc)
|
||||
local function removeMenuItemFunc(menuOption)
|
||||
if type(menuOption) == "string" then
|
||||
self:RemoveCustomAvatarMenuItem(menuOption)
|
||||
elseif typeof(menuOption) == "EnumItem" then
|
||||
if self:IsContextAvatarEnumItem(menuOption) then
|
||||
self:RemoveDefaultMenuItem(menuOption)
|
||||
else
|
||||
error("RemoveAvatarContextMenuOption given EnumItem is not valid")
|
||||
end
|
||||
else
|
||||
error("RemoveAvatarContextMenuOption first argument must be a string or Enum.AvatarContextMenuOption")
|
||||
end
|
||||
end
|
||||
StarterGui:RegisterSetCore("RemoveAvatarContextMenuOption", removeMenuItemFunc)
|
||||
end
|
||||
|
||||
function ContextMenuItems:CreateCustomMenuItems()
|
||||
for buttonText, itemInfo in pairs(CustomContextMenuItems) do
|
||||
AnalyticsService:TrackEvent("Game", "AvatarContextMenuCustomButton", "name: " .. tostring(buttonText))
|
||||
local function customButtonFunc()
|
||||
if self.CloseMenuFunc then self:CloseMenuFunc() end
|
||||
|
||||
itemInfo.event:Fire(self.SelectedPlayer)
|
||||
end
|
||||
buttonText = GameTranslator:TranslateGameText(self.MenuItemFrame, buttonText)
|
||||
local customButton = ContextMenuUtil:MakeStyledButton(
|
||||
"CustomButton",
|
||||
buttonText,
|
||||
UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET),
|
||||
customButtonFunc,
|
||||
ThemeHandler:GetTheme()
|
||||
)
|
||||
customButton.Name = "CustomButton"
|
||||
customButton.LayoutOrder = itemInfo.layoutOrder
|
||||
customButton.Parent = self.MenuItemFrame
|
||||
end
|
||||
end
|
||||
|
||||
-- PUBLIC METHODS
|
||||
|
||||
local addFriendString = "Add Friend"
|
||||
local friendsString = "Friends"
|
||||
local friendRequestPendingString = "Friend Request Pending"
|
||||
local acceptFriendRequestString = "Accept Friend Request"
|
||||
local blockedString = "Player Blocked"
|
||||
|
||||
local addFriendDisabledTransparency = 0.75
|
||||
local friendStatusChangedConn = nil
|
||||
function ContextMenuItems:CreateFriendButton(status, isBlocked)
|
||||
addFriendString = RobloxTranslator:FormatByKey("Corescripts.AvatarContextMenu.AddFriend")
|
||||
friendsString = RobloxTranslator:FormatByKey("Corescripts.AvatarContextMenu.Friends")
|
||||
friendRequestPendingString = RobloxTranslator:FormatByKey("Corescripts.AvatarContextMenu.FriendRequestPending")
|
||||
acceptFriendRequestString = RobloxTranslator:FormatByKey("Corescripts.AvatarContextMenu.AcceptFriendRequest")
|
||||
blockedString = RobloxTranslator:FormatByKey("Corescripts.AvatarContextMenu.PlayerBlocked")
|
||||
|
||||
local friendLabel = self.MenuItemFrame:FindFirstChild("FriendStatus")
|
||||
if friendLabel then
|
||||
friendLabel:Destroy()
|
||||
friendLabel = nil
|
||||
end
|
||||
if friendStatusChangedConn then
|
||||
friendStatusChangedConn:disconnect()
|
||||
end
|
||||
local friendLabelText = nil
|
||||
|
||||
local addFriendFunc = function()
|
||||
if friendLabelText and friendLabel.Selectable then
|
||||
friendLabel.Selectable = false
|
||||
friendLabelText.TextTransparency = addFriendDisabledTransparency
|
||||
friendLabelText.Text = friendRequestPendingString
|
||||
AnalyticsService:ReportCounter("AvatarContextMenu-RequestFriendship")
|
||||
AnalyticsService:TrackEvent("Game", "RequestFriendship", "AvatarContextMenu")
|
||||
LocalPlayer:RequestFriendship(self.SelectedPlayer)
|
||||
end
|
||||
end
|
||||
|
||||
friendLabel, friendLabelText = ContextMenuUtil:MakeStyledButton(
|
||||
"FriendStatus",
|
||||
addFriendString,
|
||||
UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET),
|
||||
addFriendFunc,
|
||||
ThemeHandler:GetTheme()
|
||||
)
|
||||
|
||||
if isBlocked then
|
||||
friendLabel.Selectable = false
|
||||
friendLabelText.TextTransparency = addFriendDisabledTransparency
|
||||
friendLabelText.Text = blockedString
|
||||
elseif status == Enum.FriendStatus.Friend then
|
||||
friendLabel.Selectable = false
|
||||
friendLabelText.TextTransparency = addFriendDisabledTransparency
|
||||
friendLabelText.Text = friendsString
|
||||
elseif status == Enum.FriendStatus.FriendRequestSent then
|
||||
friendLabel.Selectable = false
|
||||
friendLabelText.TextTransparency = addFriendDisabledTransparency
|
||||
friendLabelText.Text = friendRequestPendingString
|
||||
elseif status == Enum.FriendStatus.FriendRequestReceived then
|
||||
friendLabelText.Text = acceptFriendRequestString
|
||||
else
|
||||
friendLabel.Selectable = true
|
||||
friendLabelText.TextTransparency = 0
|
||||
end
|
||||
|
||||
friendLabel.LayoutOrder = FRIEND_LAYOUT_ORDER + CustomItemAddedOrder
|
||||
friendLabel.Parent = self.MenuItemFrame
|
||||
end
|
||||
|
||||
function ContextMenuItems:UpdateFriendButton(status, isBlocked)
|
||||
local friendLabel = self.MenuItemFrame:FindFirstChild("FriendStatus")
|
||||
if friendLabel then
|
||||
self:CreateFriendButton(status, isBlocked)
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenuItems:CreateInspectAndBuyButton()
|
||||
local function browseItems()
|
||||
if self.CloseMenuFunc then self:CloseMenuFunc() end
|
||||
|
||||
-- If the developer disables the menu while someone is already looking at the ACM
|
||||
-- the button doesn't disappear, so we need to check again.
|
||||
if not EnabledContextMenuItems[Enum.AvatarContextMenuOption.InspectMenu] then
|
||||
warn("The Inspect Menu is not currently available.")
|
||||
return
|
||||
end
|
||||
|
||||
GuiService:InspectPlayerFromUserIdWithCtx(self.SelectedPlayer.UserId, "avatarContextMenu")
|
||||
end
|
||||
local browseItemsButton = ContextMenuUtil:MakeStyledButton(
|
||||
"View",
|
||||
RobloxTranslator:FormatByKey(VIEW_KEY),
|
||||
UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET),
|
||||
browseItems,
|
||||
ThemeHandler:GetTheme()
|
||||
)
|
||||
browseItemsButton.LayoutOrder = INSPECT_AND_BUY_LAYOUT_ORDER + CustomItemAddedOrder
|
||||
browseItemsButton.Parent = self.MenuItemFrame
|
||||
end
|
||||
|
||||
function ContextMenuItems:CreateEmoteButton()
|
||||
local function wave()
|
||||
if self.CloseMenuFunc then self:CloseMenuFunc() end
|
||||
|
||||
AnalyticsService:ReportCounter("AvatarContextMenu-Wave")
|
||||
AnalyticsService:TrackEvent("Game", "AvatarContextMenuWave", "placeId: " .. tostring(game.PlaceId))
|
||||
|
||||
PlayersService:Chat("/e wave")
|
||||
end
|
||||
|
||||
local waveString = RobloxTranslator:FormatByKey("Corescripts.AvatarContextMenu.Wave")
|
||||
local waveButton = ContextMenuUtil:MakeStyledButton(
|
||||
"Wave",
|
||||
waveString,
|
||||
UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET),
|
||||
wave,
|
||||
ThemeHandler:GetTheme()
|
||||
)
|
||||
waveButton.LayoutOrder = WAVE_LAYOUT_ORDER + CustomItemAddedOrder
|
||||
waveButton.Parent = self.MenuItemFrame
|
||||
end
|
||||
|
||||
|
||||
function ContextMenuItems:CreateChatButton()
|
||||
local chatDisabled = false
|
||||
local function chatFunc()
|
||||
if chatDisabled then
|
||||
return
|
||||
end
|
||||
|
||||
if self.CloseMenuFunc then self:CloseMenuFunc() end
|
||||
|
||||
AnalyticsService:ReportCounter("AvatarContextMenu-Chat")
|
||||
AnalyticsService:TrackEvent("Game", "AvatarContextMenuChat", "placeId: " .. tostring(game.PlaceId))
|
||||
|
||||
local ChatModule = require(RobloxGui.Modules.ChatSelector)
|
||||
ChatModule:SetVisible(true)
|
||||
local eventDidFire = ChatModule:EnterWhisperState(self.SelectedPlayer)
|
||||
if not eventDidFire then
|
||||
-- Fallback to the old version for backwards compatibility with old chat versions
|
||||
local ChatBar = nil
|
||||
pcall(function() ChatBar = LocalPlayer.PlayerGui.Chat.Frame.ChatBarParentFrame.Frame.BoxFrame.Frame.ChatBar end)
|
||||
if ChatBar then
|
||||
ChatBar.Text = "/w " .. self.SelectedPlayer.Name
|
||||
end
|
||||
ChatModule:FocusChatBar()
|
||||
end
|
||||
end
|
||||
|
||||
local chatString = RobloxTranslator:FormatByKey("Corescripts.AvatarContextMenu.Chat")
|
||||
local chatButton, chatLabelText = ContextMenuUtil:MakeStyledButton(
|
||||
"ChatStatus",
|
||||
chatString,
|
||||
UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET),
|
||||
chatFunc,
|
||||
ThemeHandler:GetTheme()
|
||||
)
|
||||
chatButton.LayoutOrder = CHAT_LAYOUT_ORDER + CustomItemAddedOrder
|
||||
|
||||
local success, canLocalUserChat = pcall(function() return Chat:CanUserChatAsync(LocalPlayer.UserId) end)
|
||||
local canChat = success and (RunService:IsStudio() or canLocalUserChat)
|
||||
|
||||
if canChat then
|
||||
chatButton.Parent = self.MenuItemFrame
|
||||
|
||||
local canChatWith = ContextMenuUtil:GetCanChatWith(self.SelectedPlayer)
|
||||
|
||||
if not canChatWith then
|
||||
chatDisabled = true
|
||||
chatButton.Selectable = false
|
||||
chatLabelText.TextTransparency = addFriendDisabledTransparency
|
||||
chatLabelText.Text = RobloxTranslator:FormatByKey("Corescripts.AvatarContextMenu.ChatDisabled")
|
||||
end
|
||||
else
|
||||
chatButton.Parent = nil
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenuItems:RemoveLastButtonUnderline()
|
||||
local buttons = self.MenuItemFrame:GetChildren()
|
||||
local lastButton = nil
|
||||
local highestLayoutOrder = -1
|
||||
for _, button in pairs(buttons) do
|
||||
if button:IsA("GuiObject") and button.LayoutOrder > highestLayoutOrder then
|
||||
highestLayoutOrder = button.LayoutOrder
|
||||
lastButton = button
|
||||
end
|
||||
end
|
||||
if lastButton then
|
||||
local underline = lastButton:FindFirstChild("Underline")
|
||||
if underline then
|
||||
underline:Destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenuItems:BuildContextMenuItems(player)
|
||||
if not player then return end
|
||||
|
||||
local friendStatus = ContextMenuUtil:GetFriendStatus(player)
|
||||
local isBlocked = BlockingUtility:IsPlayerBlockedByUserId(player.UserId)
|
||||
self:ClearMenuItems()
|
||||
self:SetSelectedPlayer(player)
|
||||
if EnabledContextMenuItems[Enum.AvatarContextMenuOption.Friend] then
|
||||
self:CreateFriendButton(friendStatus, isBlocked)
|
||||
end
|
||||
if EnabledContextMenuItems[Enum.AvatarContextMenuOption.Chat] then
|
||||
self:CreateChatButton()
|
||||
end
|
||||
if EnabledContextMenuItems[Enum.AvatarContextMenuOption.Emote] then
|
||||
self:CreateEmoteButton()
|
||||
end
|
||||
|
||||
if EnabledContextMenuItems[Enum.AvatarContextMenuOption.InspectMenu] then
|
||||
self:CreateInspectAndBuyButton()
|
||||
end
|
||||
self:CreateCustomMenuItems()
|
||||
|
||||
self:RemoveLastButtonUnderline()
|
||||
end
|
||||
|
||||
function ContextMenuItems:SetSelectedPlayer(selectedPlayer)
|
||||
self.SelectedPlayer = selectedPlayer
|
||||
end
|
||||
|
||||
function ContextMenuItems:SetCloseMenuFunc(closeMenuFunc)
|
||||
self.CloseMenuFunc = closeMenuFunc
|
||||
end
|
||||
|
||||
function ContextMenuItems.new(menuItemFrame)
|
||||
local obj = setmetatable({}, ContextMenuItems)
|
||||
|
||||
obj.MenuItemFrame = menuItemFrame
|
||||
obj.SelectedPlayer = nil
|
||||
|
||||
obj:RegisterCoreMethods()
|
||||
|
||||
-- If disabled in a script, sometimes it registers before we can receive the signal.
|
||||
ContextMenuItems:UpdateInspectMenuEnabled()
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
GuiService.InspectMenuEnabledChangedSignal:Connect(function(enabled)
|
||||
if FFlagInspectMenuSubjectToPolicy then
|
||||
enabled = enabled and PolicyService:IsSubjectToChinaPolicies()
|
||||
end
|
||||
|
||||
if not enabled then
|
||||
ContextMenuItems:RemoveDefaultMenuItem(Enum.AvatarContextMenuOption.InspectMenu)
|
||||
else
|
||||
ContextMenuItems:EnableDefaultMenuItem(Enum.AvatarContextMenuOption.InspectMenu)
|
||||
end
|
||||
end)
|
||||
|
||||
if FFlagInspectMenuSubjectToPolicy then
|
||||
spawn(function()
|
||||
-- Check whether InspectMenu is disabled by policy after PolicyService is finished initializing
|
||||
PolicyService:InitAsync()
|
||||
ContextMenuItems:UpdateInspectMenuEnabled()
|
||||
end)
|
||||
end
|
||||
|
||||
return ContextMenuItems
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
--[[
|
||||
// FileName: ContextMenuUtil.lua
|
||||
// Written by: TheGamer101
|
||||
// Description: Module for utility funcitons of the avatar context menu.
|
||||
]]
|
||||
|
||||
--[[
|
||||
// FileName: ContextMenuGui.lua
|
||||
// Written by: TheGamer101
|
||||
// Description: Module for creating the context GUI.
|
||||
]]
|
||||
|
||||
--- CONSTANTS
|
||||
|
||||
local STOP_MOVEMENT_ACTION_NAME = "AvatarContextMenuStopInput"
|
||||
-- todo: remove with GetFFlagUseThumbnailUrl
|
||||
local MAX_THUMBNAIL_RETRIES = 4
|
||||
|
||||
--- SERVICES
|
||||
local CoreGuiService = game:GetService("CoreGui")
|
||||
local PlayersService = game:GetService("Players")
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local RobloxReplicatedStorage = game:GetService("RobloxReplicatedStorage")
|
||||
|
||||
--- VARIABLES
|
||||
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
|
||||
|
||||
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
|
||||
local BlockingUtility = require(CoreGuiModules.BlockingUtility)
|
||||
|
||||
local LocalPlayer = PlayersService.LocalPlayer
|
||||
while not LocalPlayer do
|
||||
PlayersService.PlayerAdded:wait()
|
||||
LocalPlayer = PlayersService.LocalPlayer
|
||||
end
|
||||
|
||||
-- FLAGS
|
||||
local GetFFlagUseThumbnailUrl = require(CoreGuiModules.Common.Flags.GetFFlagUseThumbnailUrl)
|
||||
|
||||
local ContextMenuUtil = {}
|
||||
ContextMenuUtil.__index = ContextMenuUtil
|
||||
|
||||
-- PUBLIC METHODS
|
||||
|
||||
function ContextMenuUtil:GetHeadshotForPlayer(player)
|
||||
if GetFFlagUseThumbnailUrl() then
|
||||
return "rbxthumb://type=AvatarHeadShot&id=" .. player.UserId .. "&w=150&h=150"
|
||||
else
|
||||
if self.HeadShotUrlCache[player] ~= nil and self.HeadShotUrlCache[player] ~= "" then
|
||||
return self.HeadShotUrlCache[player]
|
||||
end
|
||||
if self.HeadShotUrlCache[player] == nil then
|
||||
-- Mark that we are getting a headshot for this player.
|
||||
self.HeadShotUrlCache[player] = ""
|
||||
end
|
||||
|
||||
local startTime = tick()
|
||||
local headshotUrl, isFinal = PlayersService:GetUserThumbnailAsync(player.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size180x180)
|
||||
|
||||
if not isFinal then
|
||||
for i = 0, MAX_THUMBNAIL_RETRIES do
|
||||
headshotUrl, isFinal = PlayersService:GetUserThumbnailAsync(player.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size180x180)
|
||||
if isFinal then
|
||||
break
|
||||
end
|
||||
wait(i ^ 2)
|
||||
end
|
||||
end
|
||||
self.HeadShotUrlCache[player] = headshotUrl
|
||||
|
||||
return headshotUrl
|
||||
end
|
||||
end
|
||||
|
||||
function ContextMenuUtil:HasOrGettingHeadShot(player)
|
||||
return self.HeadShotUrlCache[player] ~= nil
|
||||
end
|
||||
|
||||
function ContextMenuUtil:FindPlayerFromPart(part)
|
||||
if part and part.Parent then
|
||||
local possibleCharacter = part
|
||||
while possibleCharacter and not possibleCharacter:IsA("Model") do
|
||||
possibleCharacter = possibleCharacter.Parent
|
||||
end
|
||||
if possibleCharacter then
|
||||
return PlayersService:GetPlayerFromCharacter(possibleCharacter)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function ContextMenuUtil:GetPlayerPosition(player)
|
||||
if player.Character then
|
||||
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
|
||||
if hrp then
|
||||
return hrp.Position
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local playerMovementEnabled = true
|
||||
|
||||
function ContextMenuUtil:DisablePlayerMovement()
|
||||
if not playerMovementEnabled then return end
|
||||
playerMovementEnabled = false
|
||||
|
||||
local noOpFunc = function(actionName, actionState)
|
||||
if actionState == Enum.UserInputState.End then
|
||||
return Enum.ContextActionResult.Pass
|
||||
end
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
|
||||
ContextActionService:BindCoreAction(STOP_MOVEMENT_ACTION_NAME, noOpFunc, false,
|
||||
Enum.PlayerActions.CharacterForward,
|
||||
Enum.PlayerActions.CharacterBackward,
|
||||
Enum.PlayerActions.CharacterLeft,
|
||||
Enum.PlayerActions.CharacterRight,
|
||||
Enum.PlayerActions.CharacterJump,
|
||||
Enum.UserInputType.Gamepad1, Enum.UserInputType.Gamepad2, Enum.UserInputType.Gamepad3, Enum.UserInputType.Gamepad4
|
||||
)
|
||||
end
|
||||
|
||||
function ContextMenuUtil:EnablePlayerMovement()
|
||||
if playerMovementEnabled then return end
|
||||
playerMovementEnabled = true
|
||||
|
||||
ContextActionService:UnbindCoreAction(STOP_MOVEMENT_ACTION_NAME)
|
||||
end
|
||||
|
||||
function ContextMenuUtil:GetFriendStatus(player)
|
||||
local success, result = pcall(function()
|
||||
-- NOTE: Core script only
|
||||
return LocalPlayer:GetFriendStatus(player)
|
||||
end)
|
||||
if success then
|
||||
return result
|
||||
else
|
||||
return Enum.FriendStatus.NotFriend
|
||||
end
|
||||
end
|
||||
|
||||
local CanChatWithMap = {}
|
||||
coroutine.wrap(function()
|
||||
local RemoteEvent_CanChatWith = RobloxReplicatedStorage:WaitForChild("CanChatWith", math.huge)
|
||||
RemoteEvent_CanChatWith.OnClientEvent:Connect(function(userId, canChat)
|
||||
CanChatWithMap[userId] = canChat
|
||||
end)
|
||||
end)()
|
||||
function ContextMenuUtil:GetCanChatWith(otherPlayer)
|
||||
if BlockingUtility:IsPlayerBlockedByUserId(otherPlayer.UserId) then
|
||||
-- This can be removed when Chat:CanUsersChatAsync() correctly respects blocked status.
|
||||
return false
|
||||
end
|
||||
if CanChatWithMap[otherPlayer.UserId] ~= nil then
|
||||
return CanChatWithMap[otherPlayer.UserId]
|
||||
end
|
||||
-- Assume we can chat if we have not received information from the server yet.
|
||||
return true
|
||||
end
|
||||
|
||||
local SelectionOverrideObject = Instance.new("ImageLabel")
|
||||
SelectionOverrideObject.Image = ""
|
||||
SelectionOverrideObject.BackgroundTransparency = 1
|
||||
|
||||
local function MakeDefaultButton(name, size, clickFunc, theme)
|
||||
|
||||
local button = Instance.new("ImageButton")
|
||||
button.Name = name
|
||||
button.Image = theme.ButtonImage
|
||||
button.ScaleType = theme.ButtonImageScaleType
|
||||
button.SliceCenter = theme.ButtonImageSliceCenter
|
||||
button.BackgroundColor3 = theme.ButtonColor
|
||||
button.BackgroundTransparency = theme.ButtonTransparency
|
||||
button.AutoButtonColor = false
|
||||
button.Size = size
|
||||
button.ZIndex = 2
|
||||
button.SelectionImageObject = SelectionOverrideObject
|
||||
button.BorderSizePixel = 0
|
||||
|
||||
local underline = Instance.new("Frame")
|
||||
underline.Name = "Underline"
|
||||
underline.BackgroundColor3 = theme.ButtonUnderlineColor
|
||||
underline.AnchorPoint = Vector2.new(0.5,1)
|
||||
underline.BorderSizePixel = 0
|
||||
underline.Position = UDim2.new(0.5,0,1,0)
|
||||
underline.Size = UDim2.new(0.95,0,0,1)
|
||||
underline.Parent = button
|
||||
|
||||
if clickFunc then
|
||||
button.MouseButton1Click:Connect(function()
|
||||
clickFunc(UserInputService:GetLastInputType())
|
||||
end)
|
||||
end
|
||||
|
||||
local function isPointerInput(inputObject)
|
||||
return inputObject.UserInputType == Enum.UserInputType.MouseMovement or inputObject.UserInputType == Enum.UserInputType.Touch
|
||||
end
|
||||
|
||||
local function selectButton()
|
||||
button.BackgroundColor3 = theme.ButtonHoverColor
|
||||
button.BackgroundTransparency = theme.ButtonHoverTransparency
|
||||
end
|
||||
|
||||
local function deselectButton()
|
||||
button.BackgroundColor3 = theme.ButtonColor
|
||||
button.BackgroundTransparency = theme.ButtonTransparency
|
||||
end
|
||||
|
||||
button.InputBegan:Connect(function(inputObject)
|
||||
if button.Selectable and isPointerInput(inputObject) then
|
||||
selectButton()
|
||||
inputObject:GetPropertyChangedSignal("UserInputState"):connect(function()
|
||||
if inputObject.UserInputState == Enum.UserInputState.End then
|
||||
deselectButton()
|
||||
end
|
||||
end)
|
||||
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 GuiService.SelectedCoreObject == nil or GuiService.SelectedCoreObject ~= button then
|
||||
deselectButton()
|
||||
return
|
||||
end
|
||||
|
||||
if button.Selectable then
|
||||
selectButton()
|
||||
end
|
||||
end)
|
||||
|
||||
return button
|
||||
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
|
||||
|
||||
function ContextMenuUtil:MakeStyledButton(name, text, size, clickFunc, theme)
|
||||
local button = MakeDefaultButton(name, size, clickFunc, theme)
|
||||
|
||||
local textLabel = Instance.new("TextLabel")
|
||||
textLabel.Name = name .. "TextLabel"
|
||||
textLabel.BackgroundTransparency = 1
|
||||
textLabel.BorderSizePixel = 0
|
||||
textLabel.Size = UDim2.new(1, 0, 1, -8)
|
||||
textLabel.Position = UDim2.new(0,0,0,0)
|
||||
textLabel.TextColor3 = Color3.fromRGB(255,255,255)
|
||||
textLabel.TextYAlignment = Enum.TextYAlignment.Center
|
||||
textLabel.Font = theme.Font
|
||||
textLabel.TextSize = 24 * theme.TextScale
|
||||
if isSmallTouchScreen() then
|
||||
textLabel.TextSize = 18 * theme.TextScale
|
||||
elseif GuiService:IsTenFootInterface() then
|
||||
textLabel.TextSize = 36 * theme.TextScale
|
||||
end
|
||||
textLabel.Text = text
|
||||
textLabel.TextScaled = true
|
||||
textLabel.TextWrapped = true
|
||||
textLabel.ZIndex = 2
|
||||
textLabel.Parent = button
|
||||
|
||||
local constraint = Instance.new("UITextSizeConstraint", textLabel)
|
||||
constraint.MaxTextSize = textLabel.TextSize
|
||||
|
||||
return button, textLabel
|
||||
end
|
||||
|
||||
function ContextMenuUtil.new()
|
||||
local obj = setmetatable({}, ContextMenuUtil)
|
||||
|
||||
-- todo: remove with GetFFlagUseThumbnailUrl
|
||||
obj.HeadShotUrlCache = {}
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return ContextMenuUtil.new()
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
--[[
|
||||
// FileName: PlayerCarousel.lua
|
||||
// Written by: darthskrill
|
||||
// Description: Module for building the UI for the player selection carousel
|
||||
]]
|
||||
|
||||
local PlayerCarousel = {}
|
||||
PlayerCarousel.__index = PlayerCarousel
|
||||
|
||||
-- SERVICES
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local CoreGuiService = game:GetService("CoreGui")
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
-- CONSTANTS
|
||||
local BACKGROUND_SELECTED_COLOR = Color3.fromRGB(0,162,255)
|
||||
local BACKGROUND_DEFAULT_COLOR = Color3.fromRGB(0,0,0)
|
||||
local PAGE_LAYOUT_TWEEN_TIME = 0.25
|
||||
|
||||
local CAROUSEL_DIVIDER_NAME = "_CarouselDivider"
|
||||
|
||||
-- VARIABLES
|
||||
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
|
||||
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
|
||||
local AvatarMenuModules = CoreGuiModules:WaitForChild("AvatarContextMenu")
|
||||
local selectedPlayer = nil
|
||||
local uiPageLayout = nil
|
||||
local playerChangedEvent = nil
|
||||
local buttonToPlayerMap = {}
|
||||
local playerToButtonMap = {}
|
||||
|
||||
local function getSortedCarouselButtons()
|
||||
local buttonChildIndexs = {}
|
||||
local carouselButtons = {}
|
||||
for childIndex, child in ipairs(selectedPlayer:GetChildren()) do
|
||||
if child:IsA("GuiObject") and child.Name ~= CAROUSEL_DIVIDER_NAME then
|
||||
table.insert(carouselButtons, child)
|
||||
buttonChildIndexs[child] = childIndex
|
||||
end
|
||||
end
|
||||
table.sort(carouselButtons, function(a, b)
|
||||
if a.LayoutOrder == b.LayoutOrder then
|
||||
return buttonChildIndexs[a] < buttonChildIndexs[b]
|
||||
end
|
||||
return a.LayoutOrder < b.LayoutOrder
|
||||
end)
|
||||
return carouselButtons
|
||||
end
|
||||
|
||||
local function CreateMenuCarousel(theme)
|
||||
local playerSelection = Instance.new("Frame")
|
||||
playerSelection.Name = "PlayerCarousel"
|
||||
playerSelection.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
playerSelection.BackgroundTransparency = 1
|
||||
playerSelection.Position = UDim2.new(0.5,0,0.5,0)
|
||||
playerSelection.Size = UDim2.new(1, 0, 0.28, 0)
|
||||
playerSelection.ClipsDescendants = true
|
||||
|
||||
local innerFrame = Instance.new("Frame")
|
||||
innerFrame.Name = "InnerFrame"
|
||||
innerFrame.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
innerFrame.BackgroundTransparency = 1
|
||||
innerFrame.Position = UDim2.new(0.5, 0, 0.5, 0)
|
||||
innerFrame.Size = UDim2.new(0.8, 0, 1, 0)
|
||||
innerFrame.ClipsDescendants = true
|
||||
innerFrame.Active = true
|
||||
innerFrame.Parent = playerSelection
|
||||
|
||||
selectedPlayer = Instance.new("Frame")
|
||||
selectedPlayer.Name = "SelectedPlayer"
|
||||
selectedPlayer.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
selectedPlayer.BackgroundTransparency = 1
|
||||
selectedPlayer.Position = UDim2.new(0.5, 0, 0.5, 0)
|
||||
selectedPlayer.Size = UDim2.new(0, 100, 1, -10)
|
||||
selectedPlayer.Parent = innerFrame
|
||||
|
||||
uiPageLayout = Instance.new("UIPageLayout")
|
||||
uiPageLayout.EasingDirection = Enum.EasingDirection.Out
|
||||
uiPageLayout.EasingStyle = Enum.EasingStyle.Quad
|
||||
uiPageLayout.Padding = UDim.new(0, 5)
|
||||
uiPageLayout.TweenTime = PAGE_LAYOUT_TWEEN_TIME
|
||||
uiPageLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
|
||||
uiPageLayout.VerticalAlignment = Enum.VerticalAlignment.Center
|
||||
uiPageLayout.TouchInputEnabled = false
|
||||
uiPageLayout.Circular = true
|
||||
uiPageLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
|
||||
local aspectRatioConstraint = Instance.new("UIAspectRatioConstraint")
|
||||
aspectRatioConstraint.DominantAxis = Enum.DominantAxis.Height
|
||||
aspectRatioConstraint.Parent = selectedPlayer
|
||||
|
||||
playerChangedEvent = Instance.new("BindableEvent")
|
||||
playerChangedEvent.Name = "PlayerChanged"
|
||||
|
||||
local lastPage = nil
|
||||
uiPageLayout:GetPropertyChangedSignal("CurrentPage"):Connect(function()
|
||||
if uiPageLayout.CurrentPage then
|
||||
if uiPageLayout.CurrentPage.Name == CAROUSEL_DIVIDER_NAME then
|
||||
local carouselButtons = getSortedCarouselButtons()
|
||||
if lastPage == carouselButtons[1] then
|
||||
uiPageLayout:Previous()
|
||||
else
|
||||
uiPageLayout:Next()
|
||||
end
|
||||
else
|
||||
uiPageLayout.CurrentPage.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR
|
||||
lastPage = uiPageLayout.CurrentPage
|
||||
end
|
||||
end
|
||||
if GuiService.SelectedCoreObject and GuiService.SelectedCoreObject.Parent == uiPageLayout.Parent then
|
||||
GuiService.SelectedCoreObject.BackgroundColor3 = BACKGROUND_SELECTED_COLOR
|
||||
end
|
||||
GuiService.SelectedCoreObject = uiPageLayout.CurrentPage
|
||||
if uiPageLayout.CurrentPage then playerChangedEvent:Fire(buttonToPlayerMap[uiPageLayout.CurrentPage]) end
|
||||
end)
|
||||
uiPageLayout.Parent = selectedPlayer
|
||||
|
||||
local nextButton = Instance.new("ImageButton")
|
||||
nextButton.Name = "NextButton"
|
||||
nextButton.Image = theme.ScrollRightImage
|
||||
nextButton.BackgroundTransparency = 1
|
||||
nextButton.AnchorPoint = Vector2.new(1,0.5)
|
||||
nextButton.Position = UDim2.new(1,-5,0.5,0)
|
||||
nextButton.Size = UDim2.new(0.3,0,0.3,0)
|
||||
nextButton.Selectable = false
|
||||
nextButton.Parent = playerSelection
|
||||
|
||||
local nextButtonAspectRatio = Instance.new("UIAspectRatioConstraint")
|
||||
nextButtonAspectRatio.DominantAxis = Enum.DominantAxis.Width
|
||||
nextButtonAspectRatio.Parent = nextButton
|
||||
|
||||
local prevButton = nextButton:Clone()
|
||||
prevButton.Name = "PrevButton"
|
||||
if theme.ScrollLeftImage ~= "" then
|
||||
prevButton.Image = theme.ScrollLeftImage
|
||||
else
|
||||
prevButton.Rotation = 180
|
||||
end
|
||||
prevButton.AnchorPoint = Vector2.new(0, 0.5)
|
||||
prevButton.Position = UDim2.new(0, 5, 0.5, 0)
|
||||
prevButton.Selectable = false
|
||||
prevButton.Parent = playerSelection
|
||||
|
||||
local function moveChangePage(goToNext)
|
||||
if goToNext then
|
||||
uiPageLayout:Next()
|
||||
else
|
||||
uiPageLayout:Previous()
|
||||
end
|
||||
end
|
||||
|
||||
nextButton.MouseButton1Click:Connect(function() moveChangePage(true) end)
|
||||
prevButton.MouseButton1Click:Connect(function() moveChangePage(false) end)
|
||||
|
||||
local defaultChildrenSize = 3 -- aspectRatioConstraint, PageLayout, first button in pagelayout
|
||||
|
||||
local function checkButtonVisibility()
|
||||
local lastInputIsTouch = UserInputService:GetLastInputType() == Enum.UserInputType.Touch
|
||||
prevButton.Visible = not lastInputIsTouch and (#selectedPlayer:GetChildren() > defaultChildrenSize)
|
||||
nextButton.Visible = not lastInputIsTouch and (#selectedPlayer:GetChildren() > defaultChildrenSize)
|
||||
end
|
||||
checkButtonVisibility()
|
||||
UserInputService.LastInputTypeChanged:Connect(checkButtonVisibility)
|
||||
|
||||
return playerSelection
|
||||
end
|
||||
|
||||
function PlayerCarousel:UpdateGuiTheme(theme)
|
||||
self.rbxGui.NextButton.Image = theme.ScrollRightImage
|
||||
|
||||
if theme.ScrollLeftImage ~= "" then
|
||||
self.rbxGui.PrevButton.Image = theme.ScrollLeftImage
|
||||
else
|
||||
self.rbxGui.PrevButton.Image = theme.ScrollRightImage
|
||||
self.rbxGui.PrevButton.Rotation = 180
|
||||
end
|
||||
end
|
||||
|
||||
function PlayerCarousel:FadeTowardsEdges()
|
||||
if not uiPageLayout.CurrentPage then
|
||||
return
|
||||
end
|
||||
|
||||
local carouselButtons = getSortedCarouselButtons()
|
||||
local currentPageIndex = 0
|
||||
for index, button in ipairs(carouselButtons) do
|
||||
if button == uiPageLayout.CurrentPage then
|
||||
currentPageIndex = index
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for index, button in ipairs(carouselButtons) do
|
||||
local distanceToLeft = (currentPageIndex - index) % #carouselButtons
|
||||
local distanceToRight = (index - currentPageIndex) % #carouselButtons
|
||||
local distance = math.min(distanceToLeft, distanceToRight)
|
||||
if distance >= 2 then
|
||||
button.ImageTransparency = 0.7
|
||||
elseif distance == 1 then
|
||||
button.ImageTransparency = 0.4
|
||||
else
|
||||
button.ImageTransparency = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PlayerCarousel:AddCarouselDivider()
|
||||
local carouselDivider = selectedPlayer:FindFirstChild(CAROUSEL_DIVIDER_NAME)
|
||||
if carouselDivider then
|
||||
carouselDivider:Destroy()
|
||||
end
|
||||
|
||||
local buttonCount = #selectedPlayer:GetChildren() - 1
|
||||
if buttonCount < 5 then
|
||||
return
|
||||
end
|
||||
|
||||
carouselDivider = Instance.new("Frame")
|
||||
carouselDivider.Name = CAROUSEL_DIVIDER_NAME
|
||||
carouselDivider.Size = UDim2.new(1, 0, 1, 0)
|
||||
carouselDivider.BackgroundTransparency = 1
|
||||
carouselDivider.Parent = selectedPlayer
|
||||
carouselDivider.LayoutOrder = 1000000
|
||||
|
||||
local line = Instance.new("Frame")
|
||||
line.Name = "line"
|
||||
line.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
|
||||
line.BorderSizePixel = 0
|
||||
line.Position = UDim2.new(0.25, 0, 0, -3)
|
||||
line.Size = UDim2.new(0, 2, 1, 6)
|
||||
line.Parent = carouselDivider
|
||||
|
||||
local line2 = line:Clone()
|
||||
line2.Position = UDim2.new(0.5, 0, 0, -3)
|
||||
line2.Parent = carouselDivider
|
||||
|
||||
local line3 = line:Clone()
|
||||
line3.Position = UDim2.new(0.75, 0, 0, -3)
|
||||
line3.Parent = carouselDivider
|
||||
end
|
||||
|
||||
function PlayerCarousel:RemovePlayerEntry(player)
|
||||
local button = playerToButtonMap[player]
|
||||
|
||||
if button then
|
||||
playerToButtonMap[player] = nil
|
||||
buttonToPlayerMap[button] = nil
|
||||
|
||||
button:Destroy()
|
||||
if uiPageLayout then
|
||||
uiPageLayout:ApplyLayout()
|
||||
end
|
||||
self:FadeTowardsEdges()
|
||||
end
|
||||
end
|
||||
|
||||
function PlayerCarousel:ClearPlayerEntries()
|
||||
for button in pairs(buttonToPlayerMap) do
|
||||
button:Destroy()
|
||||
end
|
||||
|
||||
buttonToPlayerMap = {}
|
||||
playerToButtonMap = {}
|
||||
end
|
||||
|
||||
function PlayerCarousel:CreatePlayerEntry(player, distanceToLocalPlayer)
|
||||
local playerButton = playerToButtonMap[player]
|
||||
if playerButton then
|
||||
playerButton.LayoutOrder = distanceToLocalPlayer
|
||||
return
|
||||
end
|
||||
|
||||
local button = Instance.new("ImageButton")
|
||||
button.Name = player.Name
|
||||
button.BorderSizePixel = 0
|
||||
button.LayoutOrder = distanceToLocalPlayer
|
||||
button.BackgroundColor3 = Color3.fromRGB(0,0,0)
|
||||
button.BackgroundTransparency = 0
|
||||
button.Size = UDim2.new(1, 0, 1, 0)
|
||||
|
||||
button.SelectionLost:connect(function() button.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR end)
|
||||
button.SelectionGained:connect(function() button.BackgroundColor3 = BACKGROUND_SELECTED_COLOR end)
|
||||
|
||||
local tweenStyle = TweenInfo.new(1, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, -1, true)
|
||||
local buttonLoadingTween
|
||||
buttonLoadingTween = TweenService:Create(
|
||||
button,
|
||||
tweenStyle,
|
||||
{BackgroundColor3 = Color3.fromRGB(255,255,255)}
|
||||
)
|
||||
buttonLoadingTween:Play()
|
||||
|
||||
buttonToPlayerMap[button] = player
|
||||
playerToButtonMap[player] = button
|
||||
|
||||
button.MouseButton1Click:Connect(function()
|
||||
uiPageLayout:JumpTo(button)
|
||||
end)
|
||||
|
||||
button.Parent = selectedPlayer
|
||||
|
||||
spawn(function()
|
||||
local ContextMenuUtil = require(AvatarMenuModules:WaitForChild("ContextMenuUtil"))
|
||||
button.Image = ContextMenuUtil:GetHeadshotForPlayer(player)
|
||||
buttonLoadingTween:Cancel()
|
||||
buttonLoadingTween = nil
|
||||
if button == GuiService.SelectedCoreObject then
|
||||
button.BackgroundColor3 = BACKGROUND_SELECTED_COLOR
|
||||
else
|
||||
button.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function PlayerCarousel:SwitchToPlayerEntry(player, dontTween)
|
||||
if not player then return end
|
||||
|
||||
local button = playerToButtonMap[player]
|
||||
if not button then
|
||||
self:CreatePlayerEntry(player, 0)
|
||||
button = playerToButtonMap[player]
|
||||
end
|
||||
|
||||
if dontTween then
|
||||
uiPageLayout.TweenTime = 0
|
||||
end
|
||||
uiPageLayout:JumpTo(button)
|
||||
spawn(function()
|
||||
uiPageLayout.TweenTime = PAGE_LAYOUT_TWEEN_TIME
|
||||
end)
|
||||
end
|
||||
|
||||
function PlayerCarousel:OffsetPlayerEntry(offset)
|
||||
if offset == 0 then return end
|
||||
|
||||
if offset > 0 then
|
||||
uiPageLayout:Next()
|
||||
else
|
||||
uiPageLayout:Previous()
|
||||
end
|
||||
end
|
||||
|
||||
function PlayerCarousel:GetSelectedPlayer()
|
||||
return buttonToPlayerMap[uiPageLayout.CurrentPage]
|
||||
end
|
||||
|
||||
function PlayerCarousel.new(theme)
|
||||
local obj = setmetatable({}, PlayerCarousel)
|
||||
|
||||
obj.rbxGui = CreateMenuCarousel(theme)
|
||||
obj.PlayerChanged = playerChangedEvent.Event
|
||||
|
||||
playerChangedEvent.Event:Connect(function()
|
||||
obj:FadeTowardsEdges()
|
||||
end)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return PlayerCarousel
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
--[[
|
||||
// FileName: SelectedCharacterIndicator.lua
|
||||
// Written by: TheGamer101
|
||||
// Description: Module for rendering an effect for the selected character .
|
||||
]]
|
||||
|
||||
local SelectedCharacterIndicator = {}
|
||||
SelectedCharacterIndicator.__index = SelectedCharacterIndicator
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local InsertService = game:GetService("InsertService")
|
||||
|
||||
local RENDER_ARROW_CONTEXT_ACTION = "ContextActionMenuRenderArrow"
|
||||
|
||||
local CurrentCamera = Workspace.CurrentCamera
|
||||
|
||||
-- This isn't currently necessary but done as a percaution in case
|
||||
-- we move the selected character indicator elsewhere later.
|
||||
local function removeScripts(object)
|
||||
for _, descendant in ipairs(object:GetDescendants()) do
|
||||
if descendant:IsA("LuaSourceContainer") then
|
||||
descendant:Destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ApplyArrow(character, theme)
|
||||
local baseModel = Instance.new("Model")
|
||||
baseModel.Name = "ContextMenuArrow"
|
||||
baseModel.Parent = CurrentCamera
|
||||
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
if humanoid == nil then
|
||||
humanoid = character:WaitForChild("Humanoid", 15)
|
||||
if humanoid == nil then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local torso = character:WaitForChild("HumanoidRootPart")
|
||||
|
||||
local arrowPart = theme.SelectedCharacterIndicator:Clone()
|
||||
removeScripts(arrowPart)
|
||||
arrowPart.Anchored = true
|
||||
arrowPart.Transparency = 0
|
||||
arrowPart.CanCollide = false
|
||||
arrowPart.Parent = baseModel
|
||||
|
||||
local arrowTween = TweenService:Create(
|
||||
arrowPart,
|
||||
TweenInfo.new(4, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, -1, false),
|
||||
{Orientation = Vector3.new(0, 360, 180)}
|
||||
)
|
||||
arrowTween:Play()
|
||||
|
||||
local function update()
|
||||
arrowPart.Position = torso.Position + Vector3.new(0, 5, 0)
|
||||
end
|
||||
|
||||
local isKilled = false
|
||||
local function kill()
|
||||
if isKilled then
|
||||
return
|
||||
end
|
||||
isKilled = true
|
||||
baseModel:Destroy()
|
||||
arrowTween:Destroy()
|
||||
RunService:UnbindFromRenderStep(RENDER_ARROW_CONTEXT_ACTION)
|
||||
end
|
||||
|
||||
humanoid.Died:Connect(kill)
|
||||
character.AncestryChanged:Connect(kill)
|
||||
RunService:BindToRenderStep(RENDER_ARROW_CONTEXT_ACTION, Enum.RenderPriority.Camera.Value + 1 , update)
|
||||
baseModel.Parent = CurrentCamera
|
||||
|
||||
return kill
|
||||
end
|
||||
|
||||
function SelectedCharacterIndicator:ChangeSelectedPlayer(selectedPlayer, theme)
|
||||
coroutine.wrap(function()
|
||||
if self.SelectedPlayer then
|
||||
self.SelectedPlayer = nil
|
||||
self.CharacterAddedConn:Disconnect()
|
||||
self.CharacterAddedConn = nil
|
||||
if self.KillOldRenderFunction then
|
||||
self.KillOldRenderFunction()
|
||||
self.KillOldRenderFunction = nil
|
||||
end
|
||||
end
|
||||
|
||||
if selectedPlayer then
|
||||
self.SelectedPlayer = selectedPlayer
|
||||
self.CharacterAddedConn = selectedPlayer.CharacterAdded:Connect(function(character)
|
||||
if self.KillOldRenderFunction then
|
||||
self.KillOldRenderFunction()
|
||||
end
|
||||
self.KillOldRenderFunction = ApplyArrow(character, theme)
|
||||
end)
|
||||
if selectedPlayer.Character then
|
||||
self.KillOldRenderFunction = ApplyArrow(selectedPlayer.Character, theme)
|
||||
end
|
||||
end
|
||||
end)()
|
||||
end
|
||||
|
||||
function SelectedCharacterIndicator.new()
|
||||
local obj = setmetatable({}, SelectedCharacterIndicator)
|
||||
|
||||
obj.KillOldRenderFunction = nil
|
||||
obj.SelectedPlayer = nil
|
||||
obj.CharacterAddedConn = nil
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return SelectedCharacterIndicator.new()
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
local InsertService = game:GetService("InsertService")
|
||||
local StarterGui = game:GetService("StarterGui")
|
||||
|
||||
-- Keep around any entries removed from this table to avoid breaking
|
||||
-- AvatarContextMenuTheme backwards compatibility.
|
||||
local DEFAULT_THEME = {
|
||||
BackgroundImage = "rbxasset://textures/blackBkg_round.png",
|
||||
BackgroundImageScaleType = Enum.ScaleType.Slice,
|
||||
BackgroundImageSliceCenter = Rect.new(12, 12, 12, 12),
|
||||
BackgroundImageTransparency = 0,
|
||||
|
||||
BackgroundTransparency = 1,
|
||||
BackgroundColor = Color3.fromRGB(31, 31, 31),
|
||||
|
||||
NameTagColor = Color3.fromRGB(79, 79, 79),
|
||||
NameUnderlineColor = Color3.fromRGB(255, 255, 255),
|
||||
|
||||
ButtonFrameColor = Color3.fromRGB(79, 79, 79),
|
||||
ButtonFrameTransparency = 0,
|
||||
|
||||
ButtonImage = "",
|
||||
ButtonImageScaleType = Enum.ScaleType.Slice,
|
||||
ButtonImageSliceCenter = Rect.new(8, 6, 46, 44),
|
||||
ButtonColor = Color3.fromRGB(79, 79, 79),
|
||||
ButtonTransparency = 1,
|
||||
ButtonHoverColor = Color3.fromRGB(163, 162, 165),
|
||||
ButtonHoverTransparency = 0.5,
|
||||
ButtonUnderlineColor = Color3.fromRGB(137, 137, 137),
|
||||
|
||||
Font = Enum.Font.SourceSansBold,
|
||||
TextColor = Color3.fromRGB(255, 255, 255),
|
||||
TextScale = 1,
|
||||
|
||||
LeaveMenuImage = "rbxasset://textures/loading/cancelButton.png",
|
||||
ScrollRightImage = "rbxasset://textures/ui/AvatarContextMenu_Arrow.png",
|
||||
ScrollLeftImage = "", --If no ScrollLeftImage is provided, the right image is rotated 180 degrees.
|
||||
|
||||
SelectedCharacterIndicator = InsertService:LoadLocalAsset(
|
||||
"rbxasset://models/AvatarContextMenu/AvatarContextArrow.rbxm"
|
||||
),
|
||||
|
||||
Size = UDim2.new(0.95, 0, 0.9, 0),
|
||||
MinSize = Vector2.new(200, 200),
|
||||
MaxSize = Vector2.new(300, 300),
|
||||
AspectRatio = 1.15,
|
||||
AnchorPoint = Vector2.new(0.5, 1),
|
||||
OnScreenPosition = UDim2.new(0.5, 0, 0.98, 0),
|
||||
OffScreenPosition = UDim2.new(0.5, 0, 1, 300),
|
||||
}
|
||||
|
||||
local ThemeHandler = {}
|
||||
ThemeHandler.__index = ThemeHandler
|
||||
|
||||
function ThemeHandler:UpdateTheme(newThemeData)
|
||||
local newTheme = {}
|
||||
for key, value in pairs(DEFAULT_THEME) do
|
||||
if typeof(newThemeData[key]) == typeof(value) then
|
||||
newTheme[key] = newThemeData[key]
|
||||
elseif newThemeData[key] == nil then
|
||||
newTheme[key] = value
|
||||
else
|
||||
error(string.format(
|
||||
"AvatarContextMenuTheme wrong type for key %s: %s. Expected type %s",
|
||||
key,
|
||||
typeof(newThemeData[key]),
|
||||
typeof(value)
|
||||
), 2)
|
||||
end
|
||||
end
|
||||
self.Theme = newTheme
|
||||
end
|
||||
|
||||
function ThemeHandler:RegisterCoreMethods()
|
||||
local function setMenuTheme(newTheme)
|
||||
if type(newTheme) == "table" then
|
||||
for key in pairs(newTheme) do
|
||||
if DEFAULT_THEME[key] == nil then
|
||||
error(string.format("AvatarContextMenuTheme got invalid key: %s", key))
|
||||
end
|
||||
end
|
||||
self:UpdateTheme(newTheme)
|
||||
else
|
||||
error("AvatarContextMenuTheme argument must be a table")
|
||||
end
|
||||
end
|
||||
StarterGui:RegisterSetCore("AvatarContextMenuTheme", setMenuTheme)
|
||||
end
|
||||
|
||||
-- PUBLIC METHODS
|
||||
|
||||
function ThemeHandler:GetTheme()
|
||||
return self.Theme
|
||||
end
|
||||
|
||||
function ThemeHandler.new()
|
||||
local obj = setmetatable({}, ThemeHandler)
|
||||
|
||||
obj.Theme = DEFAULT_THEME
|
||||
|
||||
obj:RegisterCoreMethods()
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return ThemeHandler.new()
|
||||
Reference in New Issue
Block a user