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"));