add gs
This commit is contained in:
+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
|
||||
Reference in New Issue
Block a user