Merge branch 'master' into add-content-tree-view

This commit is contained in:
Chandler Cox
2026-03-27 10:48:08 -05:00
310 changed files with 6339 additions and 1378 deletions

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please attach any minimal reproduction projects!
Thanks for taking the time to fill out this bug report! Please attach a minimal reproduction project if available!
- type: textarea
id: description-area
attributes:
@@ -17,19 +17,19 @@ body:
id: steps-area
attributes:
label: Steps to reproduce
description: Please provide reproduction steps if possible.
description: Please provide reproduction steps if available.
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of Flax are you running?
description: What version of Flax did you experience the bug in?
options:
- '1.8'
- '1.9'
- '1.10'
- '1.11'
- '1.12'
- master branch
default: 3
validations:

View File

@@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out a feature request!
Thank you for taking the time to submit this feature request!
- type: textarea
id: description-area
attributes:
@@ -17,6 +17,6 @@ body:
id: benefits-area
attributes:
label: Benefits
description: Please provide what benefits this feature would provide to the engine!
description: Please list what benefits this feature would provide to the engine!
validations:
required: true

View File

@@ -4,6 +4,7 @@ on: [push, pull_request]
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
DOTNET_ROLL_FORWARD: 'minor'
jobs:
@@ -19,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Setup .NET Workload
run: |
dotnet workload install android
@@ -33,4 +34,7 @@ jobs:
git lfs pull
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame

View File

@@ -4,6 +4,7 @@ on: [push, pull_request]
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
DOTNET_ROLL_FORWARD: 'minor'
jobs:
@@ -19,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
@@ -30,6 +31,9 @@ jobs:
git lfs pull
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
# Game
@@ -44,7 +48,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
@@ -55,4 +59,7 @@ jobs:
git lfs pull
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame

View File

@@ -7,6 +7,7 @@ on:
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
DOTNET_ROLL_FORWARD: 'minor'
GIT_LFS_PULL_OPTIONS: '-c lfs.concurrenttransfers=1 -c lfs.transfer.maxretries=2 -c http.version="HTTP/1.1" -c lfs.activitytimeout=60'
jobs:
@@ -27,13 +28,16 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
dotnet workload --info
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\PackageEditor.bat -arch=x64 -platform=Windows -deployOutput=Output -dotnet=8
- name: Upload
uses: actions/upload-artifact@v4
@@ -60,13 +64,16 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
dotnet workload --info
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\PackagePlatforms.bat -arch=x64 -platform=Windows -deployOutput=Output -dotnet=8
- name: Upload
uses: actions/upload-artifact@v4

View File

@@ -4,6 +4,7 @@ on: [push, pull_request]
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: false
DOTNET_ROLL_FORWARD: 'minor'
jobs:
@@ -59,7 +60,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.0.419
- name: Print .NET info
run: |
dotnet --info
@@ -70,6 +71,9 @@ jobs:
git lfs pull
- name: Build
run: |
PowerShell "(Get-Content global.json).Replace('latestMajor', 'minor') | Set-Content global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/global.json).Replace('latestMajor', 'minor') | Set-Content Source/Tools/Flax.Build/global.json"
PowerShell "(Get-Content Source/Tools/Flax.Build/Flax.Build.csproj).Replace('LatestMajor', 'Minor') | Set-Content Source/Tools/Flax.Build/Flax.Build.csproj"
.\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=8
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/HSWheel.flax LFS Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 11,
"Revision": 0,
"Build": 6807
"Build": 6809
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",

View File

@@ -184,15 +184,13 @@ public class ContentFolderTreeNode : TreeNode
}
}
bool isExpanded = isAnyChildVisible;
if (isExpanded)
if (!noFilter)
{
Expand(true);
}
else
{
Collapse(true);
bool isExpanded = isAnyChildVisible;
if (isExpanded)
Expand(true);
else
Collapse(true);
}
Visible = isThisVisible | isAnyChildVisible;

View File

@@ -52,6 +52,7 @@ namespace FlaxEditor.CustomEditors
private readonly List<CustomEditor> _children = new List<CustomEditor>();
private ValueContainer _values;
private bool _isSetBlocked;
private bool _isRebuilding;
private bool _skipChildrenRefresh;
private bool _hasValueDirty;
private bool _rebuildOnRefresh;
@@ -178,7 +179,7 @@ namespace FlaxEditor.CustomEditors
public void RebuildLayout()
{
// Skip rebuilding during init
if (CurrentCustomEditor == this)
if (CurrentCustomEditor == this || _isRebuilding)
return;
// Special case for root objects to run normal layout build
@@ -197,6 +198,7 @@ namespace FlaxEditor.CustomEditors
_parent?.RebuildLayout();
return;
}
_isRebuilding = true;
var control = layout.ContainerControl;
var parent = _parent;
var parentScrollV = (_presenter?.Panel.Parent as Panel)?.VScrollBar?.Value ?? -1;
@@ -216,6 +218,7 @@ namespace FlaxEditor.CustomEditors
// Restore scroll value
if (parentScrollV > -1 && _presenter != null && _presenter.Panel.Parent is Panel panel && panel.VScrollBar != null)
panel.VScrollBar.Value = parentScrollV;
_isRebuilding = false;
}
/// <summary>

View File

@@ -103,11 +103,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
var actors = ScriptsEditor.ParentEditor.Values;
foreach (var a in actors)
{
if (a.GetType() != requireActor.RequiredType)
{
item.Enabled = false;
break;
}
if (a.GetType() == requireActor.RequiredType)
continue;
if (requireActor.IncludeInheritedTypes && a.GetType().IsSubclassOf(requireActor.RequiredType))
continue;
item.Enabled = false;
break;
}
}
cm.AddItem(item);
@@ -739,6 +740,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
var style = FlaxEngine.GUI.Style.Current;
// Area for drag&drop scripts
var dragArea = layout.CustomContainer<DragAreaControl>();
dragArea.CustomControl.ScriptsEditor = this;
@@ -800,17 +803,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
bool hasAllRequirements = true;
if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false))
{
RequireScriptAttribute scriptAttribute = null;
foreach (var e in scriptType.GetAttributes(false))
var attribute = (RequireScriptAttribute)scriptType.GetAttributes(false).FirstOrDefault(x => x is RequireScriptAttribute);
if (attribute != null)
{
if (e is not RequireScriptAttribute requireScriptAttribute)
continue;
scriptAttribute = requireScriptAttribute;
}
if (scriptAttribute != null)
{
foreach (var type in scriptAttribute.RequiredTypes)
foreach (var type in attribute.RequiredTypes)
{
if (!type.IsSubclassOf(typeof(Script)))
continue;
@@ -825,19 +821,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
if (scriptType.HasAttribute(typeof(RequireActorAttribute), false))
{
RequireActorAttribute attribute = null;
foreach (var e in scriptType.GetAttributes(false))
{
if (e is not RequireActorAttribute requireActorAttribute)
continue;
attribute = requireActorAttribute;
break;
}
var attribute = (RequireActorAttribute)scriptType.GetAttributes(false).FirstOrDefault(x => x is RequireActorAttribute);
if (attribute != null)
{
var actor = script.Actor;
if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType))
if (actor.GetType() != attribute.RequiredType && (attribute.IncludeInheritedTypes && !actor.GetType().IsSubclassOf(attribute.RequiredType)))
{
Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Actor of type `{attribute.RequiredType}`.");
hasAllRequirements = false;
@@ -850,7 +838,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name);
var group = layout.Group(title, editor);
if (!hasAllRequirements)
group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.Statusbar.Failed;
group.Panel.HeaderTextColor = style.Statusbar.Failed;
if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0)
{
if (Editor.Instance.ProjectCache.IsGroupToggled(title))
@@ -863,9 +851,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
group.Panel.Open();
// Customize
float totalHeaderButtonsOffset = 0f;
group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType);
if (script.HasPrefabLink)
group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.ProgressNormal;
group.Panel.HeaderTextColor = style.ProgressNormal;
// Add toggle button to the group
var headerHeight = group.Panel.HeaderHeight;
@@ -889,7 +878,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
TooltipText = "Script reference.",
AutoFocus = true,
IsScrollable = false,
Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
Color = style.ForegroundGrey,
Parent = group.Panel,
Bounds = new Rectangle(scriptToggle.Right, 0.5f, headerHeight, headerHeight),
Margin = new Margin(1),
@@ -908,10 +897,35 @@ namespace FlaxEditor.CustomEditors.Dedicated
var settingsButton = group.AddSettingsButton();
settingsButton.Tag = script;
settingsButton.Clicked += OnSettingsButtonClicked;
totalHeaderButtonsOffset += settingsButton.Width + FlaxEditor.Utilities.Constants.UIMargin;
// Add script obsolete icon to the group
if (scriptType.HasAttribute(typeof(ObsoleteAttribute), false))
{
var attribute = (ObsoleteAttribute)scriptType.GetAttributes(false).First(x => x is ObsoleteAttribute);
var tooltip = "Script marked as obsolete." +
(string.IsNullOrEmpty(attribute.Message) ? "" : $"\n{attribute.Message}") +
(string.IsNullOrEmpty(attribute.DiagnosticId) ? "" : $"\n{attribute.DiagnosticId}");
var obsoleteButton = group.AddHeaderButton(tooltip, totalHeaderButtonsOffset, Editor.Instance.Icons.Info32);
obsoleteButton.Color = Color.Orange;
obsoleteButton.MouseOverColor = Color.DarkOrange;
totalHeaderButtonsOffset += obsoleteButton.Width;
}
// Show visual indicator if script only exists in prefab instance and is not part of the prefab
bool isPrefabActor = scripts.Any(s => s.Actor.HasPrefabLink);
if (isPrefabActor && script.PrefabID == Guid.Empty)
{
var prefabInstanceButton = group.AddHeaderButton("Script only exists in this prefab instance.", totalHeaderButtonsOffset, Editor.Instance.Icons.Add32);
prefabInstanceButton.Color = style.ProgressNormal;
prefabInstanceButton.MouseOverColor = style.ProgressNormal * 0.9f;
totalHeaderButtonsOffset += prefabInstanceButton.Width;
}
// Adjust margin to not overlap with other ui elements in the header
group.Panel.HeaderTextMargin = group.Panel.HeaderTextMargin with { Left = scriptDrag.Right - 12, Right = settingsButton.Width + Utilities.Constants.UIMargin };
group.Object(values, editor);
// Remove drop down arrows and containment lines if no objects in the group
if (group.Children.Count == 0)
{

View File

@@ -18,7 +18,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
public class SplineEditor : ActorEditor
{
/// <summary>
/// Storage undo spline data
/// Stores undo spline data.
/// </summary>
private struct UndoData
{
@@ -83,7 +83,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
/// <summary>
/// Edit curve options manipulate the curve as free mode
/// Edit curve options manipulate the curve as free mode.
/// </summary>
private sealed class FreeTangentMode : EditTangentOptionBase
{
@@ -98,7 +98,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
/// <summary>
/// Edit curve options to set tangents to linear
/// Edit curve options to set tangents to linear.
/// </summary>
private sealed class LinearTangentMode : EditTangentOptionBase
{
@@ -107,13 +107,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
SetKeyframeLinear(spline, index);
// change the selection to tangent parent (a spline point / keyframe)
// Change the selection to tangent parent (a spline point / keyframe)
Editor.SetSelectSplinePointNode(spline, index);
}
}
/// <summary>
/// Edit curve options to align tangents of selected spline
/// Edit curve options to align tangents of selected spline.
/// </summary>
private sealed class AlignedTangentMode : EditTangentOptionBase
{
@@ -168,8 +168,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
/// <summary>
/// Edit curve options manipulate the curve setting selected point
/// tangent in as smoothed but tangent out as linear
/// Edit curve options manipulate the curve setting selected point tangent in as smoothed but tangent out as linear.
/// </summary>
private sealed class SmoothInTangentMode : EditTangentOptionBase
{
@@ -182,8 +181,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
/// <summary>
/// Edit curve options manipulate the curve setting selected point
/// tangent in as linear but tangent out as smoothed
/// Edit curve options manipulate the curve setting selected point tangent in as linear but tangent out as smoothed.
/// </summary>
private sealed class SmoothOutTangentMode : EditTangentOptionBase
{
@@ -219,7 +217,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
var enabled = EnabledInHierarchy && tab.EnabledInHierarchy;
var style = FlaxEngine.GUI.Style.Current;
var size = Size;
var textHeight = 16.0f;
var textHeight = 30.0f;
// Make icons smaller when tabs get thinner
var iconSize = size.Y - textHeight;
var iconRect = new Rectangle((Width - iconSize) / 2, 0, iconSize, iconSize);
if (tab._mirrorIcon)
@@ -230,8 +229,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
var color = style.Foreground;
if (!enabled)
color *= 0.6f;
var textRect = new Rectangle(0, size.Y - textHeight, size.X, textHeight);
Render2D.PushClip(new Rectangle(Float2.Zero, Size));
Render2D.DrawSprite(tab._customIcon, iconRect, color);
Render2D.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center);
Render2D.DrawText(style.FontMedium, tab._customText, textRect, color, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords, 0.65f);
Render2D.PopClip();
}
}
@@ -291,18 +293,21 @@ namespace FlaxEditor.CustomEditors.Dedicated
return;
_selectedSpline = spline;
layout.Space(10);
var tabSize = 46;
//var tabSize = 46;
var tabSize = 60;
var icons = Editor.Instance.Icons;
layout.Header("Selected spline point");
var group = layout.Group("Selected Point");
_selectedPointsTabs = new Tabs
{
Height = tabSize,
TabsSize = new Float2(tabSize),
AutoTabsSize = true,
Parent = layout.ContainerControl,
Parent = group.ContainerControl,
};
// Move the group above the group containing spline points
group.Control.IndexInParent = 3;
_linearTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedLinear, "Linear", icons.SplineLinear64));
_freeTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedFree, "Free", icons.SplineFree64));
_alignedTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedAligned, "Aligned", icons.SplineAligned64));
@@ -310,13 +315,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
_smoothOutTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothOut, "Smooth Out", icons.SplineSmoothIn64, true));
_selectedPointsTabs.SelectedTabIndex = -1;
layout.Header("All spline points");
group = layout.Group("All Points");
_allPointsTabs = new Tabs
{
Height = tabSize,
TabsSize = new Float2(tabSize),
AutoTabsSize = true,
Parent = layout.ContainerControl,
Parent = group.ContainerControl,
};
_setLinearAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsLinear, "Set Linear Tangents", icons.SplineLinear64));
_setSmoothAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsSmooth, "Set Smooth Tangents", icons.SplineAligned64));

View File

@@ -690,7 +690,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
return grid;
}
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor, out FloatValueBox valueBox)
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color highlightColor, out FloatValueBox valueBox)
{
valueBox = null;
var grid = UniformGridTwoByOne(el);
@@ -701,8 +701,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
valueBox = floatEditorElement.ValueBox;
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
valueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor);
valueBox.BorderSelectedColor = borderColor;
valueBox.HighlightColor = highlightColor;
valueBox.BorderSelectedColor = highlightColor;
}
return grid;
}

View File

@@ -14,22 +14,17 @@ namespace FlaxEditor.CustomEditors.Editors
/// <summary>
/// The X axis color.
/// </summary>
public static Color AxisColorX = new Color(1.0f, 0.0f, 0.02745f, 1.0f);
public static Color AxisColorX = new Color(0.8f, 0.0f, 0.027f, 1.0f);
/// <summary>
/// The Y axis color.
/// </summary>
public static Color AxisColorY = new Color(0.239215f, 1.0f, 0.047058f, 1.0f);
public static Color AxisColorY = new Color(0.239215f, 0.65f, 0.047058f, 1.0f);
/// <summary>
/// The Z axis color.
/// </summary>
public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f);
/// <summary>
/// The axes colors grey out scale when input field is not focused.
/// </summary>
public static float AxisGreyOutFactor = 0.6f;
public static Color AxisColorZ = new Color(0.0f, 0.42352f, 0.8f, 1.0f);
/// <summary>
/// Custom editor for actor position property.
@@ -43,18 +38,20 @@ namespace FlaxEditor.CustomEditors.Editors
base.Initialize(layout);
if (XElement.ValueBox.Parent is UniformGridPanel ug)
{
ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f);
CheckLayout(ug);
}
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX;
XElement.ValueBox.Category = Utils.ValueCategory.Distance;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY;
YElement.ValueBox.Category = Utils.ValueCategory.Distance;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
XElement.ValueBox.HighlightColor = AxisColorX;
XElement.ValueBox.Category = Utils.ValueCategory.Distance;
YElement.ValueBox.HighlightColor = AxisColorY;
YElement.ValueBox.Category = Utils.ValueCategory.Distance;
ZElement.ValueBox.HighlightColor = AxisColorZ;
ZElement.ValueBox.Category = Utils.ValueCategory.Distance;
}
}
@@ -71,18 +68,20 @@ namespace FlaxEditor.CustomEditors.Editors
base.Initialize(layout);
if (XElement.ValueBox.Parent is UniformGridPanel ug)
{
ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f);
CheckLayout(ug);
}
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX;
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY;
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
XElement.ValueBox.HighlightColor = AxisColorX;
XElement.ValueBox.Category = Utils.ValueCategory.Angle;
YElement.ValueBox.HighlightColor = AxisColorY;
YElement.ValueBox.Category = Utils.ValueCategory.Angle;
ZElement.ValueBox.HighlightColor = AxisColorZ;
ZElement.ValueBox.Category = Utils.ValueCategory.Angle;
}
}
@@ -129,17 +128,19 @@ namespace FlaxEditor.CustomEditors.Editors
}
if (XElement.ValueBox.Parent is UniformGridPanel ug)
{
ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f);
CheckLayout(ug);
}
// Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f;
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
XElement.ValueBox.HighlightColor = AxisColorX;
YElement.ValueBox.HighlightColor = AxisColorY;
ZElement.ValueBox.HighlightColor = AxisColorZ;
}
/// <summary>

View File

@@ -37,25 +37,35 @@ namespace FlaxEditor.CustomEditors.Elements
public override ContainerControl ContainerControl => Panel;
/// <summary>
/// Adds utility settings button to the group header.
/// Add utility settings button to the group header.
/// </summary>
/// <returns>The created control.</returns>
public Image AddSettingsButton()
{
return AddHeaderButton("Settings", 0, Style.Current.Settings);
}
/// <summary>
/// Adds a button to the group header.
/// </summary>
/// <returns>The created control.</returns>
public Image AddHeaderButton(string tooltipText, float xOffset, SpriteHandle sprite)
{
var style = Style.Current;
const float padding = 2.0f;
var settingsButtonSize = Panel.HeaderHeight;
Panel.HeaderTextMargin = Panel.HeaderTextMargin with { Right = settingsButtonSize + Utilities.Constants.UIMargin };
; return new Image
{
TooltipText = "Settings",
TooltipText = tooltipText,
AutoFocus = true,
AnchorPreset = AnchorPresets.TopRight,
Parent = Panel,
Bounds = new Rectangle(Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize),
Bounds = new Rectangle(Panel.Width - settingsButtonSize - xOffset, padding * 0.5f, settingsButtonSize - padding, settingsButtonSize - padding),
IsScrollable = false,
Color = style.ForegroundGrey,
Margin = new Margin(1),
Brush = new SpriteBrush(style.Settings),
Brush = new SpriteBrush(sprite),
};
}
}

View File

@@ -70,9 +70,9 @@ namespace FlaxEditor.CustomEditors.GUI
UpdateSplitRect();
}
private void AutoSizeSplitter()
private void AutoSizeSplitter(bool ignoreCustomSplitterValue = false)
{
if (_hasCustomSplitterValue || !Editor.Instance.Options.Options.Interface.AutoSizePropertiesPanelSplitter)
if (_hasCustomSplitterValue && !ignoreCustomSplitterValue)
return;
Font font = Style.Current.FontMedium;
@@ -178,6 +178,21 @@ namespace FlaxEditor.CustomEditors.GUI
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _splitterRect.Contains(location))
{
if (_splitterClicked)
EndTracking();
AutoSizeSplitter(true);
return true;
}
return base.OnMouseDoubleClick(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
@@ -220,7 +235,8 @@ namespace FlaxEditor.CustomEditors.GUI
// Refresh
UpdateSplitRect();
PerformLayout(true);
AutoSizeSplitter();
if (Editor.Instance.Options.Options.Interface.AutoSizePropertiesPanelSplitter)
AutoSizeSplitter();
}
/// <inheritdoc />

View File

@@ -61,6 +61,7 @@ public class Editor : EditorModule
options.PrivateDependencies.Add("Renderer");
options.PrivateDependencies.Add("TextureTool");
options.PrivateDependencies.Add("Particles");
options.PrivateDependencies.Add("Terrain");
var platformToolsRoot = Path.Combine(FolderPath, "Cooker", "Platform");
var platformToolsRootExternal = Path.Combine(Globals.EngineRoot, "Source", "Platforms");

View File

@@ -526,6 +526,23 @@ int32 Editor::LoadProduct()
return 12;
}
// Get the last opened project path
String localCachePath;
FileSystem::GetSpecialFolderPath(SpecialFolder::AppData, localCachePath);
String editorConfigPath = localCachePath / TEXT("Flax");
String lastProjectSettingPath = editorConfigPath / TEXT("LastProject.txt");
if (!FileSystem::DirectoryExists(editorConfigPath))
FileSystem::CreateDirectory(editorConfigPath);
String lastProjectPath;
if (FileSystem::FileExists(lastProjectSettingPath))
File::ReadAllText(lastProjectSettingPath, lastProjectPath);
if (!FileSystem::DirectoryExists(lastProjectPath))
lastProjectPath = String::Empty;
// Try to open the last project when requested
if (projectPath.IsEmpty() && CommandLine::Options.LastProject.IsTrue() && !lastProjectPath.IsEmpty())
projectPath = lastProjectPath;
// Missing project case
if (projectPath.IsEmpty())
{
@@ -541,7 +558,7 @@ int32 Editor::LoadProduct()
Array<String> files;
if (FileSystem::ShowOpenFileDialog(
nullptr,
StringView::Empty,
lastProjectPath,
TEXT("Project files (*.flaxproj)\0*.flaxproj\0All files (*.*)\0*.*\0"),
false,
TEXT("Select project to open in Editor"),
@@ -625,6 +642,10 @@ int32 Editor::LoadProduct()
}
}
// Update the last opened project path
if (lastProjectPath.Compare(Project->ProjectFolderPath) != 0)
File::WriteAllText(lastProjectSettingPath, Project->ProjectFolderPath, Encoding::UTF8);
return 0;
}

View File

@@ -69,6 +69,11 @@ namespace FlaxEditor
/// </summary>
public static string WindowIcon = "Editor/EditorIcon";
/// <summary>
/// The material used for the HS color wheel.
/// </summary>
public static string HSWheelMaterial = "Editor/HSWheel";
/// <summary>
/// The window icons font.
/// </summary>

View File

@@ -1,10 +1,11 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tabs;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using System.Collections.Generic;
namespace FlaxEditor.GUI.Dialogs
{
@@ -25,15 +26,16 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="FlaxEditor.GUI.Dialogs.Dialog" />
public class ColorPickerDialog : Dialog, IColorPickerDialog
{
private const float ButtonsWidth = 60.0f;
private const float PickerMargin = 6.0f;
private const float EyedropperMargin = 8.0f;
private const float RGBAMargin = 12.0f;
private const float HSVMargin = 0.0f;
private const float ChannelsMargin = 4.0f;
private const float ChannelTextWidth = 12.0f;
private const float SavedColorButtonWidth = 20.0f;
private const float SavedColorButtonHeight = 20.0f;
private const float TabHeight = 20;
private const float ValueBoxesWidth = 100.0f;
private const float HSVRGBTextWidth = 15.0f;
private const float ColorPreviewHeight = 50.0f;
private const int SavedColorsAmount = 10;
private Color _initialValue;
private Color _value;
@@ -45,16 +47,19 @@ namespace FlaxEditor.GUI.Dialogs
private ColorValueBox.ColorPickerClosedEvent _onClosed;
private ColorSelectorWithSliders _cSelector;
private Tabs.Tabs _hsvRGBTabs;
private Tab _RGBTab;
private Panel _rgbPanel;
private FloatValueBox _cRed;
private FloatValueBox _cGreen;
private FloatValueBox _cBlue;
private FloatValueBox _cAlpha;
private Tab _hsvTab;
private Panel _hsvPanel;
private FloatValueBox _cHue;
private FloatValueBox _cSaturation;
private FloatValueBox _cValue;
private TextBox _cHex;
private Button _cCancel;
private Button _cOK;
private FloatValueBox _cAlpha;
private Button _cEyedropper;
private List<Color> _savedColors = new List<Color>();
@@ -124,97 +129,110 @@ namespace FlaxEditor.GUI.Dialogs
_savedColors = JsonSerializer.Deserialize<List<Color>>(savedColors);
// Selector
_cSelector = new ColorSelectorWithSliders(180, 18)
_cSelector = new ColorSelectorWithSliders(180, 21)
{
Location = new Float2(PickerMargin, PickerMargin),
Parent = this
};
_cSelector.ColorChanged += x => SelectedColor = x;
// Red
_cRed = new FloatValueBox(0, _cSelector.Right + PickerMargin + RGBAMargin + ChannelTextWidth, PickerMargin, 100, 0, float.MaxValue, 0.001f)
_hsvRGBTabs = new Tabs.Tabs
{
Parent = this
Location = new Float2(_cSelector.Right + 30.0f, PickerMargin),
TabsTextHorizontalAlignment = TextAlignment.Center,
Width = ValueBoxesWidth + HSVRGBTextWidth * 2.0f + ChannelsMargin * 2.0f,
Height = (FloatValueBox.DefaultHeight + ChannelsMargin) * 4 + ChannelsMargin,
Parent = this,
};
_hsvRGBTabs.TabsSize = new Float2(_hsvRGBTabs.Width * 0.5f, TabHeight);
_hsvRGBTabs.SelectedTabChanged += SelectedTabChanged;
// RGB Tab
_RGBTab = _hsvRGBTabs.AddTab(new Tab("RGB"));
_rgbPanel = new Panel(ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = _RGBTab,
};
// HSV Tab
_hsvTab = _hsvRGBTabs.AddTab(new Tab("HSV"));
_hsvPanel = new Panel(ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
Parent = _hsvTab,
};
// Red
_cRed = new FloatValueBox(0, HSVRGBTextWidth + ChannelsMargin, PickerMargin, ValueBoxesWidth, 0, float.MaxValue, 0.001f)
{
Parent = _rgbPanel,
};
_cRed.ValueChanged += OnRGBAChanged;
// Green
_cGreen = new FloatValueBox(0, _cRed.X, _cRed.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
Parent = _rgbPanel,
};
_cGreen.ValueChanged += OnRGBAChanged;
// Blue
_cBlue = new FloatValueBox(0, _cRed.X, _cGreen.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
Parent = _rgbPanel,
};
_cBlue.ValueChanged += OnRGBAChanged;
// Alpha
_cAlpha = new FloatValueBox(0, _cRed.X, _cBlue.Bottom + ChannelsMargin, _cRed.Width, 0, float.MaxValue, 0.001f)
{
Parent = this
};
_cAlpha.ValueChanged += OnRGBAChanged;
// Hue
_cHue = new FloatValueBox(0, PickerMargin + HSVMargin + ChannelTextWidth, _cSelector.Bottom + PickerMargin, 100, 0, 360)
_cHue = new FloatValueBox(0, HSVRGBTextWidth + ChannelsMargin, PickerMargin, ValueBoxesWidth)
{
Parent = this
Parent = _hsvPanel,
Category = Utils.ValueCategory.Angle,
};
_cHue.ValueChanged += OnHSVChanged;
// Saturation
_cSaturation = new FloatValueBox(0, _cHue.X, _cHue.Bottom + ChannelsMargin, _cHue.Width, 0, 100.0f, 0.1f)
{
Parent = this
Parent = _hsvPanel,
};
_cSaturation.ValueChanged += OnHSVChanged;
// Value
_cValue = new FloatValueBox(0, _cHue.X, _cSaturation.Bottom + ChannelsMargin, _cHue.Width, 0, float.MaxValue, 0.1f)
{
Parent = this
Parent = _hsvPanel,
};
_cValue.ValueChanged += OnHSVChanged;
// Set valid dialog size based on UI content
_dialogSize = Size = new Float2(_cRed.Right + PickerMargin, 300);
// Alpha
_cAlpha = new FloatValueBox(0, _hsvRGBTabs.Left + HSVRGBTextWidth + ChannelsMargin, _hsvRGBTabs.Bottom + ChannelsMargin * 4.0f, ValueBoxesWidth, 0, float.MaxValue, 0.001f)
{
Parent = this,
};
_cAlpha.ValueChanged += OnRGBAChanged;
// Hex
const float hexTextBoxWidth = 80;
_cHex = new TextBox(false, Width - hexTextBoxWidth - PickerMargin, _cSelector.Bottom + PickerMargin, hexTextBoxWidth)
_cHex = new TextBox(false, _hsvRGBTabs.Left + HSVRGBTextWidth + ChannelsMargin, _cAlpha.Bottom + ChannelsMargin * 2.0f, ValueBoxesWidth)
{
Parent = this
Parent = this,
};
_cHex.EditEnd += OnHexChanged;
// Cancel
_cCancel = new Button(Width - ButtonsWidth - PickerMargin, Height - Button.DefaultHeight - PickerMargin, ButtonsWidth)
{
Text = "Cancel",
Parent = this
};
_cCancel.Clicked += OnCancel;
// OK
_cOK = new Button(_cCancel.Left - ButtonsWidth - PickerMargin, _cCancel.Y, ButtonsWidth)
{
Text = "Ok",
Parent = this
};
_cOK.Clicked += OnSubmit;
// Set valid dialog size based on UI content
_dialogSize = Size = new Float2(_hsvRGBTabs.Right + PickerMargin, _cHex.Bottom + 40.0f + ColorPreviewHeight + PickerMargin);
// Create saved color buttons
CreateAllSaveButtons();
CreateAllSavedColorsButtons();
// Eyedropper button
var style = Style.Current;
_cEyedropper = new Button(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin)
_cEyedropper = new Button(_cSelector.BottomLeft.X, _cSelector.BottomLeft.Y - 25.0f, 25.0f, 25.0f)
{
TooltipText = "Eyedropper tool to pick a color directly from the screen",
TooltipText = "Eyedropper tool to pick a color directly from the screen.",
BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Search32),
BackgroundColor = style.Foreground,
BackgroundColorHighlighted = style.Foreground.RGBMultiplied(0.9f),
@@ -223,9 +241,6 @@ namespace FlaxEditor.GUI.Dialogs
Parent = this,
};
_cEyedropper.Clicked += OnEyedropStart;
_cEyedropper.Height = (_cValue.Bottom - _cEyedropper.Y) * 0.5f;
_cEyedropper.Width = _cEyedropper.Height;
_cEyedropper.X -= _cEyedropper.Width;
// Set initial color
SelectedColor = initialValue;
@@ -244,7 +259,7 @@ namespace FlaxEditor.GUI.Dialogs
}
}
// Set color of button to current value;
// Set color of button to current value
button.BackgroundColor = _value;
button.BackgroundColorHighlighted = _value;
button.BackgroundColorSelected = _value.RGBMultiplied(0.8f);
@@ -256,10 +271,10 @@ namespace FlaxEditor.GUI.Dialogs
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
// create new + button
if (_savedColorButtons.Count < 8)
// Create new + button
if (_savedColorButtons.Count < SavedColorsAmount)
{
var savedColorButton = new Button(PickerMargin * (_savedColorButtons.Count + 1) + SavedColorButtonWidth * _savedColorButtons.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (_savedColorButtons.Count + 1) + SavedColorButtonWidth * _savedColorButtons.Count, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
@@ -276,11 +291,32 @@ namespace FlaxEditor.GUI.Dialogs
}
}
private void SelectedTabChanged(Tabs.Tabs tabs)
{
if (_rgbPanel == null || _hsvPanel == null)
return;
switch (tabs.SelectedTabIndex)
{
// RGB
case 0:
_rgbPanel.Visible = true;
_hsvPanel.Visible = false;
break;
// HSV
case 1:
_rgbPanel.Visible = false;
_hsvPanel.Visible = true;
break;
}
}
private void OnColorPicked(Color32 colorPicked)
{
if (_activeEyedropper)
{
_activeEyedropper = false;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.Foreground;
SelectedColor = colorPicked;
ScreenUtilities.PickColorDone -= OnColorPicked;
}
@@ -289,6 +325,7 @@ namespace FlaxEditor.GUI.Dialogs
private void OnEyedropStart()
{
_activeEyedropper = true;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.BackgroundHighlighted;
ScreenUtilities.PickColor();
ScreenUtilities.PickColorDone += OnColorPicked;
}
@@ -306,6 +343,7 @@ namespace FlaxEditor.GUI.Dialogs
if (_disableEvents)
return;
_cHue.Value = Mathf.Wrap(_cHue.Value, 0f, 360f);
SelectedColor = Color.FromHSV(_cHue.Value, _cSaturation.Value / 100.0f, _cValue.Value / 100.0f, _cAlpha.Value);
}
@@ -339,64 +377,76 @@ namespace FlaxEditor.GUI.Dialogs
base.Draw();
// RGBA
var rgbaR = new Rectangle(_cRed.Left - ChannelTextWidth, _cRed.Y, 10000, _cRed.Height);
Render2D.DrawText(style.FontMedium, "R", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cGreen.Y;
Render2D.DrawText(style.FontMedium, "G", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cBlue.Y;
Render2D.DrawText(style.FontMedium, "B", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
rgbaR.Location.Y = _cAlpha.Y;
Render2D.DrawText(style.FontMedium, "A", rgbaR, textColor, TextAlignment.Near, TextAlignment.Center);
switch (_hsvRGBTabs.SelectedTabIndex)
{
// RGB
case 0:
var rgbRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _hsvRGBTabs.Top + TabHeight + PickerMargin, 10000, _cRed.Height);
Render2D.DrawText(style.FontMedium, "R", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
rgbRect.Location.Y += _cRed.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "G", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
rgbRect.Location.Y += _cRed.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "B", rgbRect, textColor, TextAlignment.Near, TextAlignment.Center);
break;
// HSV
case 1:
// Left
var hsvLeftRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _hsvRGBTabs.Top + TabHeight + PickerMargin, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "H", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvLeftRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "S", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvLeftRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "V", hsvLeftRect, textColor, TextAlignment.Near, TextAlignment.Center);
// HSV left
var hsvHl = new Rectangle(_cHue.Left - ChannelTextWidth, _cHue.Y, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "H", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHl.Location.Y = _cSaturation.Y;
Render2D.DrawText(style.FontMedium, "S", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHl.Location.Y = _cValue.Y;
Render2D.DrawText(style.FontMedium, "V", hsvHl, textColor, TextAlignment.Near, TextAlignment.Center);
// Right
var hsvRightRect = new Rectangle(_hsvRGBTabs.Right - HSVRGBTextWidth, _hsvRGBTabs.Top + TabHeight + PickerMargin, ChannelTextWidth, _cHue.Height);
Render2D.DrawText(style.FontMedium, "°", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvRightRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "%", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
hsvRightRect.Location.Y += _cHue.Height + ChannelsMargin;
Render2D.DrawText(style.FontMedium, "%", hsvRightRect, textColor, TextAlignment.Near, TextAlignment.Center);
break;
}
// HSV right
var hsvHr = new Rectangle(_cHue.Right + 2, _cHue.Y, 10000, _cHue.Height);
Render2D.DrawText(style.FontMedium, "°", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHr.Location.Y = _cSaturation.Y;
Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
hsvHr.Location.Y = _cValue.Y;
Render2D.DrawText(style.FontMedium, "%", hsvHr, textColor, TextAlignment.Near, TextAlignment.Center);
// A
var alphaHexRect = new Rectangle(_hsvRGBTabs.Left + PickerMargin, _cAlpha.Top, ChannelTextWidth, _cAlpha.Height);
Render2D.DrawText(style.FontMedium, "A", alphaHexRect, textColor, TextAlignment.Near, TextAlignment.Center);
// Hex
var hex = new Rectangle(_cHex.Left - 26, _cHex.Y, 10000, _cHex.Height);
Render2D.DrawText(style.FontMedium, "Hex", hex, textColor, TextAlignment.Near, TextAlignment.Center);
alphaHexRect.Y += _cAlpha.Height + ChannelsMargin * 2.0f;
alphaHexRect.X -= 5.0f; // "Hex" is two characters wider than the other labels so we need to adjust for that
Render2D.DrawText(style.FontMedium, "Hex", alphaHexRect, textColor, TextAlignment.Far, TextAlignment.Center);
// Color difference
var newRect = new Rectangle(_cOK.X - 3, _cHex.Bottom + PickerMargin, 130, 0);
newRect.Size.Y = 50;
Render2D.FillRectangle(newRect, Color.White);
var smallRectSize = 10;
var numHor = Mathf.FloorToInt(newRect.Width / smallRectSize);
var numVer = Mathf.FloorToInt(newRect.Height / smallRectSize);
var differenceRect = new Rectangle(_hsvRGBTabs.Left, _cHex.Bottom + 40.0f, _hsvRGBTabs.Width, ColorPreviewHeight);
// Draw checkerboard for background of color to help with transparency
Render2D.FillRectangle(differenceRect, Color.White);
var smallRectSize = 10;
var numHor = Mathf.CeilToInt(differenceRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(differenceRect.Height / smallRectSize);
Render2D.PushClip(differenceRect);
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0)
{
var rect = new Rectangle(newRect.X + smallRectSize * i, newRect.Y + smallRectSize * j, new Float2(smallRectSize));
var rect = new Rectangle(differenceRect.X + smallRectSize * i, differenceRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.FillRectangle(rect, Color.Gray);
}
}
}
Render2D.FillRectangle(newRect, _value);
Render2D.PopClip();
Render2D.FillRectangle(differenceRect, _value);
}
/// <inheritdoc />
protected override void OnShow()
{
// Auto cancel on lost focus
// Apply changes on lost focus
#if !PLATFORM_LINUX
((WindowRootControl)Root).Window.LostFocus += OnWindowLostFocus;
((WindowRootControl)Root).Window.LostFocus += OnSubmit;
#endif
base.OnShow();
@@ -409,6 +459,7 @@ namespace FlaxEditor.GUI.Dialogs
{
// Cancel eye dropping
_activeEyedropper = false;
_cEyedropper.BackgroundColor = _cEyedropper.BackgroundColorHighlighted = Style.Current.Foreground;
ScreenUtilities.PickColorDone -= OnColorPicked;
return true;
}
@@ -420,9 +471,7 @@ namespace FlaxEditor.GUI.Dialogs
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
{
return true;
}
var child = GetChildAtRecursive(location);
if (button == MouseButton.Right && child is Button b && b.Tag is Color c)
@@ -484,20 +533,20 @@ namespace FlaxEditor.GUI.Dialogs
}
_savedColorButtons.Clear();
CreateAllSaveButtons();
CreateAllSavedColorsButtons();
// Save new colors
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
}
private void CreateAllSaveButtons()
private void CreateAllSavedColorsButtons()
{
// Create saved color buttons
for (int i = 0; i < _savedColors.Count; i++)
{
var savedColor = _savedColors[i];
var savedColorButton = new Button(PickerMargin * (i + 1) + SavedColorButtonWidth * i, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (i + 1) + SavedColorButtonWidth * i, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Parent = this,
Tag = savedColor,
@@ -508,9 +557,9 @@ namespace FlaxEditor.GUI.Dialogs
savedColorButton.ButtonClicked += OnSavedColorButtonClicked;
_savedColorButtons.Add(savedColorButton);
}
if (_savedColors.Count < 8)
if (_savedColors.Count < SavedColorsAmount)
{
var savedColorButton = new Button(PickerMargin * (_savedColors.Count + 1) + SavedColorButtonWidth * _savedColors.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
var savedColorButton = new Button(PickerMargin * (_savedColors.Count + 1) + SavedColorButtonWidth * _savedColors.Count, _cHex.Bottom + 40.0f + ColorPreviewHeight * 0.5f, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
@@ -522,19 +571,6 @@ namespace FlaxEditor.GUI.Dialogs
}
}
private void OnWindowLostFocus()
{
// Auto apply color on defocus
var autoAcceptColorPickerChange = Editor.Instance.Options.Options.Interface.AutoAcceptColorPickerChange;
if (_useDynamicEditing && _initialValue != _value && _canPassLastChangeEvent && autoAcceptColorPickerChange)
{
_canPassLastChangeEvent = false;
_onChanged?.Invoke(_value, false);
}
OnCancel();
}
/// <inheritdoc />
public override void OnSubmit()
{
@@ -542,6 +578,9 @@ namespace FlaxEditor.GUI.Dialogs
return;
_disableEvents = true;
// Ensure the cursor is restored
Cursor = CursorType.Default;
// Send color event if modified
if (_value != _initialValue)
{
@@ -558,6 +597,9 @@ namespace FlaxEditor.GUI.Dialogs
return;
_disableEvents = true;
// Ensure the cursor is restored
Cursor = CursorType.Default;
// Restore color if modified
if (_useDynamicEditing && _initialValue != _value && _canPassLastChangeEvent)
{

View File

@@ -12,6 +12,13 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
public class ColorSelector : ContainerControl
{
private const String GrayedOutParamName = "GrayedOut";
/// <summary>
/// Offset value applied to mouse cursor position when the user lets go of wheel or sliders.
/// </summary>
protected const float MouseCursorOffset = 6.0f;
/// <summary>
/// The color.
/// </summary>
@@ -22,9 +29,22 @@ namespace FlaxEditor.GUI.Dialogs
/// </summary>
protected Rectangle _wheelRect;
private readonly SpriteHandle _colorWheelSprite;
private readonly MaterialBase _hsWheelMaterial;
private bool _isMouseDownWheel;
private Rectangle wheelDragRect
{
get
{
var hsv = _color.ToHSV();
float hAngle = hsv.X * Mathf.DegreesToRadians;
float hRadius = hsv.Y * _wheelRect.Width * 0.5f;
var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle));
float wheelBoxSize = IsSliding ? 9.0f : 5.0f;
return new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize));
}
}
/// <summary>
/// Occurs when selected color gets changed.
/// </summary>
@@ -78,7 +98,8 @@ namespace FlaxEditor.GUI.Dialogs
{
AutoFocus = true;
_colorWheelSprite = Editor.Instance.Icons.ColorWheel128;
_hsWheelMaterial = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.HSWheelMaterial);
_hsWheelMaterial = _hsWheelMaterial.CreateVirtualInstance();
_wheelRect = new Rectangle(0, 0, wheelSize, wheelSize);
}
@@ -165,17 +186,19 @@ namespace FlaxEditor.GUI.Dialogs
{
base.Draw();
var hsv = _color.ToHSV();
bool enabled = EnabledInHierarchy;
_hsWheelMaterial.SetParameterValue(GrayedOutParamName, enabled ? 1.0f : 0.5f);
Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray);
// Wheel
float boxExpand = (2.0f * 4.0f / 128.0f) * _wheelRect.Width;
Render2D.DrawSprite(_colorWheelSprite, _wheelRect.MakeExpanded(boxExpand), enabled ? Color.White : Color.Gray);
float hAngle = hsv.X * Mathf.DegreesToRadians;
float hRadius = hsv.Y * _wheelRect.Width * 0.5f;
var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle));
const float wheelBoxSize = 4.0f;
Render2D.DrawRectangle(new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize)), _isMouseDownWheel ? Color.Gray : Color.Black);
Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray);
Float3 hsv = _color.ToHSV();
Color hsColor = Color.FromHSV(new Float3(hsv.X, hsv.Y, 1));
Rectangle wheelRect = wheelDragRect;
Render2D.FillRectangle(wheelRect, hsColor);
Render2D.DrawRectangle(wheelRect, Color.Black, _isMouseDownWheel ? 2.0f : 1.0f);
}
/// <inheritdoc />
@@ -202,6 +225,7 @@ namespace FlaxEditor.GUI.Dialogs
if (!_isMouseDownWheel)
{
_isMouseDownWheel = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
SlidingStart?.Invoke();
}
@@ -218,6 +242,10 @@ namespace FlaxEditor.GUI.Dialogs
if (button == MouseButton.Left && _isMouseDownWheel)
{
EndMouseCapture();
// Make the cursor appear where the user expects it to be (position of selection rectangle)
Rectangle dragRect = wheelDragRect;
Root.MousePosition = dragRect.Center + MouseCursorOffset;
Cursor = CursorType.Default;
EndSliding();
return true;
}
@@ -246,10 +274,10 @@ namespace FlaxEditor.GUI.Dialogs
/// <seealso cref="ColorSelector" />
public class ColorSelectorWithSliders : ColorSelector
{
private Rectangle _slider1Rect;
private Rectangle _slider2Rect;
private bool _isMouseDownSlider1;
private bool _isMouseDownSlider2;
private Rectangle _valueSliderRect;
private Rectangle _alphaSliderRect;
private bool _isMouseDownValueSlider;
private bool _isMouseDownAlphaSlider;
/// <summary>
/// Initializes a new instance of the <see cref="ColorSelectorWithSliders"/> class.
@@ -260,26 +288,26 @@ namespace FlaxEditor.GUI.Dialogs
: base(wheelSize)
{
// Setup dimensions
const float slidersMargin = 8.0f;
_slider1Rect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize);
_slider2Rect = new Rectangle(_slider1Rect.Right + slidersMargin, _slider1Rect.Y, slidersThickness, _slider1Rect.Height);
Size = new Float2(_slider2Rect.Right, wheelSize);
const float slidersMargin = 10.0f;
_valueSliderRect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize);
_alphaSliderRect = new Rectangle(_valueSliderRect.Right + slidersMargin * 1.5f, _valueSliderRect.Y, slidersThickness, _valueSliderRect.Height);
Size = new Float2(_alphaSliderRect.Right, wheelSize);
}
/// <inheritdoc />
protected override void UpdateMouse(ref Float2 location)
{
if (_isMouseDownSlider1)
if (_isMouseDownValueSlider)
{
var hsv = _color.ToHSV();
hsv.Z = 1.0f - Mathf.Saturate((location.Y - _slider1Rect.Y) / _slider1Rect.Height);
hsv.Z = 1.0f - Mathf.Saturate((location.Y - _valueSliderRect.Y) / _valueSliderRect.Height);
Color = Color.FromHSV(hsv, _color.A);
}
else if (_isMouseDownSlider2)
else if (_isMouseDownAlphaSlider)
{
var color = _color;
color.A = 1.0f - Mathf.Saturate((location.Y - _slider2Rect.Y) / _slider2Rect.Height);
color.A = 1.0f - Mathf.Saturate((location.Y - _alphaSliderRect.Y) / _alphaSliderRect.Height);
Color = color;
}
@@ -300,32 +328,61 @@ namespace FlaxEditor.GUI.Dialogs
var hs = hsv;
hs.Z = 1.0f;
Color hsC = Color.FromHSV(hs);
const float slidersOffset = 3.0f;
const float slidersThickness = 4.0f;
// Value
float valueY = _slider2Rect.Height * (1 - hsv.Z);
var valueR = new Rectangle(_slider1Rect.X - slidersOffset, _slider1Rect.Y + valueY - slidersThickness / 2, _slider1Rect.Width + slidersOffset * 2, slidersThickness);
Render2D.FillRectangle(_slider1Rect, hsC, hsC, Color.Black, Color.Black);
Render2D.DrawRectangle(_slider1Rect, _isMouseDownSlider1 ? style.BackgroundSelected : Color.Black);
Render2D.DrawRectangle(valueR, _isMouseDownSlider1 ? Color.White : Color.Gray);
// Value slider
float valueKnobExpand = _isMouseDownValueSlider ? 10.0f : 4.0f;
float valueY = _valueSliderRect.Height * (1 - hsv.Z);
float valueKnobWidth = _valueSliderRect.Width + valueKnobExpand;
float valueKnobHeight = _isMouseDownValueSlider ? 7.0f : 4.0f;
float valueKnobX = _valueSliderRect.X - valueKnobExpand * 0.5f;
float valueKnobY = _valueSliderRect.Y + valueY - valueKnobHeight * 0.5f;
Rectangle valueKnobRect = new Rectangle(valueKnobX, valueKnobY, valueKnobWidth, valueKnobHeight);
Render2D.FillRectangle(_valueSliderRect, hsC, hsC, Color.Black, Color.Black);
// Draw one black and one white border to make the knob visible at any saturation level
Render2D.DrawRectangle(valueKnobRect, Color.White, _isMouseDownValueSlider ? 3.0f : 2.0f);
Render2D.DrawRectangle(valueKnobRect, Color.Black, _isMouseDownValueSlider ? 2.0f : 1.0f);
// Alpha
float alphaY = _slider2Rect.Height * (1 - _color.A);
var alphaR = new Rectangle(_slider2Rect.X - slidersOffset, _slider2Rect.Y + alphaY - slidersThickness / 2, _slider2Rect.Width + slidersOffset * 2, slidersThickness);
// Draw checkerboard pattern as background of alpha slider
Render2D.FillRectangle(_alphaSliderRect, Color.White);
var smallRectSize = _alphaSliderRect.Width / 2.0f;
var numHor = Mathf.CeilToInt(_alphaSliderRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(_alphaSliderRect.Height / smallRectSize);
Render2D.PushClip(_alphaSliderRect);
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0)
{
var rect = new Rectangle(_alphaSliderRect.X + smallRectSize * i, _alphaSliderRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.FillRectangle(rect, Color.Gray);
}
}
}
Render2D.PopClip();
// Alpha slider
float alphaKnobExpand = _isMouseDownAlphaSlider ? 10.0f : 4.0f;
float alphaY = _alphaSliderRect.Height * (1 - _color.A);
float alphaKnobWidth = _alphaSliderRect.Width + alphaKnobExpand;
float alphaKnobHeight = _isMouseDownAlphaSlider ? 7.0f : 4.0f;
float alphaKnobX = _alphaSliderRect.X - alphaKnobExpand * 0.5f;
float alphaKnobY = _alphaSliderRect.Y + alphaY - alphaKnobExpand * 0.5f;
Rectangle alphaKnobRect = new Rectangle(alphaKnobX, alphaKnobY, alphaKnobWidth, alphaKnobHeight);
var color = _color;
color.A = 1; // Keep slider 2 fill rect from changing color alpha while selecting.
Render2D.FillRectangle(_slider2Rect, color, color, Color.Transparent, Color.Transparent);
Render2D.DrawRectangle(_slider2Rect, _isMouseDownSlider2 ? style.BackgroundSelected : Color.Black);
Render2D.DrawRectangle(alphaR, _isMouseDownSlider2 ? Color.White : Color.Gray);
color.A = 1; // Prevent alpha slider fill from becoming transparent
Render2D.FillRectangle(_alphaSliderRect, color, color, Color.Transparent, Color.Transparent);
// Draw one black and one white border to make the knob visible at any saturation level
Render2D.DrawRectangle(alphaKnobRect, Color.White, _isMouseDownAlphaSlider ? 3.0f : 2.0f);
Render2D.DrawRectangle(alphaKnobRect, Color.Black, _isMouseDownAlphaSlider ? 2.0f : 1.0f);
}
/// <inheritdoc />
public override void OnLostFocus()
{
// Clear flags
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
base.OnLostFocus();
}
@@ -333,15 +390,17 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _slider1Rect.Contains(location))
if (button == MouseButton.Left && _valueSliderRect.Contains(location))
{
_isMouseDownSlider1 = true;
_isMouseDownValueSlider = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
UpdateMouse(ref location);
}
if (button == MouseButton.Left && _slider2Rect.Contains(location))
if (button == MouseButton.Left && _alphaSliderRect.Contains(location))
{
_isMouseDownSlider2 = true;
_isMouseDownAlphaSlider = true;
Cursor = CursorType.Hidden;
StartMouseCapture();
UpdateMouse(ref location);
}
@@ -352,10 +411,16 @@ namespace FlaxEditor.GUI.Dialogs
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && (_isMouseDownSlider1 || _isMouseDownSlider2))
if (button == MouseButton.Left && (_isMouseDownValueSlider || _isMouseDownAlphaSlider))
{
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
// Make the cursor appear where the user expects it to be (center of slider horizontally and slider knob position vertically)
float sliderCenter = _isMouseDownValueSlider ? _valueSliderRect.Center.X : _alphaSliderRect.Center.X;
// Calculate y position based on the slider knob to avoid incrementing value by a small amount when the user repeatedly clicks the slider (f.e. when moving in small steps)
float mouseSliderPosition = _isMouseDownValueSlider ? _valueSliderRect.Y + _valueSliderRect.Height * (1 - _color.ToHSV().Z) + MouseCursorOffset : _alphaSliderRect.Y + _alphaSliderRect.Height * (1 - _color.A) + MouseCursorOffset;
Root.MousePosition = new Float2(sliderCenter + MouseCursorOffset, Mathf.Clamp(mouseSliderPosition, _valueSliderRect.Top, _valueSliderRect.Bottom));
Cursor = CursorType.Default;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
EndMouseCapture();
return true;
}
@@ -367,8 +432,8 @@ namespace FlaxEditor.GUI.Dialogs
public override void OnEndMouseCapture()
{
// Clear flags
_isMouseDownSlider1 = false;
_isMouseDownSlider2 = false;
_isMouseDownValueSlider = false;
_isMouseDownAlphaSlider = false;
base.OnEndMouseCapture();
}

View File

@@ -89,6 +89,11 @@ namespace FlaxEditor.GUI.Input
/// </summary>
public bool IsSliding => _isSliding;
/// <summary>
/// The color of the highlight to the left of the value box.
/// </summary>
public Color HighlightColor;
/// <summary>
/// Occurs when sliding starts.
/// </summary>
@@ -211,6 +216,12 @@ namespace FlaxEditor.GUI.Input
Render2D.DrawRectangle(bounds, style.SelectionBorder);
}
}
if (HighlightColor != Color.Transparent)
{
var highlightRect = new Rectangle(-3.0f, 0.0f, 3.0f, Height);
Render2D.FillRectangle(highlightRect, HighlightColor);
}
}
/// <inheritdoc />

View File

@@ -2148,10 +2148,9 @@ namespace FlaxEditor.GUI.Timeline
/// </summary>
public void ShowWholeTimeline()
{
var viewWidth = Width;
var timelineWidth = Duration * UnitsPerSecond * Zoom + 8 * StartOffset;
_backgroundArea.ViewOffset = Float2.Zero;
Zoom = viewWidth / timelineWidth;
const float padding = 40f;
Zoom = (_backgroundArea.Width - padding * 2f) / (Duration * UnitsPerSecond);
_backgroundArea.ViewOffset = new Float2(-_leftEdge.X + padding, _backgroundArea.ViewOffset.Y);
}
/// <summary>

View File

@@ -0,0 +1,289 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEditor.Options;
using FlaxEditor.Viewport;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Gizmo;
[HideInEditor]
internal class DirectionGizmo : ContainerControl
{
public const float DefaultGizmoSize = 112.5f;
private const float AxisLength = 71.25f;
private const float SpriteRadius = 10.85f;
private IGizmoOwner _owner;
private ViewportProjection _viewportProjection;
private EditorViewport _viewport;
private Vector3 _gizmoCenter;
private float _gizmoBrightness;
private float _gizmoOpacity;
private float _backgroundOpacity;
private float _axisLength;
private float _spriteRadius;
private AxisData _xAxisData;
private AxisData _yAxisData;
private AxisData _zAxisData;
private AxisData _negXAxisData;
private AxisData _negYAxisData;
private AxisData _negZAxisData;
private List<AxisData> _axisData = new List<AxisData>();
private int _hoveredAxisIndex = -1;
private SpriteHandle _posHandle;
private SpriteHandle _negHandle;
private FontReference _fontReference;
// Store sprite positions for hover detection
private List<(Float2 position, AxisDirection direction)> _spritePositions = new List<(Float2, AxisDirection)>();
private struct ViewportProjection
{
private Matrix _viewProjection;
private BoundingFrustum _frustum;
private FlaxEngine.Viewport _viewport;
private Vector3 _origin;
public void Init(EditorViewport editorViewport)
{
// Inline EditorViewport.ProjectPoint to save on calculation for large set of points
_viewport = new FlaxEngine.Viewport(0, 0, editorViewport.Width, editorViewport.Height);
_frustum = editorViewport.ViewFrustum;
_viewProjection = _frustum.Matrix;
_origin = editorViewport.Task.View.Origin;
}
public void ProjectPoint(Vector3 worldSpaceLocation, out Float2 viewportSpaceLocation)
{
worldSpaceLocation -= _origin;
_viewport.Project(ref worldSpaceLocation, ref _viewProjection, out var projected);
viewportSpaceLocation = new Float2((float)projected.X, (float)projected.Y);
}
}
private struct AxisData
{
public Float2 Delta;
public float Distance;
public string Label;
public Color AxisColor;
public bool Negative;
public AxisDirection Direction;
}
private enum AxisDirection
{
PosX,
PosY,
PosZ,
NegX,
NegY,
NegZ
}
/// <summary>
/// Constructor of the Direction Gizmo
/// </summary>
/// <param name="owner">The owner of this object.</param>
public DirectionGizmo(IGizmoOwner owner)
{
_owner = owner;
_viewport = owner.Viewport;
_viewportProjection.Init(owner.Viewport);
_xAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "X", AxisColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f), Negative = false, Direction = AxisDirection.PosX };
_yAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Y", AxisColor = new Color(0.239215f, 1.0f, 0.047058f, 1.0f), Negative = false, Direction = AxisDirection.PosY };
_zAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "Z", AxisColor = new Color(0.0f, 0.3607f, 0.9f, 1.0f), Negative = false, Direction = AxisDirection.PosZ };
_negXAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-X", AxisColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f), Negative = true, Direction = AxisDirection.NegX };
_negYAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Y", AxisColor = new Color(0.239215f, 1.0f, 0.047058f, 1.0f), Negative = true, Direction = AxisDirection.NegY };
_negZAxisData = new AxisData { Delta = new Float2(0, 0), Distance = 0, Label = "-Z", AxisColor = new Color(0.0f, 0.3607f, 0.9f, 1.0f), Negative = true, Direction = AxisDirection.NegZ };
_axisData.EnsureCapacity(6);
_spritePositions.EnsureCapacity(6);
var editor = Editor.Instance;
_posHandle = editor.Icons.VisjectBoxClosed32;
_negHandle = editor.Icons.VisjectBoxOpen32;
_fontReference = new FontReference(Style.Current.FontSmall);
editor.Options.OptionsChanged += OnEditorOptionsChanged;
OnEditorOptionsChanged(editor.Options.Options);
}
private void OnEditorOptionsChanged(EditorOptions options)
{
float gizmoScale = options.Viewport.DirectionGizmoScale;
_axisLength = AxisLength * gizmoScale;
_spriteRadius = SpriteRadius * gizmoScale;
_gizmoBrightness = options.Viewport.DirectionGizmoBrightness;
_gizmoOpacity = options.Viewport.DirectionGizmoOpacity;
_backgroundOpacity = options.Viewport.DirectionGizmoBackgroundOpacity;
_fontReference.Size = 8.25f * gizmoScale;
}
private bool IsPointInSprite(Float2 point, Float2 spriteCenter)
{
Float2 delta = point - spriteCenter;
float distanceSq = delta.LengthSquared;
float radiusSq = _spriteRadius * _spriteRadius;
return distanceSq <= radiusSq;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
_hoveredAxisIndex = -1;
// Check which axis is being hovered - check from closest to farthest for proper layering
for (int i = _spritePositions.Count - 1; i >= 0; i--)
{
if (IsPointInSprite(location, _spritePositions[i].position))
{
_hoveredAxisIndex = i;
break;
}
}
base.OnMouseMove(location);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
// Check which axis is being clicked - check from closest to farthest for proper layering
for (int i = _spritePositions.Count - 1; i >= 0; i--)
{
if (IsPointInSprite(location, _spritePositions[i].position))
{
OrientViewToAxis(_spritePositions[i].direction);
return true;
}
}
return false;
}
private void OrientViewToAxis(AxisDirection direction)
{
Quaternion orientation = direction switch
{
AxisDirection.PosX => Quaternion.Euler(0, -90, 0),
AxisDirection.NegX => Quaternion.Euler(0, 90, 0),
AxisDirection.PosY => Quaternion.Euler(90, 0, 0),
AxisDirection.NegY => Quaternion.Euler(-90, 0, 0),
AxisDirection.PosZ => Quaternion.Euler(0, 180, 0),
AxisDirection.NegZ => Quaternion.Euler(0, 0, 0),
_ => Quaternion.Identity
};
_viewport.OrientViewport(ref orientation);
}
/// <summary>
/// Used to Draw the gizmo.
/// </summary>
public override void DrawSelf()
{
base.DrawSelf();
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
_viewportProjection.Init(_owner.Viewport);
_gizmoCenter = _viewport.Task.View.WorldPosition + _viewport.Task.View.Direction * 1500;
_viewportProjection.ProjectPoint(_gizmoCenter, out var gizmoCenterScreen);
var relativeCenter = Size * 0.5f;
// Project unit vectors
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Right, out var xProjected);
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Up, out var yProjected);
_viewportProjection.ProjectPoint(_gizmoCenter + Vector3.Forward, out var zProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Right, out var negXProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Up, out var negYProjected);
_viewportProjection.ProjectPoint(_gizmoCenter - Vector3.Forward, out var negZProjected);
// Normalize by viewport height to keep size independent of FOV and viewport dimensions
float heightNormalization = _viewport.Height / 720.0f; // 720 = reference height
if (_owner.Viewport.UseOrthographicProjection)
heightNormalization /= _owner.Viewport.OrthographicScale * 0.5f; // Fix in ortho view to keep consistent size regardless of zoom level
Float2 xDelta = (xProjected - gizmoCenterScreen) / heightNormalization;
Float2 yDelta = (yProjected - gizmoCenterScreen) / heightNormalization;
Float2 zDelta = (zProjected - gizmoCenterScreen) / heightNormalization;
Float2 negXDelta = (negXProjected - gizmoCenterScreen) / heightNormalization;
Float2 negYDelta = (negYProjected - gizmoCenterScreen) / heightNormalization;
Float2 negZDelta = (negZProjected - gizmoCenterScreen) / heightNormalization;
// Calculate distances from camera to determine draw order
Vector3 cameraPosition = _viewport.Task.View.Position;
float xDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Right);
float yDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Up);
float zDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter + Vector3.Forward);
float negXDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Right);
float negYDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Up);
float negZDistance = (float)Vector3.Distance(cameraPosition, _gizmoCenter - Vector3.Forward);
_xAxisData.Delta = xDelta;
_xAxisData.Distance = xDistance;
_yAxisData.Delta = yDelta;
_yAxisData.Distance = yDistance;
_zAxisData.Delta = zDelta;
_zAxisData.Distance = zDistance;
_negXAxisData.Delta = negXDelta;
_negXAxisData.Distance = negXDistance;
_negYAxisData.Delta = negYDelta;
_negYAxisData.Distance = negYDistance;
_negZAxisData.Delta = negZDelta;
_negZAxisData.Distance = negZDistance;
// Sort for correct draw order.
_axisData.Clear();
_axisData.AddRange([_xAxisData, _yAxisData, _zAxisData, _negXAxisData, _negYAxisData, _negZAxisData]);
_axisData.Sort((a, b) => -a.Distance.CompareTo(b.Distance));
// Rebuild sprite positions list for hover detection
_spritePositions.Clear();
Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(_backgroundOpacity));
// Draw in order from farthest to closest
for (int i = 0; i < _axisData.Count; i++)
{
var axis = _axisData[i];
Float2 tipScreen = relativeCenter + axis.Delta * _axisLength;
bool isHovered = _hoveredAxisIndex == i;
// Store sprite position for hover detection
_spritePositions.Add((tipScreen, axis.Direction));
var axisColor = isHovered ? new Color(1.0f, 0.8980392f, 0.039215688f) : axis.AxisColor;
axisColor = axisColor.RGBMultiplied(_gizmoBrightness).AlphaMultiplied(_gizmoOpacity);
var font = _fontReference.GetFont();
if (!axis.Negative)
{
Render2D.DrawLine(relativeCenter, tipScreen, axisColor, 1.5f);
Render2D.DrawSprite(_posHandle, new Rectangle(tipScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor);
Render2D.DrawText(font, axis.Label, Color.Black, tipScreen - font.MeasureText(axis.Label) * 0.5f);
}
else
{
Render2D.DrawSprite(_posHandle, new Rectangle(tipScreen - new Float2(_spriteRadius), new Float2(_spriteRadius * 2)), axisColor.RGBMultiplied(0.85f).AlphaMultiplied(0.8f));
if (isHovered)
Render2D.DrawText(font, axis.Label, Color.Black, tipScreen - font.MeasureText(axis.Label) * 0.5f);
}
}
Render2D.Features = features;
}
}

View File

@@ -180,6 +180,11 @@ namespace FlaxEditor
/// </summary>
public bool EnableCamera => _view != null && EnableBackground;
/// <summary>
/// True if enable grid drawing.
/// </summary>
public bool ShowGrid { get; set; } = true;
/// <summary>
/// Transform gizmo to use sync with (selection, snapping, transformation settings).
/// </summary>
@@ -387,19 +392,53 @@ namespace FlaxEditor
if (_mouseMovesWidget && _activeWidget.UIControl)
{
// Calculate transform delta
var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute;
var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var delta = location - _mouseMovesPos;
// TODO: scale/size snapping?
delta *= resizeAxisAbs;
// Resize control via widget
var moveLocation = _mouseMovesPos + delta;
var control = _activeWidget.UIControl.Control;
var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation);
control.LocalLocation += uiControlDelta * resizeAxisNeg;
control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg;
// Transform delta to control local space
var rotation = GetTotalRotation(control) * Mathf.DegreesToRadians;
var cos = Mathf.Cos(rotation);
var sin = Mathf.Sin(rotation);
var localDeltaX = uiControlDelta.X * cos + uiControlDelta.Y * sin;
var localDeltaY = uiControlDelta.Y * cos - uiControlDelta.X * sin;
var localDelta = new Float2(localDeltaX, localDeltaY);
localDelta *= _activeWidget.ResizeAxis.Absolute;
// Calculate size change
var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One);
var dSizeScaled = localDelta * resizeAxisPos - localDelta * resizeAxisNeg;
var scale = control.Scale;
var dSize = new Float2(
Mathf.Abs(scale.X) > Mathf.Epsilon ? dSizeScaled.X / scale.X : 0,
Mathf.Abs(scale.Y) > Mathf.Epsilon ? dSizeScaled.Y / scale.Y : 0);
// Apply size change
control.Size += dSize;
// Calculate location offset to keep the opposite edge stationary
// When PivotRelative is false, resizing keeps Top-Left (Location) constant,
// so we only need to slide back if we are resizing Left or Top edges.
if (!control.PivotRelative)
{
var pivotOffset = Float2.Zero;
if (_activeWidget.ResizeAxis.X < 0 && Mathf.Abs(dSize.X) > Mathf.Epsilon)
pivotOffset.X = -dSize.X * scale.X;
if (_activeWidget.ResizeAxis.Y < 0 && Mathf.Abs(dSize.Y) > Mathf.Epsilon)
pivotOffset.Y = -dSize.Y * scale.Y;
// Transform offset back to parent space and apply
var dLocationX = pivotOffset.X * cos - pivotOffset.Y * sin;
var dLocationY = pivotOffset.X * sin + pivotOffset.Y * cos;
var dLocation = new Float2(dLocationX, dLocationY);
control.LocalLocation += dLocation;
}
// Don't move if layout doesn't allow it
if (control.Parent != null)
@@ -492,17 +531,20 @@ namespace FlaxEditor
// Draw background
Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height);
// Draw grid
var viewRect = GetClientArea();
var upperLeft = _view.PointFromParent(viewRect.Location);
var bottomRight = _view.PointFromParent(viewRect.Size);
var min = Float2.Min(upperLeft, bottomRight);
var max = Float2.Max(upperLeft, bottomRight);
var pixelRange = (max - min) * ViewScale;
Render2D.PushClip(ref viewRect);
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
Render2D.PopClip();
if (ShowGrid)
{
// Draw grid
var viewRect = GetClientArea();
var upperLeft = _view.PointFromParent(viewRect.Location);
var bottomRight = _view.PointFromParent(viewRect.Size);
var min = Float2.Min(upperLeft, bottomRight);
var max = Float2.Max(upperLeft, bottomRight);
var pixelRange = (max - min) * ViewScale;
Render2D.PushClip(ref viewRect);
DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X);
DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y);
Render2D.PopClip();
}
}
base.Draw();
@@ -634,7 +676,7 @@ namespace FlaxEditor
// Draw sizing widgets
if (_widgets == null)
_widgets = new List<Widget>();
var widgetSize = 10.0f;
var widgetSize = 8.0f;
var viewScale = ViewScale;
if (viewScale < 0.7f)
widgetSize *= viewScale;
@@ -685,7 +727,7 @@ namespace FlaxEditor
anchorRectSize *= viewScale;
// Make anchor rects and rotate if parent is rotated.
var parentRotation = controlParent.Rotation * Mathf.DegreesToRadians;
var parentRotation = GetTotalRotation(controlParent) * Mathf.DegreesToRadians;
var rect1Axis = new Float2(-1, -1);
var rect1 = new Rectangle(anchorUpperLeft +
@@ -717,17 +759,25 @@ namespace FlaxEditor
}
}
private float GetTotalRotation(Control control)
{
if (control.Parent != null)
return control.Rotation + GetTotalRotation(control.Parent);
return control.Rotation;
}
private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, float scale, Float2 resizeAxis, CursorType cursor)
{
var style = Style.Current;
var control = uiControl.Control;
var rotation = control.Rotation;
var rotation = GetTotalRotation(control);
var rotationInRadians = rotation * Mathf.DegreesToRadians;
var rect = new Rectangle((pos +
new Float2(resizeAxis.X * Mathf.Cos(rotationInRadians) - resizeAxis.Y * Mathf.Sin(rotationInRadians),
resizeAxis.Y * Mathf.Cos(rotationInRadians) + resizeAxis.X * Mathf.Sin(rotationInRadians)) * 10 * scale) - size * 0.5f,
size);
var position = (pos + new Float2(resizeAxis.X * Mathf.Cos(rotationInRadians) - resizeAxis.Y * Mathf.Sin(rotationInRadians),
resizeAxis.Y * Mathf.Cos(rotationInRadians) + resizeAxis.X * Mathf.Sin(rotationInRadians)) * 4 * (scale < 0.7f ? scale : 1));
var halfSize = size * 0.5f;
// Keep at 0, 0 rect position until later to correctly render rotation.
var rect = new Rectangle(0, 0, size);
// Find more correct cursor at different angles
var unwindRotation = Mathf.UnwindDegrees(rotation);
if (unwindRotation is (>= 45 and < 135) or (> -135 and <= -45) )
@@ -749,6 +799,10 @@ namespace FlaxEditor
default: break;
}
}
Render2D.PushTransform(Matrix3x3.Translation2D(position));
Render2D.PushTransform(Matrix3x3.RotationZ(rotationInRadians));
Render2D.PushTransform(Matrix3x3.Translation2D(-1 * halfSize));
if (rect.Contains(ref mousePos))
{
Render2D.FillRectangle(rect, style.Foreground);
@@ -759,9 +813,14 @@ namespace FlaxEditor
Render2D.FillRectangle(rect, style.ForegroundGrey);
Render2D.DrawRectangle(rect, style.Foreground);
}
Render2D.PopTransform();
Render2D.PopTransform();
Render2D.PopTransform();
if (!_mouseMovesWidget && uiControl != null)
{
// Collect widget
rect.Location = position - halfSize;
_widgets.Add(new Widget
{
UIControl = uiControl,

View File

@@ -325,7 +325,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
var codeEditor = options.SourceCode.SourceCodeEditor;
if (codeEditor != "None")
{
foreach (var e in Editor.Instance.CodeEditing.Editors)
foreach (var e in _editors)
{
if (string.Equals(codeEditor, e.Name, StringComparison.OrdinalIgnoreCase))
{
@@ -334,7 +334,10 @@ namespace FlaxEditor.Modules.SourceCodeEditing
}
}
}
Editor.Instance.CodeEditing.SelectedEditor = editor;
if (editor == null && _editors.Count != 0)
editor = _editors[0];
SelectedEditor = editor;
}
/// <summary>

View File

@@ -41,9 +41,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing
var vsCode = codeEditing.GetInBuildEditor(CodeEditorTypes.VSCode);
var rider = codeEditing.GetInBuildEditor(CodeEditorTypes.Rider);
#if PLATFORM_WINDOW
#if PLATFORM_WINDOWS
// Favor the newest Visual Studio
for (int i = (int)CodeEditorTypes.VS2019; i >= (int)CodeEditorTypes.VS2008; i--)
for (int i = (int)CodeEditorTypes.VS2026; i >= (int)CodeEditorTypes.VS2008; i--)
{
var visualStudio = codeEditing.GetInBuildEditor((CodeEditorTypes)i);
if (visualStudio != null)
@@ -74,7 +74,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
public string Name => "Default";
/// <inheritdoc />
public string GenerateProjectCustomArgs => null;
public string GenerateProjectCustomArgs => _currentEditor?.GenerateProjectCustomArgs;
/// <inheritdoc />
public void OpenSolution()

View File

@@ -22,71 +22,14 @@ namespace FlaxEditor.Modules.SourceCodeEditing
public InBuildSourceCodeEditor(CodeEditorTypes type)
{
Type = type;
switch (type)
{
case CodeEditorTypes.Custom:
Name = "Custom";
break;
case CodeEditorTypes.SystemDefault:
Name = "System Default";
break;
case CodeEditorTypes.VS2008:
Name = "Visual Studio 2008";
break;
case CodeEditorTypes.VS2010:
Name = "Visual Studio 2010";
break;
case CodeEditorTypes.VS2012:
Name = "Visual Studio 2012";
break;
case CodeEditorTypes.VS2013:
Name = "Visual Studio 2013";
break;
case CodeEditorTypes.VS2015:
Name = "Visual Studio 2015";
break;
case CodeEditorTypes.VS2017:
Name = "Visual Studio 2017";
break;
case CodeEditorTypes.VS2019:
Name = "Visual Studio 2019";
break;
case CodeEditorTypes.VS2022:
Name = "Visual Studio 2022";
break;
case CodeEditorTypes.VS2026:
Name = "Visual Studio 2026";
break;
case CodeEditorTypes.VSCode:
Name = "Visual Studio Code";
break;
case CodeEditorTypes.VSCodeInsiders:
Name = "Visual Studio Code - Insiders";
break;
case CodeEditorTypes.Rider:
Name = "Rider";
break;
default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
Name = CodeEditingManager.GetName(type);
}
/// <inheritdoc />
public string Name { get; set; }
/// <inheritdoc />
public string GenerateProjectCustomArgs
{
get
{
switch (Type)
{
case CodeEditorTypes.VSCodeInsiders:
case CodeEditorTypes.VSCode: return "-vscode -vs2022";
case CodeEditorTypes.Rider: return "-vs2022";
default: return null;
}
}
}
public string GenerateProjectCustomArgs => CodeEditingManager.GetGenerateProjectCustomArgs(Type);
/// <inheritdoc />
public void OpenSolution()

View File

@@ -710,6 +710,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Node Editors"), EditorOrder(4580)]
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Shift+F")]
[EditorDisplay("Node Editors"), EditorOrder(4590)]
public InputBinding FocusSelectedNodes = new InputBinding(KeyboardKeys.F, KeyboardKeys.Shift);
#endregion
}
}

View File

@@ -159,7 +159,7 @@ namespace FlaxEditor.Options
}
/// <summary>
/// Options focus Game Window behaviour when play mode is entered.
/// Options for focus Game Window behaviour when play mode is entered.
/// </summary>
public enum PlayModeFocus
{
@@ -179,6 +179,22 @@ namespace FlaxEditor.Options
GameWindowThenRestore,
}
/// <summary>
/// Generic options for a disabled or hidden state. Used for example in create content button.
/// </summary>
public enum DisabledHidden
{
/// <summary>
/// Disabled state.
/// </summary>
Disabled,
/// <summary>
/// Hidden state.
/// </summary>
Hidden,
}
/// <summary>
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
/// </summary>
@@ -207,13 +223,6 @@ namespace FlaxEditor.Options
[EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")]
public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal;
/// <summary>
/// If checked, color pickers will always modify the color unless 'Cancel' if pressed, otherwise color won't change unless 'Ok' is pressed.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Interface"), EditorOrder(290)]
public bool AutoAcceptColorPickerChange { get; set; } = true;
/// <summary>
/// Gets or sets the formatting option for numeric values in the editor.
/// </summary>
@@ -525,6 +534,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Visject", "Warn when deleting used parameter"), EditorOrder(552)]
public bool WarnOnDeletingUsedVisjectParameter { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating what should happen to unavaliable options in the content create menu.
/// </summary>
[DefaultValue(DisabledHidden.Hidden)]
[EditorDisplay("Content"), EditorOrder(600)]
public DisabledHidden UnavaliableContentCreateOptions { get; set; } = DisabledHidden.Hidden;
private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.PrimaryFont);
private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal<FontAsset>(EditorAssets.InconsolataRegularFont);

View File

@@ -171,5 +171,40 @@ namespace FlaxEditor.Options
[DefaultValue(1000.0f), Limit(0.0f, 20000.0f, 5.0f)]
[EditorDisplay("Viewport Icons"), EditorOrder(410)]
public float MaxSizeDistance { get; set; } = 1000.0f;
/// <summary>
/// Gets or sets a value that indicates whether the main viewports <see cref="Gizmo.DirectionGizmo"/> is visible.
/// </summary>
[DefaultValue(true)]
[EditorDisplay("Direction Gizmo"), EditorOrder(500), Tooltip("Sets the visibility of the direction gizmo in the main editor viewport.")]
public bool ShowDirectionGizmo { get; set; } = true;
/// <summary>
/// Gets or sets a value by which the main viewports <see cref="Gizmo.DirectionGizmo"/> size is multiplied with.
/// </summary>
[DefaultValue(1f), Limit(0.0f, 2.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(501), Tooltip("The scale of the direction gizmo in the main viewport.")]
public float DirectionGizmoScale { get; set; } = 1f;
/// <summary>
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/> background.
/// </summary>
[DefaultValue(0.1f), Limit(0.0f, 1.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(502), Tooltip("The background opacity of the of the direction gizmo in the main viewport.")]
public float DirectionGizmoBackgroundOpacity { get; set; } = 0.1f;
/// <summary>
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/>.
/// </summary>
[DefaultValue(0.6f), Limit(0.0f, 1.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(503), Tooltip("The opacity of the of the direction gizmo in the main viewport.")]
public float DirectionGizmoOpacity { get; set; } = 0.6f;
/// <summary>
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/>.
/// </summary>
[DefaultValue(1f), Limit(0.0f, 2.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(504), Tooltip("The brightness of the of the direction gizmo in the main viewport.")]
public float DirectionGizmoBrightness{ get; set; } = 1f;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
{
/// <summary>
/// Scene tree node for <see cref="Cloth"/> actor type.
/// </summary>
[HideInEditor]
public sealed class ClothNode : ActorNode
{
/// <inheritdoc />
public ClothNode(Actor actor)
: base(actor)
{
}
/// <inheritdoc />
public override void PostSpawn()
{
base.PostSpawn();
// Snap to the parent
if (!(ParentNode is SceneNode))
Actor.LocalTransform = Transform.Identity;
}
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
{
/// <summary>
/// Scene tree node for <see cref="RigidBody"/> actor type.
/// </summary>
[HideInEditor]
public sealed class RigidBodyNode : ActorNode
{
/// <inheritdoc />
public RigidBodyNode(Actor actor)
: base(actor)
{
}
/// <inheritdoc />
public override void PostSpawn()
{
base.PostSpawn();
if (HasPrefabLink)
return;
Actor.StaticFlags = StaticFlags.None;
}
}
}

View File

@@ -324,13 +324,12 @@ namespace FlaxEditor.SceneGraph.GUI
isExpanded = Editor.Instance.ProjectCache.IsExpandedActor(ref id);
}
if (isExpanded)
if (!noFilter)
{
Expand(true);
}
else
{
Collapse(true);
if (isExpanded)
Expand(true);
else
Collapse(true);
}
Visible = isThisVisible | isAnyChildVisible;

View File

@@ -51,6 +51,7 @@ namespace FlaxEditor.SceneGraph
CustomNodesTypes.Add(typeof(AudioSource), typeof(AudioSourceNode));
CustomNodesTypes.Add(typeof(BoneSocket), typeof(BoneSocketNode));
CustomNodesTypes.Add(typeof(Decal), typeof(DecalNode));
CustomNodesTypes.Add(typeof(Cloth), typeof(ClothNode));
CustomNodesTypes.Add(typeof(BoxCollider), typeof(BoxColliderNode));
CustomNodesTypes.Add(typeof(SphereCollider), typeof(ColliderNode));
CustomNodesTypes.Add(typeof(CapsuleCollider), typeof(ColliderNode));
@@ -73,6 +74,7 @@ namespace FlaxEditor.SceneGraph
CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode));
CustomNodesTypes.Add(typeof(SpriteRender), typeof(SpriteRenderNode));
CustomNodesTypes.Add(typeof(Joint), typeof(JointNode));
CustomNodesTypes.Add(typeof(RigidBody), typeof(RigidBodyNode));
}
/// <summary>

View File

@@ -139,6 +139,34 @@ CodeEditor* CodeEditingManager::GetCodeEditor(CodeEditorTypes editorType)
return nullptr;
}
String CodeEditingManager::GetName(CodeEditorTypes editorType)
{
const auto editor = GetCodeEditor(editorType);
if (editor)
{
return editor->GetName();
}
else
{
LOG(Warning, "Missing code editor type {0}", (int32)editorType);
return String::Empty;
}
}
String CodeEditingManager::GetGenerateProjectCustomArgs(CodeEditorTypes editorType)
{
const auto editor = GetCodeEditor(editorType);
if (editor)
{
return editor->GetGenerateProjectCustomArgs();
}
else
{
LOG(Warning, "Missing code editor type {0}", (int32)editorType);
return String::Empty;
}
}
void CodeEditingManager::OpenFile(CodeEditorTypes editorType, const String& path, int32 line)
{
const auto editor = GetCodeEditor(editorType);

View File

@@ -109,9 +109,18 @@ public:
/// <summary>
/// Gets the name of the editor.
/// </summary>
/// <returns>The name</returns>
/// <returns>The name.</returns>
virtual String GetName() const = 0;
/// <summary>
/// Gets the custom arguments for the Flax.Build tool to add when generating project files for this code editor.
/// </summary>
/// <returns>The custom arguments to generate project files.</returns>
virtual String GetGenerateProjectCustomArgs() const
{
return String::Empty;
}
/// <summary>
/// Opens the file.
/// </summary>
@@ -169,6 +178,20 @@ public:
/// <returns>The editor object or null if not found.</returns>
static CodeEditor* GetCodeEditor(CodeEditorTypes editorType);
/// <summary>
/// Gets the name of the editor.
/// </summary>
/// <param name="editorType">The code editor type.</param>
/// <returns>The name.</returns>
API_FUNCTION() static String GetName(CodeEditorTypes editorType);
/// <summary>
/// Gets the custom arguments for the Flax.Build tool to add when generating project files for this code editor.
/// </summary>
/// <param name="editorType">The code editor type.</param>
/// <returns>The custom arguments to generate project files.</returns>
API_FUNCTION() static String GetGenerateProjectCustomArgs(CodeEditorTypes editorType);
/// <summary>
/// Opens the file. Handles async opening.
/// </summary>

View File

@@ -257,12 +257,17 @@ String RiderCodeEditor::GetName() const
return TEXT("Rider");
}
String RiderCodeEditor::GetGenerateProjectCustomArgs() const
{
return TEXT("-vs2022");
}
void RiderCodeEditor::OpenFile(const String& path, int32 line)
{
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open file
@@ -290,7 +295,7 @@ void RiderCodeEditor::OpenSolution()
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open solution
@@ -312,5 +317,5 @@ void RiderCodeEditor::OpenSolution()
void RiderCodeEditor::OnFileAdded(const String& path)
{
ScriptsBuilder::GenerateProject();
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}

View File

@@ -35,6 +35,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
void OnFileAdded(const String& path) override;

View File

@@ -145,7 +145,46 @@ CodeEditorTypes VisualStudioEditor::GetType() const
String VisualStudioEditor::GetName() const
{
return String(ToString(_version));
const Char* name;
switch (_version)
{
case VisualStudioVersion::VS2008:
name = TEXT("Visual Studio 2008");
break;
case VisualStudioVersion::VS2010:
name = TEXT("Visual Studio 2010");
break;
case VisualStudioVersion::VS2012:
name = TEXT("Visual Studio 2012");
break;
case VisualStudioVersion::VS2013:
name = TEXT("Visual Studio 2013");
break;
case VisualStudioVersion::VS2015:
name = TEXT("Visual Studio 2015");
break;
case VisualStudioVersion::VS2017:
name = TEXT("Visual Studio 2017");
break;
case VisualStudioVersion::VS2019:
name = TEXT("Visual Studio 2019");
break;
case VisualStudioVersion::VS2022:
name = TEXT("Visual Studio 2022");
break;
case VisualStudioVersion::VS2026:
name = TEXT("Visual Studio 2026");
break;
default:
name = ToString(_version);
break;
}
return String(name);
}
String VisualStudioEditor::GetGenerateProjectCustomArgs() const
{
return String::Format(TEXT("-{0}"), String(ToString(_version)).ToLower());
}
void VisualStudioEditor::OpenFile(const String& path, int32 line)
@@ -153,7 +192,7 @@ void VisualStudioEditor::OpenFile(const String& path, int32 line)
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
ScriptsBuilder::GenerateProject();
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open file
@@ -172,7 +211,7 @@ void VisualStudioEditor::OpenSolution()
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
ScriptsBuilder::GenerateProject();
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
}
// Open solution
@@ -187,7 +226,7 @@ void VisualStudioEditor::OpenSolution()
void VisualStudioEditor::OnFileAdded(const String& path)
{
// TODO: finish dynamic files adding to the project - for now just regenerate it
ScriptsBuilder::GenerateProject();
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
return;
if (!FileSystem::FileExists(_solutionPath))
{

View File

@@ -56,6 +56,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
void OnFileAdded(const String& path) override;

View File

@@ -128,6 +128,11 @@ String VisualStudioCodeEditor::GetName() const
return _isInsiders ? TEXT("Visual Studio Code - Insiders") : TEXT("Visual Studio Code");
}
String VisualStudioCodeEditor::GetGenerateProjectCustomArgs() const
{
return TEXT("-vs2022 -vscode");
}
void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
{
// Generate VS solution files for intellisense

View File

@@ -37,6 +37,7 @@ public:
// [CodeEditor]
CodeEditorTypes GetType() const override;
String GetName() const override;
String GetGenerateProjectCustomArgs() const override;
void OpenFile(const String& path, int32 line) override;
void OpenSolution() override;
bool UseAsyncForOpen() const override;

View File

@@ -52,6 +52,7 @@ namespace FlaxEditor.Surface.Archetypes
},
new NodeArchetype
{
// [Deprecated]
TypeID = 3,
Title = "Pack Material Layer",
Description = "Pack material properties",
@@ -75,6 +76,7 @@ namespace FlaxEditor.Surface.Archetypes
},
new NodeArchetype
{
// [Deprecated]
TypeID = 4,
Title = "Unpack Material Layer",
Description = "Unpack material properties",
@@ -120,6 +122,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 6,
Title = "Pack Material Layer",
Description = "Pack material properties",
AlternativeTitles = new[] { "Make Material Layer", "Construct Material Layer", "Compose Material Layer" },
Flags = NodeFlags.MaterialGraph,
Size = new Float2(200, 280),
Elements = new[]
@@ -146,6 +149,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 7,
Title = "Unpack Material Layer",
Description = "Unpack material properties",
AlternativeTitles = new[] { "Break Material Layer", "Deconstruct Material Layer", "Decompose Material Layer", "Split Material Layer" },
Flags = NodeFlags.MaterialGraph,
Size = new Float2(210, 280),
Elements = new[]

View File

@@ -586,8 +586,9 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 13,
Title = "Pre-skinned Local Position",
Description = "Per vertex local position (before skinning)",
AlternativeTitles = new[] { "Vertex Position", "Pre skinning Local Vertex Position" },
Flags = NodeFlags.MaterialGraph,
Size = new Float2(230, 40),
Size = new Float2(270, 40),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0),
@@ -598,8 +599,9 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 14,
Title = "Pre-skinned Local Normal",
Description = "Per vertex local normal (before skinning)",
AlternativeTitles = new[] { "Vertex Normal", "Pre skinning Local Normal" },
Flags = NodeFlags.MaterialGraph,
Size = new Float2(230, 40),
Size = new Float2(270, 40),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0),

View File

@@ -342,6 +342,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 20,
Title = "Pack Float2",
Description = "Pack components to Float2",
AlternativeTitles = new[] { "Make Float2", "Construct Float2", "Compose Float2" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
DefaultValues = new object[]
@@ -361,6 +362,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 21,
Title = "Pack Float3",
Description = "Pack components to Float3",
AlternativeTitles = new[] { "Make Float3", "Construct Float3", "Compose Float3" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
DefaultValues = new object[]
@@ -382,6 +384,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 22,
Title = "Pack Float4",
Description = "Pack components to Float4",
AlternativeTitles = new[] { "Make Float4", "Construct Float4", "Compose Float4" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
DefaultValues = new object[]
@@ -405,6 +408,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 23,
Title = "Pack Rotation",
Description = "Pack components to Rotation",
AlternativeTitles = new[] { "Make Rotation", "Construct Rotation", "Compose Rotation" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
DefaultValues = new object[]
@@ -426,6 +430,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 24,
Title = "Pack Transform",
Description = "Pack components to Transform",
AlternativeTitles = new[] { "Make Transform", "Construct Transform", "Compose Transform" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
Elements = new[]
@@ -441,6 +446,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 25,
Title = "Pack Box",
Description = "Pack components to BoundingBox",
AlternativeTitles = new[] { "Make Box", "Construct Box", "Compose Box" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
Elements = new[]
@@ -454,6 +460,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 26,
Title = "Pack Structure",
AlternativeTitles = new[] { "Make Structure", "Construct Structure", "Compose Structure" },
Create = (id, context, arch, groupArch) => new PackStructureNode(id, context, arch, groupArch),
IsInputCompatible = PackStructureNode.IsInputCompatible,
IsOutputCompatible = PackStructureNode.IsOutputCompatible,
@@ -479,6 +486,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 30,
Title = "Unpack Float2",
Description = "Unpack components from Float2",
AlternativeTitles = new[] { "Break Float2", "Deconstruct Float2", "Decompose Float2", "Split Float2" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 40),
Elements = new[]
@@ -493,6 +501,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 31,
Title = "Unpack Float3",
Description = "Unpack components from Float3",
AlternativeTitles = new[] { "Break Float3", "Deconstruct Float3", "Decompose Float3", "Split Float3" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 60),
Elements = new[]
@@ -508,6 +517,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 32,
Title = "Unpack Float4",
Description = "Unpack components from Float4",
AlternativeTitles = new[] { "Break Float4", "Deconstruct Float4", "Decompose Float4", "Split Float4" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(150, 80),
Elements = new[]
@@ -524,6 +534,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 33,
Title = "Unpack Rotation",
Description = "Unpack components from Rotation",
AlternativeTitles = new[] { "Break Rotation", "Deconstruct Rotation", "Decompose Rotation", "Split Rotation" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 60),
Elements = new[]
@@ -539,6 +550,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 34,
Title = "Unpack Transform",
Description = "Unpack components from Transform",
AlternativeTitles = new[] { "Break Transform", "Deconstruct Transform", "Decompose Transform", "Split Transform" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 60),
Elements = new[]
@@ -554,6 +566,7 @@ namespace FlaxEditor.Surface.Archetypes
TypeID = 35,
Title = "Unpack Box",
Description = "Unpack components from BoundingBox",
AlternativeTitles = new[] { "Break BoundingBox", "Deconstruct BoundingBox", "Decompose BoundingBox", "Split BoundingBox" },
Flags = NodeFlags.AllGraphs,
Size = new Float2(170, 40),
Elements = new[]
@@ -572,6 +585,7 @@ namespace FlaxEditor.Surface.Archetypes
IsOutputCompatible = UnpackStructureNode.IsOutputCompatible,
GetInputOutputDescription = UnpackStructureNode.GetInputOutputDescription,
Description = "Breaks the structure data to allow extracting components from it.",
AlternativeTitles = new[] { "Break Structure", "Deconstruct Structure", "Decompose Structure", "Split Structure" },
Flags = NodeFlags.VisualScriptGraph | NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
Size = new Float2(180, 20),
DefaultValues = new object[]

View File

@@ -22,15 +22,97 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Parameters
{
/// <summary>
/// Surface node type for parameters group Get/Set nodes.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public abstract class SurfaceNodeParamsBase : SurfaceNode
{
/// <summary>
/// The combobox for picking parameter.
/// </summary>
protected ComboBoxElement _combobox;
/// <summary>
/// Fag used to block layout updated when updating node.
/// </summary>
protected bool _isUpdateLocked;
/// <inheritdoc />
protected SurfaceNodeParamsBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
/// <summary>
/// Gets the selected parameter.
/// </summary>
/// <returns>Surface parameter object or null if nothing selected or cannot find it.</returns>
protected SurfaceParameter GetSelected()
{
if (Surface != null)
return Surface.GetParameter(_combobox.SelectedItem);
return Context.GetParameter((Guid)Values[0]);
}
/// <summary>
/// Updates the combo box.
/// </summary>
protected void UpdateCombo()
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
{
_combobox = GetChild<ComboBoxElement>();
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
string toSelect = null;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
var parameters = Surface.Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var param = parameters[i];
if (!param.IsPublic && !Surface.CanShowPrivateParameters)
continue;
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = param.Name;
}
_combobox.SelectedItem = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
if (selectedID != (Guid)Values[0])
Set(selected, ref selectedID);
}
/// <summary>
/// Sets the selected parameter.
/// </summary>
/// <param name="selected">Parameter.</param>
/// <param name="selectedID">Parameter identifier.</param>
protected virtual void Set(SurfaceParameter selected, ref Guid selectedID)
{
SetValue(0, selectedID);
}
}
/// <summary>
/// Surface node type for parameters group Get node.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class SurfaceNodeParamsGet : SurfaceNode, IParametersDependantNode
public class SurfaceNodeParamsGet : SurfaceNodeParamsBase, IParametersDependantNode
{
private ComboBoxElement _combobox;
private readonly List<ISurfaceNodeElement> _dynamicChildren = new List<ISurfaceNodeElement>();
private bool _isUpdateLocked;
private ScriptType _layoutType;
private NodeElementArchetype[] _layoutElements;
@@ -306,49 +388,6 @@ namespace FlaxEditor.Surface.Archetypes
UpdateTitle();
}
private void UpdateCombo()
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
{
_combobox = (ComboBoxElement)_children[0];
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
int toSelect = -1;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
for (int i = 0; i < Surface.Parameters.Count; i++)
{
var param = Surface.Parameters[i];
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = i;
}
_combobox.SelectedIndex = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
SetValue(0, selectedID);
}
private SurfaceParameter GetSelected()
{
if (Surface != null)
{
var selectedIndex = _combobox.SelectedIndex;
return selectedIndex >= 0 && selectedIndex < Surface.Parameters.Count ? Surface.Parameters[selectedIndex] : null;
}
return Context.GetParameter((Guid)Values[0]);
}
private void ClearDynamicElements()
{
for (int i = 0; i < _dynamicChildren.Count; i++)
@@ -463,15 +502,19 @@ namespace FlaxEditor.Surface.Archetypes
else if (!_isUpdateLocked)
{
_isUpdateLocked = true;
int toSelect = -1;
string toSelect = null;
Guid loadedSelected = (Guid)Values[0];
for (int i = 0; i < Surface.Parameters.Count; i++)
var parameters = Surface.Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var param = Surface.Parameters[i];
var param = parameters[i];
if (param.ID == loadedSelected)
toSelect = i;
{
toSelect = param.Name;
break;
}
}
_combobox.SelectedIndex = toSelect;
_combobox.SelectedItem = toSelect;
_isUpdateLocked = false;
}
UpdateLayout();
@@ -817,66 +860,23 @@ namespace FlaxEditor.Surface.Archetypes
/// Surface node type for parameters group Set node.
/// </summary>
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
public class SurfaceNodeParamsSet : SurfaceNode, IParametersDependantNode
public class SurfaceNodeParamsSet : SurfaceNodeParamsBase, IParametersDependantNode
{
private ComboBoxElement _combobox;
private bool _isUpdateLocked;
/// <inheritdoc />
public SurfaceNodeParamsSet(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
private void UpdateCombo()
/// <inheritdoc />
protected override void Set(SurfaceParameter selected, ref Guid selectedID)
{
if (_isUpdateLocked)
return;
_isUpdateLocked = true;
if (_combobox == null)
SetValues(new[]
{
_combobox = GetChild<ComboBoxElement>();
_combobox.SelectedIndexChanged += OnSelectedChanged;
}
int toSelect = -1;
Guid loadedSelected = (Guid)Values[0];
_combobox.ClearItems();
for (int i = 0; i < Surface.Parameters.Count; i++)
{
var param = Surface.Parameters[i];
_combobox.AddItem(param.Name);
if (param.ID == loadedSelected)
toSelect = i;
}
_combobox.SelectedIndex = toSelect;
_isUpdateLocked = false;
}
private void OnSelectedChanged(ComboBox cb)
{
if (_isUpdateLocked)
return;
var selected = GetSelected();
var selectedID = selected?.ID ?? Guid.Empty;
if (selectedID != (Guid)Values[0])
{
SetValues(new[]
{
selectedID,
selected != null ? TypeUtils.GetDefaultValue(selected.Type) : null,
});
UpdateUI();
}
}
private SurfaceParameter GetSelected()
{
if (Surface != null)
{
var selectedIndex = _combobox.SelectedIndex;
return selectedIndex >= 0 && selectedIndex < Surface.Parameters.Count ? Surface.Parameters[selectedIndex] : null;
}
return Context.GetParameter((Guid)Values[0]);
selectedID,
selected != null ? TypeUtils.GetDefaultValue(selected.Type) : null,
});
UpdateUI();
}
/// <inheritdoc />

View File

@@ -436,10 +436,11 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 102,
Title = "Particle Lifetime",
Description = "Particle lifetime (in seconds).",
Title = "Particle Total Lifetime",
Description = "Total particle lifetime (in seconds) at the time when the particle was created. Always the same, no matter the particles age.",
AlternativeTitles = new[] { "Age" },
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Float2(200, 30),
Size = new Float2(250, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),
@@ -449,9 +450,10 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 103,
Title = "Particle Age",
Description = "Particle age (in seconds).",
Description = "Particle age (in seconds). How long the particle has been alive since it was created.",
AlternativeTitles = new[] { "Lifetime" },
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Float2(200, 30),
Size = new Float2(170, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),
@@ -533,9 +535,10 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 110,
Title = "Particle Normalized Age",
Description = "Particle normalized age to range 0-1 (age divided by lifetime).",
Description = "The normalized age of the particle, represented as 0 (max lifetime) to 1 (max age). (Same as age divided by lifetime.)",
AlternativeTitles = new[] { "Lifetime" },
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Float2(230, 30),
Size = new Float2(250, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),

View File

@@ -585,7 +585,7 @@ namespace FlaxEditor.Surface.ContextMenu
private void UpdateFilters()
{
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes[0] == null)
if (string.IsNullOrEmpty(_searchBox.Text) && _selectedBoxes.Count == 0)
{
ResetView();
Profiler.EndEvent();

View File

@@ -34,11 +34,6 @@ namespace FlaxEditor.Surface.Elements
/// </summary>
public const float DefaultConnectionOffset = 24f;
/// <summary>
/// Distance for the mouse to be considered above the connection
/// </summary>
public float MouseOverConnectionDistance => 100f / Surface.ViewScale;
/// <inheritdoc />
public OutputBox(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(parentNode, archetype, archetype.Position + new Float2(parentNode.Archetype.Size.X, 0))
@@ -109,12 +104,13 @@ namespace FlaxEditor.Surface.Elements
/// </summary>
/// <param name="targetBox">The other box.</param>
/// <param name="mousePosition">The mouse position</param>
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition)
/// <param name="distance">Distance at which its an intersection</param>
public bool IntersectsConnection(Box targetBox, ref Float2 mousePosition, float distance)
{
float connectionOffset = Mathf.Max(0f, DefaultConnectionOffset * (1 - Editor.Instance.Options.Options.Interface.ConnectionCurvature));
Float2 start = new Float2(ConnectionOrigin.X + connectionOffset, ConnectionOrigin.Y);
Float2 end = new Float2(targetBox.ConnectionOrigin.X - connectionOffset, targetBox.ConnectionOrigin.Y);
return IntersectsConnection(ref start, ref end, ref mousePosition, MouseOverConnectionDistance);
return IntersectsConnection(ref start, ref end, ref mousePosition, distance);
}
/// <summary>
@@ -182,7 +178,7 @@ namespace FlaxEditor.Surface.Elements
{
// Draw all the connections
var style = Surface.Style;
var mouseOverDistance = MouseOverConnectionDistance;
var mouseOverDistance = Surface.MouseOverConnectionDistance;
var startPos = ConnectionOrigin;
var startHighlight = ConnectionsHighlightIntensity;
for (int i = 0; i < Connections.Count; i++)

View File

@@ -573,13 +573,13 @@ namespace FlaxEditor.Surface
var showSearch = () => editor.ContentFinding.ShowSearch(window);
// Toolstrip
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save", ref inputOptions.Save);
saveButton = toolStrip.AddButton(editor.Icons.Save64, window.Save).LinkTooltip("Save.", ref inputOptions.Save);
toolStrip.AddSeparator();
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo", ref inputOptions.Undo);
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo", ref inputOptions.Redo);
undoButton = toolStrip.AddButton(editor.Icons.Undo64, undo.PerformUndo).LinkTooltip("Undo.", ref inputOptions.Undo);
redoButton = toolStrip.AddButton(editor.Icons.Redo64, undo.PerformRedo).LinkTooltip("Redo.", ref inputOptions.Redo);
toolStrip.AddSeparator();
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool", ref inputOptions.Search);
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph");
toolStrip.AddButton(editor.Icons.Search64, showSearch).LinkTooltip("Open content search tool.", ref inputOptions.Search);
toolStrip.AddButton(editor.Icons.CenterView64, surface.ShowWholeGraph).LinkTooltip("Show whole graph.");
var gridSnapButton = toolStrip.AddButton(editor.Icons.Grid32, surface.ToggleGridSnapping);
gridSnapButton.LinkTooltip("Toggle grid snapping for nodes.");
gridSnapButton.AutoCheck = true;

View File

@@ -410,8 +410,11 @@ namespace FlaxEditor.Surface
}
menu.AddSeparator();
_cmFormatNodesMenu = menu.AddChildMenu("Format node(s)");
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
bool allNodesNoMove = SelectedNodes.All(n => n.Archetype.Flags.HasFlag(NodeFlags.NoMove));
bool clickedNodeNoMove = ((SelectedNodes.Count == 1 && controlUnderMouse is SurfaceNode n && n.Archetype.Flags.HasFlag(NodeFlags.NoMove)));
_cmFormatNodesMenu = menu.AddChildMenu("Format nodes");
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection && !(allNodesNoMove || clickedNodeNoMove);
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Straighten connections", Editor.Instance.Options.Options.Input.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); });

View File

@@ -213,6 +213,44 @@ namespace FlaxEditor.Surface
}
}
/// <summary>
/// Draw connection hints for lazy connect feature.
/// </summary>
protected virtual void DrawLazyConnect()
{
var style = FlaxEngine.GUI.Style.Current;
if (_lazyConnectStartNode != null)
{
Float2 upperLeft = _rootControl.PointToParent(_lazyConnectStartNode.UpperLeft);
Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectStartNode.Size - 1f);
startNodeOutline.Size *= ViewScale;
Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
}
if (_lazyConnectEndNode != null)
{
Float2 upperLeft = _rootControl.PointToParent(_lazyConnectEndNode.UpperLeft);
Rectangle startNodeOutline = new Rectangle(upperLeft + 1f, _lazyConnectEndNode.Size - 1f);
startNodeOutline.Size *= ViewScale;
Render2D.DrawRectangle(startNodeOutline.MakeExpanded(4f), style.BackgroundSelected, 4f);
}
Rectangle startRect = new Rectangle(_rightMouseDownPos - 6f, new Float2(12f));
Rectangle endRect = new Rectangle(_mousePos - 6f, new Float2(12f));
// Start and end shadows/ outlines
Render2D.FillRectangle(startRect.MakeExpanded(2.5f), Color.Black);
Render2D.FillRectangle(endRect.MakeExpanded(2.5f), Color.Black);
Render2D.DrawLine(_rightMouseDownPos, _mousePos, Color.Black, 7.5f);
Render2D.DrawLine(_rightMouseDownPos, _mousePos, style.ForegroundGrey, 5f);
// Draw start and end boxes over the lines to hide ugly artifacts at the ends
Render2D.FillRectangle(startRect, style.ForegroundGrey);
Render2D.FillRectangle(endRect, style.ForegroundGrey);
}
/// <summary>
/// Draws the contents of the surface (nodes, connections, comments, etc.).
/// </summary>
@@ -260,6 +298,9 @@ namespace FlaxEditor.Surface
DrawContents();
if (_isLazyConnecting)
DrawLazyConnect();
//Render2D.DrawText(style.FontTitle, string.Format("Scale: {0}", _rootControl.Scale), rect, Enabled ? Color.Red : Color.Black);
// Draw border

View File

@@ -39,6 +39,8 @@ namespace FlaxEditor.Surface
if (nodes.Count <= 1)
return;
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
var nodesToVisit = new HashSet<SurfaceNode>(nodes);
// While we haven't formatted every node
@@ -73,18 +75,23 @@ namespace FlaxEditor.Surface
}
}
FormatConnectedGraph(connectedNodes);
undoActions.AddRange(FormatConnectedGraph(connectedNodes));
}
Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
MarkAsEdited(false);
}
/// <summary>
/// Formats a graph where all nodes are connected.
/// </summary>
/// <param name="nodes">List of connected nodes.</param>
protected void FormatConnectedGraph(List<SurfaceNode> nodes)
private List<MoveNodesAction> FormatConnectedGraph(List<SurfaceNode> nodes)
{
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
if (nodes.Count <= 1)
return;
return undoActions;
var boundingBox = GetNodesBounds(nodes);
@@ -140,7 +147,6 @@ namespace FlaxEditor.Surface
}
// Set the node positions
var undoActions = new List<MoveNodesAction>();
var topRightPosition = endNodes[0].Location;
for (int i = 0; i < nodes.Count; i++)
{
@@ -155,16 +161,18 @@ namespace FlaxEditor.Surface
}
}
MarkAsEdited(false);
Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
return undoActions;
}
/// <summary>
/// Straightens every connection between nodes in <paramref name="nodes"/>.
/// </summary>
/// <param name="nodes">List of nodes.</param>
/// <returns>List of undo actions.</returns>
public void StraightenGraphConnections(List<SurfaceNode> nodes)
{
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if (nodes.Count <= 1)
return;
@@ -350,8 +358,10 @@ namespace FlaxEditor.Surface
/// <param name="nodes">List of nodes.</param>
/// <param name="alignmentType">Alignemnt type.</param>
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
{
if(nodes.Count <= 1)
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if (nodes.Count <= 1)
return;
var undoActions = new List<MoveNodesAction>();
@@ -392,6 +402,8 @@ namespace FlaxEditor.Surface
/// <param name="vertically">If false will be done horizontally, if true will be done vertically.</param>
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
{
nodes = nodes.Where(n => !n.Archetype.Flags.HasFlag(NodeFlags.NoMove)).ToList();
if(nodes.Count <= 1)
return;

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static FlaxEditor.Surface.Archetypes.Particles;
using FlaxEditor.Options;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Surface.Undo;
@@ -23,12 +24,26 @@ namespace FlaxEditor.Surface
/// </summary>
public bool PanWithMiddleMouse = false;
/// <summary>
/// Distance for the mouse to be considered above the connection.
/// </summary>
public float MouseOverConnectionDistance => 100f / ViewScale;
/// <summary>
/// Distance of a node from which it is able to be slotted into an existing connection.
/// </summary>
public float SlotNodeIntoConnectionDistance => 250f / ViewScale;
private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta;
private Float2 _gridRoundingDelta;
private HashSet<SurfaceNode> _movingNodes;
private HashSet<SurfaceNode> _temporarySelectedNodes;
private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>();
private bool _isLazyConnecting;
private SurfaceNode _lazyConnectStartNode;
private SurfaceNode _lazyConnectEndNode;
private InputBinding _focusSelectedNodeBinding;
private class InputBracket
{
@@ -250,8 +265,13 @@ namespace FlaxEditor.Surface
// Cache mouse location
_mousePos = location;
if (_isLazyConnecting && GetControlUnderMouse() is SurfaceNode nodeUnderMouse && !(nodeUnderMouse is SurfaceComment || nodeUnderMouse is ParticleEmitterNode))
_lazyConnectEndNode = nodeUnderMouse;
else if (_isLazyConnecting && Nodes.Count > 0)
_lazyConnectEndNode = GetClosestNodeAtLocation(location);
// Moving around surface with mouse
if (_rightMouseDown)
if (_rightMouseDown && !_isLazyConnecting)
{
// Calculate delta
var delta = location - _rightMouseDownPos;
@@ -321,6 +341,33 @@ namespace FlaxEditor.Surface
foreach (var node in _movingNodes)
{
// Allow ripping the node from its current connection
if (RootWindow.GetKey(KeyboardKeys.Alt))
{
InputBox nodeConnectedInput = null;
OutputBox nodeConnectedOuput = null;
var boxes = node.GetBoxes();
foreach (var box in boxes)
{
if (!box.IsOutput && box.Connections.Count > 0)
{
nodeConnectedInput = (InputBox)box;
continue;
}
if (box.IsOutput && box.Connections.Count > 0)
{
nodeConnectedOuput = (OutputBox)box;
continue;
}
}
if (nodeConnectedInput != null && nodeConnectedOuput != null)
TryConnect(nodeConnectedOuput.Connections[0], nodeConnectedInput.Connections[0]);
node.RemoveConnections();
}
if (gridSnap)
{
Float2 unroundedLocation = node.Location;
@@ -420,7 +467,7 @@ namespace FlaxEditor.Surface
if (!handled && CanEdit && CanUseNodeType(7, 29))
{
var mousePos = _rootControl.PointFromParent(ref _mousePos);
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox) && GetControlUnderMouse() == null)
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox, MouseOverConnectionDistance) && GetControlUnderMouse() == null)
{
if (Undo != null)
{
@@ -515,11 +562,17 @@ namespace FlaxEditor.Surface
_middleMouseDownPos = location;
}
if (root.GetKey(KeyboardKeys.Alt) && button == MouseButton.Right)
_isLazyConnecting = true;
// Check if any node is under the mouse
SurfaceControl controlUnderMouse = GetControlUnderMouse();
var cLocation = _rootControl.PointFromParent(ref location);
if (controlUnderMouse != null)
{
if (controlUnderMouse is SurfaceNode node && _isLazyConnecting && !(controlUnderMouse is SurfaceComment || controlUnderMouse is ParticleEmitterNode))
_lazyConnectStartNode = node;
// Check if mouse is over header and user is pressing mouse left button
if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation))
{
@@ -554,6 +607,9 @@ namespace FlaxEditor.Surface
}
else
{
if (_isLazyConnecting && Nodes.Count > 0)
_lazyConnectStartNode = GetClosestNodeAtLocation(location);
// Cache flags and state
if (_leftMouseDown)
{
@@ -602,8 +658,71 @@ namespace FlaxEditor.Surface
{
if (_movingNodes != null && _movingNodes.Count > 0)
{
// Allow dropping a single node onto an existing connection and connect it
if (_movingNodes.Count == 1)
{
var mousePos = _rootControl.PointFromParent(ref _mousePos);
InputBox intersectedConnectionInputBox;
OutputBox intersectedConnectionOutputBox;
if (IntersectsConnection(mousePos, out intersectedConnectionInputBox, out intersectedConnectionOutputBox, SlotNodeIntoConnectionDistance))
{
SurfaceNode node = _movingNodes.First();
InputBox nodeInputBox = (InputBox)node.GetBoxes().First(b => !b.IsOutput);
OutputBox nodeOutputBox = (OutputBox)node.GetBoxes().First(b => b.IsOutput);
TryConnect(intersectedConnectionOutputBox, nodeInputBox);
TryConnect(nodeOutputBox, intersectedConnectionInputBox);
float intersectedConnectionNodesXDistance = intersectedConnectionInputBox.ParentNode.Left - intersectedConnectionOutputBox.ParentNode.Right;
float paddedNodeWidth = node.Width + 2f;
if (intersectedConnectionNodesXDistance < paddedNodeWidth)
{
List<SurfaceNode> visitedNodes = new List<SurfaceNode>{ node };
List<SurfaceNode> movedNodes = new List<SurfaceNode>();
Float2 locationDelta = new Float2(paddedNodeWidth, 0f);
MoveConnectedNodes(intersectedConnectionInputBox.ParentNode);
void MoveConnectedNodes(SurfaceNode node)
{
// Only move node if it is to the right of the node we have connected the moved node to
if (node.Right > intersectedConnectionInputBox.ParentNode.Left + 15f && !node.Archetype.Flags.HasFlag(NodeFlags.NoMove))
{
node.Location += locationDelta;
movedNodes.Add(node);
}
visitedNodes.Add(node);
foreach (var box in node.GetBoxes())
{
if (!box.HasAnyConnection || box == intersectedConnectionInputBox)
continue;
foreach (var connectedBox in box.Connections)
{
SurfaceNode nextNode = connectedBox.ParentNode;
if (visitedNodes.Contains(nextNode))
continue;
MoveConnectedNodes(nextNode);
}
}
}
Float2 nodeMoveOffset = new Float2(node.Width * 0.5f, 0f);
node.Location += nodeMoveOffset;
var moveNodesAction = new MoveNodesAction(Context, movedNodes.Select(n => n.ID).ToArray(), locationDelta);
var moveNodeAction = new MoveNodesAction(Context, [node.ID], nodeMoveOffset);
var multiAction = new MultiUndoAction(moveNodeAction, moveNodesAction);
AddBatchedUndoAction(multiAction);
}
}
}
if (Undo != null && !_movingNodesDelta.IsZero && CanEdit)
Undo.AddAction(new MoveNodesAction(Context, _movingNodes.Select(x => x.ID).ToArray(), _movingNodesDelta));
AddBatchedUndoAction(new MoveNodesAction(Context, _movingNodes.Select(x => x.ID).ToArray(), _movingNodesDelta));
_movingNodes.Clear();
}
_movingNodesDelta = Float2.Zero;
@@ -630,12 +749,36 @@ namespace FlaxEditor.Surface
{
// Check if any control is under the mouse
_cmStartPos = location;
if (controlUnderMouse == null)
if (controlUnderMouse == null && !_isLazyConnecting)
{
showPrimaryMenu = true;
}
}
_mouseMoveAmount = 0;
if (_isLazyConnecting)
{
if (_lazyConnectStartNode != null && _lazyConnectEndNode != null && _lazyConnectStartNode != _lazyConnectEndNode)
{
// First check if there is a type matching input and output where input
OutputBox startNodeOutput = (OutputBox)_lazyConnectStartNode.GetBoxes().FirstOrDefault(b => b.IsOutput, null);
InputBox endNodeInput = null;
if (startNodeOutput != null)
endNodeInput = (InputBox)_lazyConnectEndNode.GetBoxes().FirstOrDefault(b => !b.IsOutput && b.CurrentType == startNodeOutput.CurrentType && !b.HasAnyConnection && b.IsActive && b.CanConnectWith(startNodeOutput), null);
// Perform less strict checks (less ideal conditions for connection but still good) if the first checks failed
if (endNodeInput == null)
endNodeInput = (InputBox)_lazyConnectEndNode.GetBoxes().FirstOrDefault(b => !b.IsOutput && !b.HasAnyConnection && b.CanConnectWith(startNodeOutput), null);
if (startNodeOutput != null && endNodeInput != null)
TryConnect(startNodeOutput, endNodeInput);
}
_isLazyConnecting = false;
_lazyConnectStartNode = null;
_lazyConnectEndNode = null;
}
}
if (_middleMouseDown && button == MouseButton.Middle)
{
@@ -651,7 +794,7 @@ namespace FlaxEditor.Surface
{
// Surface was not moved with MMB so try to remove connection underneath
var mousePos = _rootControl.PointFromParent(ref location);
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox))
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox, MouseOverConnectionDistance))
{
var action = new EditNodeConnections(inputBox.ParentNode.Context, inputBox.ParentNode);
inputBox.BreakConnection(outputBox);
@@ -704,13 +847,21 @@ namespace FlaxEditor.Surface
private void MoveSelectedNodes(Float2 delta)
{
// TODO: undo
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
delta /= _targetScale;
OnGetNodesToMove();
foreach (var node in _movingNodes)
{
node.Location += delta;
if (Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { node.ID }, delta));
}
_isMovingSelection = false;
MarkAsEdited(false);
if (undoActions.Count > 0)
Undo?.AddAction(new MultiUndoAction(undoActions, "Moved "));
}
/// <inheritdoc />
@@ -837,6 +988,29 @@ namespace FlaxEditor.Surface
return false;
}
private SurfaceNode GetClosestNodeAtLocation(Float2 location)
{
SurfaceNode currentClosestNode = null;
float currentClosestDistanceSquared = float.MaxValue;
foreach (var node in Nodes)
{
if (node is SurfaceComment || node is ParticleEmitterNode)
continue;
Float2 nodeSurfaceLocation = _rootControl.PointToParent(node.Center);
float distanceSquared = Float2.DistanceSquared(location, nodeSurfaceLocation);
if (distanceSquared < currentClosestDistanceSquared)
{
currentClosestNode = node;
currentClosestDistanceSquared = distanceSquared;
}
}
return currentClosestNode;
}
private void ResetInput()
{
InputText = "";
@@ -845,7 +1019,8 @@ namespace FlaxEditor.Surface
private void CurrentInputTextChanged(string currentInputText)
{
if (string.IsNullOrEmpty(currentInputText))
// Check if focus selected nodes binding is being pressed to prevent it triggering primary menu
if (string.IsNullOrEmpty(currentInputText) || _focusSelectedNodeBinding.Process(RootWindow))
return;
if (IsPrimaryMenuOpened || !CanEdit)
{
@@ -1025,7 +1200,7 @@ namespace FlaxEditor.Surface
return new Float2(xLocation, yLocation);
}
private bool IntersectsConnection(Float2 mousePosition, out InputBox inputBox, out OutputBox outputBox)
private bool IntersectsConnection(Float2 mousePosition, out InputBox inputBox, out OutputBox outputBox, float distance)
{
for (int i = 0; i < Nodes.Count; i++)
{
@@ -1035,7 +1210,7 @@ namespace FlaxEditor.Surface
{
for (int k = 0; k < ob.Connections.Count; k++)
{
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition))
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition, distance))
{
outputBox = ob;
inputBox = ob.Connections[k] as InputBox;

View File

@@ -423,8 +423,9 @@ namespace FlaxEditor.Surface
new InputActionsContainer.Binding(options => options.NodesAlignLeft, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Left); }),
new InputActionsContainer.Binding(options => options.NodesAlignCenter, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); }),
new InputActionsContainer.Binding(options => options.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); }),
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
new InputActionsContainer.Binding(options => options.FocusSelectedNodes, () => { FocusSelectionOrWholeGraph(); }),
});
Context.ControlSpawned += OnSurfaceControlSpawned;
@@ -436,7 +437,10 @@ namespace FlaxEditor.Surface
DragHandlers.Add(_dragAssets = new DragAssets<DragDropEventArgs>(ValidateDragItem));
DragHandlers.Add(_dragParameters = new DragNames<DragDropEventArgs>(SurfaceParameter.DragPrefix, ValidateDragParameter));
OnEditorOptionsChanged(Editor.Instance.Options.Options);
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
}
private void OnScriptsReloadBegin()
@@ -446,6 +450,11 @@ namespace FlaxEditor.Surface
_cmPrimaryMenu = null;
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_focusSelectedNodeBinding = options.Input.FocusSelectedNodes;
}
/// <summary>
/// Gets the display name of the connection type used in the surface.
/// </summary>
@@ -583,6 +592,11 @@ namespace FlaxEditor.Surface
/// </summary>
public virtual bool CanSetParameters => false;
/// <summary>
/// Gets a value indicating whether surface private parameters can be used, otherwise they will remain hidden.
/// </summary>
public virtual bool CanShowPrivateParameters => false;
/// <summary>
/// True of the context menu should make use of a description panel drawn at the bottom of the menu
/// </summary>
@@ -643,6 +657,37 @@ namespace FlaxEditor.Surface
ViewCenterPosition = areaRect.Center;
}
/// <summary>
/// Adjusts the view to focus on the currently selected nodes, or the entire graph if no nodes are selected.
/// </summary>
public void FocusSelectionOrWholeGraph()
{
if (SelectedNodes.Count > 0)
ShowSelection();
else
ShowWholeGraph();
}
/// <summary>
/// Shows the selected controls by changing the view scale and the position.
/// </summary>
public void ShowSelection()
{
var selection = SelectedControls;
if (selection.Count == 0)
return;
// Calculate the bounds of all selected controls
Rectangle bounds = selection[0].Bounds;
for (int i = 1; i < selection.Count; i++)
bounds = Rectangle.Union(bounds, selection[i].Bounds);
// Add margin
bounds = bounds.MakeExpanded(250.0f);
ShowArea(bounds);
}
/// <summary>
/// Shows the given surface node by changing the view scale and the position and focuses the node.
/// </summary>
@@ -1066,6 +1111,7 @@ namespace FlaxEditor.Surface
_cmPrimaryMenu?.Dispose();
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
base.OnDestroy();
}

View File

@@ -254,9 +254,10 @@ namespace FlaxEditor.Surface
public SurfaceParameter GetParameter(Guid id)
{
SurfaceParameter result = null;
for (int i = 0; i < Parameters.Count; i++)
var parameters = Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var parameter = Parameters[i];
var parameter = parameters[i];
if (parameter.ID == id)
{
result = parameter;
@@ -274,9 +275,10 @@ namespace FlaxEditor.Surface
public SurfaceParameter GetParameter(string name)
{
SurfaceParameter result = null;
for (int i = 0; i < Parameters.Count; i++)
var parameters = Parameters;
for (int i = 0; i < parameters.Count; i++)
{
var parameter = Parameters[i];
var parameter = parameters[i];
if (parameter.Name == name)
{
result = parameter;

View File

@@ -708,12 +708,30 @@ namespace FlaxEditor.Surface
{
var index = (int)label.Tag;
menu.AddSeparator();
menu.AddButton("Copy name", () => Clipboard.Text = ((IVisjectSurfaceWindow)Values[0]).VisjectSurface.Parameters[index].Name);
// TODO: move 'Copy all names' to context menu of the Properties category (as it's not item-specific)
menu.AddButton("Copy all names", CopyAllParameterNamesAsConstantCSharpCode);
menu.AddSeparator();
menu.AddButton("Rename", () => StartParameterRenaming(index, label));
menu.AddButton("Edit attributes...", () => EditAttributesParameter(index, label));
menu.AddButton("Delete", () => DeleteParameter(index));
OnParamContextMenu(index, menu);
}
private void CopyAllParameterNamesAsConstantCSharpCode()
{
string allParamNames = "";
foreach (var param in ((IVisjectSurfaceWindow)Values[0]).VisjectSurface.Parameters)
{
string cleanParamName = param.Name.Replace(" ", "");
// Filter out headers and other non-parameter entries that can be present in the parameters list
if (string.IsNullOrEmpty(cleanParamName))
continue;
allParamNames += $"private const string {cleanParamName}ParameterName = \"{param.Name}\";\n";
}
Clipboard.Text = allParamNames;
}
private void StartParameterRenaming(int index, Control label)
{
var window = (IVisjectSurfaceWindow)Values[0];

View File

@@ -187,6 +187,9 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
public override bool CanSetParameters => true;
/// <inheritdoc />
public override bool CanShowPrivateParameters => true;
/// <inheritdoc />
public override bool UseContextMenuDescriptionPanel => true;

View File

@@ -89,7 +89,7 @@ namespace FlaxEditor.Tools.Terrain
if (!terrain.HasPatch(ref patchCoord) && _planeModel)
{
var planeSize = 100.0f;
var patchSize = terrain.ChunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
Matrix world = Matrix.RotationX(-Mathf.PiOverTwo) *
Matrix.Scaling(patchSize / planeSize) *
Matrix.Translation(patchSize * (0.5f + patchCoord.X), 0, patchSize * (0.5f + patchCoord.Y)) *

View File

@@ -69,9 +69,9 @@ namespace FlaxEditor.Tools.Terrain.Paint
var splatmapIndex = ActiveSplatmapIndex;
var splatmapIndexOther = (splatmapIndex + 1) % 2;
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
var heightmapLength = heightmapSize * heightmapSize;
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer();
var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer();
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;

View File

@@ -70,9 +70,9 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
// Prepare
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
var heightmapLength = heightmapSize * heightmapSize;
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var patchSize = terrain.PatchSize;
var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer();
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;

View File

@@ -382,7 +382,8 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
const Int2 heightmapSize = size * Terrain::ChunksCountEdge * terrain->GetChunkSize() + 1;
Array<float> heightmap;
heightmap.Resize(heightmapSize.X * heightmapSize.Y);
heightmap.SetAll(firstPatch->GetHeightmapData()[0]);
if (const float* heightmapData = firstPatch->GetHeightmapData())
heightmap.SetAll(heightmapData[0]);
// Fill heightmap with data from all patches
const int32 rowSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1;
@@ -392,8 +393,16 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder)
const Int2 pos(patch->GetX() - start.X, patch->GetZ() - start.Y);
const float* src = patch->GetHeightmapData();
float* dst = heightmap.Get() + pos.X * (rowSize - 1) + pos.Y * heightmapSize.X * (rowSize - 1);
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
if (src)
{
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryCopy(dst + row * heightmapSize.X, src + row * rowSize, rowSize * sizeof(float));
}
else
{
for (int32 row = 0; row < rowSize; row++)
Platform::MemoryClear(dst + row * heightmapSize.X, rowSize * sizeof(float));
}
}
// Interpolate to 16-bit int

View File

@@ -85,8 +85,7 @@ namespace FlaxEditor.Tools.Terrain.Undo
{
_terrain = terrain.ID;
_patches = new List<PatchData>(4);
var chunkSize = terrain.ChunkSize;
var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
var heightmapSize = terrain.HeightmapSize;
_heightmapLength = heightmapSize * heightmapSize;
_heightmapDataSize = _heightmapLength * stride;

View File

@@ -163,7 +163,6 @@ namespace FlaxEditor.Viewport
private bool _useMouseAcceleration;
// Input
internal bool _disableInputUpdate;
private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown;
private int _deltaFilteringStep;
@@ -247,8 +246,8 @@ namespace FlaxEditor.Viewport
{
_movementSpeed = value;
if (_cameraButton != null)
_cameraButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
if (_orthographicModeButton != null)
_orthographicModeButton.Text = string.Format(MovementSpeedTextFormat, _movementSpeed);
}
}
@@ -581,7 +580,7 @@ namespace FlaxEditor.Viewport
// Camera Settings Menu
var cameraCM = new ContextMenu();
_cameraButton = new ViewportWidgetButton(string.Format(MovementSpeedTextFormat, _movementSpeed), _editor.Icons.Camera64, cameraCM, false, cameraSpeedTextWidth)
_cameraButton = new ViewportWidgetButton("", _editor.Icons.Camera64, cameraCM)
{
Tag = this,
TooltipText = "Camera Settings.",
@@ -590,7 +589,7 @@ namespace FlaxEditor.Viewport
_cameraWidget.Parent = this;
// Orthographic/Perspective Mode Widget
_orthographicModeButton = new ViewportWidgetButton(string.Empty, _editor.Icons.CamSpeed32, null, true)
_orthographicModeButton = new OrthoCamToggleViewportWidgetButton(cameraSpeedTextWidth)
{
Checked = !_isOrtho,
TooltipText = "Toggle Orthographic/Perspective Mode.",
@@ -1223,7 +1222,7 @@ namespace FlaxEditor.Viewport
/// Orients the viewport.
/// </summary>
/// <param name="orientation">The orientation.</param>
protected void OrientViewport(Quaternion orientation)
public void OrientViewport(Quaternion orientation)
{
OrientViewport(ref orientation);
}
@@ -1232,7 +1231,7 @@ namespace FlaxEditor.Viewport
/// Orients the viewport.
/// </summary>
/// <param name="orientation">The orientation.</param>
protected virtual void OrientViewport(ref Quaternion orientation)
public virtual void OrientViewport(ref Quaternion orientation)
{
if (ViewportCamera is FPSCamera fpsCamera)
{

View File

@@ -1,18 +1,19 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using Object = FlaxEngine.Object;
using FlaxEditor.Content;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Options;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Modes;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.Windows;
using FlaxEngine;
using FlaxEngine.Gizmo;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport
{
@@ -26,6 +27,7 @@ namespace FlaxEditor.Viewport
private readonly ContextMenuButton _showGridButton;
private readonly ContextMenuButton _showNavigationButton;
private readonly ContextMenuButton _toggleGameViewButton;
private readonly ContextMenuButton _showDirectionGizmoButton;
private SelectionOutline _customSelectionOutline;
/// <summary>
@@ -108,13 +110,14 @@ namespace FlaxEditor.Viewport
private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32);
private EditorSpritesRenderer _editorSpritesRenderer;
private ViewportRubberBandSelector _rubberBandSelector;
private DirectionGizmo _directionGizmo;
private bool _gameViewActive;
private ViewFlags _preGameViewFlags;
private ViewMode _preGameViewViewMode;
private bool _gameViewWasGridShown;
private bool _gameViewWasFpsCounterShown;
private bool _gameViewWasNagivationShown;
private bool _gameViewWasNavigationShown;
/// <summary>
/// Drag and drop handlers
@@ -226,6 +229,13 @@ namespace FlaxEditor.Viewport
// Add rubber band selector
_rubberBandSelector = new ViewportRubberBandSelector(this);
// Add direction gizmo
_directionGizmo = new DirectionGizmo(this)
{
AnchorPreset = AnchorPresets.TopRight,
Parent = this,
};
// Add grid
Grid = new GridGizmo(this);
Grid.EnabledChanged += gizmo => _showGridButton.Icon = gizmo.Enabled ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
@@ -244,6 +254,11 @@ namespace FlaxEditor.Viewport
_showNavigationButton = ViewWidgetShowMenu.AddButton("Navigation", inputOptions.ToggleNavMeshVisibility, () => ShowNavigation = !ShowNavigation);
_showNavigationButton.CloseMenuOnClick = false;
// Show direction gizmo widget
_showDirectionGizmoButton = ViewWidgetShowMenu.AddButton("Direction Gizmo", () => _directionGizmo.Visible = !_directionGizmo.Visible);
_showDirectionGizmoButton.AutoCheck = true;
_showDirectionGizmoButton.CloseMenuOnClick = false;
// Game View
ViewWidgetButtonMenu.AddSeparator();
_toggleGameViewButton = ViewWidgetButtonMenu.AddButton("Game View", inputOptions.ToggleGameView, ToggleGameView);
@@ -277,6 +292,18 @@ namespace FlaxEditor.Viewport
// Game View
InputActions.Add(options => options.ToggleGameView, ToggleGameView);
editor.Options.OptionsChanged += OnEditorOptionsChanged;
OnEditorOptionsChanged(editor.Options.Options);
}
private void OnEditorOptionsChanged(EditorOptions options)
{
_directionGizmo.Visible = options.Viewport.ShowDirectionGizmo;
_showDirectionGizmoButton.Checked = _directionGizmo.Visible;
_directionGizmo.Size = new Float2(DirectionGizmo.DefaultGizmoSize * options.Viewport.DirectionGizmoScale);
_directionGizmo.LocalX = -_directionGizmo.Size.X * 0.5f;
_directionGizmo.LocalY = _directionGizmo.Size.Y * 0.5f + ViewportWidgetsContainer.WidgetsHeight;
}
/// <inheritdoc />
@@ -514,14 +541,14 @@ namespace FlaxEditor.Viewport
_preGameViewViewMode = Task.ViewMode;
_gameViewWasGridShown = Grid.Enabled;
_gameViewWasFpsCounterShown = ShowFpsCounter;
_gameViewWasNagivationShown = ShowNavigation;
_gameViewWasNavigationShown = ShowNavigation;
}
// Set flags & values
Task.ViewFlags = _gameViewActive ? _preGameViewFlags : ViewFlags.DefaultGame;
Task.ViewMode = _gameViewActive ? _preGameViewViewMode : ViewMode.Default;
ShowFpsCounter = _gameViewActive ? _gameViewWasFpsCounterShown : false;
ShowNavigation = _gameViewActive ? _gameViewWasNagivationShown : false;
ShowNavigation = _gameViewActive ? _gameViewWasNavigationShown : false;
Grid.Enabled = _gameViewActive ? _gameViewWasGridShown : false;
_gameViewActive = !_gameViewActive;
@@ -647,7 +674,7 @@ namespace FlaxEditor.Viewport
}
/// <inheritdoc />
protected override void OrientViewport(ref Quaternion orientation)
public override void OrientViewport(ref Quaternion orientation)
{
if (TransformGizmo.SelectedParents.Count != 0)
FocusSelection(ref orientation);

View File

@@ -6,7 +6,6 @@ using System.Linq;
using FlaxEditor.Content;
using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.Modules;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
@@ -15,7 +14,6 @@ using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using Utils = FlaxEditor.Utilities.Utils;
namespace FlaxEditor.Viewport
@@ -185,6 +183,7 @@ namespace FlaxEditor.Viewport
showGridButton.Clicked += () =>
{
_gridGizmo.Enabled = !_gridGizmo.Enabled;
_uiRoot.ShowGrid = _gridGizmo.Enabled;
showGridButton.Checked = _gridGizmo.Enabled;
};
showGridButton.Checked = true;
@@ -682,7 +681,7 @@ namespace FlaxEditor.Viewport
}
/// <inheritdoc />
protected override void OrientViewport(ref Quaternion orientation)
public override void OrientViewport(ref Quaternion orientation)
{
if (TransformGizmo.SelectedParents.Count != 0)
FocusSelection(ref orientation);

View File

@@ -303,8 +303,7 @@ namespace FlaxEditor.Viewport.Previews
{
_terrain = new Terrain();
_terrain.Setup(1, 63);
var chunkSize = _terrain.ChunkSize;
var heightMapSize = chunkSize * Terrain.PatchEdgeChunksCount + 1;
var heightMapSize = _terrain.HeightmapSize;
var heightMapLength = heightMapSize * heightMapSize;
var heightmap = new float[heightMapLength];
var patchCoord = new Int2(0, 0);

View File

@@ -198,6 +198,11 @@ namespace FlaxEditor.Viewport
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
_owner.Spawn(actor);
_viewport.Focus();
// Scroll to the new actor in the hierarchy
var actorNode = Editor.Instance.Scene.GetActorNode(actor);
if (actorNode?.TreeNode.ParentTree.Parent is Panel treePanel)
FlaxEngine.Scripting.InvokeOnUpdate(() => treePanel.ScrollViewTo(actorNode.TreeNode));
}
private void Spawn(ScriptItem item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)

View File

@@ -7,6 +7,100 @@ using FlaxEngine.GUI;
namespace FlaxEditor.Viewport.Widgets
{
/// <summary>
/// Otrhographic view toggle viewport Widget Button class.
/// Will draw a custom camera frustum to represent an orthographic or perspective camera.
/// </summary>
/// <seealso cref="ViewportWidgetButton" />
[HideInEditor]
public class OrthoCamToggleViewportWidgetButton : ViewportWidgetButton
{
private const int iconPointCount = 4;
private const float iconRenderScale = 4.0f;
private readonly Float2 iconDrawOffset = new Float2(3.0f, 3.0f);
private readonly Float2[] iconPointsPerspective = new[]
{
new Float2(0.0f, 1.0f), // Top left
new Float2(4.0f, 0.0f), // Top right
new Float2(4.0f, 3.0f), // Bottom right
new Float2(0.0f, 2.0f), // Bottom left
};
private readonly Float2[] iconPointsOrtho = new[]
{
new Float2(0.0f, 0.0f), // Top left
new Float2(4.0f, 0.0f), // Top right
new Float2(4.0f, 3.0f), // Bottom right
new Float2(0.0f, 3.0f), // Bottom left
};
private bool wasChecked;
private float lerpWeight;
/// <summary>
/// Initializes a new instance of the <see cref="OrthoCamToggleViewportWidgetButton"/> class.
/// </summary>
public OrthoCamToggleViewportWidgetButton(float speedTextWidth)
: base("00.0", SpriteHandle.Invalid, null, true, 20.0f + speedTextWidth)
{
wasChecked = Checked;
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
if (wasChecked != Checked)
{
lerpWeight = 0.0f;
wasChecked = Checked;
}
if (lerpWeight <= 1.0f)
lerpWeight += deltaTime * 4.2f;
base.Update(deltaTime);
}
/// <inheritdoc />
public override void Draw()
{
// Cache data
var style = Style.Current;
var textRect = new Rectangle(0.0f, 0.0f, Width - 2.0f, Height);
var backgroundRect = textRect with { Width = Width - 1.0f };
// Check if is checked or mouse is over
if (Checked)
Render2D.FillRectangle(backgroundRect, style.BackgroundSelected * (IsMouseOver ? 0.9f : 0.6f));
else if (IsMouseOver)
Render2D.FillRectangle(backgroundRect, style.BackgroundHighlighted);
// Draw text
Render2D.DrawText(style.FontMedium, Text, textRect, style.ForegroundViewport * (IsMouseOver ? 1.0f : 0.9f), TextAlignment.Far, TextAlignment.Center);
// Draw camera frustum icon
Float2[] currentStart = Checked ? iconPointsOrtho : iconPointsPerspective;
Float2[] currentTarget = Checked ? iconPointsPerspective : iconPointsOrtho;
var features = Render2D.Features;
Render2D.Features = features & ~Render2D.RenderingFeatures.VertexSnapping;
for (int i = 1; i < iconPointCount + 1; i++)
{
int endPointIndex = Mathf.Wrap(i, 0, iconPointCount - 1);
Float2 lineStart = Float2.Lerp(currentStart[i - 1], currentTarget[i - 1], lerpWeight);
Float2 lineEnd = Float2.Lerp(currentStart[endPointIndex], currentTarget[endPointIndex], lerpWeight);
lineStart = lineStart * iconRenderScale + iconDrawOffset;
lineEnd = lineEnd * iconRenderScale + iconDrawOffset;
Render2D.DrawLine(lineStart, lineEnd, Color.White);
}
Render2D.Features = features;
}
}
/// <summary>
/// Viewport Widget Button class.
/// </summary>

View File

@@ -2,6 +2,7 @@
//#define USE_AUTODESK_FBX_SDK
using System.Collections.Generic;
using System.Reflection;
using FlaxEditor.GUI.Dialogs;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -45,9 +46,16 @@ namespace FlaxEditor.Windows
VerticalAlignment = TextAlignment.Center,
Parent = this
};
var assembly = typeof(Editor).Assembly;
var assemblyCopyright = assembly.GetCustomAttribute<AssemblyCopyrightAttribute>();
var assemblyInformationalVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
var versionParts = assemblyInformationalVersion.InformationalVersion.Split('+');
string versionInfo = string.Empty;
if (versionParts.Length == 3)
versionInfo = $"\nBranch: {versionParts[1]}+{(versionParts[2].Length == 40 ? versionParts[2].Substring(0, 8) : versionParts[2])}";
new Label(nameLabel.Left, nameLabel.Bottom + 4, nameLabel.Width, 50)
{
Text = string.Format("Version: {0}\nCopyright (c) 2012-2025 Wojciech Figat.\nAll rights reserved.", Globals.EngineVersion),
Text = $"Version: {Globals.EngineVersion}{versionInfo}\n{assemblyCopyright.Copyright.Replace(". ", ".\n")}",
HorizontalAlignment = TextAlignment.Near,
VerticalAlignment = TextAlignment.Near,
Parent = this
@@ -98,6 +106,7 @@ namespace FlaxEditor.Windows
"Chandler Cox",
"Ari Vuollet",
"Vincent Saarmann",
"Michael Salvini",
});
authors.Sort();
var authorsLabel = new Label(4, topParentControl.Bottom + 20, Width - 8, 70)

View File

@@ -431,6 +431,9 @@ namespace FlaxEditor.Windows.Assets
_isWaitingForTimelineLoad = true;
base.OnItemReimported(item);
// Drop virtual asset state and get a new one from the reimported file
LoadFromOriginal();
}
/// <inheritdoc />
@@ -455,6 +458,7 @@ namespace FlaxEditor.Windows.Assets
_timeline.Enabled = true;
_timeline.SetNoTracksText(null);
ClearEditedFlag();
_timeline.ShowWholeTimeline();
}
}

View File

@@ -53,7 +53,7 @@ namespace FlaxEditor.Windows.Assets
{
Parent = this
};
_toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window");
_toolstrip.AddButton(editor.Icons.Search64, () => Editor.Windows.ContentWin.Select(_item)).LinkTooltip("Show and select in Content Window.");
InputActions.Add(options => options.Save, Save);
@@ -527,6 +527,16 @@ namespace FlaxEditor.Windows.Assets
return false;
}
/// <summary>
/// Loads the asset from the original location to reflect the state (eg. after original asset reimport).
/// </summary>
protected virtual void LoadFromOriginal()
{
_asset = LoadAsset();
OnAssetLoaded();
ClearEditedFlag();
}
/// <inheritdoc />
protected override T LoadAsset()
{

View File

@@ -21,6 +21,10 @@ namespace FlaxEditor.Windows.Assets
/// </summary>
private sealed class PropertiesProxy
{
[DefaultValue(FontRasterMode.Bitmap)]
[EditorOrder(5), EditorDisplay("Properties"), Tooltip("The rasterization mode used when generating font atlases.")]
public FontRasterMode RasterMode;
[DefaultValue(FontHinting.Default)]
[EditorOrder(10), EditorDisplay("Properties"), Tooltip("The font hinting used when rendering characters.")]
public FontHinting Hinting;
@@ -41,7 +45,8 @@ namespace FlaxEditor.Windows.Assets
{
options = new FontOptions
{
Hinting = Hinting
Hinting = Hinting,
RasterMode = RasterMode,
};
if (AntiAliasing)
options.Flags |= FontFlags.AntiAliasing;
@@ -57,6 +62,7 @@ namespace FlaxEditor.Windows.Assets
AntiAliasing = (options.Flags & FontFlags.AntiAliasing) == FontFlags.AntiAliasing;
Bold = (options.Flags & FontFlags.Bold) == FontFlags.Bold;
Italic = (options.Flags & FontFlags.Italic) == FontFlags.Italic;
RasterMode = options.RasterMode;
}
}

View File

@@ -39,8 +39,7 @@ namespace FlaxEditor.Windows
folder = CurrentViewFolder;
}
Assert.IsNotNull(folder);
bool isRootFolder = CurrentViewFolder == _root.Folder;
// Create context menu
ContextMenuButton b;
ContextMenu cm = new ContextMenu
@@ -178,19 +177,61 @@ namespace FlaxEditor.Windows
cm.AddSeparator();
CreateNewModuleMenu(cm, folder);
CreateNewFolderMenu(cm, folder, false, item);
CreateNewContentItemMenu(cm, folder);
if (folder.CanHaveAssets)
{
cm.AddButton("Import file", () =>
{
_view.ClearSelection();
Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder);
});
}
// Remove any leftover separator
if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator)
cm.ItemsContainer.Children.Last().Dispose();
// Show it
cm.Show(this, location);
}
private void CreateNewModuleMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false)
{
// Check if is source folder to add new module
if (folder?.ParentFolder?.Node is ProjectFolderTreeNode parentFolderNode && folder.Node == parentFolderNode.Source)
{
var button = cm.AddButton("New module");
var button = menu.AddButton("New module");
button.CloseMenuOnClick = false;
button.Clicked += () => NewModule(button, parentFolderNode.Source.Path);
}
if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectFolderTreeNode))
else if (disableUncreatable)
{
cm.AddButton("New folder", NewFolder);
var button = menu.AddButton("New module");
button.Enabled = false;
}
}
private bool CanCreateFolder(ContentItem item = null)
{
bool canCreateFolder = CurrentViewFolder != _root.Folder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectFolderTreeNode);
return canCreateFolder;
}
private void CreateNewFolderMenu(ContextMenu menu, ContentFolder folder, bool disableUncreatable = false, ContentItem item = null)
{
bool canCreateFolder = CanCreateFolder(item);
if (canCreateFolder || disableUncreatable)
{
var b = menu.AddButton("New folder", NewFolder);
b.Enabled = canCreateFolder;
}
}
private void CreateNewContentItemMenu(ContextMenu menu, ContentFolder folder, bool showNew = true, bool disableUncreatable = false)
{
// Loop through each proxy and user defined json type and add them to the context menu
var actorType = new ScriptType(typeof(Actor));
var scriptType = new ScriptType(typeof(Script));
@@ -230,7 +271,8 @@ namespace FlaxEditor.Windows
if (p == null)
continue;
if (p.CanCreate(folder))
bool canCreate = p.CanCreate(folder);
if (canCreate || disableUncreatable)
{
var parts = attribute.Path.Split('/');
ContextMenuChildMenu childCM = null;
@@ -238,16 +280,20 @@ namespace FlaxEditor.Windows
for (int i = 0; i < parts?.Length; i++)
{
var part = parts[i].Trim();
if (part == "New" && !showNew)
continue;
if (i == parts.Length - 1)
{
if (mainCM)
{
cm.AddButton(part, () => NewItem(p));
var b = menu.AddButton(part, () => NewItem(p));
b.Enabled = canCreate;
mainCM = false;
}
else if (childCM != null)
{
childCM.ContextMenu.AddButton(part, () => NewItem(p));
var b = childCM.ContextMenu.AddButton(part, () => NewItem(p));
b.Enabled = canCreate;
childCM.ContextMenu.AutoSort = true;
}
}
@@ -255,35 +301,21 @@ namespace FlaxEditor.Windows
{
if (mainCM)
{
childCM = cm.GetOrAddChildMenu(part);
childCM = menu.GetOrAddChildMenu(part);
childCM.ContextMenu.AutoSort = true;
childCM.Enabled = canCreate;
mainCM = false;
}
else if (childCM != null)
{
childCM = childCM.ContextMenu.GetOrAddChildMenu(part);
childCM.ContextMenu.AutoSort = true;
childCM.Enabled = canCreate;
}
}
}
}
}
if (folder.CanHaveAssets)
{
cm.AddButton("Import file", () =>
{
_view.ClearSelection();
Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder);
});
}
// Remove any leftover separator
if (cm.ItemsContainer.Children.LastOrDefault() is ContextMenuSeparator)
cm.ItemsContainer.Children.Last().Dispose();
// Show it
cm.Show(this, location);
}
private void OnExpandAllClicked(ContextMenuButton button)

View File

@@ -116,6 +116,7 @@ namespace FlaxEditor.Windows
var root = _root;
root.LockChildrenRecursive();
_suppressExpandedStateSave = true;
PerformLayout();
// Update tree
var query = _foldersSearchBox.Text;

View File

@@ -42,6 +42,7 @@ namespace FlaxEditor.Windows
private readonly ToolStrip _toolStrip;
private readonly ToolStripButton _importButton;
private readonly ToolStripButton _createNewButton;
private readonly ToolStripButton _navigateBackwardButton;
private readonly ToolStripButton _navigateForwardButton;
private readonly ToolStripButton _navigateUpButton;
@@ -167,11 +168,12 @@ namespace FlaxEditor.Windows
{
Parent = this,
};
_importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content");
_importButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Import64, () => Editor.ContentImporting.ShowImportFileDialog(CurrentViewFolder)).LinkTooltip("Import content.");
_createNewButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Add64, OnCreateNewItemButtonClicked).LinkTooltip("Create a new asset. Shift + left click to create a new folder.");
_toolStrip.AddSeparator();
_navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward");
_navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward");
_navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up");
_navigateBackwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Left64, NavigateBackward).LinkTooltip("Navigate backward.");
_navigateForwardButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Right64, NavigateForward).LinkTooltip("Navigate forward.");
_navigateUpButton = (ToolStripButton)_toolStrip.AddButton(Editor.Icons.Up64, NavigateUp).LinkTooltip("Navigate up.");
_toolStrip.AddSeparator();
// Split panel
@@ -319,6 +321,42 @@ namespace FlaxEditor.Windows
ApplyTreeViewScale();
}
private void OnCreateNewItemButtonClicked()
{
if (Input.GetKey(KeyboardKeys.Shift) && CanCreateFolder())
{
NewFolder();
return;
}
var menu = new ContextMenu();
InterfaceOptions interfaceOptions = Editor.Instance.Options.Options.Interface;
bool disableUnavaliable = interfaceOptions.UnavaliableContentCreateOptions == InterfaceOptions.DisabledHidden.Disabled;
CreateNewFolderMenu(menu, CurrentViewFolder, disableUnavaliable);
CreateNewModuleMenu(menu, CurrentViewFolder, disableUnavaliable);
menu.AddSeparator();
CreateNewContentItemMenu(menu, CurrentViewFolder, false, disableUnavaliable);
// Hack: Show the menu once to get the direction, then show it above or below the button depending on the direction.
menu.Show(this, _createNewButton.UpperLeft);
var direction = menu.Direction;
menu.Hide();
bool below = false;
switch (direction)
{
case ContextMenuDirection.RightDown:
case ContextMenuDirection.LeftDown:
below = true;
break;
case ContextMenuDirection.RightUp:
case ContextMenuDirection.LeftUp:
below = false;
break;
}
menu.Show(this, below ? _createNewButton.BottomLeft : _createNewButton.UpperLeft, direction);
}
private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox)
{
var menu = new ContextMenu();
@@ -1592,6 +1630,8 @@ namespace FlaxEditor.Windows
if (Editor.ContentDatabase.Find(_lastViewedFolderBeforeReload) is ContentFolder folder)
_tree.Select(folder.Node);
}
OnFoldersSearchBoxTextChanged();
}
private void Refresh()

View File

@@ -257,7 +257,7 @@ namespace FlaxEditor.Windows
private void UpdateCameraPreview()
{
// Disable rendering preview during GI baking
if (Editor.StateMachine.CurrentState.IsPerformanceHeavy)
if (Editor == null || Editor.StateMachine.CurrentState.IsPerformanceHeavy)
{
HideAllCameraPreviews();
return;
@@ -406,6 +406,14 @@ namespace FlaxEditor.Windows
}
}
/// <inheritdoc />
protected override void OnSizeChanged()
{
base.OnSizeChanged();
UpdateCameraPreview();
}
/// <inheritdoc />
public override void OnDestroy()
{

View File

@@ -68,6 +68,7 @@ namespace FlaxEditor.Windows
TooltipText = "Search the scene tree.\n\nYou can prefix your search with different search operators:\ns: -> Actor with script of type\na: -> Actor type\nc: -> Control type",
};
_searchBox.TextChanged += OnSearchBoxTextChanged;
ScriptsBuilder.ScriptsReloadEnd += OnSearchBoxTextChanged;
// Scene tree panel
_sceneTreePanel = new Panel
@@ -112,7 +113,7 @@ namespace FlaxEditor.Windows
InputActions.Add(options => options.LockFocusSelection, () => Editor.Windows.EditWin.Viewport.LockFocusSelection());
InputActions.Add(options => options.Rename, RenameSelection);
}
/// <inheritdoc />
public override void OnPlayBeginning()
{
@@ -125,6 +126,7 @@ namespace FlaxEditor.Windows
{
base.OnPlayBegin();
_blockSceneTreeScroll = false;
OnSearchBoxTextChanged();
}
/// <inheritdoc />
@@ -139,6 +141,7 @@ namespace FlaxEditor.Windows
{
base.OnPlayEnd();
_blockSceneTreeScroll = true;
OnSearchBoxTextChanged();
}
/// <summary>
@@ -174,6 +177,7 @@ namespace FlaxEditor.Windows
return;
_tree.LockChildrenRecursive();
PerformLayout();
// Update tree
var query = _searchBox.Text;
@@ -600,6 +604,7 @@ namespace FlaxEditor.Windows
_dragHandlers = null;
_tree = null;
_searchBox = null;
ScriptsBuilder.ScriptsReloadEnd -= OnSearchBoxTextChanged;
base.OnDestroy();
}

View File

@@ -109,7 +109,14 @@ void Behavior::UpdateAsync()
const BehaviorUpdateResult result = tree->Graph.Root->InvokeUpdate(context);
if (result != BehaviorUpdateResult::Running)
_result = result;
if (_result != BehaviorUpdateResult::Running)
if (_result != BehaviorUpdateResult::Running && tree->Graph.Root->Loop)
{
// Reset State
_result = BehaviorUpdateResult::Running;
_accumulatedTime = 0.0f;
_totalTime = 0;
}
else if (_result != BehaviorUpdateResult::Running)
{
Finished();
}

View File

@@ -96,6 +96,10 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeRootNode : public BehaviorTre
// The target amount of the behavior logic updates per second.
API_FIELD(Attributes="EditorOrder(100)")
float UpdateFPS = 10.0f;
// Whether to loop the root node.
API_FIELD(Attributes="EditorOrder(200)")
bool Loop = true;
};
/// <summary>

View File

@@ -3,7 +3,6 @@
#include "Audio.h"
#include "AudioBackend.h"
#include "AudioSettings.h"
#include "FlaxEngine.Gen.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Level/Level.h"

View File

@@ -207,16 +207,16 @@ public:
API_PROPERTY() void SetAttenuation(float value);
/// <summary>
/// Gets the doppler effect factor. Scale for source velocity. Default is 1.
/// Gets the doppler effect factor. Scale for source velocity. Default is 1. Used by spatial sources only. Cannot scale the effect up (only dim it).
/// </summary>
API_PROPERTY(Attributes="EditorOrder(75), DefaultValue(1.0f), Limit(0, float.MaxValue, 0.1f), EditorDisplay(\"Audio Source\")")
API_PROPERTY(Attributes="EditorOrder(75), DefaultValue(1.0f), Limit(0, 1.0f, 0.1f), EditorDisplay(\"Audio Source\")")
FORCE_INLINE float GetDopplerFactor() const
{
return _dopplerFactor;
}
/// <summary>
/// Sets the doppler effect factor. Scale for source velocity. Default is 1.
/// Sets the doppler effect factor. Scale for source velocity. Default is 1. Used by spatial sources only. Cannot scale the effect up (only dim it).
/// </summary>
API_PROPERTY() void SetDopplerFactor(float value);

View File

@@ -829,7 +829,6 @@ bool AudioBackendOAL::Base_Init()
}
// Init
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1);
if (clampedIndex == Audio::GetActiveDeviceIndex())
@@ -841,6 +840,7 @@ bool AudioBackendOAL::Base_Init()
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel);
#endif
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
ALC::Inited = true;
// Log service info

View File

@@ -8,6 +8,8 @@
#include "Loading/Tasks/LoadAssetTask.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
@@ -703,6 +705,38 @@ void Asset::onUnload_MainThread()
OnUnloaded(this);
}
bool Asset::WaitForInitGraphics()
{
#define IS_GPU_NOT_READY() (GPUDevice::Instance == nullptr || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
if (!IsInMainThread() && IS_GPU_NOT_READY())
{
PROFILE_CPU();
ZoneColor(TracyWaitZoneColor);
int32 timeout = 1000;
while (IS_GPU_NOT_READY() && timeout-- > 0)
Platform::Sleep(1);
if (IS_GPU_NOT_READY())
return true;
}
#undef IS_GPU_NOT_READY
return false;
}
bool Asset::WaitForInitPhysics()
{
if (!IsInMainThread() && !Physics::DefaultScene)
{
PROFILE_CPU();
ZoneColor(TracyWaitZoneColor);
int32 timeout = 1000;
while (!Physics::DefaultScene && timeout-- > 0)
Platform::Sleep(1);
if (!Physics::DefaultScene)
return true;
}
return false;
}
#if USE_EDITOR
bool Asset::OnCheckSave(const StringView& path) const

View File

@@ -285,6 +285,10 @@ protected:
virtual void onRename(const StringView& newPath) = 0;
#endif
// Utilities to ensure specific engine systems are initialized before loading asset (eg. assets can be loaded during engine startup).
static bool WaitForInitGraphics();
static bool WaitForInitPhysics();
public:
// [ManagedScriptingObject]
String ToString() const override;

View File

@@ -691,11 +691,7 @@ Asset::LoadResult Animation::load()
continue;
}
#if USE_EDITOR
if (!_registeredForScriptingReload)
{
_registeredForScriptingReload = true;
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
}
_registerForScriptingReload = true;
#endif
}
}
@@ -733,6 +729,7 @@ void Animation::unload(bool isReloading)
{
ScopeWriteLock systemScope(Animations::SystemLocker);
#if USE_EDITOR
_registerForScriptingReload = false;
if (_registeredForScriptingReload)
{
_registeredForScriptingReload = false;
@@ -752,6 +749,22 @@ void Animation::unload(bool isReloading)
NestedAnims.Clear();
}
#if USE_EDITOR
void Animation::onLoaded_MainThread()
{
if (_registerForScriptingReload && !_registeredForScriptingReload)
{
_registeredForScriptingReload = true;
Level::ScriptsReloadStart.Bind<Animation, &Animation::OnScriptsReloadStart>(this);
}
_registerForScriptingReload = false;
BinaryAsset::onLoaded_MainThread();
}
#endif
AssetChunksFlag Animation::getChunksToPreload() const
{
return GET_CHUNK_FLAG(0);

View File

@@ -78,6 +78,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Animation : public BinaryAsset
private:
#if USE_EDITOR
bool _registerForScriptingReload = false;
bool _registeredForScriptingReload = false;
void OnScriptsReloadStart();
#endif
@@ -163,5 +164,8 @@ protected:
// [BinaryAsset]
LoadResult load() override;
void unload(bool isReloading) override;
#if USE_EDITOR
void onLoaded_MainThread() override;
#endif
AssetChunksFlag getChunksToPreload() const override;
};

View File

@@ -5,7 +5,6 @@
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Content/Upgraders/ShaderAssetUpgrader.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Materials/MaterialShader.h"
#include "Engine/Graphics/Shaders/Cache/ShaderCacheManager.h"
@@ -156,16 +155,8 @@ Asset::LoadResult Material::load()
FlaxChunk* materialParamsChunk;
// Wait for the GPU Device to be ready (eg. case when loading material before GPU init)
#define IS_GPU_NOT_READY() (GPUDevice::Instance == nullptr || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
if (!IsInMainThread() && IS_GPU_NOT_READY())
{
int32 timeout = 1000;
while (IS_GPU_NOT_READY() && timeout-- > 0)
Platform::Sleep(1);
if (IS_GPU_NOT_READY())
return LoadResult::InvalidData;
}
#undef IS_GPU_NOT_READY
if (WaitForInitGraphics())
return LoadResult::CannotLoadData;
// If engine was compiled with shaders compiling service:
// - Material should be changed in need to convert it to the newer version (via Visject Surface)

View File

@@ -19,10 +19,10 @@ Variant MaterialBase::GetParameterValue(const StringView& name)
if (!IsLoaded() && WaitForLoaded())
return Variant::Null;
const auto param = Params.Get(name);
if (IsMaterialInstance() && param && !param->IsOverride() && ((MaterialInstance*)this)->GetBaseMaterial())
return ((MaterialInstance*)this)->GetBaseMaterial()->GetParameterValue(name);
if (param)
{
return param->GetValue();
}
LOG(Warning, "Missing material parameter '{0}' in material {1}", String(name), ToString());
return Variant::Null;
}

View File

@@ -57,6 +57,8 @@ public:
/// <summary>
/// Gets the material parameter value.
/// </summary>
/// <remarks>For material instances that inherit a base material, returned value might come from base material if the current one doesn't override it.</remarks>
/// <param name="name">The parameter name.</param>
/// <returns>The parameter value.</returns>
API_FUNCTION() Variant GetParameterValue(const StringView& name);

View File

@@ -46,6 +46,7 @@ namespace
ContentStorageService ContentStorageServiceInstance;
TimeSpan ContentStorageManager::UnusedStorageLifetime = TimeSpan::FromSeconds(0.5f);
TimeSpan ContentStorageManager::UnusedDataChunksLifetime = TimeSpan::FromSeconds(10);
FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, bool loadIt)

Some files were not shown because too many files have changed in this diff Show More