Merge remote-tracking branch 'origin/master' into 1.9
# Conflicts: # Flax.flaxproj
This commit is contained in:
@@ -74,6 +74,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION_005FMEMBER/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LO/@EntryIndexedValue">LO</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPC/@EntryIndexedValue">RPC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SDK/@EntryIndexedValue">SDK</s:String>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// The keyframes.
|
||||
/// </summary>
|
||||
[EditorDisplay("Keyframes", EditorDisplayAttribute.InlineStyle), ExpandGroups]
|
||||
[Collection(CanReorderItems = false, ReadOnly = true)]
|
||||
[Collection(CanReorderItems = false, CanResize = true)]
|
||||
public List<KeyValuePair<string, object>> Keyframes;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// The parameters values.
|
||||
/// </summary>
|
||||
[EditorDisplay("Parameters", EditorDisplayAttribute.InlineStyle), ExpandGroups]
|
||||
[Collection(CanReorderItems = false, ReadOnly = true)]
|
||||
[Collection(CanReorderItems = false, CanResize = true)]
|
||||
public object[] Parameters;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -253,7 +253,11 @@ namespace FlaxEditor
|
||||
{
|
||||
// Select node (with additive mode)
|
||||
var selection = new List<SceneGraphNode>();
|
||||
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;
|
||||
|
||||
@@ -202,6 +202,7 @@ namespace FlaxEditor.Modules
|
||||
|
||||
var prefabId = ((ActorNode)selection[0]).Actor.PrefabID;
|
||||
var prefab = FlaxEngine.Content.LoadAsync<Prefab>(prefabId);
|
||||
Editor.Windows.ContentWin.ClearItemsSearch();
|
||||
Editor.Windows.ContentWin.Select(prefab);
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<T>());
|
||||
for (rapidjson::SizeType i = 0; i < keyframesArray.Size(); i++)
|
||||
Deserialize(keyframesArray[i], keyframes[i], modifier);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1337,9 +1337,6 @@ bool FlaxStorage::CloseFileHandles()
|
||||
{
|
||||
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
|
||||
{
|
||||
Array<FileReadStream*, InlinedAllocation<8>> 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();
|
||||
|
||||
@@ -416,7 +416,7 @@ public:
|
||||
/// <summary>
|
||||
/// Ticks this instance.
|
||||
/// </summary>
|
||||
void Tick();
|
||||
void Tick(double time);
|
||||
|
||||
#if USE_EDITOR
|
||||
void OnRename(const StringView& newPath);
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace FlaxEditor.Content.Settings
|
||||
/// <summary>
|
||||
/// The layers names.
|
||||
/// </summary>
|
||||
[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];
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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<byte>(R * MAX_uint8);
|
||||
const byte g = static_cast<byte>(G * MAX_uint8);
|
||||
const byte b = static_cast<byte>(B * MAX_uint8);
|
||||
const byte a = static_cast<byte>(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
|
||||
|
||||
@@ -230,36 +230,93 @@ namespace FlaxEngine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="Color"/> from the RGB value and separate alpha channel.
|
||||
/// Creates <see cref="Color"/> from the RGB value (bottom bits contain Blue) and separate alpha channel.
|
||||
/// </summary>
|
||||
/// <param name="rgb">The packed RGB value.</param>
|
||||
/// <param name="rgb">The packed RGB value (bottom bits contain Blue).</param>
|
||||
/// <param name="a">The alpha channel value.</param>
|
||||
/// <returns>The color.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="Color"/> from the RGBA value.
|
||||
/// Creates <see cref="Color"/> from the ARGB value (bottom bits contain Blue).
|
||||
/// </summary>
|
||||
/// <param name="rgb">The packed RGBA value.</param>
|
||||
/// <param name="argb">The packed ARGB value (bottom bits contain Blue).</param>
|
||||
/// <returns>The color.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color value as the hexadecimal string.
|
||||
/// Creates <see cref="Color"/> from the RGBA value (bottom bits contain Alpha).
|
||||
/// </summary>
|
||||
/// <param name="rgba">The packed RGBA value (bottom bits Alpha Red).</param>
|
||||
/// <returns>The color.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="Color"/> from the Hex string.
|
||||
/// </summary>
|
||||
/// <param name="hex">The hexadecimal color string.</param>
|
||||
/// <returns>The output color value.</returns>
|
||||
public static Color FromHex(string hex)
|
||||
{
|
||||
FromHex(hex, out var color);
|
||||
return color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="Color"/> from the Hex string.
|
||||
/// </summary>
|
||||
/// <param name="hex">The hexadecimal color string.</param>
|
||||
/// <param name="color">The output color value. Valid if method returns true.</param>
|
||||
/// <returns>True if method was able to convert color, otherwise false.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color value as the hexadecimal string (in RGBA order).
|
||||
/// </summary>
|
||||
/// <returns>Hex string.</returns>
|
||||
public string ToHexString()
|
||||
@@ -287,7 +344,7 @@ namespace FlaxEngine
|
||||
|
||||
return new string(result);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="Color"/> from the text string (hex or color name).
|
||||
/// </summary>
|
||||
|
||||
@@ -126,9 +126,9 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="rgb">The packed RGB value.</param>
|
||||
/// <param name="rgb">The packed RGB value (bottom bits contain Blue).</param>
|
||||
/// <param name="a">The alpha channel.</param>
|
||||
/// <returns>The color.</returns>
|
||||
static Color FromRGB(uint32 rgb, float a = 1.0f)
|
||||
@@ -137,22 +137,32 @@ public:
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes from packed RGBA value.
|
||||
/// Initializes from packed ARGB value (bottom bits contain Blue).
|
||||
/// </summary>
|
||||
/// <param name="rgba">The packed RGBA value.</param>
|
||||
/// <param name="argb">The packed ARGB value (bottom bits contain Blue).</param>
|
||||
/// <returns>The color.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes from packed RGBA value (bottom bits contain Alpha).
|
||||
/// </summary>
|
||||
/// <param name="rgba">The packed RGBA value (bottom bits contain Alpha).</param>
|
||||
/// <returns>The color.</returns>
|
||||
static Color FromRGBA(uint32 rgba)
|
||||
{
|
||||
return Color(static_cast<float>(rgba >> 16 & 0xff) / 255.0f, static_cast<float>(rgba >> 8 & 0xff) / 255.0f, static_cast<float>(rgba & 0xff) / 255.0f, static_cast<float>(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);
|
||||
|
||||
/// <summary>
|
||||
/// Creates RGB color from Hue[0-360], Saturation[0-1] and Value[0-1].
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the brush proxies per surface.
|
||||
/// </summary>
|
||||
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<BrushSurface> GetSurfaces() const;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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<Model>(TEXT("Editor/Camera/O_Camera"));
|
||||
}
|
||||
}
|
||||
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the model entries collection. Each entry contains data how to render meshes using this entry (transformation, material, shadows casting, etc.).
|
||||
/// </summary>
|
||||
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<ModelInstanceEntry>& GetEntries() const
|
||||
{
|
||||
return Entries;
|
||||
|
||||
@@ -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<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(NetworkChannelType::UnreliableOrdered))
|
||||
else if (info->Channel != static_cast<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(NetworkChannelType::UnreliableOrdered))
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -46,6 +46,11 @@ namespace FlaxEngine
|
||||
/// </summary>
|
||||
public bool CanReorderItems = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether items can be added or removed from this collection.
|
||||
/// </summary>
|
||||
public bool CanResize = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the items of this collection can be null. If <c>true</c>, applications using this collection should prevent user to add null items to the collection.
|
||||
/// </summary>
|
||||
|
||||
@@ -23,11 +23,14 @@ namespace FlaxEngine
|
||||
public int FontSize;
|
||||
|
||||
/// <summary>
|
||||
/// The custom header color (as 32-bit uint).
|
||||
/// The custom header color (as 32-bit uint in RGB order, bottom bits contain Blue).
|
||||
/// </summary>
|
||||
public uint Color;
|
||||
|
||||
private HeaderAttribute()
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HeaderAttribute"/> class.
|
||||
/// </summary>
|
||||
public HeaderAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine;
|
||||
|
||||
@@ -15,7 +15,7 @@ public class WatermarkAttribute : Attribute
|
||||
public string WatermarkText;
|
||||
|
||||
/// <summary>
|
||||
/// The watermark color.
|
||||
/// The watermark color (as 32-bit uint in RGB order, bottom bits contain Blue).
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Control>());
|
||||
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;
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[EditorDisplay("Text Style"), EditorOrder(2025)]
|
||||
public MaterialBase Material { get; set; }
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user