add gs
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
local TextService = game:GetService('TextService')
|
||||
local ShellModules = script.Parent.Parent
|
||||
|
||||
local GlobalSettings = require(ShellModules.GlobalSettings)
|
||||
local Strings = require(ShellModules.LocalizedStrings)
|
||||
local Utility = require(ShellModules.Utility)
|
||||
|
||||
local AccountAgeStatus = {}
|
||||
AccountAgeStatus.__index = AccountAgeStatus
|
||||
|
||||
function AccountAgeStatus.new(store, parent)
|
||||
local self = {}
|
||||
|
||||
self.StoreChangedCn = nil
|
||||
|
||||
self.rbx = Utility.Create'Frame' {
|
||||
Name = "AccountAgeContainer",
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Parent = parent,
|
||||
|
||||
Utility.Create'TextLabel' {
|
||||
Name = "AccountAgeText",
|
||||
Size = UDim2.new(1, -12, 1, -12),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
FontSize = Enum.FontSize.Size14,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
Text = "",
|
||||
ZIndex = 2,
|
||||
}
|
||||
}
|
||||
|
||||
self.StoreChangedCn = store.changed:connect(function(newState, oldState)
|
||||
if newState.RobloxUser.under13 ~= oldState.RobloxUser.under13 then
|
||||
local isUnder13 = newState.RobloxUser.under13
|
||||
-- clear out if under13 is nil
|
||||
if isUnder13 == nil then
|
||||
self.rbx.AccountAgeText.Text = ""
|
||||
return
|
||||
end
|
||||
|
||||
local newText = Strings:LocalizedString("AccountUnder13Phrase")
|
||||
if isUnder13 == false then
|
||||
newText = Strings:LocalizedString("AccountOver13Phrase")
|
||||
end
|
||||
|
||||
if newText == self.rbx.AccountAgeText.Text then
|
||||
return
|
||||
end
|
||||
|
||||
self.rbx.AccountAgeText.Text = newText
|
||||
-- update layout
|
||||
local textSize = TextService:GetTextSize(newText,
|
||||
Utility.ConvertFontSizeEnumToInt(self.rbx.AccountAgeText.FontSize), self.rbx.AccountAgeText.Font, Vector2.new(0, 0))
|
||||
self.rbx.Size = UDim2.new(0, textSize.x, 0, 50)
|
||||
self.rbx.Position = UDim2.new(1, -textSize.x, 0, 0)
|
||||
end
|
||||
end)
|
||||
|
||||
setmetatable(self, AccountAgeStatus)
|
||||
return self
|
||||
end
|
||||
|
||||
function AccountAgeStatus:Destruct()
|
||||
self.rbx:Destroy()
|
||||
self.rbx = nil
|
||||
if self.StoreChangedCn then
|
||||
self.StoreChangedCn:disconnect()
|
||||
self.StoreChangedCn = nil
|
||||
end
|
||||
end
|
||||
|
||||
return AccountAgeStatus
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
return function()
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
local Store = require(Modules.Common.Rodux).Store
|
||||
local AccountAgeStatus = require(script.Parent.AccountAgeStatus)
|
||||
local SetRobloxUser = require(Modules.Shell.Actions.SetRobloxUser)
|
||||
|
||||
it("should construct and destroy the object", function()
|
||||
local reducer = require(Modules.Shell.Reducers.AppShellReducer)
|
||||
local store = Store.new(reducer, {})
|
||||
local object = AccountAgeStatus.new(store, nil)
|
||||
|
||||
expect(object).to.be.ok()
|
||||
expect(object).to.be.a("table")
|
||||
|
||||
object:Destruct()
|
||||
|
||||
expect(object.rbx).never.to.be.ok()
|
||||
expect(object.StoreChangedCn).never.to.be.ok()
|
||||
|
||||
store:destruct()
|
||||
end)
|
||||
|
||||
it("should update status on store changed", function()
|
||||
local reducer = require(Modules.Shell.Reducers.AppShellReducer)
|
||||
local store = Store.new(reducer, {})
|
||||
local object = AccountAgeStatus.new(store, nil)
|
||||
|
||||
expect(object.rbx.AccountAgeText.Text).to.equal("")
|
||||
|
||||
local userInfo = {
|
||||
under13 = true,
|
||||
}
|
||||
|
||||
store:dispatch(SetRobloxUser(userInfo))
|
||||
store:flush()
|
||||
|
||||
expect(object.rbx.AccountAgeText.Text).never.to.equal("")
|
||||
|
||||
store:destruct()
|
||||
end)
|
||||
end
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
--[[
|
||||
A Roact alert overlay screen.
|
||||
Props:
|
||||
Key : Variant - The key of the overlay screen.
|
||||
InFocus : bool - Is the component in focus.
|
||||
Title : String - The title of the screen.
|
||||
Description : String - The description of the screen.
|
||||
ImageLabel : Roact.Component - the image to be displayed.
|
||||
ButtonTextYes : String - The text on the yes button.
|
||||
ButtonTextNo : String - The text on the no button.
|
||||
CallbackYes : function() - The callback function for the yes button.
|
||||
CallbackNo : function() - The callback function for the no button.
|
||||
CallbackBack : function() - The callback function for when back is pressed.
|
||||
DefaultButton : 0 or 1 - The default selection for the overlay screen.
|
||||
0 for Yes
|
||||
1 for No
|
||||
]]
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui.RobloxGui
|
||||
local Modules = RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
local SoundManager = require(Modules.Shell.SoundManager)
|
||||
|
||||
local RoundedButton = require(Modules.Shell.Components.Common.RoundedButton)
|
||||
local RedirectComponent = require(Modules.Shell.Components.Common.RedirectComponent)
|
||||
local AlertOverlay = Roact.PureComponent:extend("AlertOverlay")
|
||||
|
||||
local TEXT_LEFT_EDGE = 776
|
||||
local CONFRIM_KEY = "ComfirmButtonKey"
|
||||
local CANCEL_KEY = "CancelButtonKey"
|
||||
function AlertOverlay:init()
|
||||
self.key = self.props.Key
|
||||
self.guiObjs = {}
|
||||
self.defaultItemKey = CANCEL_KEY
|
||||
if self.props.DefaultButton == 0 then
|
||||
self.defaultItemKey = CONFRIM_KEY
|
||||
end
|
||||
self.state =
|
||||
{
|
||||
currentItemKey = self.defaultItemKey
|
||||
}
|
||||
self.onSelectionGained = function(key)
|
||||
self:setState(
|
||||
{
|
||||
currentItemKey = key
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function AlertOverlay:willUpdate(nextProps, nextState)
|
||||
self.defaultItemKey = nil
|
||||
if self.props.InFocus == nextProps.InFocus then
|
||||
return
|
||||
end
|
||||
if nextProps.InFocus then
|
||||
self.defaultItemKey = nextState.currentItemKey
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AlertOverlay:render()
|
||||
local confirmButton;
|
||||
if not self.props.CallbackYes and self.defaultItemKey == CONFRIM_KEY then
|
||||
self.defaultItemKey = CANCEL_KEY
|
||||
elseif not self.props.CallbackNo and self.defaultItemKey == CANCEL_KEY then
|
||||
self.defaultItemKey = CONFRIM_KEY
|
||||
end
|
||||
if self.props.CallbackYes then
|
||||
local confirmButtonProps =
|
||||
{
|
||||
Position = UDim2.new(0, TEXT_LEFT_EDGE, 1, -166);
|
||||
AnchorPoint = Vector2.new(0,0),
|
||||
Size = UDim2.new(0, 320, 0, 66);
|
||||
}
|
||||
local confirmButtonTextProps =
|
||||
{
|
||||
Text = self.props.ButtonTextYes,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
}
|
||||
confirmButton = Roact.createElement(RoundedButton,
|
||||
{
|
||||
Button = confirmButtonProps,
|
||||
Text = confirmButtonTextProps,
|
||||
Focused = self.props.InFocus and self.state.currentItemKey == CONFRIM_KEY,
|
||||
Selected = self.defaultItemKey == CONFRIM_KEY,
|
||||
OnSelectionGained = function()
|
||||
self.onSelectionGained(CONFRIM_KEY)
|
||||
end,
|
||||
OnActivated = function()
|
||||
SoundManager:Play('ButtonPress')
|
||||
self.props.CallbackYes()
|
||||
end
|
||||
})
|
||||
end
|
||||
local cancelButton;
|
||||
if self.props.CallbackNo then
|
||||
local cancelButtonProps =
|
||||
{
|
||||
Position = UDim2.new(0, 1106, 1 ,-166),
|
||||
AnchorPoint = Vector2.new(0, 0),
|
||||
Size = UDim2.new(0, 320, 0, 66),
|
||||
}
|
||||
local cancelButtonTextProps =
|
||||
{
|
||||
Text = self.props.ButtonTextNo,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
}
|
||||
cancelButton = Roact.createElement(RoundedButton,
|
||||
{
|
||||
Button = cancelButtonProps,
|
||||
Text = cancelButtonTextProps,
|
||||
Focused = self.props.InFocus and self.state.currentItemKey == CANCEL_KEY,
|
||||
Selected = self.defaultItemKey == CANCEL_KEY,
|
||||
OnSelectionGained = function()
|
||||
self.onSelectionGained(CANCEL_KEY)
|
||||
end,
|
||||
OnActivated = function()
|
||||
SoundManager:Play('ButtonPress')
|
||||
self.props.CallbackNo()
|
||||
end
|
||||
})
|
||||
elseif self.defaultItemKey == CANCEL_KEY then
|
||||
self.defaultItemKey = CONFRIM_KEY
|
||||
end
|
||||
local titleText = Roact.createElement("TextLabel",
|
||||
{
|
||||
Size = UDim2.new(0, 0, 0, 0),
|
||||
Position = UDim2.new(0, TEXT_LEFT_EDGE, 0, 136),
|
||||
BackgroundTransparency = 1,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
FontSize = GlobalSettings.HeaderSize,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
Text = self.props.Title,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
})
|
||||
local descriptionText = Roact.createElement("TextLabel",
|
||||
{
|
||||
Size = UDim2.new(0, 762, 0, 304),
|
||||
Position = UDim2.new(0, TEXT_LEFT_EDGE, 0, 200),
|
||||
BackgroundTransparency = 1,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
Font = GlobalSettings.LightFont,
|
||||
FontSize = GlobalSettings.TitleSize,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
TextWrapped = true,
|
||||
Text = self.props.Description,
|
||||
})
|
||||
local reportIcon = self.props.ImageLabel
|
||||
if reportIcon == nil then
|
||||
reportIcon = Roact.createElement("ImageLabel",
|
||||
{
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
BackgroundTransparency = 1,
|
||||
Image = GlobalSettings.Images.LargeErrorIcon,
|
||||
Size = UDim2.new(0, 321, 0, 264),
|
||||
})
|
||||
end
|
||||
local imageContainer = Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(0, 576, 0, 642),
|
||||
Position = UDim2.new(0, 100, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = 1,
|
||||
},{
|
||||
reportImage = reportIcon
|
||||
})
|
||||
local container = Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(1, 0, 0, 640),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
Position = UDim2.new(0, 0, 0.5, 0),
|
||||
BackgroundColor3 = GlobalSettings.Colors.OverlayColor;
|
||||
},{
|
||||
ImageContainer = imageContainer,
|
||||
TitleText = titleText,
|
||||
DescriptionText = descriptionText,
|
||||
CancelButton = cancelButton,
|
||||
ConfirmButton = confirmButton,
|
||||
})
|
||||
local modalOverlay = Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
Position = UDim2.new(0, 0, 0.5, 0),
|
||||
BackgroundColor3 = Color3.new(0, 0, 0),
|
||||
BackgroundTransparency = 0.3,
|
||||
[Roact.Ref] = function(rbx)
|
||||
self.ref = rbx
|
||||
end,
|
||||
},{
|
||||
Container = container,
|
||||
})
|
||||
local redirectObj;
|
||||
if self.props.CallbackBack then
|
||||
redirectObj = Roact.createElement(RedirectComponent,
|
||||
{
|
||||
Key = self.key,
|
||||
InFocus = true,
|
||||
RedirectBack = function()
|
||||
self.props.CallbackBack()
|
||||
end,
|
||||
})
|
||||
end
|
||||
return Roact.createElement(Roact.Portal,
|
||||
{
|
||||
target = CoreGui
|
||||
},{
|
||||
[self.key] = Roact.createElement("ScreenGui",
|
||||
{
|
||||
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
DisplayOrder = 1
|
||||
},
|
||||
{
|
||||
ModalOverlay = modalOverlay,
|
||||
RedirectObj = redirectObj,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
function AlertOverlay:didMount()
|
||||
delay(0,function()
|
||||
Utility.AddSelectionParent(self.key, self.ref)
|
||||
end)
|
||||
SoundManager:Play("OverlayOpen")
|
||||
end
|
||||
|
||||
function AlertOverlay:willUnmount()
|
||||
Utility.RemoveSelectionGroup(self.key)
|
||||
end
|
||||
|
||||
return AlertOverlay
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
--[[
|
||||
A simple base screen for Roact components.
|
||||
|
||||
Props:
|
||||
BackPageTitle : string - The title of the parent page
|
||||
Content : Roact.Component - The content of the frame
|
||||
]]
|
||||
local RobloxGui = game:GetService("CoreGui").RobloxGui
|
||||
local Modules = RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
local ContextActionEvent = require(Modules.Shell.Components.ContextActionEvent)
|
||||
|
||||
local BaseScreen = Roact.PureComponent:extend("BaseScreen")
|
||||
|
||||
local BACK_IMAGE = "rbxasset://textures/ui/Shell/Icons/BackIcon@1080.png"
|
||||
local BACK_IMAGE_SIZE = 48
|
||||
|
||||
function BaseScreen:init()
|
||||
self.onBack = function(actionName, inputState, inputObject)
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
self._seenPressed = true
|
||||
elseif inputState == Enum.UserInputState.End and self._seenPressed then
|
||||
self.props.onUnmount()
|
||||
end
|
||||
end
|
||||
self.onCreate = function(rbx)
|
||||
end
|
||||
end
|
||||
|
||||
function BaseScreen:render()
|
||||
local backPageTitle = self.props.BackPageTitle
|
||||
local onCreate = self.props.OnCreate or self.onCreate
|
||||
return Roact.createElement("Frame",
|
||||
{
|
||||
[Roact.Ref] = function(rbx)
|
||||
onCreate(rbx)
|
||||
end,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0.5,0,0.5,0),
|
||||
AnchorPoint = Vector2.new(0.5,0.5),
|
||||
BackgroundTransparency = 1,
|
||||
},{
|
||||
backImage = Roact.createElement("ImageButton",
|
||||
{
|
||||
Size = UDim2.new(0,BACK_IMAGE_SIZE,0,BACK_IMAGE_SIZE),
|
||||
BackgroundTransparency = 1,
|
||||
Image = BACK_IMAGE,
|
||||
Selectable = false,
|
||||
[Roact.Event.Activated] = self.onBack
|
||||
}),
|
||||
BackPageTitleLabel = Roact.createElement("TextLabel",
|
||||
{
|
||||
Size = UDim2.new(0, 0, 0, BACK_IMAGE_SIZE),
|
||||
Position = UDim2.new(0,BACK_IMAGE_SIZE+8,0,BACK_IMAGE_SIZE/2),
|
||||
AnchorPoint = Vector2.new(0,0.5),
|
||||
BackgroundTransparency = 1,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
FontSize = GlobalSettings.ButtonSize,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
Text = backPageTitle
|
||||
}),
|
||||
-- NOTE: This will need to be changed when the screen is actually connected to the tree.
|
||||
BackConnector = Roact.createElement(ContextActionEvent, {
|
||||
name = "GoBackTo" .. backPageTitle,
|
||||
callback = self.onBack,
|
||||
binds = { Enum.KeyCode.ButtonB },
|
||||
}),
|
||||
view = Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(1,0,1,-(BACK_IMAGE_SIZE+2)),
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.new(0,0,0,(BACK_IMAGE_SIZE+2)),
|
||||
},{self.props.Content})
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
|
||||
return BaseScreen
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
--[[
|
||||
Creates a Roact component provides a vertical list menu for multiple elements
|
||||
Props:
|
||||
Key : Variant - The key for this category menu. The key cannot be changed after init.
|
||||
InFocus : bool - Is the component in focus.
|
||||
Navigator : Navigator - The Navigator object.
|
||||
DefaultCategoryFocus : Variant - This is the key of the default focus button.
|
||||
DefaultCategoryKey : Variant - The default selection of the category menu.
|
||||
Categories : Categories - An object that provides data needed for the creating the categories
|
||||
The object must have an order mapping and a StringKeys mapping for localization.
|
||||
E.g.
|
||||
Categories[CategoryKey].Key = "Category"
|
||||
Categories[CategoryKey].Order = 1
|
||||
Categories[CategoryKey].StringKey = "CategoryStringKey"
|
||||
|
||||
OnSelectSection : function(Key : Variant) - Callback for when a section of the key is select.
|
||||
OnLeaveSection : function(Key : Variant) - Callback for when a section of the key is no longer selected.
|
||||
EnterSection : function() - Callback functin to enter the menu section.
|
||||
RedirectUp : function() - Callback functin when redirect up.
|
||||
RedirectDown : function() - Callback functin when redirect down.
|
||||
ActionPriority : int - The action priority.
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local LocalizedStrings = require(Modules.Shell.LocalizedStrings)
|
||||
|
||||
local VerticalListView = require(Modules.Shell.Components.Common.VerticalListView)
|
||||
local SelectorButton = require(Modules.Shell.Components.Common.SelectorButton)
|
||||
local RedirectComponent = require(Modules.Shell.Components.Common.RedirectComponent)
|
||||
|
||||
local CategoryMenuView = Roact.PureComponent:extend("CategoryMenuView")
|
||||
|
||||
local BUTTON_WIDTH = 360
|
||||
local BUTTON_HEIGHT = 80
|
||||
|
||||
function CategoryMenuView:OnSelectSection(key)
|
||||
if self.state.currentSectionKey ~= key then
|
||||
self:setState(
|
||||
{
|
||||
currentSectionKey = key,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function CategoryMenuView:init()
|
||||
self.key = self.props.Key
|
||||
self.defaultCategoryKey = self.props.DefaultCategoryKey
|
||||
self.defaultCategoryFocus = self.defaultCategoryKey or self.props.DefaultCategoryFocus
|
||||
self.state =
|
||||
{
|
||||
currentSectionKey = self.props.DefaultCategoryFocus,
|
||||
}
|
||||
self.getCurrentPageIndex = function()
|
||||
return self.CurrentSection
|
||||
end
|
||||
end
|
||||
|
||||
function CategoryMenuView:willUpdate(nextProps, nextState)
|
||||
self.defaultCategoryKey = nil
|
||||
if self.props.InFocus == nextProps.InFocus then
|
||||
return
|
||||
end
|
||||
if nextProps.InFocus then
|
||||
self.defaultCategoryKey = nextState.currentSectionKey or self.props.DefaultCategoryKey
|
||||
end
|
||||
end
|
||||
|
||||
function CategoryMenuView:render()
|
||||
self.categories = self.props.Categories
|
||||
local buttons = {}
|
||||
for k in pairs(self.categories) do
|
||||
buttons[k] = Roact.createElement(SelectorButton,
|
||||
{
|
||||
Size = UDim2.new(0, BUTTON_WIDTH, 0, BUTTON_HEIGHT),
|
||||
AnchorPoint = Vector2.new(0, 0),
|
||||
Text = LocalizedStrings:LocalizedString(self.categories[k].StringKey),
|
||||
Key = k,
|
||||
LayoutOrder = self.categories[k].Order,
|
||||
Focused = self.state.currentSectionKey == k,
|
||||
Selected = self.defaultCategoryKey == k,
|
||||
OnSelectionGained = function(key)
|
||||
self:OnSelectSection(key)
|
||||
if self.props.OnSelectSection then
|
||||
self.props.OnSelectSection(key)
|
||||
end
|
||||
end,
|
||||
OnSelectionLost = self.props.OnLeaveSection,
|
||||
OnActivated = self.props.EnterSection,
|
||||
})
|
||||
end
|
||||
|
||||
local navObj = Roact.createElement(RedirectComponent,
|
||||
{
|
||||
ActionPriority = self.props.ActionPriority,
|
||||
Key = self.key,
|
||||
InFocus = self.props.InFocus,
|
||||
RedirectRight = self.props.EnterSection,
|
||||
RedirectUp = self.props.RedirectUp,
|
||||
RedirectDown = self.props.RedirectDown,
|
||||
})
|
||||
|
||||
local buttonView = Roact.createElement(VerticalListView,
|
||||
{
|
||||
PaddingTop = UDim.new(0.005, 0),
|
||||
PaddingBottom = UDim.new(0.005, 0),
|
||||
Spacing = UDim.new(0.025, 0),
|
||||
ScrollBarThickness = 0,
|
||||
Items = buttons,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
ScrollingEnabled = false,
|
||||
})
|
||||
|
||||
return Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
AnchorPoint = Vector2.new(0, 0),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Selectable = false,
|
||||
[Roact.Ref] = function(rbx)
|
||||
self.ref = rbx
|
||||
end,
|
||||
},{
|
||||
NavObj = navObj,
|
||||
ButtonView = buttonView,
|
||||
})
|
||||
end
|
||||
|
||||
function CategoryMenuView:didMount()
|
||||
delay(0, function()
|
||||
if self.props.InFocus and self.ref then
|
||||
Utility.RemoveSelectionGroup(self.key)
|
||||
Utility.AddSelectionParent(self.key, self.ref)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function CategoryMenuView:didUpdate(previousProps, previousState)
|
||||
if self.props.InFocus == previousProps.InFocus then
|
||||
return
|
||||
end
|
||||
if self.props.InFocus and self.ref then
|
||||
Utility.RemoveSelectionGroup(self.key)
|
||||
Utility.AddSelectionParent(self.key, self.ref)
|
||||
else
|
||||
Utility.RemoveSelectionGroup(self.key)
|
||||
end
|
||||
end
|
||||
|
||||
function CategoryMenuView:willUnmount()
|
||||
Utility.RemoveSelectionGroup(self.key)
|
||||
end
|
||||
|
||||
return CategoryMenuView
|
||||
@@ -0,0 +1,33 @@
|
||||
--[[
|
||||
Creates a vertical divider
|
||||
Props:
|
||||
Position : UDim2 - The position of the divider.
|
||||
DividerWidth : UDim - The width of the divider.
|
||||
DividerLength : UDim - The length of the divider.
|
||||
Color : Color3 - The color of the divider. Default GlobalSettings.PageDivideColor
|
||||
FillDirection : FillDirection - The direction of the divider.
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
|
||||
return function(props)
|
||||
local color = props.Color or GlobalSettings.PageDivideColor
|
||||
local dividerLength = props.DividerLength
|
||||
local dividerWidth = props.DividerWidth or UDim.new(0,2)
|
||||
local position = props.Position
|
||||
local size
|
||||
if props.FillDirection == Enum.FillDirection.Horizontal then
|
||||
size = UDim2.new(dividerLength.Scale, dividerLength.Offset, dividerWidth.Scale, dividerWidth.Offset)
|
||||
else
|
||||
size = UDim2.new(dividerWidth.Scale, dividerWidth.Offset, dividerLength.Scale, dividerLength.Offset)
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame",{
|
||||
BackgroundColor3 = color,
|
||||
BorderSizePixel = 0,
|
||||
Size = size,
|
||||
Position = position,
|
||||
})
|
||||
end
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
--[[
|
||||
A component that is used for navigation and redirects.
|
||||
Note: The parent must be a scolling
|
||||
|
||||
Props:
|
||||
Key : String
|
||||
InFocus : bool
|
||||
Scale : Vector2
|
||||
RedirectBack : function()
|
||||
RedirectLeft : function(Ref<GuiObject>) - If nil,
|
||||
RedirectRight : function(Ref<GuiObject>)
|
||||
RedirectUp : function(Ref<GuiObject>)
|
||||
RedirectDown : function(Ref<GuiObject>)
|
||||
ActionPriority : The action priority
|
||||
]]
|
||||
local RobloxGui = game:GetService("CoreGui").RobloxGui
|
||||
local Modules = RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
|
||||
local RedirectComponent = Roact.PureComponent:extend("RedirectComponent")
|
||||
|
||||
local BACK_KEY = Enum.KeyCode.ButtonB
|
||||
|
||||
local DEFAULT_ACTION_PRIORITY = 2000
|
||||
|
||||
local function addBackButton(self)
|
||||
self.exitActionName = "ExitSection"..self.Key
|
||||
ContextActionService:UnbindCoreAction(self.exitActionName)
|
||||
if self.props.ActionPriority then
|
||||
ContextActionService:BindCoreActionAtPriority(self.exitActionName, self.exitAction, false, DEFAULT_ACTION_PRIORITY + self.props.ActionPriority, BACK_KEY)
|
||||
else
|
||||
ContextActionService:BindCoreAction(self.exitActionName, self.exitAction, false, BACK_KEY)
|
||||
end
|
||||
end
|
||||
|
||||
local function removeBackButton(self)
|
||||
if self.exitActionName ~= nil then
|
||||
ContextActionService:UnbindCoreAction(self.exitActionName)
|
||||
self.exitActionName = nil
|
||||
end
|
||||
end
|
||||
|
||||
function RedirectComponent:init()
|
||||
self.Key = self.props.Key or "RedirectComponent"
|
||||
self.exitActionName = nil
|
||||
self.backPressed = false
|
||||
self.exitAction = function(actionName, inputState, inputObject)
|
||||
if not self.props.InFocus then
|
||||
return Enum.ContextActionResult.Pass
|
||||
end
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
self.backPressed = true
|
||||
elseif inputState == Enum.UserInputState.End and self.backPressed then
|
||||
self.backPressed = false
|
||||
self.props.RedirectBack()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function RedirectComponent:render()
|
||||
local props = self.props
|
||||
local scale = props.Scale or Vector2.new(1,1)
|
||||
if props.InFocus and props.RedirectBack then
|
||||
addBackButton(self)
|
||||
else
|
||||
removeBackButton(self)
|
||||
end
|
||||
local redirectLeftButton;
|
||||
if props.InFocus and props.RedirectLeft then
|
||||
redirectLeftButton = Roact.createElement('TextButton',
|
||||
{
|
||||
Position = UDim2.new(-scale.X/2, -1, 0.5, 0),
|
||||
Size = UDim2.new(0, 2, 1+scale.Y, 0),
|
||||
AnchorPoint = Vector2.new(0.5,0.5),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "",
|
||||
[Roact.Event.SelectionGained] = function(rbx)
|
||||
props.RedirectLeft(rbx)
|
||||
end,
|
||||
})
|
||||
end
|
||||
local redirectRightButton;
|
||||
if props.InFocus and props.RedirectRight then
|
||||
redirectRightButton = Roact.createElement('TextButton',
|
||||
{
|
||||
Position = UDim2.new(1+scale.X/2, 1, 0.5, 0),
|
||||
Size = UDim2.new(0, 2, 1+scale.Y, 0),
|
||||
AnchorPoint = Vector2.new(0.5,0.5),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "",
|
||||
[Roact.Event.SelectionGained] = function(rbx)
|
||||
props.RedirectRight(rbx)
|
||||
end,
|
||||
})
|
||||
end
|
||||
local redirectUpButton;
|
||||
if props.InFocus and props.RedirectUp then
|
||||
redirectUpButton = Roact.createElement('TextButton',
|
||||
{
|
||||
Position = UDim2.new(0.5, 0, -scale.Y/2, -1),
|
||||
Size = UDim2.new(1+scale.X, 0, 0, 2),
|
||||
AnchorPoint = Vector2.new(0.5,0.5),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "",
|
||||
[Roact.Event.SelectionGained] = function(rbx)
|
||||
props.RedirectUp(rbx)
|
||||
end,
|
||||
})
|
||||
end
|
||||
local redirectDownButton;
|
||||
if props.InFocus and props.RedirectDown then
|
||||
redirectDownButton = Roact.createElement('TextButton',
|
||||
{
|
||||
Position = UDim2.new(0.5, 0, 1+scale.Y/2, 1),
|
||||
Size = UDim2.new(1+scale.X, 0, 0, 2),
|
||||
AnchorPoint = Vector2.new(0.5,0.5),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "",
|
||||
[Roact.Event.SelectionGained] = function(rbx)
|
||||
props.RedirectDown(rbx)
|
||||
end,
|
||||
})
|
||||
end
|
||||
return Roact.createElement('ScrollingFrame',
|
||||
{
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
AnchorPoint = Vector2.new(0.5,0.5),
|
||||
BackgroundTransparency = 1,
|
||||
Selectable = false,
|
||||
ScrollingEnabled = false,
|
||||
ScrollBarThickness = 0,
|
||||
CanvasSize = UDim2.new(0, 0, 1, 0),
|
||||
},{
|
||||
RedirectLeftButton = redirectLeftButton,
|
||||
RedirectRightButton = redirectRightButton,
|
||||
RedirectUpButton = redirectUpButton,
|
||||
RedirectDownButton = redirectDownButton,
|
||||
})
|
||||
end
|
||||
|
||||
function RedirectComponent:willUnmount()
|
||||
removeBackButton(self)
|
||||
end
|
||||
|
||||
return RedirectComponent
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
--[[
|
||||
Creates a Roact component that is a rounded button
|
||||
Props:
|
||||
Button : dictionary - Config for the button.
|
||||
.Image : Content - The image of the button.
|
||||
.Size : UDim2 - Size of the button.
|
||||
.Position : UDim2 - Position of the button.
|
||||
.AnchorPoint : UDim2 - The anchor point of the button.
|
||||
.ZIndex: int - Determines the order in which GUI objects are rendered, with 10 being in front and 1 in back.
|
||||
.LayoutOrder: int - Controls the sorting priority of this button.
|
||||
.Selectable : bool - Whether or not this object should be selectable using joysticks (controller).
|
||||
|
||||
Text : dictionary - A map of props for the text
|
||||
.Text : string - The label of the button.
|
||||
.Size : UDim2 - Size of the button.
|
||||
.Position : UDim2 - Position of the button.
|
||||
.AnchorPoint : The anchor point of the button.
|
||||
.Font : Font - The font used to display the given text.
|
||||
.TextSize : float - The font size in pixels.
|
||||
.TextXAlignment : TextXAlignment - Sets where text is placed on the X axis within the TextLabel.
|
||||
.ZIndex: int - Determines the order in which GUI objects are rendered, with 10 being in front and 1 in back.
|
||||
|
||||
|
||||
Focused : bool - Is the button in focus.
|
||||
Disabled : bool - Is the button disabled. By default a disabled button will not be selectable.
|
||||
Selected : bool - Should the button be selected.
|
||||
OnSelectionGained : bool function() -
|
||||
Fired when the GuiObject is being focused on with the Gamepad selector.
|
||||
Return true if it should be focused. False otherwise.
|
||||
OnSelectionLost : bool function() -
|
||||
Fired when the Gamepad selector stops focusing on the GuiObject.
|
||||
Return false if it should be un-focused. True otherwise.
|
||||
OnActivated : function() - Fires when the button is activated.
|
||||
|
||||
HideSelectionImage : bool - Whether or not to hide the selection object
|
||||
|
||||
DefaultProps : dictionary a map for the default props of the button.
|
||||
.ImageColor3 : Color3
|
||||
.ImageTransparency : float
|
||||
.TextColor3 : Color3
|
||||
|
||||
FocusedProps : dictionary a map for the focused props of the button.
|
||||
.ImageColor3 : Color3
|
||||
.ImageTransparency : float
|
||||
.TextColor3 : Color3
|
||||
|
||||
DisabledProps : dictionary a map for the disabled props of the button.
|
||||
.ImageColor3 : Color3
|
||||
.ImageTransparency : float
|
||||
.TextColor3 : Color3
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local Immutable = require(Modules.Common.Immutable)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
local SoundComponent = require(Modules.Shell.Components.Common.SoundComponent)
|
||||
local RoundedButton = Roact.PureComponent:extend("RoundedButton")
|
||||
|
||||
function RoundedButton:init()
|
||||
self.selectionImageObject = Utility.Create "ImageLabel"
|
||||
{
|
||||
Name = "SelectorImage",
|
||||
Image = GlobalSettings.Images.ButtonSelector,
|
||||
Position = UDim2.new(0, -7, 0, -7),
|
||||
Size = UDim2.new(1, 14, 1, 14),
|
||||
BackgroundTransparency = 1,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = Rect.new(31, 31, 63, 63),
|
||||
}
|
||||
|
||||
self.defaultProps =
|
||||
{
|
||||
ImageColor3 = GlobalSettings.Colors.WhiteButton,
|
||||
ImageTransparency = 0.8,
|
||||
TextColor3 = GlobalSettings.Colors.WhiteText,
|
||||
}
|
||||
self.focusedProps =
|
||||
{
|
||||
ImageColor3 = GlobalSettings.Colors.BlueButton,
|
||||
ImageTransparency = 0,
|
||||
TextColor3 = GlobalSettings.Colors.TextSelected,
|
||||
}
|
||||
self.disabledProps =
|
||||
{
|
||||
ImageColor3 = GlobalSettings.Colors.WhiteButton,
|
||||
ImageTransparency = 1,
|
||||
TextColor3 = GlobalSettings.Colors.WhiteText,
|
||||
}
|
||||
self.buttonImage = GlobalSettings.Images.ButtonDefault
|
||||
|
||||
--TODO: Change to new Ref API
|
||||
self.onCreate = function(rbx)
|
||||
self.ref = rbx
|
||||
end
|
||||
end
|
||||
|
||||
function RoundedButton:render()
|
||||
local button = self.props.Button or {}
|
||||
local text = self.props.Text or {}
|
||||
|
||||
local inputDefaultProps = self.props.DefaultProps or {}
|
||||
local defaultProps = {}
|
||||
for k in pairs(self.defaultProps) do
|
||||
defaultProps[k] = inputDefaultProps[k] or self.defaultProps[k]
|
||||
end
|
||||
|
||||
local inputFocusedProps = self.props.FocusedProps or {}
|
||||
local focusedProps = {}
|
||||
for k in pairs(self.focusedProps) do
|
||||
focusedProps[k] = inputFocusedProps[k] or self.focusedProps[k]
|
||||
end
|
||||
|
||||
local inputDisabledProps = self.props.DisabledProps or {}
|
||||
local disabledProps = {}
|
||||
for k in pairs(self.disabledProps) do
|
||||
disabledProps[k] = inputDisabledProps[k] or self.disabledProps[k]
|
||||
end
|
||||
|
||||
if self.props.HideSelectionImage then
|
||||
self.selectionImageObject.Visible = false
|
||||
else
|
||||
self.selectionImageObject.Visible = true
|
||||
end
|
||||
local selectable = true
|
||||
if button.Selectable == false then
|
||||
selectable = false
|
||||
end
|
||||
local currentProps = defaultProps
|
||||
if self.props.Disabled then
|
||||
currentProps = disabledProps
|
||||
selectable = button.Selectable or false
|
||||
elseif self.props.Focused then
|
||||
currentProps = focusedProps
|
||||
end
|
||||
local baseButtonProps =
|
||||
{
|
||||
Image = self.buttonImage,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Selectable = selectable,
|
||||
[Roact.Ref] = self.onCreate,
|
||||
[Roact.Event.SelectionGained] = self.props.OnSelectionGained,
|
||||
[Roact.Event.SelectionLost] = self.props.OnSelectionLost,
|
||||
[Roact.Event.Activated] = self.props.OnActivated,
|
||||
ImageColor3 = currentProps.ImageColor3,
|
||||
ImageTransparency = currentProps.ImageTransparency,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = Rect.new(8, 8, 9, 9),
|
||||
SelectionImageObject = self.selectionImageObject,
|
||||
BackgroundTransparency = 1,
|
||||
}
|
||||
local buttonProps = Immutable.JoinDictionaries(baseButtonProps, button)
|
||||
|
||||
local baseTextProps =
|
||||
{
|
||||
Text = "",
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Font = GlobalSettings.Fonts.Regular,
|
||||
TextSize = GlobalSettings.TextSizes.Button,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextColor3 = currentProps.TextColor3,
|
||||
TextTransparency = currentProps.TextTransparency,
|
||||
BackgroundTransparency = 1,
|
||||
}
|
||||
|
||||
local textProps = Immutable.JoinDictionaries(baseTextProps, text)
|
||||
|
||||
local textLabel = Roact.createElement("TextLabel", textProps)
|
||||
local moveSelection = Roact.createElement(SoundComponent,
|
||||
{
|
||||
SoundName = "MoveSelection",
|
||||
}
|
||||
)
|
||||
|
||||
local children = self.props[Roact.Children] or {}
|
||||
|
||||
return Roact.createElement("ImageButton",
|
||||
buttonProps,
|
||||
Immutable.JoinDictionaries(
|
||||
{
|
||||
Label = textLabel,
|
||||
MoveSelection = moveSelection,
|
||||
}, children)
|
||||
)
|
||||
end
|
||||
|
||||
function RoundedButton:didMount()
|
||||
delay(0, function()
|
||||
if self.props.Selected then
|
||||
Utility.SetSelectedCoreObject(self.ref)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function RoundedButton:didUpdate(previousProps, previousState)
|
||||
if not previousProps.Selected and self.props.Selected then
|
||||
Utility.SetSelectedCoreObject(self.ref)
|
||||
end
|
||||
end
|
||||
|
||||
return RoundedButton
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
--[[
|
||||
Creates a Roact component that is a category selector button
|
||||
Props:
|
||||
Key : Variant - The key for the selector button.
|
||||
Text : String - The label of the button.
|
||||
Size : UDim2 - The size of the this button.
|
||||
AnchorPoint : Vector2 - The anchor point of the button.
|
||||
Position : UDim2 - The position of the button.
|
||||
LayoutOrder : int - The layout order of the button.
|
||||
|
||||
Focused : bool - Is the button in focus.
|
||||
Disabled : bool - Is the button disabled. By default a disabled button will not be selectable.
|
||||
Selected : bool - Should the button be selected.
|
||||
OnSelectionGained : function(key : Variant) - Fired when the GuiObject
|
||||
is being focused on with the Gamepad selector.
|
||||
OnSelectionLost : function(key : Variant) - Fired when the Gamepad selector stops focusing on the GuiObject.
|
||||
OnActivated : function(key : Variant) - Fires when the button is activated.
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local Immutable = require(Modules.Common.Immutable)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
|
||||
local RoundedButton = require(Modules.Shell.Components.Common.RoundedButton)
|
||||
|
||||
local SelectorButton = Roact.PureComponent:extend("SelectorButton")
|
||||
|
||||
local SELECTOR_ICON_SIZE_X = 15
|
||||
local SELECTOR_ICON_SIZE_Y = 30
|
||||
|
||||
local SELECTOR_OFFSET_X = 14
|
||||
|
||||
function SelectorButton:init()
|
||||
self.key = self.props.Key
|
||||
self.onSelectionGained = function()
|
||||
if self.props.OnSelectionGained then self.props.OnSelectionGained(self.key) end
|
||||
end
|
||||
self.onSelectionLost = function()
|
||||
if self.props.OnSelectionLost then self.props.OnSelectionLost(self.key) end
|
||||
end
|
||||
self.onActivated = function()
|
||||
if self.props.OnActivated then self.props.OnActivated(self.key) end
|
||||
end
|
||||
end
|
||||
|
||||
function SelectorButton:render()
|
||||
local buttonProps = {}
|
||||
local textProps = {}
|
||||
local size = self.props.Size
|
||||
local position = self.props.Position
|
||||
local anchorPoint = self.props.AnchorPoint
|
||||
local focused = self.props.Focused
|
||||
local disabled = self.props.Disabled
|
||||
local selected = self.props.Selected
|
||||
self.layoutOrder = self.props.LayoutOrder
|
||||
|
||||
buttonProps.Size = UDim2.new(1, 0, 1, 0)
|
||||
buttonProps.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
buttonProps.Position = UDim2.new(0.5, 0, 0.5, 0)
|
||||
|
||||
textProps.Text = self.props.Text
|
||||
textProps.Size = UDim2.new(1, 0, 1, 0)
|
||||
textProps.AnchorPoint = Vector2.new(0, 0.5)
|
||||
textProps.Position = UDim2.new(0, 24, 0.5, 0)
|
||||
|
||||
local button = Roact.createElement(RoundedButton,
|
||||
{
|
||||
Button = buttonProps,
|
||||
Text = textProps,
|
||||
Focused = focused,
|
||||
Disabled = disabled,
|
||||
Selected = selected,
|
||||
OnSelectionGained = self.onSelectionGained,
|
||||
OnSelectionLost = self.onSelectionLost,
|
||||
OnActivated = self.onActivated,
|
||||
})
|
||||
|
||||
local selector;
|
||||
if focused == true then
|
||||
selector = Roact.createElement("ImageLabel",
|
||||
{
|
||||
Size = UDim2.new(0, SELECTOR_ICON_SIZE_X, 0, SELECTOR_ICON_SIZE_Y),
|
||||
Image = GlobalSettings.Images.RightArrow,
|
||||
Position = UDim2.new(1, SELECTOR_OFFSET_X, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
end
|
||||
|
||||
local children = self.props[Roact.Children] or {}
|
||||
|
||||
return Roact.createElement("Frame",
|
||||
{
|
||||
Size = size,
|
||||
Position = position,
|
||||
AnchorPoint = anchorPoint,
|
||||
LayoutOrder = self.layoutOrder,
|
||||
BackgroundTransparency = 1,
|
||||
},Immutable.JoinDictionaries(
|
||||
{
|
||||
Button = button,
|
||||
Selector = selector,
|
||||
}, children)
|
||||
)
|
||||
end
|
||||
|
||||
return SelectorButton
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
--[[
|
||||
Creates a component with a shadow image
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
|
||||
return function(props)
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Name = 'Shadow',
|
||||
Image = GlobalSettings.Images.Shadow,
|
||||
Size = UDim2.new(1,3,1,3),
|
||||
Position = UDim2.new(0,0,0,0),
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = Rect.new(10,10,28,28),
|
||||
BackgroundTransparency = 1,
|
||||
ZIndex = props.ZIndex or 1,
|
||||
})
|
||||
end
|
||||
@@ -0,0 +1,260 @@
|
||||
--[[
|
||||
Creates a component for sidebar
|
||||
Props:
|
||||
buttons : array An array of the buttons to be added on the sidebar.
|
||||
key - string The text to be shown on the sidebar button.
|
||||
value - function() A callback which will be called when the button is activated.
|
||||
text: string - The text to be shown on the sidebar.
|
||||
inFocus: bool - The boolean which indicates whether the sidebar is open.
|
||||
selectIndex: int - The button index we try to select when the sidebar is open.
|
||||
paddingTop : UDim - The padding to apply on the top side relative to the sidebar's normal size.
|
||||
paddingBottom : UDim - The padding to apply on the bottom side relative to the sidebar's normal size.
|
||||
displayOrder: int - The order that the sidebar ScreenGui is drawn.
|
||||
onRemoveFocus : function() - Callback function when the remove focus from sidebar
|
||||
onClose: function() - Callback function when the sidebar is closed.
|
||||
actionPriority : int - The action priority on sidebar.
|
||||
]]
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local Modules = CoreGui.RobloxGui.Modules
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
local RoactMotion = require(Modules.LuaApp.RoactMotion)
|
||||
local SoundManager = require(Modules.Shell.SoundManager)
|
||||
local Components = Modules.Shell.Components
|
||||
local SoundComponent = require(Modules.Shell.Components.Common.SoundComponent)
|
||||
local ContextActionEvent = require(Components.ContextActionEvent)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local INSET_X = 65
|
||||
local BUTTON_SIZE_Y = 75
|
||||
local SIDEBAR_SELECTION_GROUP_NAME = "SideBar"
|
||||
|
||||
local SideBar = Roact.PureComponent:extend("SideBar")
|
||||
function SideBar:init()
|
||||
self.groupKey = SIDEBAR_SELECTION_GROUP_NAME
|
||||
self.buttonImage = GlobalSettings.Images.ButtonDefault
|
||||
self.selectionImageObject = Utility.Create "ImageLabel"({
|
||||
Name = "SelectorImage",
|
||||
BackgroundTransparency = 1,
|
||||
Visible = false
|
||||
})
|
||||
|
||||
self.defaultProps = {
|
||||
buttonColor3 = GlobalSettings.Colors.WhiteButton,
|
||||
buttonTransparency = 1,
|
||||
textColor3 = GlobalSettings.Colors.WhiteText,
|
||||
}
|
||||
|
||||
self.focusedProps = {
|
||||
buttonColor3 = GlobalSettings.Colors.BlueButton,
|
||||
buttonTransparency = 0,
|
||||
textColor3 = GlobalSettings.Colors.TextSelected,
|
||||
}
|
||||
end
|
||||
|
||||
function SideBar:render()
|
||||
local props = self.props
|
||||
|
||||
local onClose = function()
|
||||
self.buttons = {}
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
if props.onClose then
|
||||
props.onClose()
|
||||
end
|
||||
end
|
||||
|
||||
local contents = {
|
||||
--Make it inside the title safe container
|
||||
UIPadding = Roact.createElement("UIPadding", {
|
||||
PaddingTop = props.paddingTop or UDim.new(0, 156),
|
||||
PaddingBottom = props.paddingBottom or UDim.new(0, 39)
|
||||
})
|
||||
}
|
||||
|
||||
if props.buttons then
|
||||
local index = 0
|
||||
for _, buttonObj in ipairs(props.buttons) do
|
||||
index = index + 1
|
||||
local btIndex = index
|
||||
local focused = self.state.selectedIndex and self.state.selectedIndex == btIndex
|
||||
local currProps = focused and self.focusedProps or self.defaultProps
|
||||
local btText = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, -INSET_X, 1, 0),
|
||||
Position = UDim2.new(0, INSET_X, 0, 0),
|
||||
AnchorPoint = Vector2.new(0, 0),
|
||||
Text = buttonObj.text,
|
||||
TextSize = GlobalSettings.TextSizes.Medium,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextColor3 = currProps.textColor3,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
local moveSelection = Roact.createElement(SoundComponent, {
|
||||
SoundName = "MoveSelection",
|
||||
})
|
||||
contents["Button"..btIndex] = Roact.createElement("ImageButton", {
|
||||
Image = self.buttonImage,
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Size = UDim2.new(1, -1, 0, BUTTON_SIZE_Y),
|
||||
LayoutOrder = btIndex,
|
||||
ImageColor3 = currProps.buttonColor3,
|
||||
ImageTransparency = currProps.buttonTransparency,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = Rect.new(8, 8, 9, 9),
|
||||
SelectionImageObject = self.selectionImageObject,
|
||||
BackgroundTransparency = 1,
|
||||
[Roact.Event.SelectionGained] = function()
|
||||
self:setState({
|
||||
selectedIndex = btIndex
|
||||
})
|
||||
end,
|
||||
[Roact.Event.SelectionLost] = function()
|
||||
self:setState({
|
||||
selectedIndex = Roact.None
|
||||
})
|
||||
end,
|
||||
[Roact.Event.Activated] = function()
|
||||
SoundManager:Play("ButtonPress")
|
||||
onClose()
|
||||
buttonObj.callback()
|
||||
end,
|
||||
[Roact.Ref] = function(bt)
|
||||
self.buttons = self.buttons or {}
|
||||
self.buttons[btIndex] = bt
|
||||
end,
|
||||
}, {
|
||||
ButtonText = btText,
|
||||
MoveSelection = moveSelection,
|
||||
})
|
||||
end
|
||||
|
||||
if index > 0 then
|
||||
contents.UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, 0),
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
})
|
||||
end
|
||||
else
|
||||
contents.TextLabel = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(1, -INSET_X - 100, 1, 0),
|
||||
Position = UDim2.new(0, INSET_X, 0, 0),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundTransparency = 1,
|
||||
Text = props.text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
FontSize = GlobalSettings.DescriptionSize,
|
||||
TextWrapped = true,
|
||||
})
|
||||
end
|
||||
|
||||
local inFocus = props.inFocus
|
||||
local modalBackgroundTransparency = inFocus and GlobalSettings.ModalBackgroundTransparency or 1
|
||||
local containerPositionXScale = inFocus and 0.7 or 1
|
||||
if not inFocus then
|
||||
self.seenPressed = false
|
||||
end
|
||||
|
||||
return Roact.createElement(RoactMotion.SimpleMotion, {
|
||||
defaultStyle = {
|
||||
modalBackgroundTransparency = 1,
|
||||
containerPositionXScale = 1,
|
||||
},
|
||||
style = {
|
||||
modalBackgroundTransparency = RoactMotion.spring(modalBackgroundTransparency, 600, 60),
|
||||
containerPositionXScale = RoactMotion.spring(containerPositionXScale, 600, 60),
|
||||
},
|
||||
onRested = not inFocus and onClose,
|
||||
render = function(values)
|
||||
return Roact.createElement(Roact.Portal, { target = CoreGui }, {
|
||||
SideBarGui = Roact.createElement("ScreenGui", {
|
||||
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
DisplayOrder = props.displayOrder or 1
|
||||
}, {
|
||||
BackConnector = inFocus and Roact.createElement(ContextActionEvent, {
|
||||
name = "CloseSideBar",
|
||||
callback = function(actionName, inputState, inputObject)
|
||||
if inputObject.KeyCode == Enum.KeyCode.ButtonB then
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
self.seenPressed = true
|
||||
elseif inputState == Enum.UserInputState.End and self.seenPressed then
|
||||
self:setState({
|
||||
selectedIndex = Roact.None
|
||||
})
|
||||
if props.onRemoveFocus then
|
||||
props.onRemoveFocus()
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
binds = { Enum.UserInputType.Gamepad1, Enum.UserInputType.Gamepad2, Enum.UserInputType.Gamepad3, Enum.UserInputType.Gamepad4 },
|
||||
actionPriority = props.actionPriority
|
||||
}),
|
||||
ModalOverlay = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = values.modalBackgroundTransparency,
|
||||
BackgroundColor3 = GlobalSettings.ModalBackgroundColor,
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
SideBarContainer = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0.3, 0, 1, 0),
|
||||
Position = UDim2.new(values.containerPositionXScale, 0, 0, 0),
|
||||
BorderSizePixel = 0,
|
||||
BackgroundColor3 = GlobalSettings.OverlayColor,
|
||||
[Roact.Ref] = function(container)
|
||||
self.container = container
|
||||
end
|
||||
}, contents)
|
||||
})
|
||||
}),
|
||||
})
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function SideBar:didMount()
|
||||
delay(0, function()
|
||||
if self.props.inFocus and self.container then
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
Utility.AddSelectionParent(self.groupKey, self.container)
|
||||
local trySelectIndex = self.props.selectIndex or 1
|
||||
if self.buttons and self.buttons[trySelectIndex] then
|
||||
if not self.state.selectedIndex then
|
||||
Utility.SetSelectedCoreObject(self.buttons[trySelectIndex])
|
||||
end
|
||||
else
|
||||
Utility.SetSelectedCoreObject(nil)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function SideBar:didUpdate(previousProps, previousState)
|
||||
if self.props.inFocus == previousProps.inFocus then
|
||||
return
|
||||
end
|
||||
if self.props.inFocus and self.container then
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
Utility.AddSelectionParent(self.groupKey, self.container)
|
||||
local trySelectIndex = self.props.selectIndex or 1
|
||||
if self.buttons and self.buttons[trySelectIndex] then
|
||||
if not self.state.selectedIndex then
|
||||
Utility.SetSelectedCoreObject(self.buttons[trySelectIndex])
|
||||
end
|
||||
else
|
||||
Utility.SetSelectedCoreObject(nil)
|
||||
end
|
||||
else
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
end
|
||||
end
|
||||
|
||||
function SideBar:willUnmount()
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
end
|
||||
|
||||
return SideBar
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
--[[
|
||||
A simple sound component
|
||||
|
||||
Props:
|
||||
SoundName : string - The name of the sound
|
||||
]]
|
||||
local RobloxGui = game:GetService("CoreGui").RobloxGui
|
||||
local Modules = RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
|
||||
return function(props)
|
||||
local soundName = props.SoundName
|
||||
local soundsUrl = GlobalSettings.Sounds[soundName]
|
||||
return Roact.createElement('Sound',
|
||||
{
|
||||
SoundId = soundsUrl
|
||||
})
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
--[[
|
||||
Creates a component as a spinner
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local Components = Modules.Shell.Components
|
||||
local RenderStep = require(Components.RenderStep)
|
||||
local SPINNER_IMAGE = 'rbxasset://textures/ui/Shell/Icons/LoadingSpinner@1080.png'
|
||||
local Spinner = Roact.PureComponent:extend("Spinner")
|
||||
|
||||
function Spinner:init()
|
||||
self.state = {
|
||||
rotation = 0
|
||||
}
|
||||
|
||||
self.speed = 360
|
||||
|
||||
self.update = function(dt)
|
||||
self:setState({
|
||||
rotation = self.state.rotation + dt * self.speed
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function Spinner:render()
|
||||
local props = self.props
|
||||
local state = self.state
|
||||
local rotation = state.rotation
|
||||
self.speed = props.speed or 360
|
||||
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Rotation = rotation,
|
||||
BackgroundTransparency = 1,
|
||||
Size = props.Size or UDim2.new(0, 100, 0, 100),
|
||||
AnchorPoint = props.AnchorPoint or Vector2.new(0.5, 0.5),
|
||||
Position = props.Position or UDim2.new(0.5, 0, 0.5, 0),
|
||||
Image = SPINNER_IMAGE,
|
||||
ZIndex = props.ZIndex or 10,
|
||||
ImageTransparency = props.ImageTransparency or 0,
|
||||
},{
|
||||
Render = Roact.createElement(RenderStep, {
|
||||
name = tick(),
|
||||
priority = Enum.RenderPriority.Input.Value,
|
||||
callback = self.update,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return Spinner
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
--[[
|
||||
Creates a Roact component provides a left and a right view
|
||||
Props:
|
||||
Bias : number (0 - 1) - The bias for the size of left view vs the right view
|
||||
LeftView : Roact.Component - The left roact element that will be a child of the SplitView
|
||||
RightView : Roact.Component - The right roact element that will be a child of the SplitView
|
||||
Divider : Roact.Component - The divide roact element that will divide the two sides.
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
|
||||
|
||||
local SplitViewLR = Roact.Component:extend("SplitViewLR")
|
||||
|
||||
function SplitViewLR:init()
|
||||
end
|
||||
|
||||
function SplitViewLR:render()
|
||||
local props = self.props
|
||||
local bias = props.Bias
|
||||
self.leftView = props.LeftView
|
||||
self.rightView = props.RightView
|
||||
self.divider = props.Divider
|
||||
|
||||
return Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(1,0,1,0),
|
||||
BackgroundTransparency = 1,
|
||||
},{
|
||||
LeftFrame = Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(bias,0,1,0),
|
||||
Position = UDim2.new(0,0,0,0),
|
||||
BackgroundTransparency = 1,
|
||||
},{Left = self.leftView}),
|
||||
Divider = Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(0,0,0,0),
|
||||
Position = UDim2.new(bias,0,0,0),
|
||||
BackgroundTransparency = 1,
|
||||
},{Divider = self.divider}),
|
||||
RightFrame = Roact.createElement("Frame",
|
||||
{
|
||||
Size = UDim2.new(1-bias,0,1,0),
|
||||
Position = UDim2.new(bias,0,0,0),
|
||||
BackgroundTransparency = 1,
|
||||
},{
|
||||
Right = self.rightView
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return SplitViewLR
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
--[[
|
||||
Create a Roact toggle button
|
||||
Props:
|
||||
Key : Variant - The key for the toggle button.
|
||||
Text : String - The text of the button.
|
||||
Size : UDim2 - The size of the this button.
|
||||
AnchorPoint : Vector2 - The anchor point of the button.
|
||||
Position : UDim2 - The position of the button.
|
||||
|
||||
Toggle : bool - Whether or not the button is toggled or not.
|
||||
Focused : bool - Is the button selected.
|
||||
Disabled : bool - Whether or not this button is disabled.
|
||||
Selected : bool - Should the button be selected.
|
||||
Selectable : bool - Whether or not the button is selectable.
|
||||
OnSelectionGained : function(key : Variant) - Fired when the GuiObject is being focused on with the Gamepad selector.
|
||||
OnSelectionLost : function(key : Variant) - Fired when the Gamepad selector stops focusing on the GuiObject.
|
||||
OnActivated : function() - Fires when the button is activated.
|
||||
]]
|
||||
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local Immutable = require(Modules.Common.Immutable)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
|
||||
local RoundedButton = require(Modules.Shell.Components.Common.RoundedButton)
|
||||
|
||||
local ToggleButton = Roact.Component:extend("ToggleButton")
|
||||
|
||||
local TOGGLE_ICON_SIZE = 32
|
||||
|
||||
local TOGGLE_ICON_OFFSET_X = 20
|
||||
|
||||
function ToggleButton:init()
|
||||
self.key = self.props.Key
|
||||
self.state =
|
||||
{
|
||||
selected = false,
|
||||
active = false,
|
||||
}
|
||||
self.onSelectionGained = function()
|
||||
if self.props.OnSelectionGained then self.props.OnSelectionGained(self.key) end
|
||||
end
|
||||
self.onSelectionLost = function()
|
||||
if self.props.OnSelectionLost then self.props.OnSelectionLost(self.key) end
|
||||
end
|
||||
self.onActivated = function()
|
||||
if self.props.OnActivated then self.props.OnActivated() end
|
||||
end
|
||||
end
|
||||
|
||||
function ToggleButton:render()
|
||||
local buttonProps = {}
|
||||
local textProps = {}
|
||||
local size = self.props.Size
|
||||
local position = self.props.Position
|
||||
local anchorPoint = self.props.AnchorPoint
|
||||
local focused = self.props.Focused
|
||||
local disabled = self.props.Disabled
|
||||
local selected = self.props.Selected
|
||||
--Default is unknown since its not on or off
|
||||
local iconColor = GlobalSettings.Colors.StatusIconUnknown
|
||||
if self.props.Toggle == true then
|
||||
iconColor = GlobalSettings.Colors.StatusIconEnabled
|
||||
elseif self.props.Toggle == false then
|
||||
iconColor = GlobalSettings.Colors.StatusIconDisabled
|
||||
end
|
||||
|
||||
buttonProps.Size = UDim2.new(1, 0, 1, 0)
|
||||
buttonProps.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
buttonProps.Position = UDim2.new(0.5, 0, 0.5, 0)
|
||||
buttonProps.Selectable = self.props.Selectable
|
||||
|
||||
textProps.Text = self.props.Text
|
||||
textProps.Size = UDim2.new(1, 0, 1, 0)
|
||||
textProps.AnchorPoint = Vector2.new(0, 0.5)
|
||||
textProps.Position = UDim2.new(0, TOGGLE_ICON_SIZE+2*TOGGLE_ICON_OFFSET_X, 0.5, 0)
|
||||
|
||||
local toggleImage = Roact.createElement("ImageLabel",
|
||||
{
|
||||
Size = UDim2.new(0, TOGGLE_ICON_SIZE, 0, TOGGLE_ICON_SIZE),
|
||||
Image = GlobalSettings.Images.EnabledStatusIcon,
|
||||
ImageColor3 = iconColor,
|
||||
Position = UDim2.new(0, TOGGLE_ICON_OFFSET_X, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
|
||||
local button = Roact.createElement(RoundedButton,
|
||||
{
|
||||
Button = buttonProps,
|
||||
Text = textProps,
|
||||
Focused = focused,
|
||||
Disabled = disabled,
|
||||
Selected = selected,
|
||||
OnSelectionGained = self.onSelectionGained,
|
||||
OnSelectionLost = self.onSelectionLost,
|
||||
OnActivated = self.onActivated,
|
||||
},{
|
||||
ToggleImage = toggleImage,
|
||||
})
|
||||
|
||||
local children = self.props[Roact.Children] or {}
|
||||
return Roact.createElement("Frame",
|
||||
{
|
||||
Size = size,
|
||||
Position = position,
|
||||
AnchorPoint = anchorPoint,
|
||||
BackgroundTransparency = 1,
|
||||
}, Immutable.JoinDictionaries(
|
||||
{
|
||||
Button = button,
|
||||
}, children))
|
||||
end
|
||||
|
||||
return ToggleButton
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
--[[
|
||||
Creates a Image Loader
|
||||
Props:
|
||||
rbxuid : int - Roblox user id.
|
||||
thumbnailType : Enum.ThumbnailType - Describes the type of user thumbnail that should be returned by GetUserThumbnailAsync.
|
||||
thumbnailSize : Enum.ThumbnailSize - Describes the resolution of a user thumbnail being returned by GetUserThumbnailAsync.
|
||||
Size : UDim2 - The thumbnail image size.
|
||||
Position : UDim2 - The thumbnail image position.
|
||||
BackgroundTransparency : float - Transparency of the thumbnail image background.
|
||||
BackgroundColor3 : Color3 - Color of the thumbnail image background.
|
||||
hasThumbnailData : bool - Whether we have the corresponding thumbnail data in store.
|
||||
imageUrl : string - The imageUrl for the thumbnail.
|
||||
isFetching : bool - Whether we are fetching the thumbnail.
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
local Components = Modules.Shell.Components
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
local RoactRodux = require(Modules.Common.RoactRodux)
|
||||
local ApiFetchUserThumbnail = require(Modules.Shell.Thunks.ApiFetchUserThumbnail)
|
||||
local Spinner = require(Components.Common.Spinner)
|
||||
local memoize = require(Modules.Common.memoize)
|
||||
local RETRIES = 6
|
||||
|
||||
local UserThumbnailLoader = Roact.PureComponent:extend("UserThumbnailLoader")
|
||||
|
||||
function UserThumbnailLoader:render()
|
||||
local props = self.props
|
||||
local rbxuid = props.rbxuid
|
||||
local children = {}
|
||||
|
||||
local imageUrl = ""
|
||||
if rbxuid and rbxuid > 0 then
|
||||
local hasThumbnailData = props.hasThumbnailData
|
||||
--TODO: Try refetch if last fetched failed after some interval
|
||||
if hasThumbnailData and props.imageUrl then
|
||||
imageUrl = props.imageUrl
|
||||
else
|
||||
props.fetchImage(rbxuid, props.thumbnailType, props.thumbnailSize)
|
||||
end
|
||||
|
||||
if props.showSpinner then
|
||||
children.Spinner = props.isFetching and Roact.createElement(Spinner)
|
||||
end
|
||||
else
|
||||
children.XboxDefaultProfileImage = Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(0.5, 0, 0.5, 0),
|
||||
Position = UDim2.new(0.25, 0, 0.25, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Image = GlobalSettings.Images.DefaultProfile,
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Image = imageUrl,
|
||||
Size = props.size or UDim2.new(1, 0, 1, 0),
|
||||
Position = props.position or UDim2.new(0, 0, 0, 0),
|
||||
BackgroundTransparency = props.backgroundTransparency or 0,
|
||||
BorderSizePixel = 0,
|
||||
BackgroundColor3 = props.backgroundColor3 or GlobalSettings.Colors.CharacterBackground,
|
||||
}, children)
|
||||
end
|
||||
|
||||
local getThumbnailData = memoize(function(thumbnailData)
|
||||
return {
|
||||
hasThumbnailData = thumbnailData ~= nil,
|
||||
isFetching = thumbnailData and thumbnailData.isFetching,
|
||||
imageUrl = thumbnailData and thumbnailData.imageUrl,
|
||||
}
|
||||
end)
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
local rbxuid = props.rbxuid
|
||||
local thumbnailType = props.thumbnailType
|
||||
local thumbnailSize = props.thumbnailSize
|
||||
local thumbnailData;
|
||||
if rbxuid and rbxuid > 0 and thumbnailType and thumbnailSize then
|
||||
local thumbnailId = table.concat{ rbxuid, thumbnailType.Name, thumbnailSize.Name }
|
||||
thumbnailData = state.UserThumbnails[thumbnailId]
|
||||
end
|
||||
return getThumbnailData(thumbnailData)
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
fetchImage = function(rbxuid, thumbnailType, thumbnailSize)
|
||||
return dispatch(ApiFetchUserThumbnail(rbxuid, thumbnailType, thumbnailSize, RETRIES))
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(UserThumbnailLoader)
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
--[[
|
||||
Allows the selector to navigate between different pages/levels.
|
||||
|
||||
How to use:
|
||||
The Navigator should be created at the root of the pages/levels.
|
||||
|
||||
- Call EnterRootSection(rootKey) to set the root page of the Navigator.
|
||||
|
||||
- Call Set(pageKey, defaultGuiObj) to set the default selection object for the given pageKey.
|
||||
|
||||
- Call Destruct() when done using the Navigator to clean up the event connections.
|
||||
|
||||
To Enter a page:
|
||||
1. Call SetNextPage(nextPageKey) to set the destination for the next page/level.
|
||||
2. Call EnterSection() to enter the next selection page/level.
|
||||
Set(pageKey, defaultGuiObj) needs to be called to set the default guiobject selection.
|
||||
Otherwise, the selection will fail and this function will return false.
|
||||
This function can be called anywhere to enter the next level.
|
||||
Eg. - Call by a GuiObject on selection.
|
||||
- Call by an event.
|
||||
- Call by an action with Rodux.
|
||||
|
||||
To Exit a page and return to the previous:
|
||||
1. Call ExitSection() to return to the previous page/level.
|
||||
This function can be called from anywhere.
|
||||
It will return false if it is unable to select the next that was set
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local ContextActionService = game:GetService('ContextActionService')
|
||||
local Nav = {}
|
||||
Nav.__index = Nav
|
||||
|
||||
local function AddEnterButtonHelper(self,keycode)
|
||||
local enterActionName = "EnterSection"..tostring(keycode)
|
||||
self._actions.EnterSection[keycode] = enterActionName
|
||||
ContextActionService:BindCoreAction(enterActionName,
|
||||
function(actionName, inputState, inputObject)
|
||||
return self:_enterAction(actionName, inputState, inputObject)
|
||||
end
|
||||
,false,keycode)
|
||||
end
|
||||
|
||||
local function AddExitButtonHelper(self,keycode)
|
||||
local exitActionName = "ExitSection"..tostring(keycode)
|
||||
self._actions.ExitSection[keycode] = exitActionName
|
||||
ContextActionService:BindCoreAction(exitActionName,
|
||||
function(actionName, inputState, inputObject)
|
||||
return self:_exitAction(actionName, inputState, inputObject)
|
||||
end
|
||||
,false,keycode)
|
||||
end
|
||||
|
||||
-- By default, the ButtonB binds to back
|
||||
-- Needs to be called before this object can be used.
|
||||
function Nav.new()
|
||||
local self = {}
|
||||
self._levels = {}
|
||||
self._guiobjs = {}
|
||||
self._nextPageKey = ""
|
||||
|
||||
self._actions = {}
|
||||
self._seenPressed = {}
|
||||
|
||||
self._actions.EnterSection = {}
|
||||
self._actions.ExitSection = {}
|
||||
|
||||
AddExitButtonHelper(self, Enum.KeyCode.ButtonB)
|
||||
setmetatable(self, Nav)
|
||||
return self
|
||||
end
|
||||
|
||||
--[[
|
||||
Sets a custom key press to enter the next page.
|
||||
args:
|
||||
keycode : Enum.KeyCode - The key code of the button.
|
||||
]]
|
||||
function Nav:AddEnterButton(keycode)
|
||||
AddEnterButtonHelper(self,keycode)
|
||||
end
|
||||
|
||||
--[[
|
||||
Sets a custom key press to exit to the previous page.
|
||||
args:
|
||||
keycode : Enum.KeyCode - The key code of the button.
|
||||
]]
|
||||
function Nav:AddExitButton(keycode)
|
||||
AddExitButtonHelper(self,keycode)
|
||||
end
|
||||
|
||||
--[[
|
||||
Sets and enters the root page. Must be called before the navigator can be used.
|
||||
args:
|
||||
rootKey : Variant - The key for the page.
|
||||
]]
|
||||
function Nav:EnterRootSection(rootKey)
|
||||
if self._guiobjs[rootKey] == nil then
|
||||
return false
|
||||
end
|
||||
self._levels = {}
|
||||
--Stack push to the first element
|
||||
table.insert(self._levels,rootKey)
|
||||
Utility.SetSelectedCoreObject(self._guiobjs[rootKey])
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
Enters the next page
|
||||
return:
|
||||
True : if it can enter the next page.
|
||||
False: otherwise.
|
||||
]]
|
||||
function Nav:EnterSection()
|
||||
local nextPageKey = self._nextPageKey
|
||||
if nextPageKey == nil or self._guiobjs[nextPageKey] == nil then
|
||||
return false
|
||||
end
|
||||
--Stack push
|
||||
table.insert(self._levels,nextPageKey)
|
||||
Utility.SetSelectedCoreObject(self._guiobjs[nextPageKey])
|
||||
self._nextPageKey = nil
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
Exits to the previous page
|
||||
return:
|
||||
True : if it can enter the previous page
|
||||
False: otherwise.
|
||||
]]
|
||||
function Nav:ExitSection()
|
||||
--stack pop
|
||||
table.remove(self._levels)
|
||||
-- stack peak
|
||||
local key = self._levels[#self._levels]
|
||||
if self._guiobjs[key] == nil then
|
||||
return false
|
||||
end
|
||||
Utility.SetSelectedCoreObject(self._guiobjs[key])
|
||||
return true
|
||||
end
|
||||
|
||||
function Nav:_enterAction(actionName, inputState, inputObject)
|
||||
-- There is no next page to go to
|
||||
if self._nextPageKey == nil then
|
||||
return Enum.ContextActionResult.Pass
|
||||
end
|
||||
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
self._seenPressed[inputObject.KeyCode] = true
|
||||
return Enum.ContextActionResult.Sink
|
||||
elseif inputState == Enum.UserInputState.End and self._seenPressed[inputObject.KeyCode] then
|
||||
self._seenPressed[inputObject.KeyCode] = false
|
||||
self:EnterSection()
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
end
|
||||
|
||||
function Nav:_exitAction(actionName, inputState, inputObject)
|
||||
--If we are current at the root page
|
||||
if(#self._levels <= 1) then
|
||||
-- There is nothing to go back to.
|
||||
return Enum.ContextActionResult.Pass
|
||||
end
|
||||
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
self._seenPressed[inputObject.KeyCode] = true
|
||||
elseif inputState == Enum.UserInputState.End and self._seenPressed[inputObject.KeyCode] then
|
||||
self._seenPressed[inputObject.KeyCode] = false
|
||||
self:ExitSection()
|
||||
end
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
|
||||
--[[
|
||||
Sets the default object to select.
|
||||
args:
|
||||
pageKey : Variant - The key for the page.
|
||||
defaultGuiObj : GuiObject - the default selected object for the page.
|
||||
]]
|
||||
function Nav:Set(pageKey, defaultGuiObj)
|
||||
self._guiobjs[pageKey] = defaultGuiObj
|
||||
end
|
||||
|
||||
--[[
|
||||
Sets the key of the next page.
|
||||
args:
|
||||
nextPageKey : Variant - The key for the next page.
|
||||
]]
|
||||
function Nav:SetNextPage(nextPageKey)
|
||||
self._nextPageKey = nextPageKey
|
||||
end
|
||||
|
||||
-- Needs to be called to clean up event connection.
|
||||
function Nav:Destruct()
|
||||
for _,v in pairs(self._actions.EnterSection) do
|
||||
ContextActionService:UnbindCoreAction(v)
|
||||
end
|
||||
for _,v in pairs(self._actions.ExitSection) do
|
||||
ContextActionService:UnbindCoreAction(v)
|
||||
end
|
||||
end
|
||||
|
||||
return Nav
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
--[[
|
||||
Creates a Roact component provides a vertical list view for multiple elements
|
||||
Props:
|
||||
PaddingTop : UDim - The top padding for the list
|
||||
PaddingBottom : UDim - The bottom padding for the list
|
||||
Spacing : UDim - The spacing between each item of the list
|
||||
HorizontalAlignment : HorizontalAlignment - Determines how grid is placed within
|
||||
it's parent's container in the x direction.
|
||||
Can be Left, Center, or Right.
|
||||
ScrollingEnabled : bool - Determines whether or not scrolling is allowed on the frame.
|
||||
If false, no scroll bars will be rendered.
|
||||
ScrollBarThickness : number - How thick the scroll bar appears.
|
||||
This applies to both the horizontal and vertical scroll bars.
|
||||
If set to 0, no scroll bars are rendered.
|
||||
CanvasPosition : Vector2 - The location within the canvas, in pixels,
|
||||
that should be drawn at the top left of the scroll frame.
|
||||
CanvasSize : UDim2 - Determines the size of the area that is scrollable.
|
||||
The UDim2 is calculated using the parent gui's size,
|
||||
similar to the regular Size property on gui objects.
|
||||
Items: array<Roact.Component> - The items in the list view
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
|
||||
|
||||
return function(props)
|
||||
local paddingTop = props.PaddingTop
|
||||
local paddingBottom = props.PaddingBottom
|
||||
local spacing = props.Spacing
|
||||
local horizontalAlignment = props.HorizontalAlignment
|
||||
local scrollBarThickness = props.ScrollBarThickness
|
||||
local scrollingEnabled = props.ScrollingEnabled
|
||||
local canvasPosition = props.CanvasPosition
|
||||
local canvasSize = props.CanvasSize
|
||||
local ListItems = {}
|
||||
ListItems["UIPadding"] = Roact.createElement("UIPadding",
|
||||
{
|
||||
PaddingTop = paddingTop,
|
||||
PaddingBottom = paddingBottom,
|
||||
})
|
||||
ListItems["UIListLayout"] = Roact.createElement("UIListLayout",
|
||||
{
|
||||
Padding = spacing,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
HorizontalAlignment = horizontalAlignment,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||
CanvasPosition = canvasPosition,
|
||||
CanvasSize = canvasSize,
|
||||
})
|
||||
for k,v in pairs(props.Items) do
|
||||
ListItems[k] = v
|
||||
end
|
||||
|
||||
return Roact.createElement("ScrollingFrame",
|
||||
{
|
||||
Size = UDim2.new(1,0,1,0),
|
||||
ScrollBarThickness = scrollBarThickness,
|
||||
BackgroundTransparency = 1,
|
||||
Selectable = false,
|
||||
ScrollingEnabled = scrollingEnabled,
|
||||
},ListItems)
|
||||
end
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
--[[
|
||||
Creates a Roact component for inifinite scrolling
|
||||
Props:
|
||||
scrollingFrameProps : dictionary - props for the scrolling frame
|
||||
.Selectable : bool - Whether or not this object should be selectable using joysticks (controller).
|
||||
.ClipsDescendants : bool - Determines whether Roblox will render any portions of its GUI descendants that are outside of its own borders.
|
||||
items : Array - An array of the input item data which is used to construct item component.
|
||||
itemSize : Vector2 - The size for each item in the scrolling frame.
|
||||
itemsPaddingOffset : int - The padding between each item.
|
||||
scrollingDirection : Enum.ScrollingDirection - The scrolling direction of the scrolling frame, can't be Enum.ScrollingDirection.XY.
|
||||
itemOffsetStart: int - The minimum distance of the selected guiobject to the window start border.
|
||||
itemOffsetEnd: int - The minimum distance of the selected guiobject to the window end border.
|
||||
customScrollDist: dictionary - Custom distances to trigger the scroll.
|
||||
generateKey : function() - Used to generate a name for the item.
|
||||
renderItem : function() -
|
||||
Input: item data, item index and an onSelectionGained callback
|
||||
Output: a Roact Component
|
||||
State:
|
||||
viewStart: int - The item start index.
|
||||
viewSize: int - The number of items can be put in the window.
|
||||
paddingStart: int - The padding to apply on the top / left side of the scrolling frame.
|
||||
]]
|
||||
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local WindowedScrollingFrame = Roact.PureComponent:extend("WindowedScrollingFrame")
|
||||
|
||||
|
||||
local function clipCanvasPosition(scrollingFrame, canvasPos)
|
||||
local canvasPosX = canvasPos.X
|
||||
local canvasPosY = canvasPos.Y
|
||||
canvasPosX = math.min(canvasPosX, scrollingFrame.CanvasSize.X.Offset - scrollingFrame.AbsoluteWindowSize.X)
|
||||
canvasPosX = math.max(0, canvasPosX)
|
||||
canvasPosY = math.min(canvasPosY, scrollingFrame.CanvasSize.Y.Offset - scrollingFrame.AbsoluteWindowSize.Y)
|
||||
canvasPosY = math.max(0, canvasPosY)
|
||||
return Vector2.new(canvasPosX, canvasPosY)
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:init()
|
||||
self.state = {
|
||||
viewStart = 0,
|
||||
viewSize = 0,
|
||||
paddingStart = 0,
|
||||
}
|
||||
|
||||
self.scrollingFrameRef = function(rbx)
|
||||
self.scrollingFrame = rbx
|
||||
end
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:onSelectionChanged(selectedItem)
|
||||
if not self.scrollingFrame then
|
||||
return
|
||||
end
|
||||
if selectedItem == nil or selectedItem == self.savedSelectedObject or not selectedItem:IsDescendantOf(self.scrollingFrame) then
|
||||
return
|
||||
end
|
||||
self.savedSelectedObject = selectedItem
|
||||
|
||||
local scrollingFrame = self.scrollingFrame
|
||||
local scrollingDirection = self.props.scrollingDirection or Enum.ScrollingDirection.Y
|
||||
local absoluteWindowSize = scrollingFrame.AbsoluteWindowSize
|
||||
local canvasPosition = scrollingFrame.CanvasPosition
|
||||
|
||||
local axisKey = "X"
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
axisKey = "Y"
|
||||
end
|
||||
|
||||
-- If our scrolling frame has zero height / width, let's not bother trying to
|
||||
-- recompute our sizing
|
||||
if absoluteWindowSize[axisKey] == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local itemOffsetStart = self.props.itemOffsetStart or 0
|
||||
local itemOffsetEnd = self.props.itemOffsetEnd or 0
|
||||
local customScrollDist = self.props.customScrollDist or {}
|
||||
--If the selected guiobject is off-window, we move it back into the window instantly
|
||||
--Then make the motion
|
||||
local instantPos;
|
||||
local tweenTargetPos;
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
local topDistance = selectedItem.AbsolutePosition.Y - scrollingFrame.AbsolutePosition.Y
|
||||
local bottomDistance = (scrollingFrame.AbsolutePosition + scrollingFrame.AbsoluteWindowSize - selectedItem.AbsolutePosition - selectedItem.AbsoluteSize).Y
|
||||
|
||||
local minDistTop = itemOffsetStart
|
||||
local minDistBottom = itemOffsetEnd
|
||||
|
||||
if topDistance < (customScrollDist.Top or minDistTop) then
|
||||
if topDistance < 0 then
|
||||
instantPos = Vector2.new(canvasPosition.X, canvasPosition.Y + topDistance)
|
||||
end
|
||||
tweenTargetPos = Vector2.new(canvasPosition.X, canvasPosition.Y - (minDistTop - topDistance))
|
||||
elseif bottomDistance < (customScrollDist.Bottom or minDistBottom) then
|
||||
if bottomDistance < 0 then
|
||||
instantPos = Vector2.new(canvasPosition.X, canvasPosition.Y - bottomDistance)
|
||||
end
|
||||
tweenTargetPos = Vector2.new(canvasPosition.X, canvasPosition.Y + minDistBottom - bottomDistance)
|
||||
end
|
||||
elseif scrollingDirection == Enum.ScrollingDirection.X then
|
||||
local leftDistance = selectedItem.AbsolutePosition.X - scrollingFrame.AbsolutePosition.X
|
||||
local rightDistance = (scrollingFrame.AbsolutePosition + scrollingFrame.AbsoluteWindowSize - selectedItem.AbsolutePosition - selectedItem.AbsoluteSize).X
|
||||
|
||||
local minDistLeft = itemOffsetStart
|
||||
local minDistRight = itemOffsetEnd
|
||||
|
||||
if leftDistance < (customScrollDist.Left or minDistLeft) then
|
||||
if leftDistance < 0 then
|
||||
instantPos = Vector2.new(canvasPosition.X + leftDistance, canvasPosition.Y)
|
||||
end
|
||||
tweenTargetPos = Vector2.new(canvasPosition.X - (minDistLeft - leftDistance), canvasPosition.Y)
|
||||
elseif rightDistance < (customScrollDist.Right or minDistRight) then
|
||||
if rightDistance < 0 then
|
||||
instantPos = Vector2.new(canvasPosition.X - rightDistance, canvasPosition.Y)
|
||||
end
|
||||
tweenTargetPos = Vector2.new(canvasPosition.X + minDistRight - rightDistance, canvasPosition.Y)
|
||||
end
|
||||
end
|
||||
if instantPos then
|
||||
instantPos = clipCanvasPosition(scrollingFrame, instantPos)
|
||||
Utility.PropertyTweener(scrollingFrame, "CanvasPosition", instantPos, instantPos, 0, Utility.EaseOutQuad, true, function()
|
||||
if tweenTargetPos then
|
||||
tweenTargetPos = clipCanvasPosition(scrollingFrame, tweenTargetPos)
|
||||
Utility.PropertyTweener(scrollingFrame, "CanvasPosition", instantPos, tweenTargetPos, 0.2, Utility.EaseOutQuad, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
if not instantPos and tweenTargetPos then
|
||||
tweenTargetPos = clipCanvasPosition(scrollingFrame, tweenTargetPos)
|
||||
Utility.PropertyTweener(scrollingFrame, "CanvasPosition", canvasPosition, tweenTargetPos, 0.2, Utility.EaseOutQuad, true)
|
||||
end
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:updateViewBounds()
|
||||
if not self.scrollingFrame then
|
||||
return
|
||||
end
|
||||
|
||||
local scrollingFrame = self.scrollingFrame
|
||||
local itemSize = self.props.itemSize
|
||||
local itemsPaddingOffset = self.props.itemsPaddingOffset or 0
|
||||
local scrollingDirection = self.props.scrollingDirection or Enum.ScrollingDirection.Y
|
||||
local absoluteWindowSize = scrollingFrame.AbsoluteWindowSize
|
||||
local canvasPosition = scrollingFrame.CanvasPosition
|
||||
|
||||
local axisKey = "X"
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
axisKey = "Y"
|
||||
end
|
||||
|
||||
-- If our scrolling frame has zero height / width, let's not bother trying to
|
||||
-- recompute our sizing
|
||||
if absoluteWindowSize[axisKey] == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
canvasPosition = clipCanvasPosition(scrollingFrame, canvasPosition)
|
||||
local itemTotalSize = (itemSize[axisKey] + itemsPaddingOffset)
|
||||
local viewSize = math.ceil(absoluteWindowSize[axisKey] / itemTotalSize) + 1
|
||||
local viewStart = math.floor(canvasPosition[axisKey] / itemTotalSize)
|
||||
local paddingStart = math.max(0, (viewStart - 1) * itemTotalSize)
|
||||
local shouldUpdate = viewSize ~= self.state.viewSize or viewStart ~= self.state.viewStart or paddingStart ~= self.state.paddingStart
|
||||
if shouldUpdate then
|
||||
self:setState({
|
||||
viewStart = viewStart,
|
||||
viewSize = viewSize,
|
||||
paddingStart = paddingStart,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:render()
|
||||
local items = self.props.items
|
||||
local generateKey = self.props.generateKey
|
||||
local renderItem = self.props.renderItem
|
||||
local itemSize = self.props.itemSize
|
||||
local itemsPaddingOffset = self.props.itemsPaddingOffset or 0
|
||||
local scrollingDirection = self.props.scrollingDirection or Enum.ScrollingDirection.Y
|
||||
assert(scrollingDirection ~= Enum.ScrollingDirection.XY, "Can't set ScrollingDirection as XY.")
|
||||
|
||||
local children = {}
|
||||
|
||||
children.UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, itemsPaddingOffset),
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
FillDirection = scrollingDirection == Enum.ScrollingDirection.Y and Enum.FillDirection.Vertical or Enum.FillDirection.Horizontal
|
||||
})
|
||||
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
children.UIPadding = Roact.createElement("UIPadding", {
|
||||
PaddingTop = UDim.new(0, self.state.paddingStart)
|
||||
})
|
||||
elseif scrollingDirection == Enum.ScrollingDirection.X then
|
||||
children.UIPadding = Roact.createElement("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, self.state.paddingStart)
|
||||
})
|
||||
end
|
||||
|
||||
local lowerBound = math.max(1, self.state.viewStart)
|
||||
local upperBound = math.min(#items, self.state.viewStart + self.state.viewSize)
|
||||
|
||||
for i = lowerBound, upperBound do
|
||||
local key = generateKey and generateKey(i) or i
|
||||
children[key] = renderItem(items[i], i)
|
||||
end
|
||||
|
||||
local scrollingFrameProps = self.props.scrollingFrameProps or {}
|
||||
local canvasSize = nil
|
||||
if scrollingDirection == Enum.ScrollingDirection.Y then
|
||||
canvasSize = UDim2.new(1, 0, 0, #items * itemSize.Y + (#items - 1) * itemsPaddingOffset)
|
||||
elseif scrollingDirection == Enum.ScrollingDirection.X then
|
||||
canvasSize = UDim2.new(0, #items * itemSize.X + (#items - 1) * itemsPaddingOffset, 1, 0)
|
||||
end
|
||||
|
||||
return Roact.createElement("ScrollingFrame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
ScrollingEnabled = false, --Don't let the default select logic affect canvas position
|
||||
CanvasSize = canvasSize,
|
||||
Selectable = scrollingFrameProps.selectable or false,
|
||||
ScrollBarThickness = 0,
|
||||
ClipsDescendants = scrollingFrameProps.clipsDescendants,
|
||||
BackgroundTransparency = 1,
|
||||
ScrollingDirection = scrollingDirection,
|
||||
[Roact.Ref] = self.scrollingFrameRef,
|
||||
[Roact.Change.CanvasPosition] = function() self:updateViewBounds() end,
|
||||
[Roact.Change.AbsoluteSize] = function() self:updateViewBounds() end,
|
||||
}, children)
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:didMount()
|
||||
self:updateViewBounds()
|
||||
end
|
||||
|
||||
function WindowedScrollingFrame:didUpdate(prevProps, prevState)
|
||||
if not prevProps.inFocus and self.props.inFocus then
|
||||
self.conn = GuiService:GetPropertyChangedSignal("SelectedCoreObject"):connect(function()
|
||||
self:onSelectionChanged(GuiService.SelectedCoreObject)
|
||||
end)
|
||||
elseif prevProps.inFocus and not self.props.inFocus then
|
||||
Utility.DisconnectEvent(self.conn)
|
||||
end
|
||||
|
||||
if self.props ~= prevProps then
|
||||
self:updateViewBounds()
|
||||
end
|
||||
end
|
||||
|
||||
return WindowedScrollingFrame
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
--[[
|
||||
A simple component that allows you to bind to ContextActionService at CoreScript level
|
||||
|
||||
Props
|
||||
name - the name of the binded action
|
||||
callback - the function that is invoked
|
||||
binds - the input that triggers the action
|
||||
this is a table - example { Enum.KeyCode.ButtonA, Enum.KeyCode.ButtonX }
|
||||
actionPriority - the action priority
|
||||
Usage:
|
||||
ContextActionCn = Roact.createElement(ContextActionEvent, {
|
||||
name = "MyContextActionBind",
|
||||
callback = function() print("context event") end,
|
||||
binds = { Enum.KeyCode.Thumbstick2, Enum.KeyCode.ButtonB, Enum.KeyCode.A },
|
||||
actionPriority = 1,
|
||||
}),
|
||||
|
||||
Note: Cannot currently write a unit test for this component because it uses functions that
|
||||
are RobloxScript security. LuaCore team is looking into a solution for this
|
||||
]]
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
|
||||
local ContextActionEvent = Roact.Component:extend("ContextActionEvent")
|
||||
|
||||
local DEFAULT_ACTION_PRIORITY = 2000
|
||||
|
||||
function ContextActionEvent:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
function ContextActionEvent:didMount()
|
||||
if self.props.actionPriority then
|
||||
ContextActionService:BindCoreActionAtPriority(self.props.name, self.props.callback, false, DEFAULT_ACTION_PRIORITY + self.props.actionPriority, unpack(self.props.binds))
|
||||
else
|
||||
ContextActionService:BindCoreAction(self.props.name, self.props.callback, false, unpack(self.props.binds))
|
||||
end
|
||||
end
|
||||
|
||||
function ContextActionEvent:didUpdate(oldProps)
|
||||
if self.props.callback ~= oldProps.callback or self.props.name ~= oldProps.name then
|
||||
ContextActionService:UnbindCoreAction(oldProps.name)
|
||||
if self.props.actionPriority then
|
||||
ContextActionService:BindCoreActionAtPriority(self.props.name, self.props.callback, false, DEFAULT_ACTION_PRIORITY + self.props.actionPriority, unpack(self.props.binds))
|
||||
else
|
||||
ContextActionService:BindCoreAction(self.props.name, self.props.callback, false, unpack(self.props.binds))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ContextActionEvent:willUnmount()
|
||||
ContextActionService:UnbindCoreAction(self.props.name)
|
||||
end
|
||||
|
||||
return ContextActionEvent
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
--[[
|
||||
Creates a component with a button image and hint text
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
|
||||
return function(props)
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(0, 65, 0, 65),
|
||||
Position = props.Position,
|
||||
BackgroundTransparency = 1,
|
||||
Image = props.Image,
|
||||
}, {
|
||||
Text = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
Position = UDim2.new(1, 5, 0, -3),
|
||||
BackgroundTransparency = 1,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
FontSize = GlobalSettings.ButtonSize,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Text = props.Text,
|
||||
})
|
||||
})
|
||||
end
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
--[[
|
||||
Creates a component with a gamepad image with a resize hint for the overscan screen
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
local Strings = require(Modules.Shell.LocalizedStrings)
|
||||
|
||||
return function(props)
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(0, 599, 0, 404),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
BackgroundTransparency = 1,
|
||||
Image = "rbxasset://textures/ui/Shell/ScreenAdjustment/Controller@1080.png",
|
||||
}, {
|
||||
Line = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, 240, 0, 1),
|
||||
Position = UDim2.new(0, 437, 0, 220),
|
||||
BackgroundTransparency = 0,
|
||||
BackgroundColor3 = Color3.new(1, 1, 1),
|
||||
BorderSizePixel = 0,
|
||||
}, {
|
||||
InputHint = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, 0, 0, 0),
|
||||
Position = UDim2.new(1, 3, 0, -1),
|
||||
BackgroundTransparency = 1,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
FontSize = GlobalSettings.ButtonSize,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Text = Strings:LocalizedString("ResizeScreenInputHint"),
|
||||
})
|
||||
}),
|
||||
})
|
||||
end
|
||||
@@ -0,0 +1,230 @@
|
||||
local GameOptionsSettings = settings():FindFirstChild("Game Options")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local PlatformService = nil
|
||||
pcall(function() PlatformService = game:GetService("PlatformService") end)
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
|
||||
local ContextActionEvent = require(Modules.Shell.Components.ContextActionEvent)
|
||||
local ExternalEventConnection = require(Modules.Common.RoactUtilities.ExternalEventConnection)
|
||||
local RenderStep = require(Modules.Shell.Components.RenderStep)
|
||||
|
||||
local MIN_EDGE_PERCENT = Vector2.new(0.85, 0.85)
|
||||
local START_EDGE_PERCENT = Vector2.new(0.9, 0.9)
|
||||
|
||||
local CONSOLE_RESOLUTION = Vector2.new(1920, 1080)
|
||||
local ZERO_VEC2 = Vector2.new(0,0)
|
||||
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, DPAD_STEP_AMOUNT);
|
||||
[Enum.KeyCode.DPadUp] = Vector2.new(0, -DPAD_STEP_AMOUNT);
|
||||
[Enum.KeyCode.DPadLeft] = Vector2.new(-DPAD_STEP_AMOUNT, 0);
|
||||
[Enum.KeyCode.DPadRight] = Vector2.new(DPAD_STEP_AMOUNT, 0);
|
||||
}
|
||||
|
||||
local function EdgeImage(props)
|
||||
return Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(0, 95, 0, 95),
|
||||
Position = props.Position,
|
||||
AnchorPoint = props.AnchorPoint,
|
||||
BackgroundTransparency = 1,
|
||||
Rotation = props.Rotation,
|
||||
Image = "rbxasset://textures/ui/Shell/ScreenAdjustment/ScreenAdjustmentArrow.png",
|
||||
})
|
||||
end
|
||||
|
||||
local Edges = Roact.Component:extend("Edges")
|
||||
|
||||
function Edges:init()
|
||||
local function getCurrentEdgePercent(newEdgePercent)
|
||||
return Utility.ClampVector2(MIN_EDGE_PERCENT, Vector2.new(1, 1), newEdgePercent)
|
||||
end
|
||||
|
||||
local function getCurrentEdgeSize(edgePercent)
|
||||
local absoluteEdgeSize = edgePercent * CONSOLE_RESOLUTION
|
||||
local roundedAbsoluteEdgeSize = Vector2.new(Utility.Round(absoluteEdgeSize.X/2), Utility.Round(absoluteEdgeSize.Y/2)) * 2
|
||||
return Utility.ClampVector2(ZERO_VEC2, CONSOLE_RESOLUTION, roundedAbsoluteEdgeSize)
|
||||
end
|
||||
|
||||
self.onAdjustThumbstick = function(actionName, inputState, inputObject)
|
||||
self._stickPosition = Utility.GamepadLinearToCurve(Vector2.new(inputObject.Position.X, -inputObject.Position.Y), 0.2)
|
||||
end
|
||||
|
||||
self.onAdjustDPad = function(actionName, inputState, inputObject)
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
local pushAmount = DPAD_CODE_TO_EDGE_PUSH[inputObject.KeyCode]
|
||||
if pushAmount then
|
||||
pushAmount = pushAmount / CONSOLE_RESOLUTION
|
||||
if Utility.IsFinite(pushAmount.X) and Utility.IsFinite(pushAmount.Y) then
|
||||
self._edgePercent = getCurrentEdgePercent(self._edgePercent + pushAmount)
|
||||
self:setState({
|
||||
currentSize = getCurrentEdgeSize(self._edgePercent)
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.onReset = function(actionName, inputState, inputObject)
|
||||
if inputState == Enum.UserInputState.End then
|
||||
self._stickPosition = ZERO_VEC2
|
||||
self._edgePercent = getCurrentEdgePercent(START_EDGE_PERCENT)
|
||||
self._acceleration = 1
|
||||
self:setState({
|
||||
currentSize = getCurrentEdgeSize(self._edgePercent),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
self.onAccept = function(actionName, inputState, inputObject)
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
self._seenAPressed = true
|
||||
elseif inputState == Enum.UserInputState.End and self._seenAPressed then
|
||||
local success, err = pcall(function()
|
||||
GameOptionsSettings.OverscanPX = math.min(1, self._edgePercent.X)
|
||||
GameOptionsSettings.OverscanPY = math.min(1, self._edgePercent.Y)
|
||||
end)
|
||||
if self.props.onSetEdges then
|
||||
self.props.onSetEdges()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.onRenderStep = function()
|
||||
local now = tick()
|
||||
if self._lastUpdate then
|
||||
if self._stickPosition ~= ZERO_VEC2 then
|
||||
local delta = now - self._lastUpdate
|
||||
local transformedStick = (self._stickPosition) * self._acceleration * delta * 0.05
|
||||
self._edgePercent = getCurrentEdgePercent(transformedStick + self._edgePercent)
|
||||
self._acceleration = math.min(self._acceleration + delta * ACCELERATION_RATE, MAX_STICK_ACCELERATION)
|
||||
|
||||
self:setState({
|
||||
currentSize = getCurrentEdgeSize(self._edgePercent)
|
||||
})
|
||||
else
|
||||
self._acceleration = 1
|
||||
end
|
||||
end
|
||||
self._lastUpdate = now
|
||||
end
|
||||
|
||||
self.onSuspended = function()
|
||||
pcall(function()
|
||||
GameOptionsSettings.OverscanPX = self._lastSavedOverscan.X
|
||||
GameOptionsSettings.OverscanPY = self._lastSavedOverscan.Y
|
||||
end)
|
||||
end
|
||||
|
||||
local overscansetting = getCurrentEdgePercent(START_EDGE_PERCENT)
|
||||
local startSize = getCurrentEdgeSize(overscansetting)
|
||||
if UserInputService:GetPlatform() == Enum.Platform.XBoxOne then
|
||||
pcall(function()
|
||||
if GameOptionsSettings.OverscanPX > 0 and GameOptionsSettings.OverscanPY > 0 then
|
||||
overscansetting = Vector2.new(GameOptionsSettings.OverscanPX, GameOptionsSettings.OverscanPY)
|
||||
overscansetting = getCurrentEdgePercent(overscansetting)
|
||||
startSize = getCurrentEdgeSize(overscansetting)
|
||||
|
||||
-- set the overscan settings to max so the user can accurately estimate their TVs overscan
|
||||
-- save previous settings so we can save on suspend
|
||||
self._lastSavedOverscan = Vector2.new(GameOptionsSettings.OverscanPX, GameOptionsSettings.OverscanPY)
|
||||
GameOptionsSettings.OverscanPX = 1
|
||||
GameOptionsSettings.OverscanPY = 1
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
self._stickPosition = ZERO_VEC2
|
||||
self._edgePercent = overscansetting
|
||||
self._lastUpdate = nil
|
||||
self._acceleration = 1
|
||||
self._seenAPressed = false
|
||||
|
||||
self.state = {
|
||||
currentSize = startSize,
|
||||
}
|
||||
end
|
||||
|
||||
function Edges:render()
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(0, self.state.currentSize.X, 0, self.state.currentSize.Y),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
SelectionImage = Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(1, 2, 1, 2),
|
||||
Position = UDim2.new(0, -1, 0, -1),
|
||||
BackgroundTransparency = 1,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = Rect.new(21, 21, 41, 41),
|
||||
Image = "rbxasset://textures/ui/Shell/ScreenAdjustment/ScreenRangeOverlay.png",
|
||||
}),
|
||||
|
||||
TopLeft = Roact.createElement(EdgeImage, {
|
||||
Rotation = 0,
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
AnchorPoint = Vector2.new(0, 0),
|
||||
}),
|
||||
|
||||
TopRight = Roact.createElement(EdgeImage, {
|
||||
Rotation = 90,
|
||||
Position = UDim2.new(1, 0, 0, 0),
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
}),
|
||||
|
||||
BottomRight = Roact.createElement(EdgeImage, {
|
||||
Rotation = 180,
|
||||
Position = UDim2.new(1, 0, 1, 0),
|
||||
AnchorPoint = Vector2.new(1, 1),
|
||||
}),
|
||||
|
||||
BottomLeft = Roact.createElement(EdgeImage, {
|
||||
Rotation = 270,
|
||||
Position = UDim2.new(0, 0, 1, 0),
|
||||
AnchorPoint = Vector2.new(0, 1),
|
||||
}),
|
||||
|
||||
Render = Roact.createElement(RenderStep, {
|
||||
name = "UpdateAdjustmentScreen",
|
||||
priority = Enum.RenderPriority.Input.Value,
|
||||
callback = self.onRenderStep,
|
||||
}),
|
||||
|
||||
AdjustConnectorThumbstick = Roact.createElement(ContextActionEvent, {
|
||||
name = "ThumbstickAdjustmentScreen",
|
||||
callback = self.onAdjustThumbstick,
|
||||
binds = { Enum.KeyCode.Thumbstick2 },
|
||||
}),
|
||||
|
||||
AdjustConnectorDPad = Roact.createElement(ContextActionEvent, {
|
||||
name = "DPadAdjustmentScreen",
|
||||
callback = self.onAdjustDPad,
|
||||
binds = { Enum.KeyCode.DPadDown, Enum.KeyCode.DPadUp, Enum.KeyCode.DPadLeft, Enum.KeyCode.DPadRight },
|
||||
}),
|
||||
|
||||
ResetConnector = Roact.createElement(ContextActionEvent, {
|
||||
name = "ResetAdjustmentScreen",
|
||||
callback = self.onReset,
|
||||
binds = { Enum.KeyCode.ButtonX },
|
||||
}),
|
||||
|
||||
AcceptConnector = Roact.createElement(ContextActionEvent, {
|
||||
name = "AcceptAdjustmentScreen",
|
||||
callback = self.onAccept,
|
||||
binds = { Enum.KeyCode.ButtonA },
|
||||
}),
|
||||
|
||||
SuspendedCn = PlatformService and Roact.createElement(ExternalEventConnection, {
|
||||
event = PlatformService.Suspended,
|
||||
callback = self.onSuspended,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return Edges
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
local TextService = game:GetService('TextService')
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
local Strings = require(Modules.Shell.LocalizedStrings)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
|
||||
local ControllerHint = require(script.Parent.ControllerHint)
|
||||
local Edges = require(script.Parent.Edges)
|
||||
local ButtonHint = require(script.Parent.ButtonHint)
|
||||
|
||||
local Overscan = Roact.Component:extend("Overscan")
|
||||
|
||||
function Overscan:render()
|
||||
-- We should really have a better API to handle text fits
|
||||
local resetOffset = TextService:GetTextSize(
|
||||
Strings:LocalizedString('ResetWord'),
|
||||
Utility.ConvertFontSizeEnumToInt(GlobalSettings.ButtonSize),
|
||||
GlobalSettings.RegularFont,
|
||||
Vector2.new(0, 0)
|
||||
)
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BorderSizePixel = 1,
|
||||
BackgroundTransparency = self.props.BackgroundTransparency,
|
||||
BackgroundColor3 = Color3.new(3/255, 3/255, 3/255),
|
||||
}, {
|
||||
Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
BackgroundColor3 = Color3.new(3/255, 3/255, 3/255),
|
||||
BorderSizePixel = 1,
|
||||
Visible = self.props.ImageVisible,
|
||||
Image = "rbxasset://textures/ui/Shell/ScreenAdjustment/Background.png",
|
||||
}),
|
||||
|
||||
Title = Roact.createElement("TextLabel", {
|
||||
Position = UDim2.new(0, 230, 0, 205),
|
||||
BackgroundTransparency = 1,
|
||||
Font = GlobalSettings.LightFont,
|
||||
FontSize = GlobalSettings.HeaderSize,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Text = Strings:LocalizedString("ScreenSizeWord");
|
||||
}),
|
||||
|
||||
Prompt = Roact.createElement("TextLabel", {
|
||||
Position = UDim2.new(0, 230, 0, 243),
|
||||
BackgroundTransparency = 1,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
FontSize = GlobalSettings.ButtonSize,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Text = Strings:LocalizedString("ResizeScreenPrompt"),
|
||||
}),
|
||||
|
||||
Controller = Roact.createElement(ControllerHint),
|
||||
|
||||
AcceptHint = Roact.createElement(ButtonHint, {
|
||||
Position = UDim2.new(0.5, 25, 0.75, 0),
|
||||
Image = "rbxasset://textures/ui/Shell/ButtonIcons/AButton.png",
|
||||
Text = Strings:LocalizedString('AcceptWord'),
|
||||
}),
|
||||
|
||||
ResetHint = Roact.createElement(ButtonHint, {
|
||||
Position = UDim2.new(0.5, -25 - 65 - resetOffset.x, 0.75, 0),
|
||||
Image = "rbxasset://textures/ui/Shell/ButtonIcons/XButton.png",
|
||||
Text = Strings:LocalizedString('ResetWord'),
|
||||
}),
|
||||
|
||||
EdgeSelector = Roact.createElement(Edges, {
|
||||
onSetEdges = self.props.onUnmount,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return Overscan
|
||||
@@ -0,0 +1,43 @@
|
||||
--[[
|
||||
A simple component that allows you to bind to RenderStep
|
||||
|
||||
Props:
|
||||
name - the name of the bind
|
||||
priority - when during the render step to call the function
|
||||
callback - function that will be invoked on render step
|
||||
|
||||
Usage:
|
||||
|
||||
RenderCn = Roact.createElement(RenderStep, {
|
||||
name = "MyRenderStep",
|
||||
priority = Enum.RenderPriority.Input.Value,
|
||||
callback = function() print("stepping") end
|
||||
})
|
||||
]]
|
||||
local RunService = game:GetService("RunService")
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
|
||||
local RenderStep = Roact.Component:extend("RenderStep")
|
||||
|
||||
function RenderStep:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
function RenderStep:didMount()
|
||||
RunService:BindToRenderStep(self.props.name, self.props.priority, self.props.callback)
|
||||
end
|
||||
|
||||
function RenderStep:didUpdate(oldProps)
|
||||
if self.props.callback ~= oldProps.callback or self.props.name ~= oldProps.name then
|
||||
RunService:UnbindFromRenderStep(oldProps.name)
|
||||
RunService:BindToRenderStep(self.props.name, self.props.priority, self.props.callback)
|
||||
end
|
||||
end
|
||||
|
||||
function RenderStep:willUnmount()
|
||||
RunService:UnbindFromRenderStep(self.props.name)
|
||||
end
|
||||
|
||||
return RenderStep
|
||||
@@ -0,0 +1,14 @@
|
||||
return function()
|
||||
local Roact = require(game:GetService("CoreGui").RobloxGui.Modules.Common.Roact)
|
||||
local RenderStep = require(script.Parent.RenderStep)
|
||||
|
||||
it("should create and destroy", function()
|
||||
local element = Roact.createElement(RenderStep, {
|
||||
name = "myRenderStep",
|
||||
priority = Enum.RenderPriority.Input.Value,
|
||||
callback = function() print("hello render step") end,
|
||||
})
|
||||
local instance = Roact.mount(element)
|
||||
Roact.unmount(instance)
|
||||
end)
|
||||
end
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
--[[
|
||||
This helper module will be a bridge to help migrate the console AppShell to using Roact.
|
||||
The idea is to add the interface of a "Screen" to the Roact component, so the ScreenManager
|
||||
can be happy and correctly manage screens that are not using Roact.
|
||||
|
||||
You will only need to wrap Roact components with this if they are the root of a route. Anything
|
||||
that is a child screen/compoent of this should use Roact Routing.
|
||||
|
||||
Usage:
|
||||
local MyScreen = require(ShellModules.Components.MyScreen)
|
||||
local myRoactScreen = RoactScreenManagerWrapper.new(MyScreen, GuiRoot, {
|
||||
backgroundTransparency = 0,
|
||||
name = "MyScreen",
|
||||
})
|
||||
|
||||
ScreenManager:OpenScreen(myRoactScreen)
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local ScreenManager = require(Modules.Shell.ScreenManager)
|
||||
local AppState = require(Modules.Shell.AppState)
|
||||
local RoactRodux = require(Modules.Common.RoactRodux)
|
||||
|
||||
local RoactScreenManagerWrapper = {}
|
||||
RoactScreenManagerWrapper.__index = RoactScreenManagerWrapper
|
||||
|
||||
function RoactScreenManagerWrapper.new(roactComponent, parent, props, closeCallback)
|
||||
local self = {}
|
||||
props = props or {}
|
||||
|
||||
-- this will be passed to top level Roact components in order to close the screen
|
||||
-- since we need some way to route back to the previous screen through the screen manager
|
||||
local function onUnmount()
|
||||
if self == ScreenManager:GetTopScreen() then
|
||||
ScreenManager:CloseCurrent()
|
||||
if closeCallback then
|
||||
closeCallback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
props.onUnmount = onUnmount
|
||||
local element = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = AppState.store,
|
||||
}, {
|
||||
Roact.createElement(roactComponent, props)
|
||||
})
|
||||
self._instance = nil
|
||||
|
||||
function self:Show()
|
||||
self._instance = Roact.mount(element, parent, tostring(roactComponent))
|
||||
end
|
||||
|
||||
function self:Hide()
|
||||
self:Destruct()
|
||||
end
|
||||
|
||||
function self:Focus()
|
||||
-- do nothing
|
||||
end
|
||||
|
||||
function self:RemoveFocus()
|
||||
-- do nothing
|
||||
end
|
||||
|
||||
setmetatable(self, RoactScreenManagerWrapper)
|
||||
return self
|
||||
end
|
||||
|
||||
function RoactScreenManagerWrapper:Destruct()
|
||||
Roact.unmount(self._instance)
|
||||
end
|
||||
|
||||
return RoactScreenManagerWrapper
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
local HttpService = game:GetService("HttpService")
|
||||
local PlatformService = nil
|
||||
pcall(function() PlatformService = game:GetService("PlatformService") end)
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
|
||||
local Components = Modules.Shell.Components
|
||||
local PresenceCard = require(Components.Social.PresenceCard)
|
||||
local WindowedScrollingFrame = require(Components.Common.WindowedScrollingFrame)
|
||||
local Immutable = require(Modules.Common.Immutable)
|
||||
local Spinner = require(Components.Common.Spinner)
|
||||
local SideBarComponent = require(Components.Common.SideBar)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local EventHub = require(Modules.Shell.EventHub)
|
||||
local GameJoinModule = require(Modules.Shell.GameJoin)
|
||||
local Strings = require(Modules.Shell.LocalizedStrings)
|
||||
local SoundManager = require(Modules.Shell.SoundManager)
|
||||
local RedirectComponent = require(Modules.Shell.Components.Common.RedirectComponent)
|
||||
local UserThumbnailLoader = require(Components.Common.UserThumbnailLoader)
|
||||
|
||||
local SIDE_BAR_ITEMS = {
|
||||
JoinGame = Strings:LocalizedString("JoinGameWord"),
|
||||
ViewDetails = Strings:LocalizedString("ViewGameDetailsWord"),
|
||||
ViewProfile = Strings:LocalizedString("ViewGamerCardWord"),
|
||||
EmptyFriendSideBar = Strings:LocalizedString("EmptyFriendSideBarWord"),
|
||||
}
|
||||
|
||||
local FriendsScrollingView = Roact.PureComponent:extend("FriendsScrollingView")
|
||||
|
||||
function FriendsScrollingView:init()
|
||||
self.state = {
|
||||
sideBarInFocus = false,
|
||||
sideBarShow = false,
|
||||
currentSelectedIndex = 1,
|
||||
}
|
||||
|
||||
self.onSideBarClose = function()
|
||||
self:setState({
|
||||
sideBarInFocus = false,
|
||||
sideBarShow = false,
|
||||
})
|
||||
end
|
||||
self.onSideBarOpen = function(data)
|
||||
Utility.SetSelectedCoreObject(nil)
|
||||
self:setState({
|
||||
sideBarInFocus = true,
|
||||
sideBarShow = true,
|
||||
})
|
||||
end
|
||||
|
||||
self.groupKey = HttpService:GenerateGUID(false)
|
||||
end
|
||||
|
||||
function FriendsScrollingView:render()
|
||||
local props = self.props
|
||||
local friendsData = props.friendsData
|
||||
local initialized = props.initialized
|
||||
local actionPriority = self.props.actionPriority or 0
|
||||
|
||||
local hide = props.hide
|
||||
local inFocus = props.inFocus
|
||||
local sideBarInFocus = false
|
||||
local friendsScrollingFrameInFocus = false
|
||||
|
||||
local children = {}
|
||||
if not hide and inFocus then
|
||||
sideBarInFocus = self.state.sideBarShow and self.state.sideBarInFocus
|
||||
if not sideBarInFocus and #friendsData > 0 then
|
||||
friendsScrollingFrameInFocus = true
|
||||
end
|
||||
end
|
||||
|
||||
if initialized and friendsData then
|
||||
if #friendsData > 0 then
|
||||
local itemSize = Vector2.new(440, 120)
|
||||
local itemsPaddingOffset = 20
|
||||
local itemTotalSizeY = itemSize.Y + itemsPaddingOffset
|
||||
local itemsCount = math.floor(self.props.size.Y.Offset / itemTotalSizeY)
|
||||
assert(itemsCount ~= 0, "The scrolling window is too small to accommodate any presence card.")
|
||||
--We should have at least two items to ensure we can always select the top second item
|
||||
--while keep the top first item fully in view.
|
||||
--If the window is too small, we will always select the top first item.
|
||||
local itemOffsetStart = itemsCount > 2 and itemTotalSizeY or 0
|
||||
--This make sure when we scroll down the top card won't be clipped
|
||||
local itemOffsetEnd = self.props.size.Y.Offset - itemsCount * itemTotalSizeY + itemsPaddingOffset
|
||||
children.FriendsScrollingFrame = Roact.createElement(WindowedScrollingFrame, {
|
||||
items = friendsData,
|
||||
itemSize = itemSize,
|
||||
itemsPaddingOffset = itemsPaddingOffset,
|
||||
itemOffsetStart = itemOffsetStart,
|
||||
itemOffsetEnd = itemOffsetEnd,
|
||||
scrollingDirection = Enum.ScrollingDirection.Y,
|
||||
inFocus = friendsScrollingFrameInFocus,
|
||||
renderItem = function(data, index)
|
||||
local presenceCardProps = Immutable.JoinDictionaries(data, {
|
||||
layoutOrder = index,
|
||||
size = UDim2.new(0, itemSize.X, 0, itemSize.Y),
|
||||
focused = inFocus and self.state.currentSelectedIndex == index,
|
||||
selected = friendsScrollingFrameInFocus and self.state.currentSelectedIndex == index,
|
||||
})
|
||||
--Set up callbacks
|
||||
presenceCardProps.onActivated = function(bt)
|
||||
SoundManager:Play("SideMenuSlideIn")
|
||||
self.onSideBarOpen()
|
||||
end
|
||||
presenceCardProps.onSelectionGained = function()
|
||||
if self.state.currentSelectedIndex ~= index then
|
||||
self:setState({
|
||||
currentSelectedIndex = index,
|
||||
})
|
||||
end
|
||||
end
|
||||
return Roact.createElement(PresenceCard, presenceCardProps)
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
local data = friendsData[self.state.currentSelectedIndex]
|
||||
if inFocus and data and data.robloxuid then
|
||||
children.ProfileImage = Roact.createElement(UserThumbnailLoader, {
|
||||
rbxuid = data.robloxuid,
|
||||
thumbnailType = Enum.ThumbnailType.AvatarThumbnail,
|
||||
thumbnailSize = Enum.ThumbnailSize.Size352x352,
|
||||
position = UDim2.new(1, 101, 0, 0),
|
||||
size = UDim2.new(0, 680, 0, 680),
|
||||
backgroundTransparency = 1,
|
||||
showSpinner = true
|
||||
})
|
||||
end
|
||||
|
||||
if self.state.sideBarShow and data then
|
||||
local sideBarButtons = {}
|
||||
if data.robloxuid and data.robloxuid > 0 and data.robloxStatus == "InGame" then
|
||||
local placeId = data.placeId
|
||||
local lastLocation = data.lastLocation
|
||||
local robloxuid = data.robloxuid
|
||||
table.insert(sideBarButtons, {
|
||||
text = SIDE_BAR_ITEMS.JoinGame,
|
||||
callback = function()
|
||||
GameJoinModule:StartGame(GameJoinModule.JoinType.Follow, robloxuid)
|
||||
end
|
||||
})
|
||||
table.insert(sideBarButtons, {
|
||||
text = SIDE_BAR_ITEMS.ViewDetails,
|
||||
callback = function()
|
||||
EventHub:dispatchEvent(EventHub.Notifications["OpenGameDetail"], placeId, lastLocation, nil)
|
||||
end
|
||||
})
|
||||
end
|
||||
if data.xuid and #data.xuid > 0 and PlatformService then
|
||||
local xuid = data.xuid
|
||||
table.insert(sideBarButtons, {
|
||||
text = SIDE_BAR_ITEMS.ViewProfile,
|
||||
callback = function()
|
||||
-- NOTE: This will try to pop up the xbox system gamer card, failure will be handled by the xbox.
|
||||
pcall(function()
|
||||
PlatformService:PopupProfileUI(Enum.UserInputType.Gamepad1, xuid)
|
||||
end)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local sideBarText = nil
|
||||
if #sideBarButtons == 0 then
|
||||
sideBarText = SIDE_BAR_ITEMS.EmptyFriendSideBar
|
||||
sideBarButtons = nil
|
||||
end
|
||||
children.SideBar = Roact.createElement(SideBarComponent, {
|
||||
actionPriority = actionPriority + 1,
|
||||
text = sideBarText,
|
||||
buttons = sideBarButtons,
|
||||
inFocus = sideBarInFocus,
|
||||
onClose = self.onSideBarClose,
|
||||
onRemoveFocus = function()
|
||||
self:setState({
|
||||
sideBarInFocus = false
|
||||
})
|
||||
end,
|
||||
})
|
||||
end
|
||||
else
|
||||
children.NoFriendsView = props.noFriendsView
|
||||
end
|
||||
else
|
||||
children.Spinner = Roact.createElement(Spinner)
|
||||
end
|
||||
|
||||
children.NavObj = Roact.createElement(RedirectComponent, {
|
||||
ActionPriority = actionPriority,
|
||||
Key = self.groupKey,
|
||||
InFocus = inFocus,
|
||||
RedirectBack = props.redirectBack,
|
||||
RedirectLeft = props.redirectLeft,
|
||||
RedirectRight = props.redirectRight,
|
||||
RedirectUp = props.redirectUp,
|
||||
RedirectDown = props.redirectDown,
|
||||
})
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = self.props.size,
|
||||
Position = self.props.position,
|
||||
[Roact.Ref] = function(rbx)
|
||||
self.ref = rbx
|
||||
end,
|
||||
Visible = not hide,
|
||||
}, children)
|
||||
end
|
||||
|
||||
|
||||
function FriendsScrollingView:didMount()
|
||||
delay(0, function()
|
||||
if self.props.hide == false and self.props.inFocus then
|
||||
if self.ref ~= nil then
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
Utility.AddSelectionParent(self.groupKey, self.ref)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function FriendsScrollingView:didUpdate(previousProps, previousState)
|
||||
if self.props.hide or self.props.inFocus == previousProps.inFocus then
|
||||
return
|
||||
end
|
||||
if self.props.inFocus then
|
||||
if self.ref then
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
Utility.AddSelectionParent(self.groupKey, self.ref)
|
||||
end
|
||||
else
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
end
|
||||
end
|
||||
|
||||
function FriendsScrollingView:willUnmount()
|
||||
Utility.RemoveSelectionGroup(self.groupKey)
|
||||
end
|
||||
|
||||
return FriendsScrollingView
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local memoize = require(Modules.Common.memoize)
|
||||
local RoactRodux = require(Modules.Common.RoactRodux)
|
||||
local CategoryMenuView = require(Modules.Shell.Components.Common.CategoryMenuView)
|
||||
local SplitViewLR = require(Modules.Shell.Components.Common.SplitViewLR)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local SoundManager = require(Modules.Shell.SoundManager)
|
||||
local Strings = require(Modules.Shell.LocalizedStrings)
|
||||
local FriendsScrollingView = require(Modules.Shell.Components.Social.FriendsScrollingView)
|
||||
local NoFriendsView = require(Modules.Shell.Components.Social.NoFriendsView)
|
||||
local PageKeys = require(Modules.Shell.PageKeys)
|
||||
|
||||
local FriendsView = Roact.PureComponent:extend("FriendsView")
|
||||
|
||||
local MenuKey = PageKeys.FriendCategories.Key
|
||||
local FriendCategoriesKeys = {
|
||||
OnlineFriends = PageKeys.FriendCategories.OnlineFriends.Key,
|
||||
AllFriends = PageKeys.FriendCategories.AllFriends.Key,
|
||||
}
|
||||
local FriendCategories = {}
|
||||
FriendCategories[FriendCategoriesKeys.OnlineFriends] = { Order = 1, StringKey = "OnlineWord" }
|
||||
FriendCategories[FriendCategoriesKeys.AllFriends] = { Order = 2, StringKey = "AllWord" }
|
||||
|
||||
local function onSelectSection(self, key)
|
||||
if self.state.currentPage ~= key then
|
||||
self:setState({
|
||||
currentPage = key,
|
||||
selectedPage = MenuKey,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function FriendsView:init()
|
||||
self.selectedPage = MenuKey
|
||||
self.onSelectionGained = function(key)
|
||||
onSelectSection(self,key)
|
||||
end
|
||||
|
||||
self.state = {
|
||||
currentPage = FriendCategoriesKeys.OnlineFriends,
|
||||
selectedPage = MenuKey,
|
||||
}
|
||||
|
||||
self.enterSection = function()
|
||||
SoundManager:Play("OverlayOpen")
|
||||
Utility.SetSelectedCoreObject(nil)
|
||||
self:setState({
|
||||
selectedPage = self.state.currentPage,
|
||||
})
|
||||
end
|
||||
self.exitSection = function()
|
||||
SoundManager:Play("PopUp")
|
||||
self:setState({
|
||||
selectedPage = MenuKey,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function FriendsView:render()
|
||||
local actionPriority = self.props.actionPriority or 0
|
||||
local currentPage = self.state.currentPage
|
||||
local friendsViewInFocus = self.props.inFocus
|
||||
local friendsViewHide = self.props.hide
|
||||
local friendCategoriesMenuInFocus = false
|
||||
local friendPagesInFocus = false
|
||||
if not friendsViewHide and friendsViewInFocus then
|
||||
if self.state.selectedPage == MenuKey then
|
||||
friendCategoriesMenuInFocus = true
|
||||
end
|
||||
if self.state.selectedPage ~= MenuKey then
|
||||
friendPagesInFocus = true
|
||||
end
|
||||
end
|
||||
|
||||
local enterSection = self.enterSection
|
||||
if currentPage == PageKeys.FriendCategories.OnlineFriends.Key and #self.props.onlineFriendsData == 0 then
|
||||
enterSection = nil
|
||||
elseif currentPage == PageKeys.FriendCategories.AllFriends.Key and #self.props.allFriendsData == 0 then
|
||||
enterSection = nil
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, -58, 1, 0),
|
||||
Position = UDim2.new(0, 58, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
},{
|
||||
Mainview = Roact.createElement(SplitViewLR, {
|
||||
Bias = 0.265,
|
||||
LeftView = Roact.createElement(CategoryMenuView, {
|
||||
Key = MenuKey,
|
||||
Categories = FriendCategories,
|
||||
InFocus = friendCategoriesMenuInFocus,
|
||||
DefaultCategoryFocus = FriendCategoriesKeys.OnlineFriends,
|
||||
OnSelectSection = self.onSelectionGained,
|
||||
EnterSection = enterSection,
|
||||
RedirectUp = self.props.redirectUp,
|
||||
ActionPriority = actionPriority,
|
||||
}),
|
||||
RightView = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
},{
|
||||
OnlineFriendsView = Roact.createElement(FriendsScrollingView, {
|
||||
friendsData = self.props.onlineFriendsData,
|
||||
initialized = self.props.initialized,
|
||||
hide = currentPage ~= PageKeys.FriendCategories.OnlineFriends.Key,
|
||||
inFocus = friendPagesInFocus,
|
||||
redirectLeft = self.exitSection,
|
||||
redirectBack = self.exitSection,
|
||||
redirectUp = self.props.redirectUp,
|
||||
redirectRight = self.props.redirectRight,
|
||||
size = UDim2.new(0, 440, 0, 770),
|
||||
noFriendsView = Roact.createElement(NoFriendsView, {
|
||||
text = Strings:LocalizedString("NoFriendsOnlinePhrase"),
|
||||
}),
|
||||
actionPriority = actionPriority + 1,
|
||||
}),
|
||||
AllFriendsView = Roact.createElement(FriendsScrollingView, {
|
||||
friendsData = self.props.allFriendsData,
|
||||
initialized = self.props.initialized,
|
||||
hide = currentPage ~= PageKeys.FriendCategories.AllFriends.Key,
|
||||
inFocus = friendPagesInFocus,
|
||||
redirectLeft = self.exitSection,
|
||||
redirectBack = self.exitSection,
|
||||
redirectUp = self.props.redirectUp,
|
||||
redirectRight = self.props.redirectRight,
|
||||
size = UDim2.new(0, 440, 0, 770),
|
||||
noFriendsView = Roact.createElement(NoFriendsView, {
|
||||
text = Strings:LocalizedString("PlayAndMakeFriendsPhrase"),
|
||||
}),
|
||||
actionPriority = actionPriority + 1,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local filterOnlineFriends = memoize(function(friendsData)
|
||||
local onlineFriendsData = {}
|
||||
for _, data in ipairs(friendsData) do
|
||||
if data.robloxStatus ~= "Offline" or data.xboxStatus == "Online" then
|
||||
table.insert(onlineFriendsData, data)
|
||||
end
|
||||
end
|
||||
return onlineFriendsData
|
||||
end)
|
||||
|
||||
local function mapStateToProps(state, props)
|
||||
local friendsData = state.RenderedFriends.data
|
||||
return {
|
||||
allFriendsData = friendsData,
|
||||
onlineFriendsData = filterOnlineFriends(friendsData),
|
||||
initialized = state.RenderedFriends.initialized
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps)(FriendsView)
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
--[[
|
||||
Creates a component for no friends
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
|
||||
return function(props)
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1
|
||||
}, {
|
||||
NoFriendsIcon = Roact.createElement("ImageLabel", {
|
||||
Size = UDim2.new(0, 296, 0, 259),
|
||||
Position = UDim2.new(0.5, -148, 0, 100),
|
||||
BackgroundTransparency = 1,
|
||||
Image = "rbxasset://textures/ui/Shell/Icons/FriendsIcon@1080.png",
|
||||
}),
|
||||
NoFriendsText = Roact.createElement("TextLabel", {
|
||||
Size = UDim2.new(0, 440, 0, 72),
|
||||
Position = UDim2.new(0.5, -220, 0, 392),
|
||||
BackgroundTransparency = 1,
|
||||
Font = GlobalSettings.RegularFont,
|
||||
FontSize = GlobalSettings.ButtonSize,
|
||||
TextColor3 = GlobalSettings.WhiteTextColor,
|
||||
Text = props.text,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
TextWrapped = true
|
||||
}),
|
||||
})
|
||||
end
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
--[[
|
||||
Creates a PresenceCard component
|
||||
Props:
|
||||
gamertag: string - Xbox Gamertag.
|
||||
robloxName: string - Roblox name.
|
||||
robloxuid: int - Roblox user id.
|
||||
xuid: int - Xbox user id.
|
||||
robloxStatus: string - User's roblox xboxStatus.
|
||||
xboxStatus: string - User's xbox xboxStatus.
|
||||
lastLocation: string - User's last location info.
|
||||
layoutOrder: int - Controls the sorting priority of this button.
|
||||
size: UDim2 - The size of the presence card.
|
||||
onSelectionGained : function(guiObject : Ref<GuiObject>) -
|
||||
Fires when the GuiObject is being focused on with the Gamepad selector.
|
||||
onSelectionLost : function(guiObject : Ref<GuiObject>) -
|
||||
Fires when the Gamepad selector stops focusing on the GuiObject.
|
||||
onActivated : function(guiObject : Ref<GuiObject>) -
|
||||
Fires when the button is activated.
|
||||
]]
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local GlobalSettings = require(Modules.Shell.GlobalSettings)
|
||||
local Components = Modules.Shell.Components
|
||||
local UserThumbnailLoader = require(Components.Common.UserThumbnailLoader)
|
||||
local Strings = require(Modules.Shell.LocalizedStrings)
|
||||
local Utility = require(Modules.Shell.Utility)
|
||||
local SoundComponent = require(Modules.Shell.Components.Common.SoundComponent)
|
||||
|
||||
local PresenceCard = Roact.PureComponent:extend("PresenceCard")
|
||||
|
||||
function PresenceCard:init()
|
||||
self.selectionImageObject = Utility.Create "ImageLabel"({
|
||||
Name = "SelectorImage",
|
||||
Image = GlobalSettings.Images.ButtonSelector,
|
||||
Position = UDim2.new(0, -7, 0, -7),
|
||||
Size = UDim2.new(1, 14, 1, 14),
|
||||
BackgroundTransparency = 1,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = Rect.new(31, 31, 63, 63),
|
||||
})
|
||||
self.onCreate = function(rbx)
|
||||
self.ref = rbx
|
||||
end
|
||||
self.defaultProps = {
|
||||
buttonColor3 = GlobalSettings.Colors.WhiteButton,
|
||||
buttonTransparency = 0.8,
|
||||
textColor3 = GlobalSettings.Colors.WhiteText,
|
||||
iconColor3 = GlobalSettings.Colors.WhiteText,
|
||||
}
|
||||
self.focusedProps = {
|
||||
buttonColor3 = GlobalSettings.Colors.BlueButton,
|
||||
buttonTransparency = 0,
|
||||
textColor3 = GlobalSettings.Colors.BlackText,
|
||||
iconColor3 = GlobalSettings.Colors.BlackText,
|
||||
}
|
||||
self.buttonImage = GlobalSettings.Images.ButtonDefault
|
||||
end
|
||||
|
||||
function PresenceCard:render()
|
||||
local props = self.props
|
||||
|
||||
local focused = self.props.focused
|
||||
local currProps = focused and self.focusedProps or self.defaultProps
|
||||
local gamertagText = props.gamertag or ""
|
||||
local robloxNameText = props.robloxName or ""
|
||||
local showGamertag = gamertagText ~= ""
|
||||
local showRobloxName = robloxNameText ~= ""
|
||||
local statusText = ""
|
||||
local statusImageColor3 = GlobalSettings.Colors.GreySelectedButton
|
||||
|
||||
local function setPresence(statusStr, statusColor)
|
||||
statusImageColor3 = statusColor
|
||||
if statusStr and statusStr ~= "" then
|
||||
statusText = statusStr
|
||||
end
|
||||
end
|
||||
if props.robloxStatus == "InGame" then
|
||||
setPresence(props.lastLocation, GlobalSettings.Colors.GreenText)
|
||||
elseif props.robloxStatus == "InStudio" then
|
||||
setPresence(props.lastLocation, GlobalSettings.Colors.OrangeText)
|
||||
elseif props.robloxStatus == "Online" then
|
||||
setPresence("Roblox", GlobalSettings.Colors.BlueText)
|
||||
else
|
||||
if props.xboxStatus and props.xboxStatus == "Online" then
|
||||
setPresence(Strings:LocalizedString("OnlineWord"), GlobalSettings.Colors.BlueText)
|
||||
else
|
||||
setPresence(Strings:LocalizedString("OfflineWord"), GlobalSettings.Colors.GreyText)
|
||||
end
|
||||
end
|
||||
|
||||
return Roact.createElement("ImageButton", {
|
||||
Image = self.buttonImage,
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
LayoutOrder = props.layoutOrder,
|
||||
Size = props.size or UDim2.new(0, 440, 0, 120),
|
||||
ImageColor3 = currProps.buttonColor3,
|
||||
ImageTransparency = currProps.buttonTransparency,
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = Rect.new(8, 8, 9, 9),
|
||||
SelectionImageObject = self.selectionImageObject,
|
||||
BackgroundTransparency = 1,
|
||||
[Roact.Event.SelectionGained] = props.onSelectionGained,
|
||||
[Roact.Event.SelectionLost] = props.onSelectionLost,
|
||||
[Roact.Event.Activated] = props.onActivated,
|
||||
[Roact.Ref] = self.onCreate,
|
||||
},{
|
||||
MoveSelection = Roact.createElement(SoundComponent, {
|
||||
SoundName = "MoveSelection",
|
||||
}),
|
||||
|
||||
AvatarImage = Roact.createElement(UserThumbnailLoader, {
|
||||
rbxuid = props.robloxuid,
|
||||
thumbnailType = Enum.ThumbnailType.HeadShot,
|
||||
thumbnailSize = Enum.ThumbnailSize.Size100x100,
|
||||
position = UDim2.new(0, 10, 0, 10),
|
||||
size = UDim2.new(0, 100, 0, 100),
|
||||
}),
|
||||
|
||||
ContentContainer = Roact.createElement("Frame",{
|
||||
Size = UDim2.new(1, -140, 1, 0),
|
||||
Position = UDim2.new(0, 126, 0, 0),
|
||||
BackgroundTransparency = 1,
|
||||
ClipsDescendants = true,
|
||||
}, {
|
||||
UIPadding = Roact.createElement("UIPadding", {
|
||||
PaddingTop = UDim.new(0, 10),
|
||||
PaddingBottom = UDim.new(0, 10)
|
||||
}),
|
||||
|
||||
UIListLayout = Roact.createElement("UIListLayout", {
|
||||
Padding = UDim.new(0, 7),
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
}),
|
||||
|
||||
GamertagContainer = showGamertag and Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 30),
|
||||
LayoutOrder = 1,
|
||||
BackgroundTransparency = 1
|
||||
},{
|
||||
GamertagLabel = Roact.createElement("TextLabel", {
|
||||
Text = gamertagText,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0, 0, 0, 0),
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextColor3 = currProps.textColor3,
|
||||
Font = GlobalSettings.Fonts.Regular,
|
||||
TextSize = 30,
|
||||
TextScaled = true,
|
||||
BackgroundTransparency = 1,
|
||||
}),
|
||||
}),
|
||||
|
||||
RobloxNameContainer = showRobloxName and Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 30),
|
||||
LayoutOrder = 2,
|
||||
BackgroundTransparency = 1
|
||||
},{
|
||||
RobloxIcon = Roact.createElement("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Image = GlobalSettings.Images.RobloxIcon,
|
||||
ImageColor3 = currProps.iconColor3,
|
||||
Position = UDim2.new(0, 0, 0, 1),
|
||||
Size = UDim2.new(0, 28, 0, 28),
|
||||
}),
|
||||
RobloxNameLabel = Roact.createElement("TextLabel", {
|
||||
Text = robloxNameText,
|
||||
Size = UDim2.new(1, -38, 1, 0),
|
||||
Position = UDim2.new(0, 38, 0, 0),
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextColor3 = currProps.textColor3,
|
||||
Font = GlobalSettings.Fonts.Regular,
|
||||
TextSize = 30,
|
||||
TextScaled = true,
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
}),
|
||||
|
||||
StatusContainer = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 0, 26),
|
||||
LayoutOrder = 3,
|
||||
BackgroundTransparency = 1
|
||||
},{
|
||||
PresenceStatusImage = Roact.createElement("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Image = GlobalSettings.Images.OnlineStatusIcon,
|
||||
Size = UDim2.new(0, 18, 0, 18),
|
||||
Position = UDim2.new(0, 5, 0, 4),
|
||||
ImageColor3 = statusImageColor3,
|
||||
}),
|
||||
PresenceLabel = Roact.createElement("TextLabel", {
|
||||
Text = statusText,
|
||||
Size = UDim2.new(1, -38, 1, 0),
|
||||
Position = UDim2.new(0, 33, 0, 0),
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextColor3 = currProps.textColor3,
|
||||
Font = GlobalSettings.Fonts.Regular,
|
||||
TextSize = 26,
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
}),
|
||||
})
|
||||
})
|
||||
end
|
||||
|
||||
function PresenceCard:didMount()
|
||||
delay(0, function()
|
||||
if self.props.selected then
|
||||
Utility.SetSelectedCoreObject(self.ref)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function PresenceCard:didUpdate(previousProps, previousState)
|
||||
if not previousProps.selected and self.props.selected then
|
||||
Utility.SetSelectedCoreObject(self.ref)
|
||||
end
|
||||
end
|
||||
|
||||
return PresenceCard
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local Modules = game:GetService("CoreGui").RobloxGui.Modules
|
||||
local ShellModules = Modules:FindFirstChild("Shell")
|
||||
local Roact = require(Modules.Common.Roact)
|
||||
local RoactRodux = require(Modules.Common.RoactRodux)
|
||||
local Strings = require(ShellModules:FindFirstChild("LocalizedStrings"))
|
||||
local ScreenManager = require(ShellModules.ScreenManager)
|
||||
local AppState = require(ShellModules.AppState)
|
||||
local Analytics = require(ShellModules:FindFirstChild("Analytics"))
|
||||
local Utility = require(ShellModules:FindFirstChild("Utility"))
|
||||
local Components = ShellModules.Components
|
||||
local FriendsView = require(Components.Social.FriendsView)
|
||||
local FriendsData = require(ShellModules.FriendsData)
|
||||
|
||||
local function CreateSocialPane(parent)
|
||||
local this = {}
|
||||
local isPaneFocused = false
|
||||
local HubContainer = parent.Parent
|
||||
local UpSelector = HubContainer:FindFirstChild("TabContainer")
|
||||
|
||||
local noSelectionObject = Utility.Create"ImageLabel"({
|
||||
BackgroundTransparency = 1,
|
||||
})
|
||||
|
||||
local SocialPaneContainer = Utility.Create"Frame"({
|
||||
Name = "SocialPane",
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Visible = false,
|
||||
SelectionImageObject = noSelectionObject,
|
||||
Parent = parent,
|
||||
})
|
||||
|
||||
local FriendsContainer = Utility.Create"Frame"({
|
||||
Name = "FriendsContainer",
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0, 0, 0, 23),
|
||||
BackgroundTransparency = 1,
|
||||
Parent = SocialPaneContainer,
|
||||
})
|
||||
|
||||
local friendsScrollerInstance;
|
||||
local function ReconcileFriendsScrollerInstance()
|
||||
if not friendsScrollerInstance then
|
||||
return
|
||||
end
|
||||
local friendsScroller = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = AppState.store,
|
||||
}, {
|
||||
FriendsView = Roact.createElement(FriendsView, {
|
||||
inFocus = isPaneFocused,
|
||||
hide = not SocialPaneContainer.Visible,
|
||||
redirectUp = function()
|
||||
Utility.SetSelectedCoreObject(UpSelector)
|
||||
end,
|
||||
})
|
||||
})
|
||||
friendsScrollerInstance = Roact.reconcile(friendsScrollerInstance, friendsScroller)
|
||||
end
|
||||
|
||||
function this:GetName()
|
||||
return Strings:LocalizedString("FriendsWord")
|
||||
end
|
||||
|
||||
function this:IsFocused()
|
||||
return isPaneFocused
|
||||
end
|
||||
|
||||
--[[ Public API ]]--
|
||||
function this:GetAnalyticsInfo()
|
||||
return {[Analytics.WidgetNames("WidgetId")] = Analytics.WidgetNames("SocialPaneId")}
|
||||
end
|
||||
|
||||
function this:Show(fromAppHub)
|
||||
SocialPaneContainer.Visible = true
|
||||
--Suspend Friends BG Update whenever we are on GamesPane
|
||||
FriendsData:SuspendUpdate()
|
||||
|
||||
--We rebuild Friends Scroller only if we navigate from other tabs
|
||||
if fromAppHub then
|
||||
if friendsScrollerInstance then
|
||||
Roact.unmount(friendsScrollerInstance)
|
||||
end
|
||||
local friendsScroller = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = AppState.store,
|
||||
}, {
|
||||
FriendsView = Roact.createElement(FriendsView, {
|
||||
hide = not SocialPaneContainer.Visible,
|
||||
inFocus = isPaneFocused,
|
||||
redirectUp = function()
|
||||
Utility.SetSelectedCoreObject(UpSelector)
|
||||
end,
|
||||
})
|
||||
})
|
||||
friendsScrollerInstance = Roact.mount(friendsScroller, FriendsContainer, "FriendsViewContainer")
|
||||
else
|
||||
ReconcileFriendsScrollerInstance()
|
||||
end
|
||||
ScreenManager:PlayDefaultOpenSound()
|
||||
end
|
||||
|
||||
function this:Hide(fromAppHub)
|
||||
SocialPaneContainer.Visible = false
|
||||
|
||||
--We destroy Friends Scroller if we navigate to other tabs
|
||||
if fromAppHub then
|
||||
if friendsScrollerInstance then
|
||||
Roact.unmount(friendsScrollerInstance)
|
||||
end
|
||||
friendsScrollerInstance = nil
|
||||
--We resume Friends Update only if we navigate to other tabs
|
||||
FriendsData:ResumeUpdate()
|
||||
else
|
||||
ReconcileFriendsScrollerInstance()
|
||||
end
|
||||
end
|
||||
|
||||
function this:Focus()
|
||||
isPaneFocused = true
|
||||
ReconcileFriendsScrollerInstance()
|
||||
end
|
||||
|
||||
function this:RemoveFocus()
|
||||
isPaneFocused = false
|
||||
ReconcileFriendsScrollerInstance()
|
||||
local selectedObject = GuiService.SelectedCoreObject
|
||||
if selectedObject and selectedObject:IsDescendantOf(SocialPaneContainer) then
|
||||
Utility.SetSelectedCoreObject(nil)
|
||||
end
|
||||
end
|
||||
|
||||
function this:SetPosition(newPosition)
|
||||
SocialPaneContainer.Position = newPosition
|
||||
end
|
||||
|
||||
function this:SetParent(newParent)
|
||||
SocialPaneContainer.Parent = newParent
|
||||
end
|
||||
|
||||
function this:IsAncestorOf(object)
|
||||
return SocialPaneContainer and SocialPaneContainer:IsAncestorOf(object)
|
||||
end
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
return CreateSocialPane
|
||||
Reference in New Issue
Block a user