Last active
October 7, 2022 08:01
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# --| 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 | |
/* ---------------------------------------------- | |
'@ |
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'
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
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.