local X function ChangeLayout(F)if X==F then return end X=F for X,F in pairs(UIElements)do local E=UI[F]E.Visible=false end local E=0 for X,F in ipairs(F)do local W=UI[F]W.Visible=true W.Position=UDim2.new(0,0,0,20)+UDim2.new(W.Position.X.Scale,W.Position.X.Offset,0,E+10)E=(E+10)+W.AbsoluteSize.Y end UI.Size=UDim2.new(0,200,0,30+E)end function UpdateUI()if not UI then return end local X=GetTextures(TextureTool.Type,TextureTool.Face)local F=UI.ImageIDOption.TextBox local E=UI.TransparencyOption.Input.TextBox local W=TextureTool.Type.."\115"if#Selection.Parts==0 then ChangeLayout(Layouts.EmptySelection)return elseif#X==0 then ChangeLayout(Layouts.NoTextures)return elseif#Selection.Parts~=#X then ChangeLayout(Layouts["\083\111\109\101"..W])elseif#Selection.Parts==#X then ChangeLayout(Layouts["\065\108\108"..W])end local k=Support.IdentifyCommonProperty(X,"\084\101\120\116\117\114\101")local S=Support.IdentifyCommonProperty(X,"\084\114\097\110\115\112\097\114\101\110\099\121")UpdateDataInputs({[F]=k and ParseAssetId(k)or k or"\042",[E]=S and Support.Round(S,3)or"\042"})if TextureTool.Type=="\084\101\120\116\117\114\101"then local F=UI.RepeatOption.XInput.TextBox local E=UI.RepeatOption.YInput.TextBox local W=Support.IdentifyCommonProperty(X,"\083\116\117\100\115\080\101\114\084\105\108\101\085")local k=Support.IdentifyCommonProperty(X,"\083\116\117\100\115\080\101\114\084\105\108\101\086")UpdateDataInputs({[F]=W and Support.Round(W,3)or"\042";[E]=k and Support.Round(k,3)or"\042"})end end function UpdateDataInputs(X)for X,F in pairs(X)do if not X:IsFocused()then X.Text=tostring(F)end end end function ParseAssetId(X)local F=tonumber(X)or tonumber((X:lower()):match("\037\063\105\100\061\040\091\048-\057\093\043\041"))or tonumber(X:match("\047\040\091\048-\057\093\043\041\047"))or tonumber((X:lower()):match("\114\098\120\097\115\115\101\116\105\100\058\047\047\040\091\048-\057\093\043\041"))return F end function SetFace(X)TextureTool.Face=X FaceDropdown.SetOption(X and X.Name or"\042")end function SetTextureType(X)TextureTool.Type=X Core.ToggleSwitch(X,UI.ModeOption)UI.AddButton.Button.Text="\065\068\068 "..X:upper()UI.RemoveButton.Button.Text="\082\069\077\079\086\069 "..X:upper()end function SetProperty(X,F,E,W)if not W then return end TrackChange()for k,S in pairs(GetTextures(X,F))do table.insert(HistoryRecord.Before,{Part=S.Parent,TextureType=X;Face=F,[E]=S[E]})table.insert(HistoryRecord.After,{Part=S.Parent,TextureType=X;Face=F;[E]=W})end RegisterChange()end function SetTextureId(X,F,E)if not E then return end local W={Texture="\114\098\120\097\115\115\101\116\105\100\058\047\047"..E};(Core.Try(Core.SyncAPI.Invoke,Core.SyncAPI,"\069\120\116\114\097\099\116\073\109\097\103\101\070\114\111\109\068\101\099\097\108",E)):Then(function(X)W.Texture="\114\098\120\097\115\115\101\116\105\100\058\047\047"..X end)TrackChange()for E,k in pairs(GetTextures(X,F))do local S,G={Part=k.Parent,TextureType=X;Face=F},{Part=k.Parent;TextureType=X;Face=F}for X,F in pairs(W)do S[X]=k[X]G[X]=F end table.insert(HistoryRecord.Before,S)table.insert(HistoryRecord.After,G)end RegisterChange()end function AddTextures(X,F)local E={}for W,k in pairs(Selection.Parts)do local S for E,W in pairs(k:GetChildren())do if W.ClassName==X and W.Face==F then S=true end end if not S then table.insert(E,{Part=k,TextureType=X,Face=F})end end local W=Core.SyncAPI:Invoke("\067\114\101\097\116\101\084\101\120\116\117\114\101\115",E)local k={Textures=W,Selection=Selection.Items;Unapply=function(X)Selection.Replace(X.Selection)Core.SyncAPI:Invoke("\082\101\109\111\118\101",X.Textures)end,Apply=function(X)Core.SyncAPI:Invoke("\085\110\100\111\082\101\109\111\118\101",X.Textures)Selection.Replace(X.Selection)end}Core.History.Add(k)end function RemoveTextures(X,F)local E=GetTextures(X,F)local W={Textures=E;Selection=Selection.Items;Unapply=function(X)Core.SyncAPI:Invoke("\085\110\100\111\082\101\109\111\118\101",X.Textures)Selection.Replace(X.Selection)end;Apply=function(X)Selection.Replace(X.Selection)Core.SyncAPI:Invoke("\082\101\109\111\118\101",X.Textures)end}Core.SyncAPI:Invoke("\082\101\109\111\118\101",E)Core.History.Add(W)end function TrackChange()HistoryRecord={Before={},After={};Selection=Selection.Items;Unapply=function(X)Selection.Replace(X.Selection)Core.SyncAPI:Invoke("\083\121\110\099\084\101\120\116\117\114\101",X.Before)end,Apply=function(X)Selection.Replace(X.Selection)Core.SyncAPI:Invoke("\083\121\110\099\084\101\120\116\117\114\101",X.After)end}end function RegisterChange()if not HistoryRecord then return end Core.SyncAPI:Invoke("\083\121\110\099\084\101\120\116\117\114\101",HistoryRecord.After)Core.History.Add(HistoryRecord)HistoryRecord=nil end

local CurrentLayout;

function ChangeLayout(Layout)
	-- Sets the UI to the given layout

	-- Make sure the new layout isn't already set
	if CurrentLayout == Layout then
		return;
	end;

	-- Set this as the current layout
	CurrentLayout = Layout;

	-- Reset the UI
	for _, ElementName in pairs(UIElements) do
		local Element = UI[ElementName];
		Element.Visible = false;
	end;

	-- Keep track of the total vertical extents of all items
	local Sum = 0;

	-- Go through each layout element
	for ItemIndex, ItemName in ipairs(Layout) do

		local Item = UI[ItemName];

		-- Make the item visible
		Item.Visible = true;

		-- Position this item underneath the past items
		Item.Position = UDim2.new(0, 0, 0, 20) + UDim2.new(
			Item.Position.X.Scale,
			Item.Position.X.Offset,
			0,
			Sum + 10
		);

		-- Update the sum of item heights
		Sum = Sum + 10 + Item.AbsoluteSize.Y;

	end;

	-- Resize the container to fit the new layout
	UI.Size = UDim2.new(0, 200, 0, 30 + Sum);

end;

function UpdateUI()
	-- Updates information on the UI

	-- Make sure the UI's on
	if not UI then
		return;
	end;

	-- Get the textures in the selection
	local Textures = GetTextures(TextureTool.Type, TextureTool.Face);

	-- References to UI elements
	local ImageIdInput = UI.ImageIDOption.TextBox;
	local TransparencyInput = UI.TransparencyOption.Input.TextBox;

	-----------------------
	-- Update the UI layout
	-----------------------

	-- Get the plural version of the current texture type
	local PluralTextureType = TextureTool.Type .. 's';

	-- Figure out the necessary UI layout
	if #Selection.Parts == 0 then
		ChangeLayout(Layouts.EmptySelection);
		return;

	-- When the selection has no textures
	elseif #Textures == 0 then
		ChangeLayout(Layouts.NoTextures);
		return;

	-- When only some selected items have textures
	elseif #Selection.Parts ~= #Textures then
		ChangeLayout(Layouts['Some' .. PluralTextureType]);

	-- When all selected items have textures
	elseif #Selection.Parts == #Textures then
		ChangeLayout(Layouts['All' .. PluralTextureType]);
	end;

	------------------------
	-- Update UI information
	------------------------

	-- Get the common properties
	local ImageId = Support.IdentifyCommonProperty(Textures, 'Texture');
	local Transparency = Support.IdentifyCommonProperty(Textures, 'Transparency');

	-- Update the common inputs
	UpdateDataInputs {
		[ImageIdInput] = ImageId and ParseAssetId(ImageId) or ImageId or '*';
		[TransparencyInput] = Transparency and Support.Round(Transparency, 3) or '*';
	};

	-- Update texture-specific information on UI
	if TextureTool.Type == 'Texture' then

		-- Get texture-specific UI elements
		local RepeatXInput = UI.RepeatOption.XInput.TextBox;
		local RepeatYInput = UI.RepeatOption.YInput.TextBox;

		-- Get texture-specific common properties
		local RepeatX = Support.IdentifyCommonProperty(Textures, 'StudsPerTileU');
		local RepeatY = Support.IdentifyCommonProperty(Textures, 'StudsPerTileV');

		-- Update inputs
		UpdateDataInputs {
			[RepeatXInput] = RepeatX and Support.Round(RepeatX, 3) or '*';
			[RepeatYInput] = RepeatY and Support.Round(RepeatY, 3) or '*';
		};

	end;

end;

function UpdateDataInputs(Data)
	-- Updates the data in the given TextBoxes when the user isn't typing in them

	-- Go through the inputs and data
	for Input, UpdatedValue in pairs(Data) do

		-- Makwe sure the user isn't typing into the input
		if not Input:IsFocused() then

			-- Set the input's value
			Input.Text = tostring(UpdatedValue);

		end;

	end;

end;

function ParseAssetId(Input)
	-- Returns the intended asset ID for the given input

	-- Get the ID number from the input
	local Id = tonumber(Input)
		or tonumber(Input:lower():match('%?id=([0-9]+)'))
		or tonumber(Input:match('/([0-9]+)/'))
		or tonumber(Input:lower():match('rbxassetid://([0-9]+)'));

	-- Return the ID
	return Id;
end;

function SetFace(Face)

	-- Update the tool option
	TextureTool.Face = Face;

	-- Update the UI
	FaceDropdown.SetOption(Face and Face.Name or '*');

end;

function SetTextureType(TextureType)

	-- Update the tool option
	TextureTool.Type = TextureType;

	-- Update the UI
	Core.ToggleSwitch(TextureType, UI.ModeOption);
	UI.AddButton.Button.Text = 'ADD ' .. TextureType:upper();
	UI.RemoveButton.Button.Text = 'REMOVE ' .. TextureType:upper();

end;

function SetProperty(TextureType, Face, Property, Value)

	-- Make sure the given value is valid
	if not Value then
		return;
	end;

	-- Start a history record
	TrackChange();

	-- Go through each texture
	for _, Texture in pairs(GetTextures(TextureType, Face)) do

		-- Store the state of the texture before modification
		table.insert(HistoryRecord.Before, { Part = Texture.Parent, TextureType = TextureType, Face = Face, [Property] = Texture[Property] });

		-- Create the change request for this texture
		table.insert(HistoryRecord.After, { Part = Texture.Parent, TextureType = TextureType, Face = Face, [Property] = Value });

	end;

	-- Register the changes
	RegisterChange();

end;

function SetTextureId(TextureType, Face, AssetId)
	-- Sets the textures in the selection to the intended, given image asset

	-- Make sure the given asset ID is valid
	if not AssetId then
		return;
	end;

	-- Prepare the change request
	local Changes = {
		Texture = 'rbxassetid://' .. AssetId;
	};

	-- Attempt an image extraction on the given asset
	Core.Try(Core.SyncAPI.Invoke, Core.SyncAPI, 'ExtractImageFromDecal', AssetId)
		:Then(function (ExtractedImage)
			Changes.Texture = 'rbxassetid://' .. ExtractedImage;
		end);

	-- Start a history record
	TrackChange();

	-- Go through each texture
	for _, Texture in pairs(GetTextures(TextureType, Face)) do

		-- Create the history change requests for this texture
		local Before, After = { Part = Texture.Parent, TextureType = TextureType, Face = Face }, { Part = Texture.Parent, TextureType = TextureType, Face = Face };

		-- Gather change information to finish up the history change requests
		for Property, Value in pairs(Changes) do
			Before[Property] = Texture[Property];
			After[Property] = Value;
		end;

		-- Store the state of the texture before modification
		table.insert(HistoryRecord.Before, Before);

		-- Create the change request for this texture
		table.insert(HistoryRecord.After, After);

	end;

	-- Register the changes
	RegisterChange();

end;

function AddTextures(TextureType, Face)

	-- Prepare the change request for the server
	local Changes = {};

	-- Go through the selection
	for _, Part in pairs(Selection.Parts) do

		-- Make sure this part doesn't already have a texture of the same type
		local HasTextures;
		for _, Child in pairs(Part:GetChildren()) do
			if Child.ClassName == TextureType and Child.Face == Face then
				HasTextures = true;
			end;
		end;

		-- Queue a texture to be created for this part, if not already existent
		if not HasTextures then
			table.insert(Changes, { Part = Part, TextureType = TextureType, Face = Face });
		end;

	end;

	-- Send the change request to the server
	local Textures = Core.SyncAPI:Invoke('CreateTextures', Changes);

	-- Put together the history record
	local HistoryRecord = {
		Textures = Textures;
		Selection = Selection.Items;

		Unapply = function (Record)
			-- Reverts this change

			-- Select changed parts
			Selection.Replace(Record.Selection)

			-- Remove the textures
			Core.SyncAPI:Invoke('Remove', Record.Textures);

		end;

		Apply = function (Record)
			-- Reapplies this change

			-- Restore the textures
			Core.SyncAPI:Invoke('UndoRemove', Record.Textures);

			-- Select changed parts
			Selection.Replace(Record.Selection)

		end;

	};

	-- Register the history record
	Core.History.Add(HistoryRecord);

end;

function RemoveTextures(TextureType, Face)

	-- Get all the textures in the selection
	local Textures = GetTextures(TextureType, Face);

	-- Create the history record
	local HistoryRecord = {
		Textures = Textures;
		Selection = Selection.Items;

		Unapply = function (Record)
			-- Reverts this change

			-- Restore the textures
			Core.SyncAPI:Invoke('UndoRemove', Record.Textures);

			-- Select changed parts
			Selection.Replace(Record.Selection)

		end;

		Apply = function (Record)
			-- Reapplies this change

			-- Select changed parts
			Selection.Replace(Record.Selection)

			-- Remove the textures
			Core.SyncAPI:Invoke('Remove', Record.Textures);

		end;

	};

	-- Send the removal request
	Core.SyncAPI:Invoke('Remove', Textures);

	-- Register the history record
	Core.History.Add(HistoryRecord);

end;

function TrackChange()

	-- Start the record
	HistoryRecord = {
		Before = {};
		After = {};
		Selection = Selection.Items;

		Unapply = function (Record)
			-- Reverts this change

			-- Select the changed parts
			Selection.Replace(Record.Selection)

			-- Send the change request
			Core.SyncAPI:Invoke('SyncTexture', Record.Before);

		end;

		Apply = function (Record)
			-- Applies this change

			-- Select the changed parts
			Selection.Replace(Record.Selection)

			-- Send the change request
			Core.SyncAPI:Invoke('SyncTexture', Record.After);

		end;

	};

end;

function RegisterChange()
	-- Finishes creating the history record and registers it

	-- Make sure there's an in-progress history record
	if not HistoryRecord then
		return;
	end;

	-- Send the change to the server
	Core.SyncAPI:Invoke('SyncTexture', HistoryRecord.After);

	-- Register the record and clear the staging
	Core.History.Add(HistoryRecord);
	HistoryRecord = nil;

end;
