diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 96456601b..8ff4f1621 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -74,6 +74,7 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> AI + ARGB LO RPC SDK diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs index bdbf22ee9..1f2211571 100644 --- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs @@ -75,7 +75,11 @@ namespace FlaxEditor.CustomEditors.Dedicated // Selecting actor prefab asset var selectPrefab = panel.Button("Select Prefab"); - selectPrefab.Button.Clicked += () => Editor.Instance.Windows.ContentWin.Select(prefab); + selectPrefab.Button.Clicked += () => + { + Editor.Instance.Windows.ContentWin.ClearItemsSearch(); + Editor.Instance.Windows.ContentWin.Select(prefab); + }; // Viewing changes applied to this actor var viewChanges = panel.Button("View Changes"); diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index de7a5d1bf..b06e2d55d 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -227,7 +227,7 @@ namespace FlaxEditor.CustomEditors.Editors var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { - _canResize = !collection.ReadOnly; + _canResize = collection.CanResize; _readOnly = collection.ReadOnly; _minCount = collection.MinCount; _maxCount = collection.MaxCount; diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs index 0bf477834..23568f0ca 100644 --- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs @@ -189,6 +189,7 @@ namespace FlaxEditor.CustomEditors.Editors var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { + _canEditKeys &= collection.CanReorderItems; _readOnly = collection.ReadOnly; _notNullItems = collection.NotNullItems; if (collection.BackgroundColor.HasValue) diff --git a/Source/Editor/CustomEditors/Editors/StringEditor.cs b/Source/Editor/CustomEditors/Editors/StringEditor.cs index 9f6b4ce32..d4ed4b3e4 100644 --- a/Source/Editor/CustomEditors/Editors/StringEditor.cs +++ b/Source/Editor/CustomEditors/Editors/StringEditor.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.CustomEditors.Editors if (watermarkAttribute is WatermarkAttribute watermark) { _watermarkText = watermark.WatermarkText; - var watermarkColor = watermark.WatermarkColor > 0 ? Color.FromRGBA(watermark.WatermarkColor) : FlaxEngine.GUI.Style.Current.ForegroundDisabled; + var watermarkColor = watermark.WatermarkColor > 0 ? Color.FromRGB(watermark.WatermarkColor) : FlaxEngine.GUI.Style.Current.ForegroundDisabled; _watermarkColor = watermarkColor; _element.TextBox.WatermarkText = watermark.WatermarkText; _element.TextBox.WatermarkTextColor = watermarkColor; diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 643601b09..d1999bb99 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -318,7 +318,7 @@ namespace FlaxEditor.CustomEditors if (header.FontSize > 0) element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize); if (header.Color > 0) - element.Label.TextColor = Color.FromRGBA(header.Color); + element.Label.TextColor = Color.FromRGB(header.Color); return element; } diff --git a/Source/Editor/GUI/Timeline/Tracks/ConductorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ConductorTrack.cs index ae25bf03c..4b2cfbe5e 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ConductorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ConductorTrack.cs @@ -130,7 +130,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks /// The keyframes. /// [EditorDisplay("Keyframes", EditorDisplayAttribute.InlineStyle), ExpandGroups] - [Collection(CanReorderItems = false, ReadOnly = true)] + [Collection(CanReorderItems = false, CanResize = true)] public List> Keyframes; /// diff --git a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs index a7cc2e582..a1c85f5c3 100644 --- a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs @@ -159,7 +159,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks /// The parameters values. /// [EditorDisplay("Parameters", EditorDisplayAttribute.InlineStyle), ExpandGroups] - [Collection(CanReorderItems = false, ReadOnly = true)] + [Collection(CanReorderItems = false, CanResize = true)] public object[] Parameters; /// diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index 5b7535b5d..26cf02972 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -253,7 +253,11 @@ namespace FlaxEditor { // Select node (with additive mode) var selection = new List(); - if (Root.GetKey(KeyboardKeys.Control)) + if (Root.GetKey(KeyboardKeys.Shift) && transformGizmo.Selection.Contains(uiControlNode)) + { + // Move whole selection + } + else if (Root.GetKey(KeyboardKeys.Control)) { // Add/remove from selection selection.AddRange(transformGizmo.Selection); @@ -261,13 +265,14 @@ namespace FlaxEditor selection.Remove(uiControlNode); else selection.Add(uiControlNode); + owner.Select(selection); } else { // Select selection.Add(uiControlNode); + owner.Select(selection); } - owner.Select(selection); // Initialize control movement _mouseMovesControl = true; diff --git a/Source/Editor/Modules/PrefabsModule.cs b/Source/Editor/Modules/PrefabsModule.cs index e0ae29299..3b3054987 100644 --- a/Source/Editor/Modules/PrefabsModule.cs +++ b/Source/Editor/Modules/PrefabsModule.cs @@ -202,6 +202,7 @@ namespace FlaxEditor.Modules var prefabId = ((ActorNode)selection[0]).Actor.PrefabID; var prefab = FlaxEngine.Content.LoadAsync(prefabId); + Editor.Windows.ContentWin.ClearItemsSearch(); Editor.Windows.ContentWin.Select(prefab); } diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 9bd5385fd..00200900d 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -565,6 +565,7 @@ namespace FlaxEditor.Modules if (item != null) Editor.ContentEditing.Open(item); }); + cm.AddButton("Editor Options", () => Editor.Windows.EditorOptionsWin.Show()); // Scene MenuScene = MainMenu.AddButton("Scene"); @@ -619,7 +620,6 @@ namespace FlaxEditor.Modules _menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot); cm.AddSeparator(); cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show()); - cm.AddButton("Options", () => Editor.Windows.EditorOptionsWin.Show()); // Window MenuWindow = MainMenu.AddButton("Window"); diff --git a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs index acdcf68eb..f7a9d1ed3 100644 --- a/Source/Editor/Tools/Foliage/FoliageTypesTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTypesTab.cs @@ -82,7 +82,7 @@ namespace FlaxEditor.Tools.Foliage } } - [EditorOrder(20), EditorDisplay("Model"), Collection(ReadOnly = true), Tooltip("Model materials override collection. Can be used to change a specific material of the mesh to the custom one without editing the asset.")] + [EditorOrder(20), EditorDisplay("Model"), Collection(CanResize = true), Tooltip("Model materials override collection. Can be used to change a specific material of the mesh to the custom one without editing the asset.")] public MaterialBase[] Materials { get diff --git a/Source/Editor/Utilities/DuplicateScenes.cs b/Source/Editor/Utilities/DuplicateScenes.cs index 7902633b2..dc2d5e4bb 100644 --- a/Source/Editor/Utilities/DuplicateScenes.cs +++ b/Source/Editor/Utilities/DuplicateScenes.cs @@ -155,8 +155,8 @@ namespace FlaxEditor.Utilities var scene = Level.LoadSceneFromBytes(data.Bytes); if (scene == null) { - Profiler.EndEvent(); - throw new Exception("Failed to deserialize scene"); + Editor.LogError("Failed to restore scene"); + continue; } // Restore `dirty` state diff --git a/Source/Editor/Windows/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs index b5449dfca..a4b031d60 100644 --- a/Source/Editor/Windows/ContentWindow.Search.cs +++ b/Source/Editor/Windows/ContentWindow.Search.cs @@ -131,7 +131,7 @@ namespace FlaxEditor.Windows public void ClearItemsSearch() { // Skip if already cleared - if (_itemsSearchBox.TextLength == 0 && !_viewDropdown.HasSelection) + if (_itemsSearchBox.TextLength == 0) return; IsLayoutLocked = true; diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 349301ec9..34a97286c 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -860,7 +860,7 @@ namespace FlaxEditor.Windows { var alpha = Mathf.Saturate(-animTime / fadeOutTime); var rect = new Rectangle(new Float2(6), Size - 12); - var text = "Press Shift+F11 to unlock the mouse"; + var text = $"Press {Editor.Options.Options.Input.DebuggerUnlockMouse} to unlock the mouse"; Render2D.DrawText(style.FontSmall, text, rect + new Float2(1.0f), style.Background * alpha, TextAlignment.Near, TextAlignment.Far); Render2D.DrawText(style.FontSmall, text, rect, style.Foreground * alpha, TextAlignment.Near, TextAlignment.Far); } diff --git a/Source/Engine/Animations/CurveSerialization.h b/Source/Engine/Animations/CurveSerialization.h index f1526214f..9a7afa7a0 100644 --- a/Source/Engine/Animations/CurveSerialization.h +++ b/Source/Engine/Animations/CurveSerialization.h @@ -163,7 +163,10 @@ namespace Serialization { const auto& keyframesArray = mKeyframes->value.GetArray(); auto& keyframes = v.GetKeyframes(); + const int32 newCount = keyframesArray.Size() - keyframes.Count(); keyframes.Resize(keyframesArray.Size()); + for (int32 i = 0; i < newCount; i++) + keyframes[keyframes.Count() + i - newCount] = KeyFrame(0.0f, AnimationUtils::GetZero()); for (rapidjson::SizeType i = 0; i < keyframesArray.Size(); i++) Deserialize(keyframesArray[i], keyframes[i], modifier); } diff --git a/Source/Engine/Animations/InverseKinematics.cpp b/Source/Engine/Animations/InverseKinematics.cpp index c5db48320..2b3940147 100644 --- a/Source/Engine/Animations/InverseKinematics.cpp +++ b/Source/Engine/Animations/InverseKinematics.cpp @@ -80,7 +80,6 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ // Calculate new positions for joint and end effector Vector3 newEndEffectorPos = targetPosition; Vector3 newMidJointPos = midJointPos; - if (toTargetLength >= totalLimbLength) { // Target is beyond the reach of the limb @@ -90,13 +89,9 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ Vector3 rootToPole = (poleVector - rootTransform.Translation).GetNormalized(); Vector3 slightBendDirection = Vector3::Cross(rootToEnd, rootToPole); if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance) - { slightBendDirection = Vector3::Up; - } else - { slightBendDirection.Normalize(); - } // Calculate the direction from root to mid joint with a slight offset towards the pole vector Vector3 midJointDirection = Vector3::Cross(slightBendDirection, rootToEnd).GetNormalized(); @@ -117,9 +112,16 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ projJointDist *= -1.0f; newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection; } - + // TODO: fix the new IK impl (https://github.com/FlaxEngine/FlaxEngine/pull/2421) to properly work for character from https://github.com/PrecisionRender/CharacterControllerPro +#define OLD 0 // Update root joint orientation { +#if OLD + const Vector3 oldDir = (midJointPos - rootTransform.Translation).GetNormalized(); + const Vector3 newDir = (newMidJointPos - rootTransform.Translation).GetNormalized(); + const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir); + rootTransform.Orientation = deltaRotation * rootTransform.Orientation; +#else // Vector from root joint to mid joint (local Y-axis direction) Vector3 localY = (newMidJointPos - rootTransform.Translation).GetNormalized(); @@ -136,20 +138,23 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ localZ = Vector3::Cross(localX, localY).GetNormalized(); // Construct a rotation from the orthogonal basis vectors - Quaternion newRootJointOrientation = Quaternion::LookRotation(localZ, localY); - - // Apply the new rotation to the root joint - rootTransform.Orientation = newRootJointOrientation; + rootTransform.Orientation = Quaternion::LookRotation(localZ, localY); +#endif } // Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane { +#if OLD + const Vector3 oldDir = (endEffectorTransform.Translation - midJointPos).GetNormalized(); + const Vector3 newDir = (newEndEffectorPos - newMidJointPos).GetNormalized(); + const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir); + midJointTransform.Orientation = deltaRotation * midJointTransform.Orientation; +#else // Vector from mid joint to end effector (local Y-axis direction after rotation) Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized(); // Calculate the plane normal using the root, mid joint, and end effector positions (will be the local Z-axis direction) Vector3 rootToMid = (newMidJointPos - rootTransform.Translation).GetNormalized(); - Vector3 planeNormal = Vector3::Cross(rootToMid, midToEnd).GetNormalized(); // Vector from mid joint to end effector (local Y-axis direction) Vector3 localY = (newEndEffectorPos - newMidJointPos).GetNormalized(); @@ -157,21 +162,18 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ // Calculate the plane normal using the root, mid joint, and end effector positions (local Z-axis direction) Vector3 localZ = Vector3::Cross(rootToMid, localY).GetNormalized(); - //// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes + // Calculate the local X-axis direction, should be perpendicular to the Y and Z axes Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized(); // Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality localZ = Vector3::Cross(localX, localY).GetNormalized(); // Construct a rotation from the orthogonal basis vectors - // The axes are used differently here than a standard LookRotation to align Z towards the end and Y perpendicular - Quaternion newMidJointOrientation = Quaternion::LookRotation(localZ, localY); // Assuming FromLookRotation creates a rotation with the first vector as forward and the second as up - - // Apply the new rotation to the mid joint - midJointTransform.Orientation = newMidJointOrientation; - midJointTransform.Translation = newMidJointPos; + midJointTransform.Orientation = Quaternion::LookRotation(localZ, localY); +#endif } - // Update end effector transform + // Update mid and end locations + midJointTransform.Translation = newMidJointPos; endEffectorTransform.Translation = newEndEffectorPos; } diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index ad373d660..3c2330c93 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -187,6 +187,9 @@ Asset::LoadResult Material::load() #endif ) { + // Guard file with the lock during shader generation (prevents FlaxStorage::Tick from messing with the file) + auto lock = Storage->Lock(); + // Prepare MaterialGenerator generator; generator.Error.Bind(&OnGeneratorError); diff --git a/Source/Engine/Content/Storage/ContentStorageManager.cpp b/Source/Engine/Content/Storage/ContentStorageManager.cpp index 949341e25..381266ac9 100644 --- a/Source/Engine/Content/Storage/ContentStorageManager.cpp +++ b/Source/Engine/Content/Storage/ContentStorageManager.cpp @@ -252,6 +252,7 @@ void ContentStorageSystem::Job(int32 index) { PROFILE_CPU_NAMED("ContentStorage.Job"); + const double time = Platform::GetTimeSeconds(); ScopeLock lock(Locker); for (auto i = StorageMap.Begin(); i.IsNotEnd(); ++i) { @@ -271,7 +272,7 @@ void ContentStorageSystem::Job(int32 index) } else { - storage->Tick(); + storage->Tick(time); } } } diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 49cd27d66..c3e377110 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -1337,9 +1337,6 @@ bool FlaxStorage::CloseFileHandles() { if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0) { - Array> streams; - _file.GetValues(streams); - ASSERT(streams.Count() == 0); return false; } PROFILE_CPU(); @@ -1401,19 +1398,18 @@ void FlaxStorage::Dispose() _version = 0; } -void FlaxStorage::Tick() +void FlaxStorage::Tick(double time) { - // Check if chunks are locked + // Skip if file is in use if (Platform::AtomicRead(&_chunksLock) != 0) return; - const double now = Platform::GetTimeSeconds(); bool wasAnyUsed = false; const float unusedDataChunksLifetime = ContentStorageManager::UnusedDataChunksLifetime.GetTotalSeconds(); for (int32 i = 0; i < _chunks.Count(); i++) { auto chunk = _chunks.Get()[i]; - const bool wasUsed = (now - chunk->LastAccessTime) < unusedDataChunksLifetime; + const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime; if (!wasUsed && chunk->IsLoaded()) { chunk->Unload(); @@ -1421,7 +1417,7 @@ void FlaxStorage::Tick() wasAnyUsed |= wasUsed; } - // Release file handles in none of chunks was not used + // Release file handles in none of chunks is in use if (!wasAnyUsed && Platform::AtomicRead(&_chunksLock) == 0) { CloseFileHandles(); diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 865b3e656..09e8c0bdd 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -416,7 +416,7 @@ public: /// /// Ticks this instance. /// - void Tick(); + void Tick(double time); #if USE_EDITOR void OnRename(const StringView& newPath); diff --git a/Source/Engine/Core/Config/LayersAndTagsSettings.cs b/Source/Engine/Core/Config/LayersAndTagsSettings.cs index 14015b36d..005906872 100644 --- a/Source/Engine/Core/Config/LayersAndTagsSettings.cs +++ b/Source/Engine/Core/Config/LayersAndTagsSettings.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.Content.Settings /// /// The layers names. /// - [EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true, Display = CollectionAttribute.DisplayType.Inline)] + [EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(CanResize = true, Display = CollectionAttribute.DisplayType.Inline)] public string[] Layers = new string[32]; /// diff --git a/Source/Engine/Core/Math/Color.cpp b/Source/Engine/Core/Math/Color.cpp index 4a551e504..de4425ba3 100644 --- a/Source/Engine/Core/Math/Color.cpp +++ b/Source/Engine/Core/Math/Color.cpp @@ -32,41 +32,39 @@ Color::Color(const Color32& color) { } -Color Color::FromHex(const String& hexString, bool& isValid) +Color Color::FromHex(const String& hex, bool& isValid) { int32 r, g, b, a = 255; isValid = true; - - int32 startIndex = !hexString.IsEmpty() && hexString[0] == Char('#') ? 1 : 0; - if (hexString.Length() == 3 + startIndex) + int32 startIndex = !hex.IsEmpty() && hex[0] == Char('#') ? 1 : 0; + if (hex.Length() == 3 + startIndex) { - r = StringUtils::HexDigit(hexString[startIndex++]); - g = StringUtils::HexDigit(hexString[startIndex++]); - b = StringUtils::HexDigit(hexString[startIndex]); + r = StringUtils::HexDigit(hex[startIndex++]); + g = StringUtils::HexDigit(hex[startIndex++]); + b = StringUtils::HexDigit(hex[startIndex]); r = (r << 4) + r; g = (g << 4) + g; b = (b << 4) + b; } - else if (hexString.Length() == 6 + startIndex) + else if (hex.Length() == 6 + startIndex) { - r = (StringUtils::HexDigit(hexString[startIndex + 0]) << 4) + StringUtils::HexDigit(hexString[startIndex + 1]); - g = (StringUtils::HexDigit(hexString[startIndex + 2]) << 4) + StringUtils::HexDigit(hexString[startIndex + 3]); - b = (StringUtils::HexDigit(hexString[startIndex + 4]) << 4) + StringUtils::HexDigit(hexString[startIndex + 5]); + r = (StringUtils::HexDigit(hex[startIndex + 0]) << 4) + StringUtils::HexDigit(hex[startIndex + 1]); + g = (StringUtils::HexDigit(hex[startIndex + 2]) << 4) + StringUtils::HexDigit(hex[startIndex + 3]); + b = (StringUtils::HexDigit(hex[startIndex + 4]) << 4) + StringUtils::HexDigit(hex[startIndex + 5]); } - else if (hexString.Length() == 8 + startIndex) + else if (hex.Length() == 8 + startIndex) { - r = (StringUtils::HexDigit(hexString[startIndex + 0]) << 4) + StringUtils::HexDigit(hexString[startIndex + 1]); - g = (StringUtils::HexDigit(hexString[startIndex + 2]) << 4) + StringUtils::HexDigit(hexString[startIndex + 3]); - b = (StringUtils::HexDigit(hexString[startIndex + 4]) << 4) + StringUtils::HexDigit(hexString[startIndex + 5]); - a = (StringUtils::HexDigit(hexString[startIndex + 6]) << 4) + StringUtils::HexDigit(hexString[startIndex + 7]); + r = (StringUtils::HexDigit(hex[startIndex + 0]) << 4) + StringUtils::HexDigit(hex[startIndex + 1]); + g = (StringUtils::HexDigit(hex[startIndex + 2]) << 4) + StringUtils::HexDigit(hex[startIndex + 3]); + b = (StringUtils::HexDigit(hex[startIndex + 4]) << 4) + StringUtils::HexDigit(hex[startIndex + 5]); + a = (StringUtils::HexDigit(hex[startIndex + 6]) << 4) + StringUtils::HexDigit(hex[startIndex + 7]); } else { r = g = b = 0; isValid = false; } - return FromBytes(r, g, b, a); } @@ -122,8 +120,9 @@ String Color::ToHexString() const const byte r = static_cast(R * MAX_uint8); const byte g = static_cast(G * MAX_uint8); const byte b = static_cast(B * MAX_uint8); + const byte a = static_cast(A * MAX_uint8); - Char result[6]; + Char result[8]; result[0] = digits[r >> 4 & 0x0f]; result[1] = digits[r & 0x0f]; @@ -134,7 +133,10 @@ String Color::ToHexString() const result[4] = digits[b >> 4 & 0x0f]; result[5] = digits[b & 0x0f]; - return String(result, 6); + result[6] = digits[a >> 4 & 0x0f]; + result[7] = digits[a & 0x0f]; + + return String(result, 8); } bool Color::IsTransparent() const diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs index c633c61b9..cb880d66d 100644 --- a/Source/Engine/Core/Math/Color.cs +++ b/Source/Engine/Core/Math/Color.cs @@ -230,36 +230,93 @@ namespace FlaxEngine } /// - /// Creates from the RGB value and separate alpha channel. + /// Creates from the RGB value (bottom bits contain Blue) and separate alpha channel. /// - /// The packed RGB value. + /// The packed RGB value (bottom bits contain Blue). /// The alpha channel value. /// The color. public static Color FromRGB(uint rgb, float a = 1.0f) { - return new Color( - ((rgb >> 16) & 0xff) / 255.0f, - ((rgb >> 8) & 0xff) / 255.0f, - (rgb & 0xff) / 255.0f, - a); + return new Color(((rgb >> 16) & 0xff) / 255.0f, ((rgb >> 8) & 0xff) / 255.0f, (rgb & 0xff) / 255.0f, a); } /// - /// Creates from the RGBA value. + /// Creates from the ARGB value (bottom bits contain Blue). /// - /// The packed RGBA value. + /// The packed ARGB value (bottom bits contain Blue). /// The color. - public static Color FromRGBA(uint rgb) + public static Color FromARGB(uint argb) { - return new Color( - ((rgb >> 16) & 0xff) / 255.0f, - ((rgb >> 8) & 0xff) / 255.0f, - (rgb & 0xff) / 255.0f, - ((rgb >> 24) & 0xff) / 255.0f); + return new Color(((argb >> 16) & 0xff) / 255.0f, ((argb >> 8) & 0xff) / 255.0f, ((argb >> 0) & 0xff) / 255.0f, ((argb >> 24) & 0xff) / 255.0f); } /// - /// Gets the color value as the hexadecimal string. + /// Creates from the RGBA value (bottom bits contain Alpha). + /// + /// The packed RGBA value (bottom bits Alpha Red). + /// The color. + public static Color FromRGBA(uint rgba) + { + return new Color(((rgba >> 24) & 0xff) / 255.0f, ((rgba >> 16) & 0xff) / 255.0f, ((rgba >> 8) & 0xff) / 255.0f, ((rgba >> 0) & 0xff) / 255.0f); + } + + /// + /// Creates from the Hex string. + /// + /// The hexadecimal color string. + /// The output color value. + public static Color FromHex(string hex) + { + FromHex(hex, out var color); + return color; + } + + /// + /// Creates from the Hex string. + /// + /// The hexadecimal color string. + /// The output color value. Valid if method returns true. + /// True if method was able to convert color, otherwise false. + public static bool FromHex(string hex, out Color color) + { + int r, g, b, a = 255; + bool isValid = true; + + int startIndex = hex.Length != 0 && hex[0] == '#' ? 1 : 0; + if (hex.Length == 3 + startIndex) + { + r = StringUtils.HexDigit(hex[startIndex++]); + g = StringUtils.HexDigit(hex[startIndex++]); + b = StringUtils.HexDigit(hex[startIndex]); + r = (r << 4) + r; + g = (g << 4) + g; + b = (b << 4) + b; + } + else if (hex.Length == 6 + startIndex) + { + r = (StringUtils.HexDigit(hex[startIndex + 0]) << 4) + StringUtils.HexDigit(hex[startIndex + 1]); + g = (StringUtils.HexDigit(hex[startIndex + 2]) << 4) + StringUtils.HexDigit(hex[startIndex + 3]); + b = (StringUtils.HexDigit(hex[startIndex + 4]) << 4) + StringUtils.HexDigit(hex[startIndex + 5]); + } + else if (hex.Length == 8 + startIndex) + { + r = (StringUtils.HexDigit(hex[startIndex + 0]) << 4) + StringUtils.HexDigit(hex[startIndex + 1]); + g = (StringUtils.HexDigit(hex[startIndex + 2]) << 4) + StringUtils.HexDigit(hex[startIndex + 3]); + b = (StringUtils.HexDigit(hex[startIndex + 4]) << 4) + StringUtils.HexDigit(hex[startIndex + 5]); + a = (StringUtils.HexDigit(hex[startIndex + 6]) << 4) + StringUtils.HexDigit(hex[startIndex + 7]); + } + else + { + r = g = b = 0; + isValid = false; + } + + color = new Color(r, g, b, a); + return isValid; + } + + /// + /// Gets the color value as the hexadecimal string (in RGBA order). /// /// Hex string. public string ToHexString() @@ -287,7 +344,7 @@ namespace FlaxEngine return new string(result); } - + /// /// Creates from the text string (hex or color name). /// diff --git a/Source/Engine/Core/Math/Color.h b/Source/Engine/Core/Math/Color.h index 49cac28db..955494f8e 100644 --- a/Source/Engine/Core/Math/Color.h +++ b/Source/Engine/Core/Math/Color.h @@ -126,9 +126,9 @@ public: } /// - /// Initializes from packed RGB value of the color and separate alpha channel value. + /// Initializes from packed RGB value (bottom bits contain Blue) of the color and separate alpha channel value. /// - /// The packed RGB value. + /// The packed RGB value (bottom bits contain Blue). /// The alpha channel. /// The color. static Color FromRGB(uint32 rgb, float a = 1.0f) @@ -137,22 +137,32 @@ public: } /// - /// Initializes from packed RGBA value. + /// Initializes from packed ARGB value (bottom bits contain Blue). /// - /// The packed RGBA value. + /// The packed ARGB value (bottom bits contain Blue). + /// The color. + static Color FromARGB(uint32 argb) + { + return Color((float)((argb >> 16) & 0xff) / 255.0f,(float)((argb >> 8) & 0xff) / 255.0f, (float)((argb >> 0) & 0xff) / 255.0f, (float)((argb >> 24) & 0xff) / 255.0f); + } + + /// + /// Initializes from packed RGBA value (bottom bits contain Alpha). + /// + /// The packed RGBA value (bottom bits contain Alpha). /// The color. static Color FromRGBA(uint32 rgba) { - return Color(static_cast(rgba >> 16 & 0xff) / 255.0f, static_cast(rgba >> 8 & 0xff) / 255.0f, static_cast(rgba & 0xff) / 255.0f, static_cast(rgba >> 24 & 0xff) / 255.0f); + return Color((float)((rgba >> 24) & 0xff) / 255.0f,(float)((rgba >> 16) & 0xff) / 255.0f, (float)((rgba >> 8) & 0xff) / 255.0f, (float)((rgba >> 0) & 0xff) / 255.0f); } - static Color FromHex(const String& hexString) + static Color FromHex(const String& hex) { bool isValid; - return FromHex(hexString, isValid); + return FromHex(hex, isValid); } - static Color FromHex(const String& hexString, bool& isValid); + static Color FromHex(const String& hex, bool& isValid); /// /// Creates RGB color from Hue[0-360], Saturation[0-1] and Value[0-1]. diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index e99700fae..e3cb6bb0c 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -42,7 +42,7 @@ void RenderTask::DrawAll() // Sort tasks (by Order property) Sorting::QuickSortObj(Tasks.Get(), Tasks.Count()); - // Render all that shit + // Render all tasks for (auto task : Tasks) { if (task->CanDraw()) diff --git a/Source/Engine/Level/Actors/BoxBrush.h b/Source/Engine/Level/Actors/BoxBrush.h index 5cb26988c..3f368693d 100644 --- a/Source/Engine/Level/Actors/BoxBrush.h +++ b/Source/Engine/Level/Actors/BoxBrush.h @@ -90,7 +90,7 @@ public: /// /// Gets the brush proxies per surface. /// - API_PROPERTY(Attributes="Serialize, EditorOrder(100), EditorDisplay(\"Surfaces\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems = false, NotNullItems = true, ReadOnly = true)") + API_PROPERTY(Attributes="Serialize, EditorOrder(100), EditorDisplay(\"Surfaces\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems = false, NotNullItems = true, CanResize = true)") Array GetSurfaces() const; /// diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index 375b0bac3..b2affbc2a 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -349,6 +349,11 @@ void Camera::Draw(RenderContext& renderContext) _previewModel->Draw(renderContext, draw); } } + // Load preview model if it doesnt exist. Ex: prefabs + else if (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::EditorSprites) && !_previewModel) + { + _previewModel = Content::LoadAsyncInternal(TEXT("Editor/Camera/O_Camera")); + } } #include "Engine/Debug/DebugDraw.h" diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 793176ae0..c7cf2001b 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -41,7 +41,7 @@ public: /// /// Gets the model entries collection. Each entry contains data how to render meshes using this entry (transformation, material, shadows casting, etc.). /// - API_PROPERTY(Attributes="Serialize, EditorOrder(1000), EditorDisplay(\"Entries\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems=false, NotNullItems=true, ReadOnly=true, Spacing=10)") + API_PROPERTY(Attributes="Serialize, EditorOrder(1000), EditorDisplay(\"Entries\", EditorDisplayAttribute.InlineStyle), Collection(CanReorderItems=false, NotNullItems=true, CanResize=false, Spacing=10)") FORCE_INLINE const Array& GetEntries() const { return Entries; diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 5c8979d42..8446f9ac3 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1392,12 +1392,20 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli if (ownerClientId == NetworkManager::LocalClientId) { // Ensure local client owns that object actually - CHECK(localRole == NetworkObjectRole::OwnedAuthoritative); + if (localRole != NetworkObjectRole::OwnedAuthoritative) + { + LOG(Error, "Cannot change overship of object (Id={}) to the local client (Id={}) if the local role is not set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + return; + } } else { // Ensure local client doesn't own that object since it's owned by other client - CHECK(localRole != NetworkObjectRole::OwnedAuthoritative); + if (localRole == NetworkObjectRole::OwnedAuthoritative) + { + LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + return; + } } #endif item.HasOwnership = true; @@ -1421,7 +1429,13 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli if (item.OwnerClientId != ownerClientId) { // Change role locally - CHECK(localRole != NetworkObjectRole::OwnedAuthoritative); +#if !BUILD_RELEASE + if (localRole == NetworkObjectRole::OwnedAuthoritative) + { + LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + return; + } +#endif if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative) Hierarchy->RemoveObject(obj); item.OwnerClientId = ownerClientId; @@ -1433,7 +1447,13 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli else { // Allow to change local role of the object (except ownership) - CHECK(localRole != NetworkObjectRole::OwnedAuthoritative); +#if !BUILD_RELEASE + if (localRole == NetworkObjectRole::OwnedAuthoritative) + { + LOG(Error, "Cannot change overship of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); + return; + } +#endif if (Hierarchy && it->Item.Role == NetworkObjectRole::OwnedAuthoritative) Hierarchy->RemoveObject(obj); item.Role = localRole; @@ -2211,7 +2231,6 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie if (!obj) return; - // Validate RPC if (info->Server && NetworkManager::IsClient()) { @@ -2234,7 +2253,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie // Execute RPC info->Execute(obj, stream, info->Tag); } - else if(info->Channel != static_cast(NetworkChannelType::Unreliable) && info->Channel != static_cast(NetworkChannelType::UnreliableOrdered)) + else if (info->Channel != static_cast(NetworkChannelType::Unreliable) && info->Channel != static_cast(NetworkChannelType::UnreliableOrdered)) { NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName)); } diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index 6c584d513..ab3066372 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -371,7 +371,7 @@ void ParticleEffect::Sync() SceneRenderTask* ParticleEffect::GetRenderTask() const { - const uint64 minFrame = Engine::UpdateCount - 2; + const uint64 minFrame = Engine::FrameCount - 2; // Custom task const auto customViewRenderTask = CustomViewRenderTask.Get(); diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index 726209399..80d260e20 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -155,9 +155,9 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) // Get the index to the glyph in the font face const FT_UInt glyphIndex = FT_Get_Char_Index(face, c); #if !BUILD_RELEASE - if (glyphIndex == 0) + if (glyphIndex == 0 && c >= '!') { - LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c); + LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font.", String(face->family_name), c); } #endif diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 8b97bc1d5..bf2941104 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -339,6 +339,32 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont } setup.UseTemporalAAJitter = aaMode == AntialiasingMode::TemporalAntialiasing; + // Disable TAA jitter in debug modes + switch (renderContext.View.Mode) + { + case ViewMode::Unlit: + case ViewMode::Diffuse: + case ViewMode::Normals: + case ViewMode::Depth: + case ViewMode::Emissive: + case ViewMode::AmbientOcclusion: + case ViewMode::Metalness: + case ViewMode::Roughness: + case ViewMode::Specular: + case ViewMode::SpecularColor: + case ViewMode::SubsurfaceColor: + case ViewMode::ShadingModel: + case ViewMode::Reflections: + case ViewMode::GlobalSDF: + case ViewMode::GlobalSurfaceAtlas: + case ViewMode::LightmapUVsDensity: + case ViewMode::MaterialComplexity: + case ViewMode::Wireframe: + case ViewMode::NoPostFx: + setup.UseTemporalAAJitter = false; + break; + } + // Customize setup (by postfx or custom gameplay effects) renderContext.Task->SetupRender(renderContext); for (PostProcessEffect* e : renderContext.List->PostFx) @@ -506,8 +532,13 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont EyeAdaptationPass::Instance()->Render(renderContext, lightBuffer); PostProcessingPass::Instance()->Render(renderContext, lightBuffer, tempBuffer, colorGradingLUT); RenderTargetPool::Release(colorGradingLUT); - RenderTargetPool::Release(lightBuffer); context->ResetRenderTarget(); + if (aaMode == AntialiasingMode::TemporalAntialiasing) + { + TAA::Instance()->Render(renderContext, tempBuffer, lightBuffer->View()); + Swap(lightBuffer, tempBuffer); + } + RenderTargetPool::Release(lightBuffer); context->SetRenderTarget(task->GetOutputView()); context->SetViewportAndScissors(task->GetOutputViewport()); context->Draw(tempBuffer); diff --git a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs index 59f24d017..d0b85d8cc 100644 --- a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs +++ b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs @@ -46,6 +46,11 @@ namespace FlaxEngine /// public bool CanReorderItems = true; + /// + /// Gets or sets whether items can be added or removed from this collection. + /// + public bool CanResize = true; + /// /// Gets or sets whether the items of this collection can be null. If true, applications using this collection should prevent user to add null items to the collection. /// diff --git a/Source/Engine/Scripting/Attributes/Editor/HeaderAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/HeaderAttribute.cs index e1ce249da..5d91023af 100644 --- a/Source/Engine/Scripting/Attributes/Editor/HeaderAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/HeaderAttribute.cs @@ -23,11 +23,14 @@ namespace FlaxEngine public int FontSize; /// - /// The custom header color (as 32-bit uint). + /// The custom header color (as 32-bit uint in RGB order, bottom bits contain Blue). /// public uint Color; - private HeaderAttribute() + /// + /// Initializes a new instance of the class. + /// + public HeaderAttribute() { } diff --git a/Source/Engine/Scripting/Attributes/Editor/WatermarkAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/WatermarkAttribute.cs index 73814d11c..456554e44 100644 --- a/Source/Engine/Scripting/Attributes/Editor/WatermarkAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/WatermarkAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace FlaxEngine; @@ -15,7 +15,7 @@ public class WatermarkAttribute : Attribute public string WatermarkText; /// - /// The watermark color. + /// The watermark color (as 32-bit uint in RGB order, bottom bits contain Blue). /// public uint WatermarkColor; @@ -26,7 +26,7 @@ public class WatermarkAttribute : Attribute public WatermarkAttribute(string text) { WatermarkText = text; - WatermarkColor = 0; // default color of watermark in textbox + WatermarkColor = 0; // Default color of watermark in textbox } /// diff --git a/Source/Engine/Tests/TestColor.cs b/Source/Engine/Tests/TestColor.cs index e0aa45539..8ba38ecf2 100644 --- a/Source/Engine/Tests/TestColor.cs +++ b/Source/Engine/Tests/TestColor.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #if FLAX_TESTS +using System; using NUnit.Framework; namespace FlaxEngine.Tests @@ -37,6 +38,15 @@ namespace FlaxEngine.Tests Assert.AreEqual(Color.Maroon, ColorHSV.FromColor(Color.Maroon).ToColor()); Assert.AreEqual(new Color(184, 209, 219, 255).ToRgba(), ColorHSV.FromColor(new Color(184, 209, 219, 255)).ToColor().ToRgba()); } + + [Test] + public void TestHexConversion() + { + string hex = Color.Blue.AlphaMultiplied(0.5f).ToHexString(); + Color col1 = Color.FromHex(hex); + Color col2 = Color.FromRGBA(0x0000FF7F); + Assert.AreEqual((Color32)col1, (Color32)col2); + } } } #endif diff --git a/Source/Engine/UI/GUI/CanvasRootControl.cs b/Source/Engine/UI/GUI/CanvasRootControl.cs index f325a7096..4cc16bc10 100644 --- a/Source/Engine/UI/GUI/CanvasRootControl.cs +++ b/Source/Engine/UI/GUI/CanvasRootControl.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; namespace FlaxEngine.GUI { @@ -204,7 +205,9 @@ namespace FlaxEngine.GUI { _navigationHeldTimeUp = _navigationHeldTimeDown = _navigationHeldTimeLeft = _navigationHeldTimeRight = 0; _navigationRateTimeUp = _navigationRateTimeDown = _navigationRateTimeLeft = _navigationRateTimeRight = 0; + return; } + if (ContainsFocus || IndexInParent == 0) { UpdateNavigation(deltaTime, _canvas.NavigateUp.Name, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp); UpdateNavigation(deltaTime, _canvas.NavigateDown.Name, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown); @@ -216,13 +219,30 @@ namespace FlaxEngine.GUI base.Update(deltaTime); } + private void ConditionalNavigate(NavDirection direction) + { + // Only currently focused canvas updates its navigation + if (!ContainsFocus) + { + // Special case when no canvas nor game UI is focused so let the first canvas to start the navigation into the UI + if (IndexInParent == 0 && Parent is CanvasContainer canvasContainer && !canvasContainer.ContainsFocus && GameRoot.ContainsFocus) + { + // Nothing is focused so go to the first control + var focused = OnNavigate(direction, Float2.Zero, this, new List()); + focused?.NavigationFocus(); + return; + } + } + Navigate(direction); + } + private void UpdateNavigation(float deltaTime, string actionName, NavDirection direction, ref float heldTime, ref float rateTime) { if (Input.GetAction(actionName)) { if (heldTime <= Mathf.Epsilon) { - Navigate(direction); + ConditionalNavigate(direction); } if (heldTime > _canvas.NavigationInputRepeatDelay) { @@ -230,7 +250,7 @@ namespace FlaxEngine.GUI } if (rateTime > _canvas.NavigationInputRepeatRate) { - Navigate(direction); + ConditionalNavigate(direction); rateTime = 0; } heldTime += deltaTime; diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index bfdad73b0..fcd66ff59 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -143,7 +143,7 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. + /// Gets or sets the custom material used to render the text. It has to have domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. /// [EditorDisplay("Text Style"), EditorOrder(2025)] public MaterialBase Material { get; set; } diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs index 021f85947..599dcde30 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs @@ -197,7 +197,7 @@ namespace FlaxEngine.GUI textBlock.Range = new TextRange { StartIndex = start + line.FirstCharIndex, - EndIndex = start + line.LastCharIndex, + EndIndex = start + line.LastCharIndex + 1, }; if (i != 0) { diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 8b4e7e6c9..e6529c5b9 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -1590,14 +1590,18 @@ namespace Flax.Build.Plugins context.Failed = true; return; } - if (method.IsVirtual) { MonoCecil.CompilationError($"Not supported virtual RPC method '{method.FullName}'.", method); context.Failed = true; return; } - + if (method.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.AsyncStateMachineAttribute") != null) + { + MonoCecil.CompilationError($"Not supported async RPC method '{method.FullName}'.", method); + context.Failed = true; + return; + } ModuleDefinition module = type.Module; var voidType = module.TypeSystem.Void; if (method.ReturnType != voidType) @@ -1606,7 +1610,6 @@ namespace Flax.Build.Plugins context.Failed = true; return; } - if (method.IsStatic) { MonoCecil.CompilationError($"Not supported static RPC method '{method.FullName}'.", method); @@ -1634,7 +1637,6 @@ namespace Flax.Build.Plugins context.Failed = true; return; } - if (!methodRPC.IsServer && !methodRPC.IsClient) { MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} needs to have Server or Client specifier.", method);