Selection.ItemsAdded=Signal.new()Selection.ItemsRemoved=Signal.new()Selection.PartsAdded=Signal.new()Selection.PartsRemoved=Signal.new()Selection.FocusChanged=Signal.new()Selection.Cleared=Signal.new()Selection.Changed=Signal.new()function Selection.IsSelected(t)return Selection.ItemIndex[t]end local function t(t,o)if t:IsA("\066\097\115\101\080\097\114\116")then o[#o+1]=t else local n=t:GetDescendants()for t,n in ipairs(n)do if n:IsA("\066\097\115\101\080\097\114\116")then o[#o+1]=n end end end end function Selection.Add(o,n)local P=GetCore()local b={}for t,o in pairs(o)do if o.Parent and not Selection.ItemIndex[o]then table.insert(b,o)end end local d=Selection.Items local M={}for o,n in pairs(b)do Selection.ItemIndex[n]=true CreateSelectionBoxes(n)local P=Maid.new()Selection.Maid[n]=P P.RemovalListener=n.AncestryChanged:Connect(function(t,o)if o==nil then Selection.Remove({n})end end)t(n,M)local b=not n:IsA("\066\097\115\101\080\097\114\116")or nil P.NewParts=b and n.DescendantAdded:Connect(function(t)if t:IsA("\066\097\115\101\080\097\114\116")then local o=(Selection.PartIndex[t]or 0)+1 Selection.PartIndex[t]=o Selection.Parts=Support.Keys(Selection.PartIndex)if o==1 then Selection.PartsAdded:Fire({t})end end end)P.RemovingParts=b and n.DescendantRemoving:Connect(function(t)if Selection.PartIndex[t]then local o=(Selection.PartIndex[t]or 0)-1 Selection.PartIndex[t]=o>0 and o or nil if o==0 then Selection.Parts=Support.Keys(Selection.PartIndex)Selection.PartsRemoved:Fire({t})end end end)end Selection.Items=Support.Keys(Selection.ItemIndex)if n and#b>0 then TrackSelectionChange(d)end local h={}for t,o in pairs(M)do local n=(Selection.PartIndex[o]or 0)+1 Selection.PartIndex[o]=n if n==1 then h[#h+1]=o end end if#h>0 then Selection.Parts=Support.Keys(Selection.PartIndex)Selection.PartsAdded:Fire(h)end if#b>0 then Selection.ItemsAdded:Fire(b)Selection.Changed:Fire()end end function Selection.Remove(o,n)local P={}for t,o in pairs(o)do if Selection.IsSelected(o)then table.insert(P,o)end end local b=Selection.Items local d={}for o,n in pairs(P)do Selection.ItemIndex[n]=nil RemoveSelectionBoxes(n)Selection.Maid[n]=nil t(n,d)end Selection.Items=Support.Keys(Selection.ItemIndex)if n and#P>0 then TrackSelectionChange(b)end local M={}for t,o in pairs(d)do local n=(Selection.PartIndex[o]or 0)-1 Selection.PartIndex[o]=n>0 and n or nil if n==0 then M[#M+1]=o end end if#M>0 then Selection.Parts=Support.Keys(Selection.PartIndex)Selection.PartsRemoved:Fire(M)end if#P>0 then Selection.ItemsRemoved:Fire(P)Selection.Changed:Fire()end end function Selection.Clear(t)Selection.Remove(Selection.Items,t)Selection.Cleared:Fire()end function Selection.Replace(t,o)local n=Selection.Items local P={}for t,o in ipairs(t)do if not Selection.ItemIndex[o]then table.insert(P,o)end end local b={}local d=Support.FlipTable(t)for t,o in ipairs(Selection.Items)do if not d[o]then table.insert(b,o)end end if#b>0 then Selection.Remove(b,false)end if#P>0 then Selection.Add(P,false)end if o then TrackSelectionChange(n)end end local function o(t)return t:IsA("\077\111\100\101\108")or t:IsA("\066\097\115\101\080\097\114\116")end local function n(t)if not t then return nil end if o(t)then return t elseif t then return t:FindFirstChildWhichIsA("\066\097\115\101\080\097\114\116")or t:FindFirstChildWhichIsA("\077\111\100\101\108")or t:FindFirstChildWhichIsA("\066\097\115\101\080\097\114\116",true)or t:FindFirstChildWhichIsA("\077\111\100\101\108",true)end end function Selection.SetFocus(t)local o=n(t)if Selection.Focus==o then return end Selection.Focus=o Selection.FocusChanged:Fire(o)end function FocusOnLastSelectedPart()if#Selection.Items==0 then Selection.SetFocus(nil)else Selection.SetFocus(Selection.Items[#Selection.Items])end end
-- Events to listen to selection changes
Selection.ItemsAdded = Signal.new()
Selection.ItemsRemoved = Signal.new()
Selection.PartsAdded = Signal.new()
Selection.PartsRemoved = Signal.new()
Selection.FocusChanged = Signal.new()
Selection.Cleared = Signal.new()
Selection.Changed = Signal.new()

function Selection.IsSelected(Item)
	-- Returns whether `Item` is selected or not

	-- Check and return item presence in index
	return Selection.ItemIndex[Item]
end

local function CollectParts(Item, Table)
	-- Adds parts found in `Item` to `Table`

	-- Collect parts
	if Item:IsA("BasePart") then
		Table[#Table + 1] = Item

	-- Collect parts inside of groups
	else
		local Items = Item:GetDescendants()
		for _, Item in ipairs(Items) do
			if Item:IsA("BasePart") then
				Table[#Table + 1] = Item
			end
		end
	end
end

function Selection.Add(Items, RegisterHistory)
	-- Adds the given items to the selection

	-- Get core API
	local Core = GetCore()

	-- Go through and validate each given item
	local SelectableItems = {}
	for _, Item in pairs(Items) do
		-- Make sure each item is valid and not already selected
		if Item.Parent and not Selection.ItemIndex[Item] then
			table.insert(SelectableItems, Item)
		end
	end

	local OldSelection = Selection.Items

	-- Track parts in new selection
	local Parts = {}

	-- Go through the valid new selection items
	for _, Item in pairs(SelectableItems) do
		-- Add each valid item to the selection
		Selection.ItemIndex[Item] = true
		CreateSelectionBoxes(Item)

		-- Create maid for cleaning up item listeners
		local ItemMaid = Maid.new()
		Selection.Maid[Item] = ItemMaid

		-- Deselect items that are destroyed
		ItemMaid.RemovalListener = Item.AncestryChanged:Connect(function(Object, Parent)
			if Parent == nil then
				Selection.Remove({ Item })
			end
		end)

		-- Collect parts within item
		CollectParts(Item, Parts)

		-- Listen for new parts in groups
		local IsGroup = not Item:IsA("BasePart") or nil
		ItemMaid.NewParts = IsGroup
			and Item.DescendantAdded:Connect(function(Descendant)
				if Descendant:IsA("BasePart") then
					local NewRefCount = (Selection.PartIndex[Descendant] or 0) + 1
					Selection.PartIndex[Descendant] = NewRefCount
					Selection.Parts = Support.Keys(Selection.PartIndex)
					if NewRefCount == 1 then
						Selection.PartsAdded:Fire({ Descendant })
					end
				end
			end)
		ItemMaid.RemovingParts = IsGroup
			and Item.DescendantRemoving:Connect(function(Descendant)
				if Selection.PartIndex[Descendant] then
					local NewRefCount = (Selection.PartIndex[Descendant] or 0) - 1
					Selection.PartIndex[Descendant] = (NewRefCount > 0) and NewRefCount or nil
					if NewRefCount == 0 then
						Selection.Parts = Support.Keys(Selection.PartIndex)
						Selection.PartsRemoved:Fire({ Descendant })
					end
				end
			end)
	end

	-- Update selected item list
	Selection.Items = Support.Keys(Selection.ItemIndex)

	-- Create a history record for this selection change, if requested
	if RegisterHistory and #SelectableItems > 0 then
		TrackSelectionChange(OldSelection)
	end

	-- Register references to new parts
	local NewParts = {}
	for _, Part in pairs(Parts) do
		local NewRefCount = (Selection.PartIndex[Part] or 0) + 1
		Selection.PartIndex[Part] = NewRefCount
		if NewRefCount == 1 then
			NewParts[#NewParts + 1] = Part
		end
	end

	-- Update parts list
	if #NewParts > 0 then
		Selection.Parts = Support.Keys(Selection.PartIndex)
		Selection.PartsAdded:Fire(NewParts)
	end

	-- Fire relevant events
	if #SelectableItems > 0 then
		Selection.ItemsAdded:Fire(SelectableItems)
		Selection.Changed:Fire()
	end
end

function Selection.Remove(Items, RegisterHistory)
	-- Removes the given items from the selection

	-- Go through and validate each given item
	local DeselectableItems = {}
	for _, Item in pairs(Items) do
		-- Make sure each item is actually selected
		if Selection.IsSelected(Item) then
			table.insert(DeselectableItems, Item)
		end
	end

	local OldSelection = Selection.Items

	-- Track parts in removing selection
	local Parts = {}

	-- Go through the valid deselectable items
	for _, Item in pairs(DeselectableItems) do
		-- Remove item from selection
		Selection.ItemIndex[Item] = nil
		RemoveSelectionBoxes(Item)

		-- Stop tracking item's parts
		Selection.Maid[Item] = nil

		-- Get parts associated with item
		CollectParts(Item, Parts)
	end

	-- Update selected item list
	Selection.Items = Support.Keys(Selection.ItemIndex)

	-- Create a history record for this selection change, if requested
	if RegisterHistory and #DeselectableItems > 0 then
		TrackSelectionChange(OldSelection)
	end

	-- Clear references to removing parts
	local RemovingParts = {}
	for _, Part in pairs(Parts) do
		local NewRefCount = (Selection.PartIndex[Part] or 0) - 1
		Selection.PartIndex[Part] = (NewRefCount > 0) and NewRefCount or nil
		if NewRefCount == 0 then
			RemovingParts[#RemovingParts + 1] = Part
		end
	end

	-- Update parts list
	if #RemovingParts > 0 then
		Selection.Parts = Support.Keys(Selection.PartIndex)
		Selection.PartsRemoved:Fire(RemovingParts)
	end

	-- Fire relevant events
	if #DeselectableItems > 0 then
		Selection.ItemsRemoved:Fire(DeselectableItems)
		Selection.Changed:Fire()
	end
end

function Selection.Clear(RegisterHistory)
	-- Clears all items from selection

	-- Remove all selected items
	Selection.Remove(Selection.Items, RegisterHistory)

	-- Fire relevant events
	Selection.Cleared:Fire()
end

function Selection.Replace(Items, RegisterHistory)
	-- Replaces the current selection with the given new items

	-- Save old selection reference for history
	local OldSelection = Selection.Items

	-- Find new items
	local NewItems = {}
	for _, Item in ipairs(Items) do
		if not Selection.ItemIndex[Item] then
			table.insert(NewItems, Item)
		end
	end

	-- Find removing items
	local RemovingItems = {}
	local NewItemIndex = Support.FlipTable(Items)
	for _, Item in ipairs(Selection.Items) do
		if not NewItemIndex[Item] then
			table.insert(RemovingItems, Item)
		end
	end

	-- Update selection
	if #RemovingItems > 0 then
		Selection.Remove(RemovingItems, false)
	end
	if #NewItems > 0 then
		Selection.Add(NewItems, false)
	end

	-- Create a history record for this selection change, if requested
	if RegisterHistory then
		TrackSelectionChange(OldSelection)
	end
end

local function IsVisible(Item)
	return Item:IsA("Model") or Item:IsA("BasePart")
end

local function GetVisibleFocus(Item)
	-- Returns a visible focus representing the item

	-- Return nil if no focus
	if not Item then
		return nil
	end

	-- Return focus if it's visible
	if IsVisible(Item) then
		return Item

	-- Return first visible item within focus if not visible itself
	elseif Item then
		return Item:FindFirstChildWhichIsA("BasePart")
			or Item:FindFirstChildWhichIsA("Model")
			or Item:FindFirstChildWhichIsA("BasePart", true)
			or Item:FindFirstChildWhichIsA("Model", true)
	end
end

function Selection.SetFocus(Item)
	-- Selects `Item` as the focused selection item

	-- Ensure focus has changed
	local Focus = GetVisibleFocus(Item)
	if Selection.Focus == Focus then
		return
	end

	-- Set new focus item
	Selection.Focus = Focus

	-- Fire relevant events
	Selection.FocusChanged:Fire(Focus)
end

function FocusOnLastSelectedPart()
	-- Sets the last part of the selection as the focus

	-- If selection is empty, clear the focus
	if #Selection.Items == 0 then
		Selection.SetFocus(nil)

	-- Otherwise, focus on the last part in the selection
	else
		Selection.SetFocus(Selection.Items[#Selection.Items])
	end
end
