added yarn spinner package

This commit is contained in:
2026-08-14 13:27:17 +02:00
parent c140236bf7
commit c483bda3ea
573 changed files with 81459 additions and 0 deletions
@@ -0,0 +1,47 @@
#nullable enable
namespace Yarn.Unity.Editor
{
using UnityEditor;
using UnityEngine;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
[CustomPropertyDrawer(typeof(MarkupPalette.BasicMarker))]
public class BasicMarkerPropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var container = new VisualElement();
var name = property.FindPropertyRelative(nameof(MarkupPalette.BasicMarker.Marker));
var nameField = new PropertyField(name);
var showColourField = new PropertyField(property.FindPropertyRelative(nameof(MarkupPalette.BasicMarker.CustomColor)));
var colourField = new PropertyField(property.FindPropertyRelative(nameof(MarkupPalette.BasicMarker.Color)));
var boldField = new PropertyField(property.FindPropertyRelative(nameof(MarkupPalette.BasicMarker.Boldened)));
var italicsField = new PropertyField(property.FindPropertyRelative(nameof(MarkupPalette.BasicMarker.Italicised)));
var underlinedField = new PropertyField(property.FindPropertyRelative(nameof(MarkupPalette.BasicMarker.Underlined)));
var strikedField = new PropertyField(property.FindPropertyRelative(nameof(MarkupPalette.BasicMarker.Strikedthrough)));
var foldout = new Foldout { text = name.stringValue };
showColourField.RegisterCallback((ChangeEvent<bool> b) =>
{
colourField.style.display = b.newValue ? DisplayStyle.Flex : DisplayStyle.None;
});
foldout.Add(nameField);
foldout.Add(showColourField);
foldout.Add(colourField);
foldout.Add(boldField);
foldout.Add(italicsField);
foldout.Add(underlinedField);
foldout.Add(strikedField);
container.Add(foldout);
return container;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5eaeba2d10a544bf58c014880b8b71dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity/Editor/Editors/PropertyDrawers/BasicMarkerPropertyDrawer.cs
uploadId: 968678
@@ -0,0 +1,238 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Linq;
using UnityEditor;
using UnityEngine;
#nullable enable
namespace Yarn.Unity.Editor
{
[CustomPropertyDrawer(typeof(YarnProjectImporter.SerializedDeclaration))]
public class DeclarationPropertyDrawer : PropertyDrawer
{
/// <summary>
/// Draws either a property field or a label field for <paramref
/// name="property"/> at <paramref name="position"/>, depending on
/// the value of <paramref name="readOnly"/>.
/// </summary>
/// <param name="position">The rectangle in which to draw the
/// control.</param>
/// <param name="property">The property to draw a control
/// for.</param>
/// <param name="readOnly">Whether the property is read-only or
/// not.</param>
private void DrawPropertyField(Rect position, SerializedProperty property, bool readOnly, string? label = null)
{
if (label == null)
{
label = property.displayName;
}
if (readOnly)
{
switch (property.propertyType)
{
case SerializedPropertyType.Integer:
EditorGUI.LabelField(position, label, property.intValue.ToString());
break;
case SerializedPropertyType.Boolean:
var boolText = property.boolValue ? "True" : "False";
EditorGUI.LabelField(position, label, boolText);
break;
case SerializedPropertyType.Float:
EditorGUI.LabelField(position, label, property.floatValue.ToString());
break;
case SerializedPropertyType.String:
EditorGUI.LabelField(position, label, property.stringValue);
break;
case SerializedPropertyType.ObjectReference:
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUI.ObjectField(position, property);
}
break;
case SerializedPropertyType.Enum:
var displayValue = property.enumDisplayNames[property.enumValueIndex];
EditorGUI.LabelField(position, label, displayValue);
break;
}
}
else
{
switch (property.propertyType)
{
case SerializedPropertyType.String:
property.stringValue = EditorGUI.TextField(position, label, property.stringValue);
break;
case SerializedPropertyType.Float:
property.floatValue = EditorGUI.FloatField(position, label, property.floatValue);
break;
case SerializedPropertyType.Integer:
property.floatValue = EditorGUI.IntField(position, label, property.intValue);
break;
default:
// Just use a regular field for other kinds of
// properties
EditorGUI.PropertyField(position, property, new GUIContent(label));
break;
}
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
// A serialized declaration is read-only if it came from a Yarn
// script. We don't allow editing those in this panel, because
// the text of the Yarn script belongs to the user.
bool propertyIsReadOnly = property.FindPropertyRelative("sourceYarnAsset").objectReferenceValue != null;
propertyIsReadOnly |= property.FindPropertyRelative("isImplicit").boolValue;
const float leftInset = 8;
Rect RectForFieldIndex(int index, int lineCount = 1)
{
float verticalOffset = EditorGUIUtility.singleLineHeight * index + EditorGUIUtility.standardVerticalSpacing * index;
float height = EditorGUIUtility.singleLineHeight * lineCount + EditorGUIUtility.standardVerticalSpacing * (lineCount - 1);
return new Rect(
position.x + leftInset,
position.y + verticalOffset,
position.width - leftInset,
height
);
}
var foldoutPosition = RectForFieldIndex(0);
SerializedProperty nameProperty = property.FindPropertyRelative("name");
string name = nameProperty.stringValue;
if (string.IsNullOrEmpty(name))
{
name = "Variable";
}
property.isExpanded = EditorGUI.Foldout(foldoutPosition, property.isExpanded, name);
if (property.isExpanded)
{
var namePosition = RectForFieldIndex(1);
var typePosition = RectForFieldIndex(2);
var defaultValuePosition = RectForFieldIndex(3);
var descriptionPosition = RectForFieldIndex(4, 2);
var sourcePosition = RectForFieldIndex(6);
DrawPropertyField(namePosition, nameProperty, propertyIsReadOnly);
SerializedProperty typeProperty = property.FindPropertyRelative("typeName");
if (propertyIsReadOnly)
{
DrawPropertyField(typePosition, typeProperty, true);
}
else
{
var popupElements = YarnProjectImporter.SerializedDeclaration.BuiltInTypesList;
var popupElementNames = popupElements.Select(t => t.Name).ToList();
var selectedIndex = popupElementNames.IndexOf(typeProperty.stringValue);
var prefixPosition = EditorGUI.PrefixLabel(typePosition, new GUIContent("Type"));
selectedIndex = EditorGUI.Popup(prefixPosition, selectedIndex, popupElementNames.ToArray());
if (selectedIndex >= 0 && selectedIndex <= popupElementNames.Count)
{
typeProperty.stringValue = popupElementNames[selectedIndex];
}
}
SerializedProperty? defaultValueProperty;
var type = YarnProjectImporter.SerializedDeclaration.BuiltInTypesList.FirstOrDefault(t => t.Name == typeProperty.stringValue);
if (type == Types.Number)
{
defaultValueProperty = property.FindPropertyRelative("defaultValueNumber");
}
else if (type == Types.String)
{
defaultValueProperty = property.FindPropertyRelative("defaultValueString");
}
else if (type == Types.Boolean)
{
defaultValueProperty = property.FindPropertyRelative("defaultValueBool");
}
else
{
defaultValueProperty = null;
}
if (defaultValueProperty == null)
{
EditorGUI.LabelField(defaultValuePosition, "Default Value", $"Variable type {typeProperty.stringValue} is not allowed");
}
else
{
DrawPropertyField(defaultValuePosition, defaultValueProperty, propertyIsReadOnly, "Default Value");
}
// Don't use DrawPropertyField here because we want to use
// a special gui style and directly use the string value
SerializedProperty descriptionProperty = property.FindPropertyRelative("description");
if (propertyIsReadOnly)
{
descriptionPosition = EditorGUI.PrefixLabel(descriptionPosition, new GUIContent(descriptionProperty.displayName));
EditorGUI.SelectableLabel(descriptionPosition, descriptionProperty.stringValue, EditorStyles.wordWrappedLabel);
}
else
{
var wordWrappedTextField = EditorStyles.textField;
wordWrappedTextField.wordWrap = true;
descriptionProperty.stringValue = EditorGUI.TextField(descriptionPosition, descriptionProperty.displayName, descriptionProperty.stringValue, wordWrappedTextField);
}
if (!propertyIsReadOnly)
{
EditorGUI.LabelField(sourcePosition, "Declared In", "this file");
}
else
{
SerializedProperty sourceProperty = property.FindPropertyRelative("sourceYarnAsset");
EditorGUI.ObjectField(sourcePosition, "Declared In", sourceProperty.objectReferenceValue, typeof(TextAsset), false);
}
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return GetPropertyHeightImpl(property, label);
}
public static float GetPropertyHeightImpl(SerializedProperty property, GUIContent label)
{
int lines;
if (property.isExpanded)
{
lines = 7;
}
else
{
lines = 1;
}
return EditorGUIUtility.singleLineHeight * lines + EditorGUIUtility.standardVerticalSpacing * lines + 1;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 05c509d58b0f0441995b7356c9876e60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity/Editor/Editors/PropertyDrawers/DeclarationPropertyDrawer.cs
uploadId: 968678
@@ -0,0 +1,82 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using UnityEditor;
using UnityEngine;
namespace Yarn.Unity.Editor
{
[CustomPropertyDrawer(typeof(FunctionInfo))]
public class DerivedFunctionsPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
const float leftInset = 8;
Rect RectForFieldIndex(int index, int lineCount = 1)
{
float verticalOffset = EditorGUIUtility.singleLineHeight * index + EditorGUIUtility.standardVerticalSpacing * index;
float height = EditorGUIUtility.singleLineHeight * lineCount + EditorGUIUtility.standardVerticalSpacing * (lineCount - 1);
return new Rect(
position.x + leftInset,
position.y + verticalOffset,
position.width - leftInset,
height
);
}
var foldoutPosition = RectForFieldIndex(0);
SerializedProperty nameProperty = property.FindPropertyRelative("Name");
string name = nameProperty?.stringValue ?? "FUNCTION NAME";
property.isExpanded = EditorGUI.Foldout(foldoutPosition, property.isExpanded, name);
if (property.isExpanded)
{
EditorGUI.indentLevel += 1;
var typePosition = RectForFieldIndex(1);
var paramPosition = RectForFieldIndex(2);
SerializedProperty typeProperty = property.FindPropertyRelative("ReturnType");
EditorGUI.LabelField(typePosition, typeProperty?.stringValue ?? "RETURN");
SerializedProperty paramProperty = property.FindPropertyRelative("Parameters");
int count = paramProperty?.arraySize ?? 0;
if (count > 0)
{
string[] p = new string[count];
for (int i = 0; i < count; i++)
{
p[i] = paramProperty.GetArrayElementAtIndex(i).stringValue;
}
EditorGUI.LabelField(paramPosition, $"({string.Join(", ", p)})");
}
else
{
EditorGUI.LabelField(paramPosition, $"No Parameters");
}
EditorGUI.indentLevel -= 1;
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
int lines = 1;
if (property.isExpanded)
{
lines = 3;
}
return EditorGUIUtility.singleLineHeight * lines + EditorGUIUtility.standardVerticalSpacing * lines + 1;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5ff5754fb8fe74e3fa7e65606e3feefa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity/Editor/Editors/PropertyDrawers/DerivedFunctionsPropertyDrawer.cs
uploadId: 968678
@@ -0,0 +1,66 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using UnityEditor;
using UnityEngine;
namespace Yarn.Unity
{
[CustomPropertyDrawer(typeof(DialogueReference))]
public class DialogueReferenceDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
position = EditorGUI.PrefixLabel(position, label);
var projectProperty = property.FindPropertyRelative(nameof(DialogueReference.project));
var nodeNameProperty = property.FindPropertyRelative(nameof(DialogueReference.nodeName));
position = GetLineRect(position, out var projectRect);
position = GetLineRect(position, out var nodeNameRect);
EditorGUI.PropertyField(projectRect, projectProperty, GUIContent.none);
EditorGUI.PropertyField(nodeNameRect, nodeNameProperty, GUIContent.none);
}
/// <summary>
/// Given a rectangle, computes a rectangle of the same width as the
/// input with the height of a single line, taking into account vertical
/// spacing.
/// </summary>
/// <param name="input">A rectangle representing the total area
/// availalbe.</param>
/// <param name="lineRect">On return, a rectangle with the same width as
/// <paramref name="input"/> and with a single line's worth of
/// height.</param>
/// <returns>The remaining available space.</returns>
private Rect GetLineRect(Rect input, out Rect lineRect)
{
lineRect = input;
lineRect.height = EditorGUIUtility.singleLineHeight;
var remainder = input;
var offset = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
remainder.y += offset;
remainder.height -= offset;
return remainder;
}
/// <summary>
/// Returns the height, in pixels, needed for drawing the inspector for
/// a <see cref="DialogueReference"/>.
/// </summary>
/// <param name="property">A serialized property representing a <see
/// cref="DialogueReference"/> object.</param>
/// <param name="label">The label to display for <paramref
/// name="property"/>.</param>
/// <returns>The displayed height for the property.</returns>
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
const int lineCount = 2;
return lineCount * EditorGUIUtility.singleLineHeight +
(lineCount - 1) * EditorGUIUtility.standardVerticalSpacing;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a2e67f1d1bf1140a6910d4cc0225235c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity/Editor/Editors/PropertyDrawers/DialogueReferenceDrawer.cs
uploadId: 968678
@@ -0,0 +1,303 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Yarn.Unity.Attributes;
#nullable enable
namespace Yarn.Unity
{
/// <summary>
/// Property drawer for <see cref="DialogueReference"/>
/// </summary>
[CustomPropertyDrawer(typeof(YarnNodeAttribute))]
public class YarnNodeAttributeDrawer : PropertyDrawer
{
private const string NodeTextControlNamePrefix = "DialogueReference.NodeName.";
private YarnProject? lastProject;
private string? lastNodeName;
private bool referenceExists;
private bool editNodeAsText;
private bool focusNodeTextField;
private GUIContent? nodenameContent;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// -- Yarn Project asset reference
SerializedProperty projectProp;
if (this.attribute is not YarnNodeAttribute attribute)
{
throw new System.InvalidOperationException($"Internal error: attribute is not a {nameof(YarnNodeAttribute)}");
}
var propertyPathComponents = new System.Collections.Generic.Stack<string>(property.propertyPath.Split('.'));
while (true)
{
string testPath;
if (propertyPathComponents.Count == 0)
{
testPath = attribute.yarnProjectAttribute;
}
else
{
var components = new System.Collections.Generic.List<string>(propertyPathComponents);
components.Reverse();
testPath = string.Join(".", components) + "." + attribute.yarnProjectAttribute;
}
projectProp = property.serializedObject.FindProperty(testPath);
if (projectProp != null)
{
break;
}
if (propertyPathComponents.Count > 0)
{
propertyPathComponents.Pop();
}
else
{
break;
}
}
if (projectProp == null)
{
EditorGUI.HelpBox(position, $"{attribute.yarnProjectAttribute} does not exist on {property.serializedObject.targetObject.name}", MessageType.Error);
return;
}
EditorGUI.BeginProperty(position, label, property);
var controlId = GUIUtility.GetControlID(FocusType.Passive);
position = EditorGUI.PrefixLabel(position, controlId, label);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var nodeNameFieldPosition = position;
var project = projectProp.hasMultipleDifferentValues ? null : projectProp.objectReferenceValue as YarnProject;
// -- Node name drop down
// If we want to edit this nodes name as a text field, or if we have
// multiple values, or if we have no project and we don't need one,
// show a text field and not a dropdown.
if ((project == null && attribute.requiresYarnProject == false) || editNodeAsText || projectProp.hasMultipleDifferentValues)
{
var controlName = NodeTextControlNamePrefix + controlId;
// Multi-selection with different projects, just show a text
// field to edit the node name. Most of the time, it will show
// the mixed value dash (—).
GUI.SetNextControlName(controlName);
using (var change = new EditorGUI.ChangeCheckScope())
{
var currentText = property.hasMultipleDifferentValues ? "-" : property.stringValue;
currentText = EditorGUI.TextField(nodeNameFieldPosition, currentText);
if (change.changed)
{
property.stringValue = currentText;
property.serializedObject.ApplyModifiedProperties();
}
}
if (editNodeAsText)
{
if (focusNodeTextField)
{
// Focusing the text field is delayed like this because
// the control needs to exist first before we can focus
// it
focusNodeTextField = false;
EditorGUI.FocusTextInControl(controlName);
}
else if (ShouldEndEditing(controlName))
{
editNodeAsText = false;
HandleUtility.Repaint();
}
}
}
else
{
// Show a dropdown that lets the user choose a node from the
// ones present in the Yarn Project.
// If the Yarn Project is not set, this dropdown is empty and
// disabled.
if (project == null)
{
using (new EditorGUI.DisabledGroupScope(true))
{
EditorGUI.DropdownButton(nodeNameFieldPosition, GUIContent.none, FocusType.Passive);
}
}
else
{
var nodeName = property.stringValue;
var nodeNameSet = !string.IsNullOrEmpty(nodeName);
// Cached check if node exists in project
if (lastProject != project || lastNodeName != nodeName)
{
lastProject = project;
lastNodeName = nodeName;
referenceExists = project.Program.Nodes.ContainsKey(nodeName);
}
if (nodenameContent == null)
{
nodenameContent = new GUIContent();
}
// Show warning icon if not does not exist in selected project
if (nodeNameSet)
{
nodenameContent.text = nodeName;
}
else
{
nodenameContent.text = "(Choose Node)";
}
MessageType iconType = MessageType.None;
if (!nodeNameSet)
{
iconType = MessageType.Info;
}
else if (!referenceExists)
{
iconType = MessageType.Warning;
}
else
{
iconType = MessageType.None;
}
switch (iconType)
{
case MessageType.Info:
nodenameContent.image = EditorGUIUtility.isProSkin ? EditorGUIUtility.IconContent("d_console.infoicon.sml").image : EditorGUIUtility.IconContent("console.infoicon.sml").image;
break;
case MessageType.Warning:
nodenameContent.image = EditorGUIUtility.isProSkin ? EditorGUIUtility.IconContent("d_console.warnicon.sml").image : EditorGUIUtility.IconContent("console.warnicon.sml").image;
break;
default:
nodenameContent.image = null;
break;
}
var hasMixedNodeValues = property.hasMultipleDifferentValues;
EditorGUI.showMixedValue = hasMixedNodeValues;
// Generate menu with node list only when user actually opens it
if (EditorGUI.DropdownButton(nodeNameFieldPosition, nodenameContent, FocusType.Keyboard))
{
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Edit..."), false, () =>
{
editNodeAsText = true;
focusNodeTextField = true;
});
menu.AddSeparator("");
menu.AddItem(new GUIContent("<None>"), !nodeNameSet, () =>
{
property.stringValue = "";
property.serializedObject.ApplyModifiedProperties();
});
if (!referenceExists && nodeNameSet && !hasMixedNodeValues)
{
menu.AddItem(new GUIContent(nodeName + " (Missing)"), true, () =>
{
property.stringValue = nodeName;
property.serializedObject.ApplyModifiedProperties();
});
}
foreach (var node in GetNodes(project))
{
var name = node.Name;
menu.AddItem(new GUIContent(name), name == nodeName && !hasMixedNodeValues, () =>
{
property.stringValue = name;
property.serializedObject.ApplyModifiedProperties();
});
}
menu.DropDown(nodeNameFieldPosition);
}
}
}
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
private static IEnumerable<Node> GetNodes(YarnProject project)
{
foreach (var node in project.Program.Nodes.Values)
{
if (node.Name.StartsWith("$"))
{
// Skip smart variable nodes
continue;
}
bool isNodeGroup = false;
foreach (var header in node.Headers)
{
if (header.Key == Node.NodeGroupHeader)
{
// This node is part of a node group; don't include it
isNodeGroup = true;
break;
}
}
if (!isNodeGroup)
{
yield return node;
}
}
}
private static bool ShouldEndEditing(string controlName)
{
if (GUI.GetNameOfFocusedControl() != controlName)
{
return false;
}
var keyCode = Event.current.keyCode;
if (keyCode != KeyCode.Return && keyCode != KeyCode.KeypadEnter)
{
return false;
}
return true;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ac84e49b2dc3b4c65a64a401894969d2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 267061
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
packageVersion: 3.2.8
assetPath: Packages/dev.yarnspinner.unity/Editor/Editors/PropertyDrawers/YarnNodeAttributeDrawer.cs
uploadId: 968678