Clients/Client2018/content/internal/AppShell/Modules/Shell/TabDock.lua

357 lines
9.0 KiB
Lua

-- Written by Kip Turner, Copyright Roblox 2015
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ShellModules = Modules:FindFirstChild("Shell")
local Utility = require(ShellModules:FindFirstChild('Utility'))
local GlobalSettings = require(ShellModules:FindFirstChild('GlobalSettings'))
local Analytics = require(ShellModules:FindFirstChild('Analytics'))
local TAB_DOCK_HEIGHT = 36
local function CreateTabDock(restPosition, offscreenPosition)
local this = {}
local Tabs = {}
local SelectedTab = nil
local SizeChangedConns = {}
this.SelectedTabChanged = Utility.Signal()
this.SelectedTabClicked = Utility.Signal()
this.TabItemClickedConn = nil
local guiServiceChangedCn = nil
local TabContainer = Utility.Create'ImageButton'
{
Size = UDim2.new(1, 0, 0, TAB_DOCK_HEIGHT);
Position = restPosition;
BackgroundTransparency = 1;
Name = 'TabContainer';
}
local SelectionBorderObject = Utility.Create'ImageLabel'
{
Name = 'SelectionBorderObject';
Size = UDim2.new(1,0,0,4);
Position = UDim2.new(0,0,1,5);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.TabUnderlineColor;
-- Image = 'rbxasset://textures/ui/SelectionBox.png';
-- ScaleType = Enum.ScaleType.Slice;
-- SliceCenter = Rect.new(19,19,43,43);
BackgroundTransparency = 0;
}
local DownSelector = Utility.Create'ImageButton'
{
Size = UDim2.new(1, 0, 0, TAB_DOCK_HEIGHT);
BackgroundTransparency = 1;
Name = 'DownSelector';
Selectable = false;
Parent = TabContainer;
}
DownSelector.SelectionGained:connect(function()
if SelectedTab then
Utility.SetSelectedCoreObject(SelectedTab:GetGuiObject())
this.SelectedTabClicked:fire(SelectedTab)
end
end)
local function onGuiServiceChanged(prop)
if prop == 'SelectedCoreObject' then
if GuiService.SelectedCoreObject == TabContainer then
local currentTab = this:GetSelectedTab()
local currentTabItem = currentTab and currentTab:GetGuiObject()
if currentTabItem then
Utility.SetSelectedCoreObject(currentTabItem)
end
end
local selectedObject = GuiService.SelectedCoreObject
local focusedTab = this:FindFocusedTabByGuiObject(selectedObject)
if SelectedTab and selectedObject ~= SelectedTab:GetGuiObject() then
SelectedTab:OnClickRelease()
end
if focusedTab then
this:SetSelectedTab(focusedTab)
for _, inputObject in pairs(UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)) do
if inputObject.KeyCode == Enum.KeyCode.ButtonA and inputObject.UserInputState == Enum.UserInputState.Begin then
if SelectedTab then
SelectedTab:OnClick()
end
end
end
ContextActionService:UnbindCoreAction("OnClickSelectedTab")
ContextActionService:BindCoreAction("OnClickSelectedTab",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
if SelectedTab then
SelectedTab:OnClick()
end
elseif inputState == Enum.UserInputState.End then
if SelectedTab then
SelectedTab:OnClickRelease()
end
this.SelectedTabClicked:fire(SelectedTab)
end
end,
false,
Enum.KeyCode.ButtonA)
else
ContextActionService:UnbindCoreAction("OnClickSelectedTab")
end
end
end
--Never shown--
function this:GetAnalyticsInfo()
return {[Analytics.WidgetNames('WidgetId')] = Analytics.WidgetNames('TabDockId')}
end
function this:FindFocusedTabByGuiObject(selectedObject)
-- NOTE: This is a sort of cheater way of culling look-up checks
if selectedObject and selectedObject:IsDescendantOf(TabContainer) then
for _, currTab in pairs(Tabs) do
local guiObject = currTab and currTab:GetGuiObject()
if guiObject and guiObject == selectedObject then
return currTab
end
end
end
end
function this:IsFocused()
local selectedObject = GuiService.SelectedCoreObject
return self:FindFocusedTabByGuiObject(selectedObject) ~= nil
end
function this:SetSelectedTab(newSelectedTab)
if newSelectedTab ~= SelectedTab then
local prevSelectedTab = SelectedTab
if SelectedTab then
SelectedTab:SetSelected(false)
SelectedTab:OnClickRelease()
end
SelectedTab = newSelectedTab
if SelectedTab then
SelectedTab:SetSelected(true)
if self:IsFocused() then
local currentTabItem = SelectedTab and SelectedTab:GetGuiObject()
if currentTabItem then
Utility.SetSelectedCoreObject(currentTabItem)
end
end
end
this.SelectedTabChanged:fire(SelectedTab)
end
end
function this:Focus()
self:Show()
if SelectedTab then
SelectedTab:SetSelected(true)
local currentTabItem = SelectedTab and SelectedTab:GetGuiObject()
if currentTabItem then
Utility.SetSelectedCoreObject(currentTabItem)
end
end
end
function this:GetSelectedTab()
return SelectedTab
end
local arrangeCount = 0
function this:ArrangeTabs()
arrangeCount = arrangeCount + 1
local currentCount = arrangeCount
local x = 0
for i, tabItem in pairs(Tabs) do
local tabItemSize = tabItem:GetSize()
local xSize = tabItemSize.X.Offset
local spacing = GlobalSettings.TabItemSpacing
if i == 1 then
spacing = 0
end
-- Stop recursion in its tracks
if currentCount == arrangeCount then
tabItem:SetPosition(UDim2.new(0, x + spacing, 0, 0))
local tabItemGuiObject = tabItem:GetGuiObject()
if tabItemGuiObject then
local prevItemGuiObject = Tabs[i-1] and Tabs[i-1]:GetGuiObject()
local nextItemGuiObject = Tabs[i+1] and Tabs[i+1]:GetGuiObject()
tabItemGuiObject.NextSelectionLeft = prevItemGuiObject or tabItemGuiObject
tabItemGuiObject.NextSelectionRight = nextItemGuiObject or tabItemGuiObject
end
else
return
end
x = x + spacing + xSize
end
end
function this:FindTabIndex(tab)
for i, currTab in pairs(Tabs) do
if tab == currTab then
return i
end
end
end
function this:ContainsTab(tab)
return self:FindTabIndex(tab) ~= nil
end
function this:GetTab(index)
return Tabs[index]
end
function this:GetNextTab()
if SelectedTab then
local index = this:FindTabIndex(SelectedTab)
return index and Tabs[index + 1]
end
end
function this:GetPreviousTab()
if SelectedTab then
local index = this:FindTabIndex(SelectedTab)
return index and Tabs[index - 1]
end
end
function this:AddTab(newTab)
local existingIndex = self:FindTabIndex(newTab)
if existingIndex then
Utility.DebugLog("Not adding tab:" , newTab:GetName() , "because that tab already exists.")
return
end
local guiObject = newTab and newTab:GetGuiObject()
if guiObject then
guiObject.SelectionImageObject = SelectionBorderObject
guiObject.NextSelectionDown = DownSelector
end
table.insert(Tabs, newTab)
newTab:SetParent(TabContainer)
Utility.DisconnectEvent(SizeChangedConns[newTab])
SizeChangedConns[newTab] = newTab.SizeChanged:connect(function()
self:ArrangeTabs()
end)
self:ArrangeTabs()
return newTab
end
function this:RemoveTab(tab)
local removeIndex = self:FindTabIndex(tab)
if removeIndex then
table.remove(Tabs, removeIndex)
if tab == SelectedTab then
this:SetSelectedTab(nil)
end
tab:SetParent(nil)
Utility.DisconnectEvent(SizeChangedConns[tab])
self:ArrangeTabs()
return true
end
return false
end
function this:SetParent(newParent)
TabContainer.Parent = newParent
end
function this:ConnectEvents()
onGuiServiceChanged('SelectedCoreObject')
guiServiceChangedCn = GuiService.Changed:connect(onGuiServiceChanged)
end
function this:DisconnectEvents()
if guiServiceChangedCn then
guiServiceChangedCn:disconnect()
guiServiceChangedCn = nil
end
end
local motionDuration = GlobalSettings.TabDockTweenDuration
local function FadeText(element, tweens, a, b, duration)
if element == nil then return end
if element:IsA('TextLabel') or element:IsA('TextBox') or element:IsA('TextButton') then
table.insert(tweens, Utility.PropertyTweener(element, 'TextTransparency', a, b, duration, Utility.EaseOutQuad))
end
for _, child in pairs(element:GetChildren()) do
FadeText(child, tweens, a, b, duration)
end
end
local tweens = {}
local showing = false;
function this:Hide()
if not showing then return end
showing = false;
Utility.CancelTweens(tweens)
local positionTweener = Utility.PropertyTweener(
TabContainer,
'Position',
restPosition,
offscreenPosition,
motionDuration,
Utility.SCurveUDim2
)
table.insert(tweens, positionTweener)
FadeText(TabContainer, tweens, 0, 1, motionDuration)
end
function this:Show()
if showing then return end
showing = true;
Utility.CancelTweens(tweens)
local positionTweener = Utility.PropertyTweener(
TabContainer,
'Position',
offscreenPosition,
restPosition,
motionDuration,
Utility.SCurveUDim2
)
table.insert(tweens, positionTweener)
FadeText(TabContainer, tweens, 1, 0, motionDuration)
end
return this
end
return CreateTabDock