diff --git a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs index 73c51c072..fe31f0e34 100644 --- a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs +++ b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs @@ -36,6 +36,7 @@ namespace FlaxEditor.Content /// A asset proxy object. /// /// + [ContentContextMenu("New/Animation/Scene Animation")] public class SceneAnimationProxy : BinaryAssetProxy { /// diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 8494c4a8b..7656351ce 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -9,6 +9,7 @@ using FlaxEditor.Content; using FlaxEditor.Content.Import; using FlaxEditor.Content.Settings; using FlaxEditor.Content.Thumbnails; +using FlaxEditor.GUI; using FlaxEditor.Modules; using FlaxEditor.Modules.SourceCodeEditing; using FlaxEditor.Options; @@ -46,6 +47,8 @@ namespace FlaxEditor private bool _isAfterInit, _areModulesInited, _areModulesAfterInitEnd, _isHeadlessMode; private string _projectToOpen; private float _lastAutoSaveTimer; + private AutoSavePopup _autoSavePopup; + private bool _autoSaveNow; private Guid _startupSceneCmdLine; private const string ProjectDataLastScene = "LastScene"; @@ -491,7 +494,28 @@ namespace FlaxEditor var timeToNextSave = options.AutoSaveFrequency * 60.0f - timeSinceLastSave; var countDownDuration = 4.0f; - if (timeToNextSave <= 0.0f) + // Show auto save popup + if (timeToNextSave <= options.AutoSaveReminderTime && timeToNextSave >= 0) + { + if (_autoSavePopup == null) + { + _autoSavePopup = AutoSavePopup.Show(Instance.Windows.MainWindow.GUI, timeToNextSave); + _autoSavePopup.SaveNowButton.Clicked += () => _autoSaveNow = true; + _autoSavePopup.CancelSaveButton.Clicked += () => + { + Log("Auto save canceled"); + _autoSavePopup.HidePopup(); + _lastAutoSaveTimer = Time.UnscaledGameTime; // Reset timer + }; + } + else if (!_autoSavePopup.Visible && !_autoSavePopup.UserClosed) + _autoSavePopup.ShowPopup(); + + if (_autoSavePopup.Visible) + _autoSavePopup.UpdateTime(timeToNextSave); + } + + if (timeToNextSave <= 0.0f || _autoSaveNow) { Log("Auto save"); _lastAutoSaveTimer = Time.UnscaledGameTime; @@ -499,6 +523,11 @@ namespace FlaxEditor Scene.SaveScenes(); if (options.AutoSaveContent) SaveContent(); + + // Hide auto save popup and reset user closed + _autoSavePopup.HidePopup(); + _autoSavePopup.UserClosed = false; + _autoSaveNow = false; } else if (timeToNextSave < countDownDuration) { diff --git a/Source/Editor/GUI/Popups/AutoSavePopup.cs b/Source/Editor/GUI/Popups/AutoSavePopup.cs new file mode 100644 index 000000000..28f9577d8 --- /dev/null +++ b/Source/Editor/GUI/Popups/AutoSavePopup.cs @@ -0,0 +1,169 @@ +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.GUI +{ + /// + /// Popup menu for reminding of an upcoming auto save. + /// + public class AutoSavePopup : ContainerControl + { + /// + /// Gets or sets the value for if the user has manually closed this popup. + /// + public bool UserClosed { get; set; } + + /// + /// The save now button. Used to skip the last remaining auto save time. + /// + public Button SaveNowButton => _saveNowButton; + + /// + /// Button that should be used to cancel the auto save. + /// + public Button CancelSaveButton => _cancelSaveButton; + + private int _timeRemaining; + private Panel _backgroundPanel; + private Label _timeLabel; + private Button _saveNowButton; + private Button _cancelSaveButton; + private Color _defaultTextColor; + private bool _isMoved = false; + + /// + /// Initialize the AutoSavePopup. + /// + /// The initial time to set the time label to. + public AutoSavePopup(float initialTime) + { + UpdateTime(initialTime); + + _backgroundPanel = new Panel(ScrollBars.None) + { + Parent = this, + AnchorPreset = AnchorPresets.StretchAll, + BackgroundColor = Color.Transparent, + }; + + _timeLabel = new Label(0, 0, 25, 10) + { + Parent = _backgroundPanel, + Text = _timeRemaining.ToString(), + HorizontalAlignment = TextAlignment.Near, + VerticalAlignment = TextAlignment.Near, + AutoWidth = true, + Height = 14, + AnchorPreset = AnchorPresets.MiddleLeft, + }; + + _saveNowButton = new Button + { + Parent = _backgroundPanel, + Height = 14, + Width = 60, + AnchorPreset = AnchorPresets.MiddleRight, + BackgroundColor = Color.Transparent, + BorderColor = Color.Transparent, + BackgroundColorHighlighted = Color.Transparent, + BackgroundColorSelected = Color.Transparent, + BorderColorHighlighted = Color.Transparent, + Text = "Save Now", + TooltipText = "Saves now and restarts the auto save timer." + }; + _saveNowButton.LocalX -= 85; + + _cancelSaveButton = new Button + { + Parent = _backgroundPanel, + Height = 14, + Width = 70, + AnchorPreset = AnchorPresets.MiddleRight, + BackgroundColor = Color.Transparent, + BorderColor = Color.Transparent, + BackgroundColorHighlighted = Color.Transparent, + BackgroundColorSelected = Color.Transparent, + BorderColorHighlighted = Color.Transparent, + Text = "Cancel Save", + TooltipText = "Cancels this auto save." + }; + _cancelSaveButton.LocalX -= 5; + + _defaultTextColor = _saveNowButton.TextColor; + } + + /// + /// Updates the time label + /// + /// The time to change the label to. + public void UpdateTime(float time) + { + _timeRemaining = Mathf.CeilToInt(time); + if (_timeLabel != null) + _timeLabel.Text = "Auto Save in: " + _timeRemaining; + + // Move on text update if the progress bar is visible - removes this call from update + if (Editor.Instance.UI.ProgressVisible && !_isMoved) + { + _isMoved = true; + LocalX -= 250; + } + else if (!Editor.Instance.UI.ProgressVisible && _isMoved) + { + LocalX += 250; + _isMoved = false; + } + } + + /// + public override void Update(float deltaTime) + { + if (IsMouseOver) + { + _saveNowButton.TextColor = _saveNowButton.IsMouseOver ? Style.Current.BackgroundHighlighted : _defaultTextColor; + _cancelSaveButton.TextColor = _cancelSaveButton.IsMouseOver ? Style.Current.BackgroundHighlighted : _defaultTextColor; + } + base.Update(deltaTime); + } + + /// + /// Creates and shows the AutoSavePopup + /// + /// The parent control. + /// The time to start at. + /// + public static AutoSavePopup Show(ContainerControl parentControl, float initialTime) + { + var popup = new AutoSavePopup(initialTime) + { + Parent = parentControl, + Height = Editor.Instance.UI.StatusBar.Height, + Width = 250, + AnchorPreset = AnchorPresets.BottomRight, + }; + popup.ShowPopup(); + return popup; + } + + /// + /// Shows the popup by changing its visibility + /// + public void ShowPopup() + { + Visible = true; + UserClosed = false; + _saveNowButton.TextColor = _defaultTextColor; + _cancelSaveButton.TextColor = _defaultTextColor; + } + + /// + /// Hides the popup by changing its visibility + /// + public void HidePopup() + { + Visible = false; + if (_containsFocus) + Defocus(); + } + } +} diff --git a/Source/Editor/Options/GeneralOptions.cs b/Source/Editor/Options/GeneralOptions.cs index 6d4fa7e67..e97278aa0 100644 --- a/Source/Editor/Options/GeneralOptions.cs +++ b/Source/Editor/Options/GeneralOptions.cs @@ -196,19 +196,26 @@ namespace FlaxEditor.Options [DefaultValue(5), Limit(1)] [EditorDisplay("Auto Save", "Auto Save Frequency"), EditorOrder(801), Tooltip("The interval between auto saves (in minutes)")] public int AutoSaveFrequency { get; set; } = 5; + + /// + /// Gets or sets a value indicating the time before the auto save that the popup shows (in seconds). + /// + [DefaultValue(10), Limit(-1)] + [EditorDisplay("Auto Save", "Auto Save Reminder Time"), EditorOrder(802), Tooltip("The time before the auto save that the reminder popup shows (in seconds). Set to -1 to not show the reminder popup.")] + public int AutoSaveReminderTime { get; set; } = 10; /// /// Gets or sets a value indicating whether enable auto saves for scenes. /// [DefaultValue(true)] - [EditorDisplay("Auto Save", "Auto Save Scenes"), EditorOrder(802), Tooltip("Enables or disables auto saving opened scenes")] + [EditorDisplay("Auto Save", "Auto Save Scenes"), EditorOrder(803), Tooltip("Enables or disables auto saving opened scenes")] public bool AutoSaveScenes { get; set; } = true; /// /// Gets or sets a value indicating whether enable auto saves for content. /// [DefaultValue(true)] - [EditorDisplay("Auto Save", "Auto Save Content"), EditorOrder(803), Tooltip("Enables or disables auto saving content")] + [EditorDisplay("Auto Save", "Auto Save Content"), EditorOrder(804), Tooltip("Enables or disables auto saving content")] public bool AutoSaveContent { get; set; } = true; /// diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index c484c12a1..258bfd41a 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -405,6 +405,31 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, "Color", typeof(Float3), 3) } }, + new NodeArchetype + { + TypeID = 17, + Create = (id, context, arch, groupArch) => new SampleTextureNode(id, context, arch, groupArch), + Title = "Procedural Sample Texture", + Description = "Samples a texture to create a more natural look with less obvious tiling.", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(240, 110), + ConnectionsHints = ConnectionsHint.Vector, + DefaultValues = new object[] + { + 2, + -1.0f, + 0, + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0), + NodeElementArchetype.Factory.Input(1, "UVs", true, null, 1), + NodeElementArchetype.Factory.Input(2, "Offset", true, typeof(Float2), 3), + NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 4), + NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"), + NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4, 100, 0, typeof(CommonSamplerType)) + } + }, }; } } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 0590326e0..9551f0f34 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -397,6 +397,13 @@ namespace FlaxEditor.Surface.ContextMenu return; Profiler.BeginEvent("VisjectCM.OnSearchFilterChanged"); + + if (string.IsNullOrEmpty(_searchBox.Text)) + { + ResetView(); + Profiler.EndEvent(); + return; + } // Update groups LockChildrenRecursive(); diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 5196e8026..5e5aa2f28 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -906,12 +906,21 @@ namespace FlaxEditor.Utilities if (name.StartsWith("g_") || name.StartsWith("m_")) startIndex = 2; + if (name.StartsWith("_")) + startIndex = 1; + // Filter text var lastChar = '\0'; for (int i = startIndex; i < length; i++) { var c = name[i]; + if (i == startIndex) + { + sb.Append(char.ToUpper(c)); + continue; + } + // Space before word starting with uppercase letter if (char.IsUpper(c) && i > 0) { diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index 98a9e7c87..103101bec 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -459,6 +459,8 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) } // Sample Texture case 9: + // Procedural Texture Sample + case 17: { enum CommonSamplerType { @@ -479,7 +481,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) // Get input boxes auto textureBox = node->GetBox(0); auto uvsBox = node->GetBox(1); - auto levelBox = node->GetBox(2); + auto levelBox = node->TryGetBox(2); auto offsetBox = node->GetBox(3); if (!textureBox->HasConnection()) { @@ -519,7 +521,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) // Get other inputs const auto level = tryGetValue(levelBox, node->Values[1]); - const bool useLevel = levelBox->HasConnection() || (int32)node->Values[1] != -1; + const bool useLevel = (levelBox && levelBox->HasConnection()) || (int32)node->Values[1] != -1; const bool useOffset = offsetBox->HasConnection(); const auto offset = useOffset ? eatBox(offsetBox->GetParent(), offsetBox->FirstConnection()) : Value::Zero; const Char* samplerName; @@ -539,32 +541,78 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) return; } - // Pick a property format string - const Char* format; - if (useLevel || !canUseSample) + // Create texture sampling code + if (node->TypeID == 9) { - if (useOffset) - format = TEXT("{0}.SampleLevel({1}, {2}, {3}, {4})"); + // Sample Texture + const Char* format; + if (useLevel || !canUseSample) + { + if (useOffset) + format = TEXT("{0}.SampleLevel({1}, {2}, {3}, {4})"); + else + format = TEXT("{0}.SampleLevel({1}, {2}, {3})"); + } else - format = TEXT("{0}.SampleLevel({1}, {2}, {3})"); + { + if (useOffset) + format = TEXT("{0}.Sample({1}, {2}, {4})"); + else + format = TEXT("{0}.Sample({1}, {2})"); + } + const String sampledValue = String::Format(format, texture.Value, samplerName, uvs.Value, level.Value, offset.Value); + textureBox->Cache = writeLocal(VariantType::Float4, sampledValue, node); } else { - if (useOffset) - format = TEXT("{0}.Sample({1}, {2}, {4})"); - else - format = TEXT("{0}.Sample({1}, {2})"); - } + // Procedural Texture Sample + textureBox->Cache = writeLocal(Value::InitForZero(ValueType::Float4), node); + createGradients(node); + auto proceduralSample = String::Format(TEXT( + " {{\n" + " float3 weights;\n" + " float2 vertex1, vertex2, vertex3;\n" + " float2 uv = {0} * 3.464; // 2 * sqrt (3);\n" + " float2 uv1, uv2, uv3;\n" + " const float2x2 gridToSkewedGrid = float2x2(1.0, 0.0, -0.57735027, 1.15470054);\n" + " float2 skewedCoord = mul(gridToSkewedGrid, uv);\n" + " int2 baseId = int2(floor(skewedCoord));\n" + " float3 temp = float3(frac(skewedCoord), 0);\n" + " temp.z = 1.0 - temp.x - temp.y;\n" + " if (temp.z > 0.0)\n" + " {{\n" + " weights = float3(temp.z, temp.y, temp.x);\n" + " vertex1 = baseId;\n" + " vertex2 = baseId + int2(0, 1);\n" + " vertex3 = baseId + int2(1, 0);\n" + " }}\n" + " else\n" + " {{\n" + " weights = float3(-temp.z, 1.0 - temp.y, 1.0 - temp.x);\n" + " vertex1 = baseId + int2(1, 1);\n" + " vertex2 = baseId + int2(1, 0);\n" + " vertex3 = baseId + int2(0, 1);\n" + " }}\n" + " uv1 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex1)) * 43758.5453);\n" + " uv2 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex2)) * 43758.5453);\n" + " uv3 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex3)) * 43758.5453);\n" + " float4 tex1 = {1}.SampleGrad({4}, uv1, {2}, {3}, {6}) * weights.x;\n" + " float4 tex2 = {1}.SampleGrad({4}, uv2, {2}, {3}, {6}) * weights.y;\n" + " float4 tex3 = {1}.SampleGrad({4}, uv3, {2}, {3}, {6}) * weights.z;\n" + " {5} = tex1 + tex2 + tex3;\n" + " }}\n" + ), + uvs.Value, // {0} + texture.Value, // {1} + _ddx.Value, // {2} + _ddy.Value, // {3} + samplerName, // {4} + textureBox->Cache.Value, // {5} + offset.Value // {6} + ); - // Sample texture - const String sampledValue = String::Format(format, - texture.Value, // {0} - samplerName, // {1} - uvs.Value, // {2} - level.Value, // {3} - offset.Value // {4} - ); - textureBox->Cache = writeLocal(VariantType::Float4, sampledValue, node); + _writer.Write(*proceduralSample); + } // Decode normal map vector if (isNormalMap) @@ -627,7 +675,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) auto textureBox = node->GetBox(0); auto scaleBox = node->GetBox(1); auto blendBox = node->GetBox(2); - + if (!textureBox->HasConnection()) { // No texture to sample @@ -660,10 +708,10 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) " {3} += {0}.Sample(SamplerLinearWrap, worldPos.xy) * normal.z;\n" " }}\n" ), - texture.Value, // {0} - scale.Value, // {1} - blend.Value, // {2} - result.Value // {3} + texture.Value, // {0} + scale.Value, // {1} + blend.Value, // {2} + result.Value // {3} ); _writer.Write(*triplanarTexture); diff --git a/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs b/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs index ef7ecaa41..e51905ff2 100644 --- a/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs +++ b/Source/Tools/FlaxEngine.Tests/TestPropertyNameUI.cs @@ -11,10 +11,10 @@ namespace FlaxEditor.Tests [Test] public void TestFormatting() { - Assert.AreEqual("property", Utils.GetPropertyNameUI("property")); - Assert.AreEqual("property", Utils.GetPropertyNameUI("_property")); - Assert.AreEqual("property", Utils.GetPropertyNameUI("m_property")); - Assert.AreEqual("property", Utils.GetPropertyNameUI("g_property")); + Assert.AreEqual("Property", Utils.GetPropertyNameUI("property")); + Assert.AreEqual("Property", Utils.GetPropertyNameUI("_property")); + Assert.AreEqual("Property", Utils.GetPropertyNameUI("m_property")); + Assert.AreEqual("Property", Utils.GetPropertyNameUI("g_property")); Assert.AreEqual("Property", Utils.GetPropertyNameUI("Property")); Assert.AreEqual("Property 1", Utils.GetPropertyNameUI("Property1")); Assert.AreEqual("Property Name", Utils.GetPropertyNameUI("PropertyName"));