357 lines
9.0 KiB
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
|