This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
using UnityEditor;
namespace Unity.VisualScripting.Analytics
{
class StateMacroSavedEvent : UnityEditor.AssetModificationProcessor
{
static string[] OnWillSaveAssets(string[] paths)
{
foreach (string path in paths)
{
var assetType = AssetDatabase.GetMainAssetTypeAtPath(path);
if (assetType == typeof(StateGraphAsset))
{
UsageAnalytics.CollectAndSend();
break;
}
}
return paths;
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(StateGraph))]
public sealed class StateGraphDescriptor : GraphDescriptor<StateGraph, GraphDescription>
{
public StateGraphDescriptor(StateGraph target) : base(target) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(StateMachine))]
public sealed class StateMachineDescriptor : MachineDescriptor<StateMachine, MachineDescription>
{
public StateMachineDescriptor(StateMachine target) : base(target) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(StateGraphAsset))]
public sealed class StateMacroDescriptor : MacroDescriptor<StateGraphAsset, MacroDescription>
{
public StateMacroDescriptor(StateGraphAsset target) : base(target) { }
}
}

View File

@@ -0,0 +1,25 @@
namespace Unity.VisualScripting
{
[GraphContextExtension(typeof(FlowGraphContext))]
public sealed class FlowGraphContextStateExtension : GraphContextExtension<FlowGraphContext>
{
public FlowGraphContextStateExtension(FlowGraphContext context) : base(context) { }
public override bool AcceptsDragAndDrop()
{
return DragAndDropUtility.Is<StateGraphAsset>();
}
public override void PerformDragAndDrop()
{
var statemacro = DragAndDropUtility.Get<StateGraphAsset>();
var stateUnit = new StateUnit(statemacro);
context.canvas.AddUnit(stateUnit, DragAndDropUtility.position);
}
public override void DrawDragAndDropPreview()
{
GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, DragAndDropUtility.Get<StateGraphAsset>().name, typeof(StateGraphAsset).Icon());
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(StateUnit))]
public class StateUnitDescriptor : NesterUnitDescriptor<StateUnit>
{
public StateUnitDescriptor(StateUnit unit) : base(unit) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Editor(typeof(StateUnit))]
public sealed class StateUnitEditor : NesterUnitEditor
{
public StateUnitEditor(Metadata metadata) : base(metadata) { }
}
}

View File

@@ -0,0 +1,41 @@
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
[Widget(typeof(StateUnit))]
public class StateUnitWidget : NestrerUnitWidget<StateUnit>, IDragAndDropHandler
{
public StateUnitWidget(FlowCanvas canvas, StateUnit unit) : base(canvas, unit) { }
public DragAndDropVisualMode dragAndDropVisualMode => DragAndDropVisualMode.Generic;
public bool AcceptsDragAndDrop()
{
return DragAndDropUtility.Is<StateGraphAsset>();
}
public void PerformDragAndDrop()
{
UndoUtility.RecordEditedObject("Drag & Drop Macro");
unit.nest.source = GraphSource.Macro;
unit.nest.macro = DragAndDropUtility.Get<StateGraphAsset>();
unit.nest.embed = null;
unit.Define();
GUI.changed = true;
}
public void UpdateDragAndDrop()
{
}
public void DrawDragAndDropPreview()
{
GraphGUI.DrawDragAndDropPreviewLabel(new Vector2(edgePosition.x, outerPosition.yMax), "Replace with: " + DragAndDropUtility.Get<StateGraphAsset>().name, typeof(StateGraphAsset).Icon());
}
public void ExitDragAndDrop()
{
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
namespace Unity.VisualScripting
{
[InitializeAfterPlugins]
public static class UnitBaseStateExtensions
{
static UnitBaseStateExtensions()
{
UnitBase.staticUnitsExtensions.Add(GetStaticOptions);
UnitBase.dynamicUnitsExtensions.Add(GetDynamicOptions);
UnitBase.contextualUnitsExtensions.Add(GetContextualOptions);
}
private static IEnumerable<IUnitOption> GetStaticOptions()
{
yield return StateUnit.WithStart().Option();
}
private static IEnumerable<IUnitOption> GetDynamicOptions()
{
var stateMacros = UnityAPI.Await(() => AssetUtility.GetAllAssetsOfType<StateGraphAsset>().ToArray());
foreach (var stateUnit in stateMacros.Select(statemacro => new StateUnit(statemacro)))
{
yield return stateUnit.Option();
}
}
private static IEnumerable<IUnitOption> GetContextualOptions(GraphReference reference)
{
yield break;
}
}
}

View File

@@ -0,0 +1,333 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
[Canvas(typeof(StateGraph))]
public sealed class StateCanvas : VisualScriptingCanvas<StateGraph>
{
public StateCanvas(StateGraph graph) : base(graph) { }
#region View
protected override bool shouldEdgePan => base.shouldEdgePan || isCreatingTransition;
#endregion
#region Drawing
protected override void DrawBackground()
{
base.DrawBackground();
if (isCreatingTransition)
{
var startRect = this.Widget(transitionSource).position;
var end = mousePosition;
Edge startEdge, endEdge;
GraphGUI.GetConnectionEdge
(
startRect.center,
end,
out startEdge,
out endEdge
);
var start = startRect.GetEdgeCenter(startEdge);
GraphGUI.DrawConnectionArrow(Color.white, start, end, startEdge, endEdge);
}
}
#endregion
#region Clipboard
public override void ShrinkCopyGroup(HashSet<IGraphElement> copyGroup)
{
copyGroup.RemoveWhere(element =>
{
if (element is IStateTransition)
{
var transition = (IStateTransition)element;
if (!copyGroup.Contains(transition.source) ||
!copyGroup.Contains(transition.destination))
{
return true;
}
}
return false;
});
}
#endregion
#region Window
public override void OnToolbarGUI()
{
if (graph.states.Any(u => u.GetException(reference) != null) || graph.transitions.Any(t => t.GetException(reference) != null))
{
if (GUILayout.Button("Clear Errors", LudiqStyles.toolbarButton))
{
foreach (var state in graph.states)
{
state.SetException(reference, null);
}
foreach (var transition in graph.transitions)
{
transition.SetException(reference, null);
}
}
}
EditorGUI.BeginChangeCheck();
BoltCore.Configuration.dimInactiveNodes = GUILayout.Toggle(BoltCore.Configuration.dimInactiveNodes, "Dim", LudiqStyles.toolbarButton);
if (EditorGUI.EndChangeCheck())
{
BoltCore.Configuration.Save();
}
base.OnToolbarGUI();
}
#endregion
#region Context
protected override void OnContext()
{
if (isCreatingTransition)
{
CancelTransition();
}
else
{
base.OnContext();
}
}
protected override IEnumerable<DropdownOption> GetContextOptions()
{
yield return new DropdownOption((Action<Vector2>)CreateFlowState, "Create Script State");
yield return new DropdownOption((Action<Vector2>)CreateSuperState, "Create Super State");
yield return new DropdownOption((Action<Vector2>)CreateAnyState, "Create Any State");
foreach (var baseOption in base.GetContextOptions())
{
yield return baseOption;
}
}
private void CreateFlowState(Vector2 position)
{
var flowState = FlowState.WithEnterUpdateExit();
if (!graph.states.Any())
{
flowState.isStart = true;
flowState.nest.embed.title = "Start";
}
AddState(flowState, position);
}
private void CreateSuperState(Vector2 position)
{
var superState = SuperState.WithStart();
if (!graph.states.Any())
{
superState.isStart = true;
superState.nest.embed.title = "Start";
}
AddState(superState, position);
}
private void CreateAnyState(Vector2 position)
{
AddState(new AnyState(), position);
}
public void AddState(IState state, Vector2 position)
{
UndoUtility.RecordEditedObject("Create State");
state.position = position;
graph.states.Add(state);
state.position -= this.Widget(state).position.size / 2;
state.position = state.position.PixelPerfect();
this.Widget(state).Reposition();
selection.Select(state);
GUI.changed = true;
}
#endregion
#region Lifecycle
public override void Close()
{
base.Close();
CancelTransition();
}
protected override void HandleHighPriorityInput()
{
if (isCreatingTransition)
{
if (e.IsMouseDrag(MouseButton.Left))
{
// Priority over lasso
e.Use();
}
else if (e.IsKeyDown(KeyCode.Escape))
{
CancelTransition();
e.Use();
}
if (e.IsMouseDown(MouseButton.Left) || e.IsMouseUp(MouseButton.Left))
{
CompleteTransitionToNewState();
e.Use();
}
}
base.HandleHighPriorityInput();
}
public void CompleteTransitionToNewState()
{
var startRect = this.Widget(transitionSource).position;
var end = mousePosition;
GraphGUI.GetConnectionEdge
(
startRect.center,
end,
out var startEdge,
out var endEdge
);
var destination = FlowState.WithEnterUpdateExit();
graph.states.Add(destination);
Vector2 offset;
var size = this.Widget(destination).position.size;
switch (endEdge)
{
case Edge.Left:
offset = new Vector2(0, -size.y / 2);
break;
case Edge.Right:
offset = new Vector2(-size.x, -size.y / 2);
break;
case Edge.Top:
offset = new Vector2(-size.x / 2, 0);
break;
case Edge.Bottom:
offset = new Vector2(-size.x / 2, -size.y);
break;
default:
throw new UnexpectedEnumValueException<Edge>(endEdge);
}
destination.position = mousePosition + offset;
destination.position = destination.position.PixelPerfect();
EndTransition(destination);
}
#endregion
#region Drag & Drop
public override bool AcceptsDragAndDrop()
{
return DragAndDropUtility.Is<ScriptGraphAsset>() || DragAndDropUtility.Is<StateGraphAsset>();
}
public override void PerformDragAndDrop()
{
if (DragAndDropUtility.Is<ScriptGraphAsset>())
{
var flowMacro = DragAndDropUtility.Get<ScriptGraphAsset>();
var flowState = new FlowState(flowMacro);
AddState(flowState, DragAndDropUtility.position);
}
else if (DragAndDropUtility.Is<StateGraphAsset>())
{
var asset = DragAndDropUtility.Get<StateGraphAsset>();
var superState = new SuperState(asset);
AddState(superState, DragAndDropUtility.position);
}
}
public override void DrawDragAndDropPreview()
{
if (DragAndDropUtility.Is<ScriptGraphAsset>())
{
GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, DragAndDropUtility.Get<ScriptGraphAsset>().name, typeof(ScriptGraphAsset).Icon());
}
else if (DragAndDropUtility.Is<StateGraphAsset>())
{
GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, DragAndDropUtility.Get<StateGraphAsset>().name, typeof(StateGraphAsset).Icon());
}
}
#endregion
#region Transition Creation
public IState transitionSource { get; set; }
public bool isCreatingTransition => transitionSource != null;
public void StartTransition(IState source)
{
transitionSource = source;
window.Focus();
}
public void EndTransition(IState destination)
{
UndoUtility.RecordEditedObject("Create State Transition");
var transition = FlowStateTransition.WithDefaultTrigger(transitionSource, destination);
graph.transitions.Add(transition);
transitionSource = null;
this.Widget(transition).BringToFront();
selection.Select(transition);
GUI.changed = true;
}
public void CancelTransition()
{
transitionSource = null;
}
#endregion
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[GraphContext(typeof(StateGraph))]
public class StateGraphContext : GraphContext<StateGraph, StateCanvas>
{
public StateGraphContext(GraphReference reference) : base(reference) { }
public override string windowTitle => "State Graph";
protected override IEnumerable<ISidebarPanelContent> SidebarPanels()
{
yield return new GraphInspectorPanel(this);
yield return new VariablesPanel(this);
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(ID)]
[PluginDependency(BoltCore.ID)]
[Product(BoltProduct.ID)]
[PluginRuntimeAssembly("Unity." + ID)]
public sealed class BoltState : Plugin
{
[RenamedFrom("Bolt.State")]
public const string ID = "VisualScripting.State";
public BoltState() : base()
{
instance = this;
}
public static BoltState instance { get; private set; }
public static BoltStateManifest Manifest => (BoltStateManifest)instance.manifest;
public static BoltStateConfiguration Configuration => (BoltStateConfiguration)instance.configuration;
public static BoltStateResources Resources => (BoltStateResources)instance.resources;
public static BoltStateResources.Icons Icons => Resources.icons;
public const string LegacyRuntimeDllGuid = "dcd2196c4e9166f499793f2007fcda35";
public const string LegacyEditorDllGuid = "25cf173c22a896d44ae550407b10ed98";
public override IEnumerable<ScriptReferenceReplacement> scriptReferenceReplacements
{
get
{
#pragma warning disable 618
yield return ScriptReferenceReplacement.From<StateMachine>(ScriptReference.Dll(LegacyRuntimeDllGuid, "Bolt", "StateMachine"));
yield return ScriptReferenceReplacement.From<StateGraphAsset>(ScriptReference.Dll(LegacyRuntimeDllGuid, "Bolt", "StateMacro"));
#pragma warning restore 618
}
}
}
}

View File

@@ -0,0 +1,35 @@
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
public sealed class BoltStateConfiguration : PluginConfiguration
{
private BoltStateConfiguration(BoltState plugin) : base(plugin) { }
public override string header => "State Graphs";
/// <summary>
/// Determines under which condition events should be shown in state nodes.
/// </summary>
[EditorPref]
public StateRevealCondition statesReveal { get; set; } = StateRevealCondition.Always;
/// <summary>
/// Determines under which condition event names should be shown in state transition.
/// </summary>
[EditorPref]
public StateRevealCondition transitionsReveal { get; set; } = StateRevealCondition.OnHoverWithAlt;
/// <summary>
/// Whether state transitions should show an arrow at their destination state. This can appear confusing when there are
/// multiple transitions.
/// </summary>
[EditorPref]
public bool transitionsEndArrow { get; set; } = false;
/// <summary>
/// Whether traversed transitions should show a droplet animation.
/// </summary>
[EditorPref]
public bool animateTransitions { get; set; } = true;
}
}

View File

@@ -0,0 +1,13 @@
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
public sealed class BoltStateManifest : PluginManifest
{
private BoltStateManifest(BoltState plugin) : base(plugin) { }
public override string name => "Visual Scripting State";
public override string author => "";
public override string description => "State-machine based visual scripting.";
public override SemanticVersion version => "1.5.1";
}
}

View File

@@ -0,0 +1,36 @@
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
public sealed class BoltStateResources : PluginResources
{
private BoltStateResources(BoltState plugin) : base(plugin)
{
icons = new Icons(this);
}
public Icons icons { get; private set; }
public override void LateInitialize()
{
icons.Load();
}
public class Icons
{
public Icons(BoltStateResources resources)
{
this.resources = resources;
}
private readonly BoltStateResources resources;
public EditorTexture graph { get; private set; }
public EditorTexture state { get; private set; }
public void Load()
{
graph = typeof(StateGraph).Icon();
state = typeof(State).Icon();
}
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_0_0 : PluginChangelog
{
public Changelog_1_0_0(Plugin plugin) : base(plugin) { }
public override string description => "Initial Release";
public override SemanticVersion version => "1.0.0";
public override DateTime date => new DateTime(2017, 07, 26);
public override IEnumerable<string> changes => Enumerable.Empty<string>();
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_0_1 : PluginChangelog
{
public Changelog_1_0_1(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.0.1";
public override DateTime date => new DateTime(2017, 08, 01);
public override IEnumerable<string> changes
{
get
{
yield return "[Fixed] State header icon size on retina displays";
yield return "[Fixed] Pasting into state transition";
yield return "[Fixed] Transition events not being triggered from state entry";
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_0_2 : PluginChangelog
{
public Changelog_1_0_2(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.0.2";
public override DateTime date => new DateTime(2017, 09, 08);
public override IEnumerable<string> changes
{
get
{
yield return "[Fixed] Order-of-operations issues with transitions and updates";
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_1_1 : PluginChangelog
{
public Changelog_1_1_1(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.1.1";
public override DateTime date => new DateTime(2017, 10, 10);
public override IEnumerable<string> changes
{
get
{
yield return "[Changed] Default transitions to not include Update event anymore";
yield return "[Fixed] Inactive states sometimes updating";
yield return "[Optimized] Editor recursion performance";
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_1_2 : PluginChangelog
{
public Changelog_1_1_2(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.1.2";
public override DateTime date => new DateTime(2017, 10, 16);
public override IEnumerable<string> changes
{
get
{
yield return "[Fixed] Issue with dragging";
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_1_3 : PluginChangelog
{
public Changelog_1_1_3(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.1.3";
public override DateTime date => new DateTime(2017, 10, 30);
public override IEnumerable<string> changes
{
get
{
yield return "[Fixed] Deserialization error due to nester owner being serialized";
yield return "[Fixed] Descriptor error with nested events";
yield return "[Fixed] Event listening state being serialized";
}
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_2_2 : PluginChangelog
{
public Changelog_1_2_2(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.2.2";
public override DateTime date => new DateTime(2017, 12, 04);
public override IEnumerable<string> changes
{
get
{
yield return "[Added] Any State";
yield return "[Added] Droplet animations for transitions";
}
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_2_3 : PluginChangelog
{
public Changelog_1_2_3(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.2.3";
public override DateTime date => new DateTime(2018, 01, 25);
public override IEnumerable<string> changes
{
get
{
yield return "[Added] Trigger enter / exit state events in transitions";
yield return "[Fixed] Fixed Update and Late Update not firing in super states";
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_2_4 : PluginChangelog
{
public Changelog_1_2_4(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.2.4";
public override DateTime date => new DateTime(2018, 02, 26);
public override IEnumerable<string> changes
{
get
{
yield return "[Fixed] Manual events not triggering in state units";
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_3_0 : PluginChangelog
{
public Changelog_1_3_0(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.3.0";
public override DateTime date => new DateTime(2018, 04, 06);
public override IEnumerable<string> changes
{
get
{
yield return "[Fixed] State unit relations";
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_4_0f6 : PluginChangelog
{
public Changelog_1_4_0f6(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.4.0f6";
public override DateTime date => new DateTime(2018, 09, 06);
public override IEnumerable<string> changes
{
get
{
yield return "[Fixed] On Enter State and On Exit State events not firing in super units";
}
}
}
[Plugin(BoltState.ID)]
internal class Changelog_1_4_0f10 : PluginChangelog
{
public Changelog_1_4_0f10(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.4.0f10";
public override DateTime date => new DateTime(2018, 10, 29);
public override IEnumerable<string> changes
{
get
{
yield return "[Fixed] Inactive states starting to listen after undo";
}
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Changelog_1_4_1 : PluginChangelog
{
public Changelog_1_4_1(Plugin plugin) : base(plugin) { }
public override SemanticVersion version => "1.4.1";
public override DateTime date => new DateTime(2019, 01, 22);
public override IEnumerable<string> changes
{
get
{
yield return "[Changed] Allowed state machines to receive Start, OnEnable and OnDisable events for consistency";
yield return "[Fixed] Graph data type mismatch in event listening handlers for state graphs";
yield return "[Fixed] Non instantiated state graphs showing force enter / force exit contextual menu options";
yield return "[Fixed] Live-added Any States not sending transitions";
yield return "[Fixed] Any States not exiting properly when stopping the graph";
yield return "[Fixed] Live-added start states not getting automatically entered";
yield return "[Fixed] Force Enter and Force Exit showing in Any State context menu";
}
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
[Plugin(BoltState.ID)]
internal class Migration_1_4_13_to_1_5_1 : PluginMigration
{
public Migration_1_4_13_to_1_5_1(Plugin plugin) : base(plugin)
{
order = 2;
}
public override SemanticVersion @from => "1.4.13";
public override SemanticVersion to => "1.5.1";
public override void Run()
{
plugin.configuration.Initialize();
try
{
MigrateProjectSettings();
}
#pragma warning disable 168
catch (Exception e)
#pragma warning restore 168
{
Debug.LogWarning("There was a problem migrating your Visual Scripting project settings. Be sure to check them in Edit -> Project Settings -> Visual Scripting");
#if VISUAL_SCRIPT_DEBUG_MIGRATION
Debug.LogError(e);
#endif
}
try
{
MigrationUtility_1_4_13_to_1_5_1.MigrateEditorPreferences(this.plugin);
}
#pragma warning disable 168
catch (Exception e)
#pragma warning restore 168
{
Debug.LogWarning("There was a problem migrating your Visual Scripting editor preferences. Be sure to check them in Edit -> Preferences -> Visual Scripting");
#if VISUAL_SCRIPT_DEBUG_MIGRATION
Debug.LogError(e);
#endif
}
}
private void MigrateProjectSettings()
{
// Bolt.State -> VisualScripting.State
var stateProjectSettings = BoltState.Configuration.projectSettings;
var boltStateProjectSettings = MigrationUtility_1_4_13_to_1_5_1.GetLegacyProjectSettingsAsset("Bolt.State");
if (boltStateProjectSettings != null)
{
MigrationUtility_1_4_13_to_1_5_1.TransferSettings(boltStateProjectSettings, stateProjectSettings);
}
}
}
[Plugin(BoltState.ID)]
internal class DeprecatedSavedVersionLoader_1_4_13_to_1_5_1 : PluginDeprecatedSavedVersionLoader
{
public DeprecatedSavedVersionLoader_1_4_13_to_1_5_1(Plugin plugin) : base(plugin) { }
public override SemanticVersion @from => "1.4.13";
public override bool Run(out SemanticVersion savedVersion)
{
var manuallyParsedVersion = MigrationUtility_1_4_13_to_1_5_1.TryManualParseSavedVersion("Bolt.State");
savedVersion = manuallyParsedVersion;
return savedVersion != "0.0.0";
}
}
}

View File

@@ -0,0 +1,41 @@
#if DISTRIBUTE_ASSEMBLIES
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Visual Scripting State (Editor)")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Unity")]
[assembly: AssemblyProduct("Visual Scripting")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("50495950-7a88-44ea-9818-505a9748a580")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
#endif

View File

@@ -0,0 +1,10 @@
namespace Unity.VisualScripting
{
[Editor(typeof(StateGraph))]
public class StateGraphEditor : GraphEditor
{
public StateGraphEditor(Metadata metadata) : base(metadata) { }
private new StateGraph graph => (StateGraph)base.graph;
}
}

View File

@@ -0,0 +1,13 @@
namespace Unity.VisualScripting
{
public enum StateRevealCondition
{
Never,
Always,
OnHover,
OnHoverWithAlt,
WhenSelected,
OnHoverOrSelected,
OnHoverWithAltOrSelected,
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(AnyState))]
public class AnyStateDescriptor : StateDescriptor<AnyState>
{
public AnyStateDescriptor(AnyState state) : base(state) { }
}
}

View File

@@ -0,0 +1,18 @@
namespace Unity.VisualScripting
{
[Widget(typeof(AnyState))]
public class AnyStateWidget : StateWidget<AnyState>
{
public AnyStateWidget(StateCanvas canvas, AnyState state) : base(canvas, state) { }
protected override NodeColorMix color => NodeColorMix.TealReadable;
protected override string summary => null;
public override bool canToggleStart => false;
public override bool canForceEnter => false;
public override bool canForceExit => false;
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(FlowState))]
public class FlowStateDescriptor : NesterStateDescriptor<FlowState>
{
public FlowStateDescriptor(FlowState state) : base(state) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Editor(typeof(FlowState))]
public sealed class FlowStateEditor : NesterStateEditor
{
public FlowStateEditor(Metadata metadata) : base(metadata) { }
}
}

View File

@@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
[Widget(typeof(FlowState))]
public sealed class FlowStateWidget : NesterStateWidget<FlowState>, IDragAndDropHandler
{
public FlowStateWidget(StateCanvas canvas, FlowState state) : base(canvas, state)
{
state.nest.beforeGraphChange += BeforeGraphChange;
state.nest.afterGraphChange += AfterGraphChange;
if (state.nest.graph != null)
{
state.nest.graph.elements.CollectionChanged += CacheEventLinesOnUnityThread;
}
}
public override void Dispose()
{
base.Dispose();
state.nest.beforeGraphChange -= BeforeGraphChange;
state.nest.afterGraphChange -= AfterGraphChange;
}
private void BeforeGraphChange()
{
if (state.nest.graph != null)
{
state.nest.graph.elements.CollectionChanged -= CacheEventLinesOnUnityThread;
}
}
private void AfterGraphChange()
{
CacheEventLinesOnUnityThread();
if (state.nest.graph != null)
{
state.nest.graph.elements.CollectionChanged += CacheEventLinesOnUnityThread;
}
}
#region Model
private List<EventLine> eventLines { get; } = new List<EventLine>();
private void CacheEventLinesOnUnityThread()
{
UnityAPI.Async(CacheEventLines);
}
private void CacheEventLines()
{
eventLines.Clear();
if (state.nest.graph != null)
{
eventLines.AddRange(state.nest.graph.units
.OfType<IEventUnit>()
.Select(e => e.GetType())
.Distinct()
.Select(eventType => new EventLine(eventType))
.OrderBy(eventLine => eventLine.content.text));
}
Reposition();
}
protected override void CacheItemFirstTime()
{
base.CacheItemFirstTime();
CacheEventLines();
}
#endregion
#region Positioning
public Dictionary<EventLine, Rect> eventLinesPositions { get; } = new Dictionary<EventLine, Rect>();
public override void CachePosition()
{
base.CachePosition();
eventLinesPositions.Clear();
var y = contentInnerPosition.y;
foreach (var eventLine in eventLines)
{
var eventLinePosition = new Rect
(
contentInnerPosition.x,
y,
contentInnerPosition.width,
eventLine.GetHeight(contentInnerPosition.width)
);
eventLinesPositions.Add(eventLine, eventLinePosition);
y += eventLinePosition.height;
}
}
protected override float GetContentHeight(float width)
{
var eventLinesHeight = 0f;
foreach (var eventLine in eventLines)
{
eventLinesHeight += eventLine.GetHeight(width);
}
return eventLinesHeight;
}
#endregion
#region Drawing
protected override bool showContent => eventLines.Count > 0;
protected override void DrawContent()
{
foreach (var eventLine in eventLines)
{
eventLine.Draw(eventLinesPositions[eventLine]);
}
}
#endregion
#region Drag & Drop
public DragAndDropVisualMode dragAndDropVisualMode => DragAndDropVisualMode.Generic;
public bool AcceptsDragAndDrop()
{
return DragAndDropUtility.Is<ScriptGraphAsset>();
}
public void PerformDragAndDrop()
{
UndoUtility.RecordEditedObject("Drag & Drop Macro");
state.nest.source = GraphSource.Macro;
state.nest.macro = DragAndDropUtility.Get<ScriptGraphAsset>();
state.nest.embed = null;
GUI.changed = true;
}
public void UpdateDragAndDrop() { }
public void DrawDragAndDropPreview()
{
GraphGUI.DrawDragAndDropPreviewLabel(new Vector2(edgePosition.x, outerPosition.yMax), "Replace with: " + DragAndDropUtility.Get<ScriptGraphAsset>().name, typeof(ScriptGraphAsset).Icon());
}
public void ExitDragAndDrop() { }
#endregion
public new static class Styles
{
static Styles()
{
eventLine = new GUIStyle(EditorStyles.label);
eventLine.wordWrap = true;
eventLine.imagePosition = ImagePosition.TextOnly; // The icon is drawn manually
eventLine.padding = new RectOffset(0, 0, 3, 3);
}
public static readonly GUIStyle eventLine;
public static readonly float spaceAroundLineIcon = 5;
}
public class EventLine
{
public EventLine(Type eventType)
{
content = new GUIContent(BoltFlowNameUtility.UnitTitle(eventType, false, true), eventType.Icon()?[IconSize.Small]);
}
public GUIContent content { get; }
public float GetHeight(float width)
{
var labelWidth = width - Styles.spaceAroundLineIcon - IconSize.Small - Styles.spaceAroundLineIcon;
return Styles.eventLine.CalcHeight(content, labelWidth);
}
public void Draw(Rect position)
{
var iconPosition = new Rect
(
position.x + Styles.spaceAroundLineIcon,
position.y + Styles.eventLine.padding.top - 1,
IconSize.Small,
IconSize.Small
);
var labelPosition = new Rect
(
iconPosition.xMax + Styles.spaceAroundLineIcon,
position.y,
position.width - Styles.spaceAroundLineIcon - iconPosition.width - Styles.spaceAroundLineIcon,
position.height
);
if (content.image != null)
{
GUI.DrawTexture(iconPosition, content.image);
}
GUI.Label(labelPosition, content, Styles.eventLine);
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Unity.VisualScripting
{
public interface IStateWidget : IGraphElementWidget
{
IState state { get; }
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Analyser(typeof(INesterState))]
public class NesterStateAnalyser<TNesterState> : StateAnalyser<TNesterState>
where TNesterState : class, INesterState
{
public NesterStateAnalyser(GraphReference reference, TNesterState state) : base(reference, state) { }
protected override IEnumerable<Warning> Warnings()
{
foreach (var baseWarning in base.Warnings())
{
yield return baseWarning;
}
if (state.childGraph == null)
{
yield return Warning.Caution("Missing nested graph.");
}
if (state.nest.hasBackgroundEmbed)
{
yield return Warning.Caution("Background embed graph detected.");
}
}
}
}

View File

@@ -0,0 +1,21 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(INesterState))]
public class NesterStateDescriptor<TNesterState> : StateDescriptor<TNesterState>
where TNesterState : class, INesterState
{
public NesterStateDescriptor(TNesterState state) : base(state) { }
[RequiresUnityAPI]
public override string Title()
{
return GraphNesterDescriptor.Title(state);
}
[RequiresUnityAPI]
public override string Summary()
{
return GraphNesterDescriptor.Summary(state);
}
}
}

View File

@@ -0,0 +1,30 @@
using UnityEngine;
namespace Unity.VisualScripting
{
[Editor(typeof(INesterState))]
public class NesterStateEditor : StateEditor
{
public NesterStateEditor(Metadata metadata) : base(metadata) { }
private Metadata nestMetadata => metadata[nameof(INesterState.nest)];
private Metadata graphMetadata => nestMetadata[nameof(IGraphNest.graph)];
protected override GraphReference headerReference => reference.ChildReference((INesterState)metadata.value, false);
protected override Metadata headerTitleMetadata => graphMetadata[nameof(IGraph.title)];
protected override Metadata headerSummaryMetadata => graphMetadata[nameof(IGraph.summary)];
protected override float GetInspectorHeight(float width)
{
return LudiqGUI.GetEditorHeight(this, nestMetadata, width);
}
protected override void OnInspectorGUI(Rect position)
{
LudiqGUI.Editor(nestMetadata, position);
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
public abstract class NesterStateWidget<TNesterState> : StateWidget<TNesterState>
where TNesterState : class, INesterState
{
protected NesterStateWidget(StateCanvas canvas, TNesterState state) : base(canvas, state) { }
protected override IEnumerable<DropdownOption> contextOptions
{
get
{
var childReference = reference.ChildReference(state, false);
if (state.childGraph != null)
{
yield return new DropdownOption((Action)(() => window.reference = childReference), "Open");
yield return new DropdownOption((Action)(() => GraphWindow.OpenTab(childReference)), "Open in new window");
}
foreach (var baseOption in base.contextOptions)
{
yield return baseOption;
}
}
}
protected override void OnDoubleClick()
{
if (state.graph.zoom == 1)
{
var childReference = reference.ChildReference(state, false);
if (childReference != null)
{
if (e.ctrlOrCmd)
{
GraphWindow.OpenTab(childReference);
}
else
{
window.reference = childReference;
}
}
e.Use();
}
else
{
base.OnDoubleClick();
}
}
}
}

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Analyser(typeof(IState))]
public class StateAnalyser<TState> : Analyser<TState, StateAnalysis>
where TState : class, IState
{
public StateAnalyser(GraphReference reference, TState target) : base(reference, target) { }
public TState state => target;
[Assigns]
protected virtual bool IsEntered()
{
using (var recursion = Recursion.New(1))
{
return IsEntered(state, recursion);
}
}
[Assigns]
protected virtual IEnumerable<Warning> Warnings()
{
if (!IsEntered())
{
yield return Warning.Info("State is never entered.");
}
}
private bool IsEntered(IState state, Recursion recursion)
{
if (state.isStart)
{
return true;
}
if (!recursion?.TryEnter(state) ?? false)
{
return false;
}
foreach (var incomingTransition in state.incomingTransitions)
{
if (IsEntered(incomingTransition.source, recursion) && incomingTransition.Analysis<StateTransitionAnalysis>(context).isTraversed)
{
recursion?.Exit(state);
return true;
}
}
recursion?.Exit(state);
return false;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Unity.VisualScripting
{
public sealed class StateAnalysis : GraphElementAnalysis
{
public bool isEntered { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
namespace Unity.VisualScripting
{
public sealed class StateDescription : GraphElementDescription
{
}
}

View File

@@ -0,0 +1,30 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(IState))]
public class StateDescriptor<TState> : Descriptor<TState, StateDescription>
where TState : class, IState
{
public StateDescriptor(TState target) : base(target) { }
public TState state => target;
[Assigns]
public override string Title()
{
return state.GetType().HumanName();
}
[Assigns]
public override string Summary()
{
return state.GetType().Summary();
}
[Assigns]
[RequiresUnityAPI]
public override EditorTexture Icon()
{
return state.GetType().Icon();
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Unity.VisualScripting
{
[Editor(typeof(IState))]
public class StateEditor : GraphElementEditor<StateGraphContext>
{
public StateEditor(Metadata metadata) : base(metadata) { }
protected IState state => (IState)element;
protected new StateDescription description => (StateDescription)base.description;
}
}

View File

@@ -0,0 +1,7 @@
namespace Unity.VisualScripting
{
public sealed class StateTransitionAnalysis : GraphElementAnalysis
{
public bool isTraversed { get; set; }
}
}

View File

@@ -0,0 +1,632 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
public abstract class StateWidget<TState> : NodeWidget<StateCanvas, TState>, IStateWidget
where TState : class, IState
{
protected StateWidget(StateCanvas canvas, TState state) : base(canvas, state)
{
minResizeSize = new Vector2(State.DefaultWidth, 0);
}
public virtual bool canForceEnter => true;
public virtual bool canForceExit => true;
public virtual bool canToggleStart => true;
#region Model
protected TState state => element;
protected IStateDebugData stateDebugData => GetDebugData<IStateDebugData>();
protected State.Data stateData => reference.hasData ? reference.GetElementData<State.Data>(state) : null;
IState IStateWidget.state => state;
protected StateDescription description { get; private set; }
protected StateAnalysis analysis => state.Analysis<StateAnalysis>(context);
protected override void CacheDescription()
{
description = state.Description<StateDescription>();
title = description.title;
summary = description.summary;
titleContent.text = " " + title;
titleContent.image = description.icon?[IconSize.Small];
summaryContent.text = summary;
Reposition();
}
#endregion
#region Lifecycle
public override void BeforeFrame()
{
base.BeforeFrame();
if (currentContentOuterHeight != targetContentOuterHeight)
{
Reposition();
}
}
public override void HandleInput()
{
if (e.IsMouseDrag(MouseButton.Left) &&
e.ctrlOrCmd &&
!canvas.isCreatingTransition)
{
if (state.canBeSource)
{
canvas.StartTransition(state);
}
else
{
Debug.LogWarning("Cannot create a transition from this state.\n");
}
e.Use();
}
else if (e.IsMouseDrag(MouseButton.Left) && canvas.isCreatingTransition)
{
e.Use();
}
else if (e.IsMouseUp(MouseButton.Left) && canvas.isCreatingTransition)
{
var source = canvas.transitionSource;
var destination = (canvas.hoveredWidget as IStateWidget)?.state;
if (destination == null)
{
canvas.CompleteTransitionToNewState();
}
else if (destination == source)
{
canvas.CancelTransition();
}
else if (destination.canBeDestination)
{
canvas.EndTransition(destination);
}
else
{
Debug.LogWarning("Cannot create a transition to this state.\n");
canvas.CancelTransition();
}
e.Use();
}
base.HandleInput();
}
#endregion
#region Contents
protected virtual string title { get; set; }
protected virtual string summary { get; set; }
private GUIContent titleContent { get; } = new GUIContent();
private GUIContent summaryContent { get; } = new GUIContent();
#endregion
#region Positioning
public override IEnumerable<IWidget> positionDependers => state.transitions.Select(transition => (IWidget)canvas.Widget(transition));
public Rect titlePosition { get; private set; }
public Rect summaryPosition { get; private set; }
public Rect contentOuterPosition { get; private set; }
public Rect contentBackgroundPosition { get; private set; }
public Rect contentInnerPosition { get; private set; }
private float targetContentOuterHeight;
private float currentContentOuterHeight;
private bool revealInitialized;
private Rect _position;
public override Rect position
{
get { return _position; }
set
{
state.position = value.position;
state.width = value.width;
}
}
public override void CachePosition()
{
var edgeOrigin = state.position;
var edgeX = edgeOrigin.x;
var edgeY = edgeOrigin.y;
var edgeWidth = state.width;
var innerOrigin = EdgeToInnerPosition(new Rect(edgeOrigin, Vector2.zero)).position;
var innerX = innerOrigin.x;
var innerY = innerOrigin.y;
var innerWidth = EdgeToInnerPosition(new Rect(0, 0, edgeWidth, 0)).width;
var innerHeight = 0f;
var y = innerY;
if (showTitle)
{
using (LudiqGUIUtility.iconSize.Override(IconSize.Small))
{
titlePosition = new Rect
(
innerX,
y,
innerWidth,
Styles.title.CalcHeight(titleContent, innerWidth)
);
y += titlePosition.height;
innerHeight += titlePosition.height;
}
}
if (showTitle && showSummary)
{
y += Styles.spaceBetweenTitleAndSummary;
innerHeight += Styles.spaceBetweenTitleAndSummary;
}
if (showSummary)
{
summaryPosition = new Rect
(
innerX,
y,
innerWidth,
Styles.summary.CalcHeight(summaryContent, innerWidth)
);
y += summaryPosition.height;
innerHeight += summaryPosition.height;
}
if (showContent)
{
var contentInnerWidth = edgeWidth - Styles.contentBackground.padding.left - Styles.contentBackground.padding.right;
targetContentOuterHeight = revealContent ? (Styles.spaceBeforeContent + Styles.contentBackground.padding.top + GetContentHeight(contentInnerWidth) + Styles.contentBackground.padding.bottom) : 0;
if (!revealInitialized)
{
currentContentOuterHeight = targetContentOuterHeight;
revealInitialized = true;
}
currentContentOuterHeight = Mathf.Lerp(currentContentOuterHeight, targetContentOuterHeight, canvas.repaintDeltaTime * Styles.contentRevealSpeed);
if (Mathf.Abs(targetContentOuterHeight - currentContentOuterHeight) < 1)
{
currentContentOuterHeight = targetContentOuterHeight;
}
contentOuterPosition = new Rect
(
edgeX,
y,
edgeWidth,
currentContentOuterHeight
);
contentBackgroundPosition = new Rect
(
0,
Styles.spaceBeforeContent,
edgeWidth,
currentContentOuterHeight - Styles.spaceBeforeContent
);
contentInnerPosition = new Rect
(
Styles.contentBackground.padding.left,
Styles.spaceBeforeContent + Styles.contentBackground.padding.top,
contentInnerWidth,
contentBackgroundPosition.height - Styles.contentBackground.padding.top
);
y += contentOuterPosition.height;
innerHeight += contentOuterPosition.height;
}
var edgeHeight = InnerToEdgePosition(new Rect(0, 0, 0, innerHeight)).height;
_position = new Rect
(
edgeX,
edgeY,
edgeWidth,
edgeHeight
);
}
protected virtual float GetContentHeight(float width) => 0;
#endregion
#region Drawing
protected virtual bool showTitle => true;
protected virtual bool showSummary => !StringUtility.IsNullOrWhiteSpace(summary);
protected virtual bool showContent => false;
protected virtual NodeColorMix baseColor => NodeColor.Gray;
protected override NodeColorMix color
{
get
{
if (stateDebugData.runtimeException != null)
{
return NodeColor.Red;
}
var color = baseColor;
if (state.isStart)
{
color = NodeColor.Green;
}
if (stateData?.isActive ?? false)
{
color = NodeColor.Blue;
}
else if (EditorApplication.isPaused)
{
if (EditorTimeBinding.frame == stateDebugData.lastEnterFrame)
{
color = NodeColor.Blue;
}
}
else
{
color.blue = Mathf.Lerp(1, 0, (EditorTimeBinding.time - stateDebugData.lastExitTime) / Styles.enterFadeDuration);
}
return color;
}
}
protected override NodeShape shape => NodeShape.Square;
private bool revealContent
{
get
{
switch (BoltState.Configuration.statesReveal)
{
case StateRevealCondition.Always:
return true;
case StateRevealCondition.Never:
return false;
case StateRevealCondition.OnHover:
return isMouseOver;
case StateRevealCondition.OnHoverWithAlt:
return isMouseOver && e.alt;
case StateRevealCondition.WhenSelected:
return selection.Contains(state);
case StateRevealCondition.OnHoverOrSelected:
return isMouseOver || selection.Contains(state);
case StateRevealCondition.OnHoverWithAltOrSelected:
return isMouseOver && e.alt || selection.Contains(state);
default:
throw new UnexpectedEnumValueException<StateRevealCondition>(BoltState.Configuration.statesReveal);
}
}
}
private bool revealedContent;
private void CheckReveal()
{
var revealContent = this.revealContent;
if (revealContent != revealedContent)
{
Reposition();
}
revealedContent = revealContent;
}
protected override bool dim
{
get
{
var dim = BoltCore.Configuration.dimInactiveNodes && !analysis.isEntered;
if (isMouseOver || isSelected)
{
dim = false;
}
return dim;
}
}
public override void DrawForeground()
{
BeginDim();
base.DrawForeground();
if (showTitle)
{
DrawTitle();
}
if (showSummary)
{
DrawSummary();
}
if (showContent)
{
DrawContentWrapped();
}
EndDim();
CheckReveal();
}
private void DrawTitle()
{
using (LudiqGUIUtility.iconSize.Override(IconSize.Small))
{
GUI.Label(titlePosition, titleContent, invertForeground ? Styles.titleInverted : Styles.title);
}
}
private void DrawSummary()
{
GUI.Label(summaryPosition, summaryContent, invertForeground ? Styles.summaryInverted : Styles.summary);
}
private void DrawContentWrapped()
{
GUI.BeginClip(contentOuterPosition);
DrawContentBackground();
DrawContent();
GUI.EndClip();
}
protected virtual void DrawContentBackground()
{
if (e.IsRepaint)
{
Styles.contentBackground.Draw(contentBackgroundPosition, false, false, false, false);
}
}
protected virtual void DrawContent() { }
#endregion
#region Selecting
public override bool canSelect => true;
#endregion
#region Dragging
protected override bool snapToGrid => BoltCore.Configuration.snapToGrid;
public override bool canDrag => true;
public override void ExpandDragGroup(HashSet<IGraphElement> dragGroup)
{
if (BoltCore.Configuration.carryChildren)
{
foreach (var transition in state.outgoingTransitions)
{
if (dragGroup.Contains(transition.destination))
{
continue;
}
dragGroup.Add(transition.destination);
canvas.Widget(transition.destination).ExpandDragGroup(dragGroup);
}
}
}
#endregion
#region Deleting
public override bool canDelete => true;
#endregion
#region Resizing
public override bool canResizeHorizontal => true;
#endregion
#region Clipboard
public override void ExpandCopyGroup(HashSet<IGraphElement> copyGroup)
{
copyGroup.UnionWith(state.transitions.Cast<IGraphElement>());
}
#endregion
#region Actions
protected override IEnumerable<DropdownOption> contextOptions
{
get
{
if (Application.isPlaying && reference.hasData)
{
if (canForceEnter)
{
yield return new DropdownOption((Action)ForceEnter, "Force Enter");
}
if (canForceExit)
{
yield return new DropdownOption((Action)ForceExit, "Force Exit");
}
}
if (canToggleStart)
{
yield return new DropdownOption((Action)ToggleStart, "Toggle Start");
}
if (state.canBeSource)
{
yield return new DropdownOption((Action)MakeTransition, "Make Transition");
}
if (state.canBeSource && state.canBeDestination)
{
yield return new DropdownOption((Action)MakeSelfTransition, "Make Self Transition");
}
foreach (var baseOption in base.contextOptions)
{
yield return baseOption;
}
}
}
private void ForceEnter()
{
using (var flow = Flow.New(reference))
{
state.OnEnter(flow, StateEnterReason.Forced);
}
}
private void ForceExit()
{
using (var flow = Flow.New(reference))
{
state.OnExit(flow, StateExitReason.Forced);
}
}
protected void MakeTransition()
{
canvas.StartTransition(state);
}
protected void MakeSelfTransition()
{
canvas.StartTransition(state);
canvas.EndTransition(state);
}
protected void ToggleStart()
{
UndoUtility.RecordEditedObject("Toggle State Start");
state.isStart = !state.isStart;
}
#endregion
public static class Styles
{
static Styles()
{
title = new GUIStyle(BoltCore.Styles.nodeLabel);
title.fontSize = 12;
title.alignment = TextAnchor.MiddleCenter;
title.wordWrap = true;
summary = new GUIStyle(BoltCore.Styles.nodeLabel);
summary.fontSize = 10;
summary.alignment = TextAnchor.MiddleCenter;
summary.wordWrap = true;
titleInverted = new GUIStyle(title);
titleInverted.normal.textColor = ColorPalette.unityBackgroundDark;
summaryInverted = new GUIStyle(summary);
summaryInverted.normal.textColor = ColorPalette.unityBackgroundDark;
contentBackground = new GUIStyle("In BigTitle");
contentBackground.padding = new RectOffset(0, 0, 4, 4);
}
public static readonly GUIStyle title;
public static readonly GUIStyle summary;
public static readonly GUIStyle titleInverted;
public static readonly GUIStyle summaryInverted;
public static readonly GUIStyle contentBackground;
public static readonly float spaceBeforeContent = 5;
public static readonly float spaceBetweenTitleAndSummary = 0;
public static readonly float enterFadeDuration = 0.5f;
public static readonly float contentRevealSpeed = 15;
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(SuperState))]
public class SuperStateDescriptor : NesterStateDescriptor<SuperState>
{
public SuperStateDescriptor(SuperState state) : base(state) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Editor(typeof(SuperState))]
public sealed class SuperStateEditor : NesterStateEditor
{
public SuperStateEditor(Metadata metadata) : base(metadata) { }
}
}

View File

@@ -0,0 +1,44 @@
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
[Widget(typeof(SuperState))]
public sealed class SuperStateWidget : NesterStateWidget<SuperState>, IDragAndDropHandler
{
public SuperStateWidget(StateCanvas canvas, SuperState state) : base(canvas, state) { }
#region Drag & Drop
public DragAndDropVisualMode dragAndDropVisualMode => DragAndDropVisualMode.Generic;
public bool AcceptsDragAndDrop()
{
return DragAndDropUtility.Is<StateGraphAsset>();
}
public void PerformDragAndDrop()
{
UndoUtility.RecordEditedObject("Drag & Drop Macro");
state.nest.source = GraphSource.Macro;
state.nest.macro = DragAndDropUtility.Get<StateGraphAsset>();
state.nest.embed = null;
GUI.changed = true;
}
public void UpdateDragAndDrop()
{
}
public void DrawDragAndDropPreview()
{
GraphGUI.DrawDragAndDropPreviewLabel(new Vector2(edgePosition.x, outerPosition.yMax), "Replace with: " + DragAndDropUtility.Get<StateGraphAsset>().name, typeof(StateGraphAsset).Icon());
}
public void ExitDragAndDrop()
{
}
#endregion
}
}

View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Linq;
namespace Unity.VisualScripting
{
[Analyser(typeof(FlowStateTransition))]
public class FlowStateTransitionAnalyser : NesterStateTransitionAnalyser<FlowStateTransition>
{
public FlowStateTransitionAnalyser(GraphReference reference, FlowStateTransition transition) : base(reference, transition) { }
protected override bool IsTraversed()
{
var graph = transition.nest.graph;
if (graph == null)
{
return false;
}
using (var recursion = Recursion.New(1))
{
foreach (var trigger in graph.GetUnitsRecursive(recursion).OfType<TriggerStateTransition>())
{
if (trigger.Analysis<UnitAnalysis>(context).isEntered)
{
return true;
}
}
}
return false;
}
protected override IEnumerable<Warning> Warnings()
{
foreach (var baseWarning in base.Warnings())
{
yield return baseWarning;
}
var graph = transition.nest.graph;
if (graph == null)
{
yield break;
}
using (var recursion = Recursion.New(1))
{
if (!graph.GetUnitsRecursive(recursion).OfType<IEventUnit>().Any())
{
yield return Warning.Caution("Transition graph is missing an event.");
}
}
using (var recursion = Recursion.New(1))
{
if (!graph.GetUnitsRecursive(recursion).OfType<TriggerStateTransition>().Any())
{
yield return Warning.Caution("Transition graph is missing a trigger unit.");
}
}
}
}
}

View File

@@ -0,0 +1,106 @@
using System.Linq;
namespace Unity.VisualScripting
{
[Descriptor(typeof(FlowStateTransition))]
public class FlowStateTransitionDescriptor : NesterStateTransitionDescriptor<FlowStateTransition>
{
public FlowStateTransitionDescriptor(FlowStateTransition transition) : base(transition) { }
public override string Title()
{
var graph = transition.nest.graph;
if (graph != null)
{
if (!StringUtility.IsNullOrWhiteSpace(graph.title))
{
return graph.title;
}
using (var recursion = Recursion.New(1))
{
var events = graph.GetUnitsRecursive(recursion).OfType<IEventUnit>().ToList();
if (events.Count == 0)
{
return "(No Event)";
}
else if (events.Count == 1)
{
return events[0].Description().title;
}
else // if (events.Count > 1)
{
return "(Multiple Events)";
}
}
}
else
{
return "(No Transition)";
}
}
public override string Summary()
{
var graph = transition.nest.graph;
if (graph != null)
{
if (!StringUtility.IsNullOrWhiteSpace(graph.summary))
{
return graph.summary;
}
using (var recursion = Recursion.New(1))
{
var events = graph.GetUnitsRecursive(recursion).OfType<IEventUnit>().ToList();
if (events.Count == 0)
{
return "Open the transition graph to add an event.";
}
else if (events.Count == 1)
{
return events[0].Description().summary;
}
else // if (events.Count > 1)
{
return "Open the transition graph to see the full transition.";
}
}
}
else
{
return "Choose a source in the graph inspector.";
}
}
public override EditorTexture Icon()
{
var graph = transition.nest.graph;
using (var recursion = Recursion.New(1))
{
if (graph != null)
{
var events = graph.GetUnitsRecursive(recursion).OfType<IEventUnit>().ToList();
if (events.Count == 1)
{
return events[0].Description().icon;
}
else
{
return typeof(IStateTransition).Icon();
}
}
else
{
return typeof(IStateTransition).Icon();
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
[Editor(typeof(FlowStateTransition))]
public sealed class FlowStateTransitionEditor : NesterStateTransitionEditor
{
public FlowStateTransitionEditor(Metadata metadata) : base(metadata) { }
}
}

View File

@@ -0,0 +1,44 @@
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
[Widget(typeof(FlowStateTransition))]
public sealed class FlowStateTransitionWidget : NesterStateTransitionWidget<FlowStateTransition>, IDragAndDropHandler
{
public FlowStateTransitionWidget(StateCanvas canvas, FlowStateTransition transition) : base(canvas, transition) { }
#region Drag & Drop
public DragAndDropVisualMode dragAndDropVisualMode => DragAndDropVisualMode.Generic;
public bool AcceptsDragAndDrop()
{
return DragAndDropUtility.Is<ScriptGraphAsset>();
}
public void PerformDragAndDrop()
{
UndoUtility.RecordEditedObject("Drag & Drop Macro");
transition.nest.source = GraphSource.Macro;
transition.nest.macro = DragAndDropUtility.Get<ScriptGraphAsset>();
transition.nest.embed = null;
GUI.changed = true;
}
public void UpdateDragAndDrop()
{
}
public void DrawDragAndDropPreview()
{
GraphGUI.DrawDragAndDropPreviewLabel(new Vector2(edgePosition.x, outerPosition.yMax), "Replace with: " + DragAndDropUtility.Get<ScriptGraphAsset>().name, typeof(ScriptGraphAsset).Icon());
}
public void ExitDragAndDrop()
{
}
#endregion
}
}

View File

@@ -0,0 +1,7 @@
namespace Unity.VisualScripting
{
public interface IStateTransitionWidget : INodeWidget
{
Edge sourceEdge { get; }
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
[Analyser(typeof(INesterStateTransition))]
public class NesterStateTransitionAnalyser<TGraphNesterStateTransition> : StateTransitionAnalyser<TGraphNesterStateTransition>
where TGraphNesterStateTransition : class, INesterStateTransition
{
public NesterStateTransitionAnalyser(GraphReference reference, TGraphNesterStateTransition transition) : base(reference, transition) { }
protected override IEnumerable<Warning> Warnings()
{
foreach (var baseWarning in base.Warnings())
{
yield return baseWarning;
}
if (transition.childGraph == null)
{
yield return Warning.Caution("Missing transition graph.");
}
if (transition.nest.hasBackgroundEmbed)
{
yield return Warning.Caution("Background embed graph detected.");
}
}
}
}

View File

@@ -0,0 +1,21 @@
namespace Unity.VisualScripting
{
[Descriptor(typeof(INesterStateTransition))]
public class NesterStateTransitionDescriptor<TNesterStateTransition> : StateTransitionDescriptor<TNesterStateTransition>
where TNesterStateTransition : class, INesterStateTransition
{
public NesterStateTransitionDescriptor(TNesterStateTransition transition) : base(transition) { }
[RequiresUnityAPI]
public override string Title()
{
return GraphNesterDescriptor.Title(transition);
}
[RequiresUnityAPI]
public override string Summary()
{
return GraphNesterDescriptor.Summary(transition);
}
}
}

View File

@@ -0,0 +1,30 @@
using UnityEngine;
namespace Unity.VisualScripting
{
[Editor(typeof(INesterStateTransition))]
public class NesterStateTransitionEditor : StateTransitionEditor
{
public NesterStateTransitionEditor(Metadata metadata) : base(metadata) { }
private Metadata nestMetadata => metadata[nameof(INesterStateTransition.nest)];
private Metadata graphMetadata => nestMetadata[nameof(IGraphNest.graph)];
protected override GraphReference headerReference => reference.ChildReference((INesterStateTransition)metadata.value, false);
protected override Metadata headerTitleMetadata => graphMetadata[nameof(IGraph.title)];
protected override Metadata headerSummaryMetadata => graphMetadata[nameof(IGraph.summary)];
protected override float GetInspectorHeight(float width)
{
return LudiqGUI.GetEditorHeight(this, nestMetadata, width);
}
protected override void OnInspectorGUI(Rect position)
{
LudiqGUI.Editor(nestMetadata, position);
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
namespace Unity.VisualScripting
{
public abstract class NesterStateTransitionWidget<TNesterStateTransition> : StateTransitionWidget<TNesterStateTransition>
where TNesterStateTransition : class, INesterStateTransition
{
protected NesterStateTransitionWidget(StateCanvas canvas, TNesterStateTransition transition) : base(canvas, transition) { }
protected override IEnumerable<DropdownOption> contextOptions
{
get
{
var childReference = reference.ChildReference(transition, false);
if (childReference != null)
{
yield return new DropdownOption((Action)(() => window.reference = childReference), "Open");
yield return new DropdownOption((Action)(() => GraphWindow.OpenTab(childReference)), "Open in new window");
}
foreach (var baseOption in base.contextOptions)
{
yield return baseOption;
}
}
}
protected override void OnDoubleClick()
{
if (transition.graph.zoom == 1)
{
var childReference = reference.ChildReference(transition, false);
if (childReference != null)
{
if (e.ctrlOrCmd)
{
GraphWindow.OpenTab(childReference);
}
else
{
window.reference = childReference;
}
}
e.Use();
}
else
{
base.OnDoubleClick();
}
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
public abstract class StateTransitionAnalyser<TStateTransition> : Analyser<TStateTransition, StateTransitionAnalysis>
where TStateTransition : IStateTransition
{
protected StateTransitionAnalyser(GraphReference reference, TStateTransition target) : base(reference, target) { }
public TStateTransition transition => target;
[Assigns]
protected virtual bool IsTraversed()
{
return true;
}
[Assigns]
protected virtual IEnumerable<Warning> Warnings()
{
if (!IsTraversed())
{
yield return Warning.Info("Transition is never traversed.");
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
public sealed class StateTransitionDescription : GraphElementDescription
{
public string label { get; set; }
public string tooltip { get; set; }
}
}

View File

@@ -0,0 +1,41 @@
namespace Unity.VisualScripting
{
public abstract class StateTransitionDescriptor<TStateTransition> : Descriptor<TStateTransition, StateTransitionDescription>
where TStateTransition : class, IStateTransition
{
protected StateTransitionDescriptor(TStateTransition target) : base(target) { }
public TStateTransition transition => target;
[Assigns]
public override string Title()
{
return "Transition";
}
[Assigns]
public override string Summary()
{
return null;
}
[Assigns]
public virtual string Label()
{
return Title();
}
[Assigns]
public virtual string Tooltip()
{
return Summary();
}
[Assigns]
[RequiresUnityAPI]
public override EditorTexture Icon()
{
return typeof(IStateTransition).Icon();
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Unity.VisualScripting
{
[Editor(typeof(IStateTransition))]
public class StateTransitionEditor : GraphElementEditor<StateGraphContext>
{
public StateTransitionEditor(Metadata metadata) : base(metadata) { }
private IStateTransition transition => (IStateTransition)element;
protected new StateTransitionDescription description => (StateTransitionDescription)base.description;
}
}

View File

@@ -0,0 +1,692 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Unity.VisualScripting
{
public abstract class StateTransitionWidget<TStateTransition> : NodeWidget<StateCanvas, TStateTransition>, IStateTransitionWidget
where TStateTransition : class, IStateTransition
{
protected StateTransitionWidget(StateCanvas canvas, TStateTransition transition) : base(canvas, transition) { }
#region Model
protected TStateTransition transition => element;
protected IStateTransitionDebugData transitionDebugData => GetDebugData<IStateTransitionDebugData>();
private StateTransitionDescription description;
private StateTransitionAnalysis analysis => transition.Analysis<StateTransitionAnalysis>(context);
protected override void CacheDescription()
{
description = transition.Description<StateTransitionDescription>();
label.text = description.label;
label.image = description.icon?[IconSize.Small];
label.tooltip = description.tooltip;
if (!revealLabel)
{
label.tooltip = label.text + ": " + label.tooltip;
}
Reposition();
}
#endregion
#region Lifecycle
public override void BeforeFrame()
{
base.BeforeFrame();
if (showDroplets)
{
GraphGUI.UpdateDroplets(canvas, droplets, transitionDebugData.lastBranchFrame, ref lastBranchTime, ref dropTime);
}
if (currentInnerWidth != targetInnerWidth)
{
Reposition();
}
}
#endregion
#region Contents
private GUIContent label { get; } = new GUIContent();
#endregion
#region Positioning
private readonly List<IStateTransition> siblingStateTransitions = new List<IStateTransition>();
private Rect sourcePosition;
private Rect destinationPosition;
private Edge sourceEdge;
private Edge entryEdge;
private Edge exitEdge;
private Edge destinationEdge;
private Vector2 sourceEdgeCenter;
private Vector2 entryEdgeCenter;
private Vector2 exitEdgeCenter;
private Vector2 destinationEdgeCenter;
private Vector2 middle;
private Rect _position;
private Rect _clippingPosition;
private float targetInnerWidth;
private float currentInnerWidth;
private bool revealInitialized;
private float minBend
{
get
{
if (transition.source != transition.destination)
{
return 15;
}
else
{
return (middle.y - canvas.Widget(transition.source).position.center.y) / 2;
}
}
}
private float relativeBend => 1 / 4f;
Edge IStateTransitionWidget.sourceEdge => sourceEdge;
public override IEnumerable<IWidget> positionDependencies
{
get
{
yield return canvas.Widget(transition.source);
yield return canvas.Widget(transition.destination);
}
}
public override IEnumerable<IWidget> positionDependers
{
get
{
// Return all sibling transitions. This is an asymetrical dependency / depender
// relation (because the siblings are not included in the dependers) to force
// repositioning of siblings while avoiding stack overflow.
foreach (var graphTransition in canvas.graph.transitions)
{
var current = transition == graphTransition;
var analog =
transition.source == graphTransition.source &&
transition.destination == graphTransition.destination;
var inverted =
transition.source == graphTransition.destination &&
transition.destination == graphTransition.source;
if (!current && (analog || inverted))
{
var widget = canvas.Widget(graphTransition);
if (widget.isPositionValid) // Avoid stack overflow
{
yield return widget;
}
}
}
}
}
public Rect iconPosition { get; private set; }
public Rect clipPosition { get; private set; }
public Rect labelInnerPosition { get; private set; }
public override Rect position
{
get { return _position; }
set { }
}
public override Rect clippingPosition => _clippingPosition;
public override void CachePositionFirstPass()
{
// Calculate the size immediately, because other transitions will rely on it for positioning
targetInnerWidth = Styles.eventIcon.fixedWidth;
var labelWidth = Styles.label.CalcSize(label).x;
var labelHeight = EditorGUIUtility.singleLineHeight;
if (revealLabel)
{
targetInnerWidth += Styles.spaceAroundIcon;
targetInnerWidth += labelWidth;
}
if (!revealInitialized)
{
currentInnerWidth = targetInnerWidth;
revealInitialized = true;
}
currentInnerWidth = Mathf.Lerp(currentInnerWidth, targetInnerWidth, canvas.repaintDeltaTime * Styles.revealSpeed);
if (Mathf.Abs(targetInnerWidth - currentInnerWidth) < 1)
{
currentInnerWidth = targetInnerWidth;
}
var innerWidth = currentInnerWidth;
var innerHeight = labelHeight;
var edgeSize = InnerToEdgePosition(new Rect(0, 0, innerWidth, innerHeight)).size;
var edgeWidth = edgeSize.x;
var edgeHeight = edgeSize.y;
_position.width = edgeWidth;
_position.height = edgeHeight;
}
public override void CachePosition()
{
var innerWidth = innerPosition.width;
var innerHeight = innerPosition.height;
var edgeWidth = edgePosition.width;
var edgeHeight = edgePosition.height;
var labelWidth = Styles.label.CalcSize(label).x;
var labelHeight = EditorGUIUtility.singleLineHeight;
sourcePosition = canvas.Widget(transition.source).position;
destinationPosition = canvas.Widget(transition.destination).position;
Vector2 sourceClosestPoint;
Vector2 destinationClosestPoint;
LudiqGUIUtility.ClosestPoints(sourcePosition, destinationPosition, out sourceClosestPoint, out destinationClosestPoint);
if (transition.destination != transition.source)
{
GraphGUI.GetConnectionEdge
(
sourceClosestPoint,
destinationClosestPoint,
out sourceEdge,
out destinationEdge
);
}
else
{
sourceEdge = Edge.Right;
destinationEdge = Edge.Left;
}
sourceEdgeCenter = sourcePosition.GetEdgeCenter(sourceEdge);
destinationEdgeCenter = destinationPosition.GetEdgeCenter(destinationEdge);
siblingStateTransitions.Clear();
var siblingIndex = 0;
// Assign one common axis for transition for all siblings,
// regardless of their inversion. The axis is arbitrarily
// chosen as the axis for the first transition.
var assignedTransitionAxis = false;
var transitionAxis = Vector2.zero;
foreach (var graphTransition in canvas.graph.transitions)
{
var current = transition == graphTransition;
var analog =
transition.source == graphTransition.source &&
transition.destination == graphTransition.destination;
var inverted =
transition.source == graphTransition.destination &&
transition.destination == graphTransition.source;
if (current)
{
siblingIndex = siblingStateTransitions.Count;
}
if (current || analog || inverted)
{
if (!assignedTransitionAxis)
{
var siblingStateTransitionDrawer = canvas.Widget<IStateTransitionWidget>(graphTransition);
transitionAxis = siblingStateTransitionDrawer.sourceEdge.Normal();
assignedTransitionAxis = true;
}
siblingStateTransitions.Add(graphTransition);
}
}
// Fix the edge case where the source and destination perfectly overlap
if (transitionAxis == Vector2.zero)
{
transitionAxis = Vector2.right;
}
// Calculate the spread axis and origin for the set of siblings
var spreadAxis = transitionAxis.Perpendicular1().Abs();
var spreadOrigin = (sourceEdgeCenter + destinationEdgeCenter) / 2;
if (transition.source == transition.destination)
{
spreadAxis = Vector2.up;
spreadOrigin = sourcePosition.GetEdgeCenter(Edge.Bottom) - Vector2.down * 10;
}
if (BoltCore.Configuration.developerMode && BoltCore.Configuration.debug)
{
Handles.BeginGUI();
Handles.color = Color.yellow;
Handles.DrawLine(spreadOrigin + spreadAxis * -1000, spreadOrigin + spreadAxis * 1000);
Handles.EndGUI();
}
// Calculate the offset of the current sibling by iterating over its predecessors
var spreadOffset = 0f;
var previousSpreadSize = 0f;
for (var i = 0; i <= siblingIndex; i++)
{
var siblingSize = canvas.Widget<IStateTransitionWidget>(siblingStateTransitions[i]).outerPosition.size;
var siblingSizeProjection = GraphGUI.SizeProjection(siblingSize, spreadOrigin, spreadAxis);
spreadOffset += previousSpreadSize / 2 + siblingSizeProjection / 2;
previousSpreadSize = siblingSizeProjection;
}
if (transition.source != transition.destination)
{
// Calculate the total spread size to center the sibling set
var totalSpreadSize = 0f;
for (var i = 0; i < siblingStateTransitions.Count; i++)
{
var siblingSize = canvas.Widget<IStateTransitionWidget>(siblingStateTransitions[i]).outerPosition.size;
var siblingSizeProjection = GraphGUI.SizeProjection(siblingSize, spreadOrigin, spreadAxis);
totalSpreadSize += siblingSizeProjection;
}
spreadOffset -= totalSpreadSize / 2;
}
// Finally, calculate the positions
middle = spreadOrigin + spreadOffset * spreadAxis;
var edgeX = middle.x - edgeWidth / 2;
var edgeY = middle.y - edgeHeight / 2;
_position = new Rect
(
edgeX,
edgeY,
edgeWidth,
edgeHeight
).PixelPerfect();
var innerX = innerPosition.x;
var innerY = innerPosition.y;
_clippingPosition = _position.Encompass(sourceEdgeCenter).Encompass(destinationEdgeCenter);
if (transition.source != transition.destination)
{
entryEdge = destinationEdge;
exitEdge = sourceEdge;
}
else
{
entryEdge = sourceEdge;
exitEdge = destinationEdge;
}
entryEdgeCenter = edgePosition.GetEdgeCenter(entryEdge);
exitEdgeCenter = edgePosition.GetEdgeCenter(exitEdge);
var x = innerX;
iconPosition = new Rect
(
x,
innerY,
Styles.eventIcon.fixedWidth,
Styles.eventIcon.fixedHeight
).PixelPerfect();
x += iconPosition.width;
var clipWidth = innerWidth - (x - innerX);
clipPosition = new Rect
(
x,
edgeY,
clipWidth,
edgeHeight
).PixelPerfect();
labelInnerPosition = new Rect
(
Styles.spaceAroundIcon,
innerY - edgeY,
labelWidth,
labelHeight
).PixelPerfect();
}
#endregion
#region Drawing
protected virtual NodeColorMix baseColor => NodeColor.Gray;
protected override NodeColorMix color
{
get
{
if (transitionDebugData.runtimeException != null)
{
return NodeColor.Red;
}
var color = baseColor;
if (analysis.warnings.Count > 0)
{
var mostSevereWarning = Warning.MostSevereLevel(analysis.warnings);
switch (mostSevereWarning)
{
case WarningLevel.Error:
color = NodeColor.Red;
break;
case WarningLevel.Severe:
color = NodeColor.Orange;
break;
case WarningLevel.Caution:
color = NodeColor.Yellow;
break;
}
}
if (EditorApplication.isPlaying)
{
if (EditorApplication.isPaused)
{
if (EditorTimeBinding.frame == transitionDebugData.lastBranchFrame)
{
color = NodeColor.Blue;
}
}
else
{
color.blue = Mathf.Lerp(1, 0, (EditorTimeBinding.time - transitionDebugData.lastBranchTime) / StateWidget<IState>.Styles.enterFadeDuration);
}
}
return color;
}
}
protected override NodeShape shape => NodeShape.Hex;
private bool revealLabel
{
get
{
switch (BoltState.Configuration.transitionsReveal)
{
case StateRevealCondition.Always:
return true;
case StateRevealCondition.Never:
return false;
case StateRevealCondition.OnHover:
return isMouseOver;
case StateRevealCondition.OnHoverWithAlt:
return isMouseOver && e.alt;
case StateRevealCondition.WhenSelected:
return selection.Contains(transition);
case StateRevealCondition.OnHoverOrSelected:
return isMouseOver || selection.Contains(transition);
case StateRevealCondition.OnHoverWithAltOrSelected:
return isMouseOver && e.alt || selection.Contains(transition);
default:
throw new UnexpectedEnumValueException<StateRevealCondition>(BoltState.Configuration.transitionsReveal);
}
}
}
private bool revealedLabel;
private void CheckReveal()
{
var revealLabel = this.revealLabel;
if (revealLabel != revealedLabel)
{
Reposition();
}
revealedLabel = revealLabel;
}
protected override bool dim
{
get
{
var dim = BoltCore.Configuration.dimInactiveNodes && !(transition.source.Analysis<StateAnalysis>(context).isEntered && analysis.isTraversed);
if (isMouseOver || isSelected)
{
dim = false;
}
return dim;
}
}
public override void DrawForeground()
{
BeginDim();
base.DrawForeground();
GUI.Label(iconPosition, label, Styles.eventIcon);
GUI.BeginClip(clipPosition);
GUI.Label(labelInnerPosition, label, invertForeground ? Styles.labelInverted : Styles.label);
GUI.EndClip();
EndDim();
CheckReveal();
}
public override void DrawBackground()
{
BeginDim();
base.DrawBackground();
DrawConnection();
if (showDroplets)
{
DrawDroplets();
}
EndDim();
}
private void DrawConnection()
{
GraphGUI.DrawConnectionArrow(Color.white, sourceEdgeCenter, entryEdgeCenter, sourceEdge, entryEdge, relativeBend, minBend);
if (BoltState.Configuration.transitionsEndArrow)
{
GraphGUI.DrawConnectionArrow(Color.white, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, relativeBend, minBend);
}
else
{
GraphGUI.DrawConnection(Color.white, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, null, Vector2.zero, relativeBend, minBend);
}
}
#endregion
#region Selecting
public override bool canSelect => true;
#endregion
#region Dragging
public override bool canDrag => false;
protected override bool snapToGrid => false;
#endregion
#region Deleting
public override bool canDelete => true;
#endregion
#region Droplets
private readonly List<float> droplets = new List<float>();
private float dropTime;
private float lastBranchTime;
protected virtual bool showDroplets => BoltState.Configuration.animateTransitions;
protected virtual Vector2 GetDropletSize()
{
return BoltFlow.Icons.valuePortConnected?[12].Size() ?? 12 * Vector2.one;
}
protected virtual void DrawDroplet(Rect position)
{
GUI.DrawTexture(position, BoltFlow.Icons.valuePortConnected?[12]);
}
private void DrawDroplets()
{
foreach (var droplet in droplets)
{
Vector2 position;
if (droplet < 0.5f)
{
var t = droplet / 0.5f;
position = GraphGUI.GetPointOnConnection(t, sourceEdgeCenter, entryEdgeCenter, sourceEdge, entryEdge, relativeBend, minBend);
}
else
{
var t = (droplet - 0.5f) / 0.5f;
position = GraphGUI.GetPointOnConnection(t, exitEdgeCenter, destinationEdgeCenter, exitEdge, destinationEdge, relativeBend, minBend);
}
var size = GetDropletSize();
using (LudiqGUI.color.Override(Color.white))
{
DrawDroplet(new Rect(position.x - size.x / 2, position.y - size.y / 2, size.x, size.y));
}
}
}
#endregion
public static class Styles
{
static Styles()
{
label = new GUIStyle(BoltCore.Styles.nodeLabel);
label.alignment = TextAnchor.MiddleCenter;
label.imagePosition = ImagePosition.TextOnly;
labelInverted = new GUIStyle(label);
labelInverted.normal.textColor = ColorPalette.unityBackgroundDark;
eventIcon = new GUIStyle();
eventIcon.imagePosition = ImagePosition.ImageOnly;
eventIcon.fixedHeight = 16;
eventIcon.fixedWidth = 16;
}
public static readonly GUIStyle label;
public static readonly GUIStyle labelInverted;
public static readonly GUIStyle eventIcon;
public static readonly float spaceAroundIcon = 5;
public static readonly float revealSpeed = 15;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Unity.VisualScripting
{
[Widget(typeof(TriggerStateTransition))]
public sealed class TriggerStateTransitionWidget : UnitWidget<TriggerStateTransition>
{
public TriggerStateTransitionWidget(FlowCanvas canvas, TriggerStateTransition unit) : base(canvas, unit) { }
protected override NodeColorMix baseColor => NodeColorMix.TealReadable;
}
}

View File

@@ -0,0 +1,16 @@
{
"name": "Unity.VisualScripting.State.Editor",
"references": [
"Unity.VisualScripting.Core",
"Unity.VisualScripting.Flow",
"Unity.VisualScripting.State",
"Unity.VisualScripting.Core.Editor",
"Unity.VisualScripting.Flow.Editor"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false
}