Skip to content

Instantly share code, notes, and snippets.

@instance-id
Last active October 7, 2022 08:01
Show Gist options
  • Save instance-id/e5bf308f3d7779fcfdaa1dd8e137d621 to your computer and use it in GitHub Desktop.
Save instance-id/e5bf308f3d7779fcfdaa1dd8e137d621 to your computer and use it in GitHub Desktop.
Write UIToolkit style code using Sass and have it converted automatically to a .uss file, usable in Unity. (Linux/Windows, but PC needs to have Sass installed)
#!/usr/bin/env pwsh
<#
.NOTES
============================================================
Last Edit: 5/15/2022
Created by: instance.id (https://github.com/instance-id)
Platform: Windows/Linux
Filename: unitytool.ps1
PSVersion: Last tested with 7.2.3
============================================================
.DESCRIPTION
Used as a Rider IDE External tool. After adding a new external tool, right-clicking the folder
you want to create the files in, select external tool, then input the filename with no extension
Rider external Tool settings:
Program:
/usr/bin/pwsh
Arguments:
-file /home/mosthated/_dev/languages/pwsh/unity/create_templates/sass/create_sass_template.ps1 -basePath $FileDir$ -fileName $Prompt$
#>
param(
[string]$basePath,
[string]$fileName
)
$typeArray = @('fields', 'elements', 'containers')
$scriptRoot = $PSScriptRoot
$textTemplate = [io.path]::combine($scriptRoot, 'text_template.ps1')
$rootFile = [io.path]::combine($basePath, "${fileName}.sass")
$elementsFolder = [io.path]::combine($basePath, 'element_types')
$indexFile = [io.path]::combine($elementsFolder, '_index.sass')
$fieldsFile = [io.path]::combine($elementsFolder, '_fields.sass')
$elementsFile = [io.path]::combine($elementsFolder, '_elements.sass')
$containersFile = [io.path]::combine($elementsFolder, '_containers.sass')
# --| Create files and directories --------------
if (!(test-path $rootFile)) { new-item -path $basePath -name "${fileName}.sass" -type file -force }
if (!(test-path $elementsFolder)) { new-item -path $basePath -name 'element_types' -type directory -force }
if (!(test-path $indexFile)) { new-item -path $elementsFolder -name '_index.sass' -type file -force }
if (!(test-path $fieldsFile)) { new-item -path $elementsFolder -name '_fields.sass' -type file -force }
if (!(test-path $elementsFile)) { new-item -path $elementsFolder -name '_elements.sass' -type file -force }
if (!(test-path $containersFile)) { new-item -path $elementsFolder -name '_containers.sass' -type file -force }
# --| Comment padding ---------------------------
$padName = "/* -- ${fileName} "
$lenTotal = 50
$commentPadding = $padName.PadRight($lenTotal - 1, '-')
. $textTemplate
$rootFileContent > $rootFile
$indexFileContents > $indexFile
$fieldsFileContents > $fieldsFile
$elementsFileContents > $elementsFile
$containersFileContents > $containersFile
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Diagnostics;
using UnityEngine.UIElements;
using Debug = UnityEngine.Debug;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
namespace instance.id.Sass
{
// -- Place in Editor folder ----------
class SassCompiler : AssetPostprocessor
{
private static bool debug = false;
// -- Set the path to your sass binary/exe file
// -- https://github.com/sass/dart-sass/releases/
// -- Add Sass executable to systems $PATH or
// -- set the path to the binary in the var below
private const string pathToSass = "/home/mosthated/.files";
// -- Called automatically by the editor when an asset refresh is performed ----
public static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (var filePath in importedAssets.Where(s => s.EndsWith(".sass") || s.EndsWith(".scss")))
{
PackageInfo packageInfo;
string currentPath = string.Empty;
string newPath = string.Empty;
string appPath = string.Empty;
var fileName = Path.GetFileName(filePath);
var parentPath = Path.GetDirectoryName(filePath);
if (fileName.StartsWith("_"))
{
var indexFile = string.Empty;
appPath = Application.dataPath.Remove(Application.dataPath.Length - "Assets".Length, "Assets".Length);
var newFilePath = !string.IsNullOrWhiteSpace(parentPath)
? Path.Combine(appPath, parentPath)
: Path.Combine(appPath, filePath);
indexFile = (newFilePath.EndsWith("_index.sass") || newFilePath.EndsWith("_index.scss"))
? newFilePath
: LocateIndexFile(newFilePath);
var sourcePath = File.ReadLines(indexFile).ToList()[0];
if (sourcePath.StartsWith("//!source "))
{
var path = sourcePath.Split(' ')[1];
var sourceFile = Path.GetFullPath(Path.Combine(newFilePath, path.Replace("'", "")));
if (File.Exists(sourceFile))
{
currentPath = sourceFile;
}
}
else Debug.LogWarning(partialInstructions);
}
else
{
var nameWoExt = Path.GetFileNameWithoutExtension(filePath);
if ((packageInfo = PackageInfo.FindForAssetPath(filePath)) != null)
currentPath = FindPackageAssetFullPath($"{nameWoExt} a:all", fileName, packageInfo).FirstOrDefault();
else
{
appPath = Application.dataPath.Remove(Application.dataPath.Length - "Assets".Length, "Assets".Length);
currentPath = Path.Combine(appPath, filePath);
}
}
if (currentPath == null)
{
Debug.LogWarning($"Could not find source file for {filePath}");
continue;
}
if (currentPath.EndsWith(".sass"))
newPath = currentPath.Replace(".sass", ".uss");
else if (currentPath.EndsWith(".scss"))
newPath = currentPath.Replace(".scss", ".uss");
if (debug)
{
Debug.Log($"currentPath: {currentPath}");
Debug.Log($"newPath: {newPath}");
}
if (!Directory.Exists(Path.GetDirectoryName(currentPath)))
Debug.LogWarning($"File not found at path: {currentPath}. " +
"If the file in a locally sourced package, but outside of the " +
"Assets or Packages folder, well, I don't know what to tell you.");
try
{
#if UNITY_EDITOR_LINUX || UNITY_EDITOR_OSX
var sassCommand = $"-c '{pathToSass}/sass --style expanded --no-source-map {currentPath} {newPath}'";
#else
// -- I don't have a windows machine in which to test this, if someone tries it, let me know -----
var sassCommand = $"--style expanded --no-source-map {currentPath} {newPath}";
#endif
ProcessStartInfo startInfo = new ProcessStartInfo
{
#if UNITY_EDITOR_LINUX || UNITY_EDITOR_OSX
FileName = "/bin/bash",
#else
// -- I don't have a windows machine in which to test this, if someone tries it, let me know -----
FileName = "sass.exe",
#endif
Arguments = sassCommand,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true
};
using (var process = new Process())
{
process.StartInfo = startInfo;
process.EnableRaisingEvents = false;
_ = process.Start();
}
Debug.Log("Sass compilation completed");
// --| Attempt to load the new USS file
AssetDatabase.Refresh();
var relativePath = newPath.Remove(0, appPath.Length);
if (!AssetDatabase.LoadAssetAtPath<StyleSheet>(relativePath))
{
// --| Refresh and import new USS file ---
AssetDatabase.Refresh();
}
// --| Reloads the USS file so that the changes take effect via hot-reload
EditorApplication.delayCall += () =>
{
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(relativePath);
if (styleSheet != null)
{
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(styleSheet), ImportAssetOptions.ForceUpdate);
Debug.Log($"USS file reloaded: {styleSheet.name}");
}
};
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
// --| Helper Methods ------------------------
// -- Found in com.unity.ide.visualstudio ----
#region Helper Methods
private const char WinSeparator = '\\';
private const char UnixSeparator = '/';
// Locate file named _index.sass in the given directory
private static string LocateIndexFile(string path)
{
var indexFile = Path.Combine(path, "_index.sass");
if (File.Exists(indexFile))
return indexFile;
var parent = Directory.GetParent(path);
if (parent == null)
return null;
return LocateIndexFile(parent.FullName);
}
private static string[] FindPackageAssetFullPath(string assetFilter, string fileFilter, PackageInfo packageInfo)
{
return AssetDatabase.FindAssets(assetFilter)
.Select(AssetDatabase.GUIDToAssetPath)
.Where(assetPath => assetPath.Contains(fileFilter))
.Select(asset => Normalize(packageInfo.resolvedPath + asset.Substring(packageInfo.assetPath.Length)))
.ToArray();
}
private static string Normalize(string path)
{
if (string.IsNullOrEmpty(path))
return path;
if (Path.DirectorySeparatorChar == WinSeparator)
path = path.Replace(UnixSeparator, WinSeparator);
if (Path.DirectorySeparatorChar == UnixSeparator)
path = path.Replace(WinSeparator, UnixSeparator);
return path.Replace(string.Concat(WinSeparator, WinSeparator), WinSeparator.ToString());
}
#endregion
const string partialInstructions = @"[Sass] When using partial sass files with this system, the _index.sass files first line must be the following:
//!source ../relative/path/to/SourceFile.sass
Example:
If the file structure is:
/Assets/MyFolder/SourceSassFile.sass
/Assets/MyFolder/Style/_index.sass
/Assets/MyFolder/Style/_firstPartial.sass
/Assets/MyFolder/Style/_otherPartial.sass
The first line of _index.sass should be:
//!source ../SourceSassFile.sass
";
}
}
#endif
# --| Main file text ----------------------------
$rootFileContent = @"
@use 'element_types'
${commentPadding}
/* ----------------------------------------------
.inspectorRoot
--header-background: rgba(21, 21, 21, 0.35)
--header-text-color: rgb(214, 242, 238)
--header-height: 25px
--alignment-center: center
flex-grow: 1
.toolTipTag
.rootHeaderLabelContainer
align-self: center
align-items: center
justify-content: center
background-color: #213434
font-size: 16px
margin-left: 0
margin-bottom: 0
margin-top: 0
padding-top: 4px
padding-bottom: 4px
color: #dcdcdc
width: 100%
border-color: rgba(21, 21, 21, 0.50)
border-width: 1px
.rootHeaderLabel
margin-bottom: 1px
color: #C4C4C4
.rootContainer
margin-left: 0
.noTopMargin
margin-top: 0
.unity-inspector-element
padding-top: 0
.defaultFields
margin-top: 2px
"@
# --| Index file contents -----------------------
$indexFileContents = @"
//!source ../${fileName}.sass
@use 'containers'
@use 'elements'
@use 'fields'
"@
# --| Container file contents -------------------
$containersFileContents = @'
/* ----------------------------------------------
/* containers
/* ----------------------------------------------
.mainContainer
padding-left: 15px
padding-right: 15px
padding-top: 3px
'@
# --| Input fields file contents ----------------
$fieldsFileContents = @'
/* ----------------------------------------------
/* fields
/* ----------------------------------------------
'@
# --| Elements file contents --------------------
$elementsFileContents = @'
/* ----------------------------------------------
/* elements
/* ----------------------------------------------
'@
@instance-id
Copy link
Author

instance-id commented Nov 1, 2021

Updated to include both Scss and Sass files, as well as automatically resolve the correct path if the file being edited/imported is located in a local package.

@instance-id
Copy link
Author

instance-id commented May 1, 2022

Note: I barely know anything about css/scss/sass, I only looked up enough to do this relatively simple task to allow me to use partial files and generate a single .uss stylesheet. I didn't look into @forward or any other keywords, and I don't know if they will work properly with this.

Updated to now allow for @use partial compilation of multiple related files into a single .uss stylesheet file. To make it simple for my own use, it checks the _index.sass file's first line for the relative path to the source/primary sass stylesheet file, which tells this compilation system exactly which file is the source and uses that

Example:

If the file structure is:
  /Assets/MyFolder/SourceSassFile.sass
  /Assets/MyFolder/Style/_index.sass
  /Assets/MyFolder/Style/_firstPartial.sass
  /Assets/MyFolder/Style/_otherPartial.sass

SourceSassFile.sass

@use 'Style' // Top line must contain use statement containing the folder name that contains your _index.sass

...
.myOtherSassData
  ...

_index.sass

// The first line of _index.sass should be the following, which tells the C# processor script where to locate the source file:
-----
line  0: //!source ../SourceSassFile.sass
line  1:
line  2: // The remaining items are @use statements which point to the rest of your partial files:
line  3: @use 'firstPartial'
line  4: @use 'otherPartial'

@instance-id
Copy link
Author

Added powershell script I use as a Rider external tool to auto-create the sass files and structure for the import/compilation script to utilize.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment