diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 6065ca9f8..40a06c1b6 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -711,7 +711,7 @@ namespace FlaxEditor.Content.GUI protected override void PerformLayoutBeforeChildren() { float width = GetClientArea().Width; - float x = 0, y = 0; + float x = 0, y = 1; float viewScale = _viewScale * 0.97f; switch (ViewType) @@ -722,21 +722,22 @@ namespace FlaxEditor.Content.GUI int itemsToFit = Mathf.FloorToInt(width / defaultItemsWidth) - 1; if (itemsToFit < 1) itemsToFit = 1; - float itemsWidth = width / Mathf.Max(itemsToFit, 1); + int xSpace = 4; + float itemsWidth = width / Mathf.Max(itemsToFit, 1) - xSpace; float itemsHeight = itemsWidth / defaultItemsWidth * (ContentItem.DefaultHeight * viewScale); var flooredItemsWidth = Mathf.Floor(itemsWidth); var flooredItemsHeight = Mathf.Floor(itemsHeight); - x = itemsToFit == 1 ? 0 : itemsWidth / itemsToFit; + x = itemsToFit == 1 ? 1 : itemsWidth / itemsToFit + xSpace; for (int i = 0; i < _children.Count; i++) { var c = _children[i]; c.Bounds = new Rectangle(Mathf.Floor(x), Mathf.Floor(y), flooredItemsWidth, flooredItemsHeight); - x += itemsWidth + itemsWidth / itemsToFit; + x += (itemsWidth + xSpace) + (itemsWidth + xSpace) / itemsToFit; if (x + itemsWidth > width) { - x = itemsToFit == 1 ? 0 : itemsWidth / itemsToFit; - y += itemsHeight + 5; + x = itemsToFit == 1 ? 1 : itemsWidth / itemsToFit + xSpace; + y += itemsHeight + 7; } } if (x > 0) @@ -751,7 +752,7 @@ namespace FlaxEditor.Content.GUI { var c = _children[i]; c.Bounds = new Rectangle(x, y, width, itemsHeight); - y += itemsHeight + 5; + y += itemsHeight + 1; } y += 40.0f; diff --git a/Source/Editor/Content/Items/ContentItem.cs b/Source/Editor/Content/Items/ContentItem.cs index 66825fb42..b27159aa0 100644 --- a/Source/Editor/Content/Items/ContentItem.cs +++ b/Source/Editor/Content/Items/ContentItem.cs @@ -483,6 +483,30 @@ namespace FlaxEditor.Content else Render2D.FillRectangle(rectangle, Color.Black); } + + /// + /// Draws the item thumbnail. + /// + /// The thumbnail rectangle. + /// /// Whether or not to draw the shadow. Overrides DrawShadow. + public void DrawThumbnail(ref Rectangle rectangle, bool shadow) + { + // Draw shadow + if (shadow) + { + const float thumbnailInShadowSize = 50.0f; + var shadowRect = rectangle.MakeExpanded((DefaultThumbnailSize - thumbnailInShadowSize) * rectangle.Width / DefaultThumbnailSize * 1.3f); + if (!_shadowIcon.IsValid) + _shadowIcon = Editor.Instance.Icons.AssetShadow128; + Render2D.DrawSprite(_shadowIcon, shadowRect); + } + + // Draw thumbnail + if (_thumbnail.IsValid) + Render2D.DrawSprite(_thumbnail, rectangle); + else + Render2D.FillRectangle(rectangle, Color.Black); + } /// /// Gets the amount of references to that item. @@ -655,9 +679,51 @@ namespace FlaxEditor.Content { case ContentViewType.Tiles: { - var thumbnailSize = size.X - 2 * DefaultMarginSize; - thumbnailRect = new Rectangle(DefaultMarginSize, DefaultMarginSize, thumbnailSize, thumbnailSize); + var thumbnailSize = size.X; + thumbnailRect = new Rectangle(0, 0, thumbnailSize, thumbnailSize); nameAlignment = TextAlignment.Center; + + if (this is ContentFolder) + { + // Small shadow + var shadowRect = new Rectangle(2, 2, clientRect.Width + 1, clientRect.Height + 1); + var color = Color.Black.AlphaMultiplied(0.2f); + Render2D.FillRectangle(shadowRect, color); + Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f)); + + if (isSelected) + Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground); + else if (IsMouseOver) + Render2D.FillRectangle(clientRect, style.BackgroundHighlighted); + + DrawThumbnail(ref thumbnailRect, false); + } + else + { + // Small shadow + var shadowRect = new Rectangle(2, 2, clientRect.Width + 1, clientRect.Height + 1); + var color = Color.Black.AlphaMultiplied(0.2f); + Render2D.FillRectangle(shadowRect, color); + + Render2D.FillRectangle(clientRect, style.Background.RGBMultiplied(1.25f)); + Render2D.FillRectangle(TextRectangle, style.LightBackground); + + var accentHeight = 2 * view.ViewScale; + var barRect = new Rectangle(0, thumbnailRect.Height - accentHeight, clientRect.Width, accentHeight); + Render2D.FillRectangle(barRect, Color.DimGray); + + DrawThumbnail(ref thumbnailRect, false); + if (isSelected) + { + Render2D.FillRectangle(textRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground); + Render2D.DrawRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground); + } + else if (IsMouseOver) + { + Render2D.FillRectangle(textRect, style.BackgroundHighlighted); + Render2D.DrawRectangle(clientRect, style.BackgroundHighlighted); + } + } break; } case ContentViewType.List: @@ -665,23 +731,21 @@ namespace FlaxEditor.Content var thumbnailSize = size.Y - 2 * DefaultMarginSize; thumbnailRect = new Rectangle(DefaultMarginSize, DefaultMarginSize, thumbnailSize, thumbnailSize); nameAlignment = TextAlignment.Near; + + if (isSelected) + Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground); + else if (IsMouseOver) + Render2D.FillRectangle(clientRect, style.BackgroundHighlighted); + + DrawThumbnail(ref thumbnailRect); break; } default: throw new ArgumentOutOfRangeException(); } - - // Draw background - if (isSelected) - Render2D.FillRectangle(clientRect, Parent.ContainsFocus ? style.BackgroundSelected : style.LightBackground); - else if (IsMouseOver) - Render2D.FillRectangle(clientRect, style.BackgroundHighlighted); - - // Draw preview - DrawThumbnail(ref thumbnailRect); - + // Draw short name Render2D.PushClip(ref textRect); - Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 0.75f, 0.95f); + Render2D.DrawText(style.FontMedium, ShowFileExtension || view.ShowFileExtensions ? FileName : ShortName, textRect, style.Foreground, nameAlignment, TextAlignment.Center, TextWrapping.WrapWords, 1f, 0.95f); Render2D.PopClip(); } diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index ced70a281..7e3af05bf 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -762,6 +762,10 @@ namespace FlaxEditor.GUI.Tree // Add/Remove tree.AddOrRemoveSelection(this); } + else if (button == MouseButton.Right && tree.Selection.Contains(this)) + { + // Do nothing + } else { // Select diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index b1f04c8f5..9d782b13b 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -931,6 +931,7 @@ namespace FlaxEditor.Windows // Add game project on top, plugins in the middle and engine at bottom _root.AddChild(Editor.ContentDatabase.Game); + Editor.ContentDatabase.Projects.Sort(); foreach (var project in Editor.ContentDatabase.Projects) { project.SortChildrenRecursive(); diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index e292a0133..698f1ca8c 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -1251,7 +1251,7 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val boxBase = node->GetBox(3); if (boxBase->HasConnection()) eatBox(node, boxBase->FirstConnection()); - Dictionary::Iterator it(dictionary, iteratorValue.Value.AsInt); + Dictionary::Iterator it(&dictionary, iteratorValue.Value.AsInt); ++it; iteratorValue.Value.AsInt = it.Index(); } @@ -1269,12 +1269,12 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val // Key case 1: if (iteratorIndex != scope->ReturnedValues.Count() && dictionaryIndex != scope->ReturnedValues.Count()) - value = Dictionary::Iterator(*scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Key; + value = Dictionary::Iterator(scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Key; break; // Value case 2: if (iteratorIndex != scope->ReturnedValues.Count() && dictionaryIndex != scope->ReturnedValues.Count()) - value = Dictionary::Iterator(*scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Value; + value = Dictionary::Iterator(scope->ReturnedValues[dictionaryIndex].Value.AsDictionary, scope->ReturnedValues[iteratorIndex].Value.AsInt)->Value; break; // Break case 5: diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 3feae4e73..58117cf0a 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -938,12 +938,12 @@ public: FORCE_INLINE bool IsEnd() const { - return _index == _array->Count(); + return _index == _array->_count; } FORCE_INLINE bool IsNotEnd() const { - return _index != _array->Count(); + return _index != _array->_count; } FORCE_INLINE T& operator*() const @@ -975,7 +975,7 @@ public: Iterator& operator++() { - if (_index != _array->Count()) + if (_index != _array->_count) _index++; return *this; } @@ -983,7 +983,7 @@ public: Iterator operator++(int) { Iterator temp = *this; - if (_index != _array->Count()) + if (_index != _array->_count) _index++; return temp; } diff --git a/Source/Engine/Core/Collections/ChunkedArray.h b/Source/Engine/Core/Collections/ChunkedArray.h index 86473763b..d01711e38 100644 --- a/Source/Engine/Core/Collections/ChunkedArray.h +++ b/Source/Engine/Core/Collections/ChunkedArray.h @@ -95,7 +95,6 @@ public: struct Iterator { friend ChunkedArray; - private: ChunkedArray* _collection; int32 _chunkIndex; diff --git a/Source/Engine/Core/Collections/Dictionary.h b/Source/Engine/Core/Collections/Dictionary.h index 2327f3c24..f5ffb1bfa 100644 --- a/Source/Engine/Core/Collections/Dictionary.h +++ b/Source/Engine/Core/Collections/Dictionary.h @@ -237,22 +237,28 @@ public: { friend Dictionary; private: - Dictionary& _collection; + Dictionary* _collection; int32 _index; public: - Iterator(Dictionary& collection, const int32 index) + Iterator(Dictionary* collection, const int32 index) : _collection(collection) , _index(index) { } - Iterator(Dictionary const& collection, const int32 index) - : _collection((Dictionary&)collection) + Iterator(Dictionary const* collection, const int32 index) + : _collection(const_cast(collection)) , _index(index) { } + Iterator() + : _collection(nullptr) + , _index(-1) + { + } + Iterator(const Iterator& i) : _collection(i._collection) , _index(i._index) @@ -273,27 +279,27 @@ public: FORCE_INLINE bool IsEnd() const { - return _index == _collection._size; + return _index == _collection->_size; } FORCE_INLINE bool IsNotEnd() const { - return _index != _collection._size; + return _index != _collection->_size; } FORCE_INLINE Bucket& operator*() const { - return _collection._allocation.Get()[_index]; + return _collection->_allocation.Get()[_index]; } FORCE_INLINE Bucket* operator->() const { - return &_collection._allocation.Get()[_index]; + return &_collection->_allocation.Get()[_index]; } FORCE_INLINE explicit operator bool() const { - return _index >= 0 && _index < _collection._size; + return _index >= 0 && _index < _collection->_size; } FORCE_INLINE bool operator!() const @@ -308,7 +314,7 @@ public: FORCE_INLINE bool operator!=(const Iterator& v) const { - return _index != v._index || &_collection != &v._collection; + return _index != v._index || _collection != v._collection; } Iterator& operator=(const Iterator& v) @@ -320,10 +326,10 @@ public: Iterator& operator++() { - const int32 capacity = _collection.Capacity(); + const int32 capacity = _collection->_size; if (_index != capacity) { - const Bucket* data = _collection._allocation.Get(); + const Bucket* data = _collection->_allocation.Get(); do { _index++; @@ -343,7 +349,7 @@ public: { if (_index > 0) { - const Bucket* data = _collection._allocation.Get(); + const Bucket* data = _collection->_allocation.Get(); do { _index--; @@ -633,7 +639,7 @@ public: /// Iterator with key and value. void Add(const Iterator& i) { - ASSERT(&i._collection != this && i); + ASSERT(i._collection != this && i); const Bucket& bucket = *i; Add(bucket.Key, bucket.Value); } @@ -667,7 +673,7 @@ public: /// True if cannot remove item from the collection because cannot find it, otherwise false. bool Remove(const Iterator& i) { - ASSERT(&i._collection == this); + ASSERT(i._collection == this); if (i) { ASSERT(_allocation.Get()[i._index].IsOccupied()); @@ -711,7 +717,7 @@ public: return End(); FindPositionResult pos; FindPosition(key, pos); - return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End(); + return pos.ObjectIndex != -1 ? Iterator(this, pos.ObjectIndex) : End(); } /// @@ -812,38 +818,38 @@ public: public: Iterator Begin() const { - Iterator i(*this, -1); + Iterator i(this, -1); ++i; return i; } Iterator End() const { - return Iterator(*this, _size); + return Iterator(this, _size); } Iterator begin() { - Iterator i(*this, -1); + Iterator i(this, -1); ++i; return i; } FORCE_INLINE Iterator end() { - return Iterator(*this, _size); + return Iterator(this, _size); } const Iterator begin() const { - Iterator i(*this, -1); + Iterator i(this, -1); ++i; return i; } FORCE_INLINE const Iterator end() const { - return Iterator(*this, _size); + return Iterator(this, _size); } protected: diff --git a/Source/Engine/Core/Collections/HashSet.h b/Source/Engine/Core/Collections/HashSet.h index ad6f8ffc6..107e42e65 100644 --- a/Source/Engine/Core/Collections/HashSet.h +++ b/Source/Engine/Core/Collections/HashSet.h @@ -213,17 +213,17 @@ public: { friend HashSet; private: - HashSet& _collection; + HashSet* _collection; int32 _index; - Iterator(HashSet& collection, const int32 index) + Iterator(HashSet* collection, const int32 index) : _collection(collection) , _index(index) { } - Iterator(HashSet const& collection, const int32 index) - : _collection((HashSet&)collection) + Iterator(HashSet const* collection, const int32 index) + : _collection(const_cast(collection)) , _index(index) { } @@ -244,27 +244,27 @@ public: public: FORCE_INLINE bool IsEnd() const { - return _index == _collection.Capacity(); + return _index == _collection->_size; } FORCE_INLINE bool IsNotEnd() const { - return _index != _collection.Capacity(); + return _index != _collection->_size; } FORCE_INLINE Bucket& operator*() const { - return _collection._allocation.Get()[_index]; + return _collection->_allocation.Get()[_index]; } FORCE_INLINE Bucket* operator->() const { - return &_collection._allocation.Get()[_index]; + return &_collection->_allocation.Get()[_index]; } FORCE_INLINE explicit operator bool() const { - return _index >= 0 && _index < _collection._size; + return _index >= 0 && _index < _collection->_size; } FORCE_INLINE bool operator !() const @@ -274,12 +274,12 @@ public: FORCE_INLINE bool operator==(const Iterator& v) const { - return _index == v._index && &_collection == &v._collection; + return _index == v._index && _collection == v._collection; } FORCE_INLINE bool operator!=(const Iterator& v) const { - return _index != v._index || &_collection != &v._collection; + return _index != v._index || _collection != v._collection; } Iterator& operator=(const Iterator& v) @@ -291,10 +291,10 @@ public: Iterator& operator++() { - const int32 capacity = _collection.Capacity(); + const int32 capacity = _collection->_size; if (_index != capacity) { - const Bucket* data = _collection._allocation.Get(); + const Bucket* data = _collection->_allocation.Get(); do { _index++; @@ -314,7 +314,7 @@ public: { if (_index > 0) { - const Bucket* data = _collection._allocation.Get(); + const Bucket* data = _collection->_allocation.Get(); do { _index--; @@ -464,7 +464,7 @@ public: /// Iterator with item to add void Add(const Iterator& i) { - ASSERT(&i._collection != this && i); + ASSERT(i._collection != this && i); const Bucket& bucket = *i; Add(bucket.Item); } @@ -498,7 +498,7 @@ public: /// True if cannot remove item from the collection because cannot find it, otherwise false. bool Remove(const Iterator& i) { - ASSERT(&i._collection == this); + ASSERT(i._collection == this); if (i) { ASSERT(_allocation.Get()[i._index].IsOccupied()); @@ -523,7 +523,7 @@ public: return End(); FindPositionResult pos; FindPosition(item, pos); - return pos.ObjectIndex != -1 ? Iterator(*this, pos.ObjectIndex) : End(); + return pos.ObjectIndex != -1 ? Iterator(this, pos.ObjectIndex) : End(); } /// @@ -559,38 +559,38 @@ public: public: Iterator Begin() const { - Iterator i(*this, -1); + Iterator i(this, -1); ++i; return i; } Iterator End() const { - return Iterator(*this, _size); + return Iterator(this, _size); } Iterator begin() { - Iterator i(*this, -1); + Iterator i(this, -1); ++i; return i; } FORCE_INLINE Iterator end() { - return Iterator(*this, _size); + return Iterator(this, _size); } const Iterator begin() const { - Iterator i(*this, -1); + Iterator i(this, -1); ++i; return i; } FORCE_INLINE const Iterator end() const { - return Iterator(*this, _size); + return Iterator(this, _size); } protected: diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 97422b9e2..e8489f5b5 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -920,6 +920,11 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo } } +void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest) +{ + DrawLine(origin, origin + direction, color, duration, depthTest); +} + void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest) { const Float3 startF = start - Context->Origin, endF = end - Context->Origin; diff --git a/Source/Engine/Debug/DebugDraw.cs b/Source/Engine/Debug/DebugDraw.cs index af39ebeea..bc000e03d 100644 --- a/Source/Engine/Debug/DebugDraw.cs +++ b/Source/Engine/Debug/DebugDraw.cs @@ -31,6 +31,18 @@ namespace FlaxEngine { } + /// + /// Draws the line in a direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + public static void DrawRay(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true) + { + } + /// /// Draws the line. /// diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 8cc5452ed..a4f69678a 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -69,6 +69,16 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// True if draw all debug shapes from scenes too or false if draw just from specified actor list. API_FUNCTION() static void DrawActors(Actor** selectedActors, int32 selectedActorsCount, bool drawScenes); + /// + /// Draws the line in a direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration = 0.0f, bool depthTest = true); + /// /// Draws the line. /// @@ -604,6 +614,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color, int32 size = 32, float duration = 0.0f); }; +#define DEBUG_DRAW_RAY(origin, direction, color, duration, depthTest) DebugDraw::DrawRay(origin, direction, color, duration, depthTest) #define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) DebugDraw::DrawLine(start, end, color, duration, depthTest) #define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) DebugDraw::DrawLines(lines, transform, color, duration, depthTest) #define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) DebugDraw::DrawBezier(p1, p2, p3, p4, color, duration, depthTest) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 879f804d2..2c1bab744 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -581,9 +581,9 @@ namespace FlaxEngine.Interop } [UnmanagedCallersOnly] - internal static IntPtr NewStringLength(sbyte* text, int length) + internal static IntPtr NewStringUTF8(sbyte* text, int length) { - return ManagedString.ToNativeWeak(new string(text, 0, length)); + return ManagedString.ToNativeWeak(new string(text, 0, length, System.Text.Encoding.UTF8)); } [UnmanagedCallersOnly] diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 388379237..394d5c9dc 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -496,6 +496,11 @@ void Actor::AddTagRecursive(const Tag& tag) Tags.AddUnique(tag); } +void Actor::RemoveTag(const Tag& tag) +{ + Tags.Remove(tag); +} + PRAGMA_DISABLE_DEPRECATION_WARNINGS const String& Actor::GetTag() const diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 31cfe2473..5d66e7a16 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -153,6 +153,11 @@ public: /// The tag to add. API_FUNCTION() void AddTagRecursive(const Tag& tag); + /// Removes a tag to the actor + /// + /// The tag to remove. + API_FUNCTION() void RemoveTag(const Tag& tag); + /// /// Gets the name of the tag. /// [Deprecated in v1.5] diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 488acd6e4..42455daa4 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -8,6 +8,8 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Engine/Time.h" +#define CC_MIN_SIZE 0.001f + CharacterController::CharacterController(const SpawnParams& params) : Collider(params) , _controller(nullptr) @@ -100,10 +102,9 @@ void CharacterController::SetStepOffset(float value) { const float scaling = _cachedScale.GetAbsolute().MaxValue(); const float contactOffset = Math::Max(_contactOffset, ZeroTolerance); - const float minSize = 0.001f; - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); - const float radius = Math::Max(Math::Abs(_radius) * scaling - contactOffset, minSize); - PhysicsBackend::SetControllerStepOffset(_controller, Math::Min(value, height + radius * 2.0f - minSize)); + const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); + const float radius = Math::Max(Math::Abs(_radius) * scaling - contactOffset, CC_MIN_SIZE); + PhysicsBackend::SetControllerStepOffset(_controller, Math::Min(value, height + radius * 2.0f - CC_MIN_SIZE)); } } @@ -175,9 +176,8 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis void CharacterController::DrawPhysicsDebug(RenderView& view) { const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); + const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); + const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); const Vector3 position = _transform.LocalToWorld(_center); if (view.Mode == ViewMode::PhysicsColliders) DEBUG_DRAW_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true); @@ -188,9 +188,8 @@ void CharacterController::DrawPhysicsDebug(RenderView& view) void CharacterController::OnDebugDrawSelected() { const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); + const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); + const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); const Vector3 position = _transform.LocalToWorld(_center); DEBUG_DRAW_WIRE_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false); @@ -231,9 +230,8 @@ void CharacterController::UpdateSize() const if (_controller) { const float scaling = _cachedScale.GetAbsolute().MaxValue(); - const float minSize = 0.001f; - const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), minSize); - const float height = Math::Max(Math::Abs(_height) * scaling, minSize); + const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), CC_MIN_SIZE); + const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); PhysicsBackend::SetControllerSize(_controller, radius, height); } } @@ -245,12 +243,14 @@ void CharacterController::CreateShape() void CharacterController::UpdateBounds() { - void* actor = _shape ? PhysicsBackend::GetShapeActor(_shape) : nullptr; - if (actor) - PhysicsBackend::GetActorBounds(actor, _box); - else - _box = BoundingBox(_transform.Translation); + const float scaling = GetScale().GetAbsolute().MaxValue(); + const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE); + const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE); + const Vector3 position = _transform.LocalToWorld(_center); + const Vector3 extent(radius, height * 0.5f + radius, radius); + _box = BoundingBox(position - extent, position + extent); BoundingSphere::FromBox(_box, _sphere); + DEBUG_DRAW_BOX(_box, Color::Red.AlphaMultiplied(0.4f), 2.0f, true); } void CharacterController::AddMovement(const Vector3& translation, const Quaternion& rotation) diff --git a/Source/Engine/Platform/Base/WindowBase.cpp b/Source/Engine/Platform/Base/WindowBase.cpp index 5cff76361..49b4cc631 100644 --- a/Source/Engine/Platform/Base/WindowBase.cpp +++ b/Source/Engine/Platform/Base/WindowBase.cpp @@ -405,6 +405,7 @@ void WindowBase::OnResize(int32 width, int32 height) _swapChain->Resize(width, height); if (RenderTask) RenderTask->Resize(width, height); + Resized({ static_cast(width), static_cast(height) }); INVOKE_EVENT_PARAMS_2(OnResize, &width, &height); } diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index e4e7d2080..812359fb4 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -315,6 +315,11 @@ public: /// Action Closed; + /// + /// Event fired when window gets resized. + /// + Delegate Resized; + /// /// Event fired when window gets focused. /// diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index f4ae5b1ec..1bcef50b8 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -365,8 +365,8 @@ MString* MCore::String::GetEmpty(MDomain* domain) MString* MCore::String::New(const char* str, int32 length, MDomain* domain) { - static void* NewStringLengthPtr = GetStaticMethodPointer(TEXT("NewStringLength")); - return (MString*)CallStaticMethod(NewStringLengthPtr, str, length); + static void* NewStringUTF8Ptr = GetStaticMethodPointer(TEXT("NewStringUTF8")); + return (MString*)CallStaticMethod(NewStringUTF8Ptr, str, length); } MString* MCore::String::New(const Char* str, int32 length, MDomain* domain) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs new file mode 100644 index 000000000..d7324ae0e --- /dev/null +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -0,0 +1,365 @@ +using System; + +namespace FlaxEngine.GUI; + +/// +/// The slider control. +/// +public class Slider : ContainerControl +{ + /// + /// The minimum value. + /// + protected float _minimum; + + /// + /// The maximum value. + /// + protected float _maximum = 100f; + + /// + /// Gets or sets the minimum value. + /// + [EditorOrder(20), Tooltip("The minimum value.")] + public float Minimum + { + get => _minimum; + set + { + if (value > _maximum) + throw new ArgumentOutOfRangeException(); + if (WholeNumbers) + value = Mathf.RoundToInt(value); + _minimum = value; + if (Value < _minimum) + Value = _minimum; + } + } + + /// + /// Gets or sets the maximum value. + /// + [EditorOrder(30), Tooltip("The maximum value.")] + public float Maximum + { + get => _maximum; + set + { + if (value < _minimum || Mathf.IsZero(value)) + throw new ArgumentOutOfRangeException(); + if (WholeNumbers) + value = Mathf.RoundToInt(value); + _maximum = value; + if (Value > _maximum) + Value = _maximum; + } + } + + private float _value = 100f; + private Rectangle _thumbRect; + private float _thumbCenter; + private Float2 _thumbSize = new Float2(16, 16); + private bool _isSliding; + + /// + /// Gets or sets the value (normalized to range 0-100). + /// + [EditorOrder(10), Tooltip("The current value.")] + public float Value + { + get => _value; + set + { + value = Mathf.Clamp(value, Minimum, Maximum); + if (WholeNumbers) + value = Mathf.RoundToInt(value); + if (!Mathf.NearEqual(value, _value)) + { + _value = value; + + // Update + UpdateThumb(); + ValueChanged?.Invoke(); + } + } + } + + /// + /// The local position of the thumb center + /// + [HideInEditor] + public Float2 ThumbCenter => new(_thumbCenter, Height / 2); + + /// + /// The local position of the beginning of the track. + /// + [HideInEditor] + public Float2 TrackBeginning => new(_thumbSize.X / 2, Height / 2); + + /// + /// The local position of the end of the track. + /// + [HideInEditor] + public Float2 TrackEnd => new(Width - _thumbSize.X / 2, Height / 2); + + /// + /// The height of the track. + /// + [EditorOrder(40), Tooltip("The track height.")] + public int TrackHeight { get; set; } = 2; + + /// + /// The thumb size. + /// + [EditorOrder(41), Tooltip("The size of the thumb.")] + public Float2 ThumbSize { + get => _thumbSize; + set + { + _thumbSize = value; + UpdateThumb(); + } + } + + /// + /// Whether to fill the track. + /// + [EditorOrder(42), Tooltip("Fill the track.")] + public bool FillTrack = true; + + /// + /// Whether to use whole numbers. + /// + [EditorOrder(43), Tooltip("Use whole numbers.")] + public bool WholeNumbers = false; + + /// + /// The color of the slider track line + /// + [EditorDisplay("Track Style"), EditorOrder(2010), Tooltip("The color of the slider track line."), ExpandGroups] + public Color TrackLineColor { get; set; } + + /// + /// The color of the slider fill track line + /// + [EditorDisplay("Track Style"), EditorOrder(2011), VisibleIf(nameof(FillTrack)), Tooltip("The color of the slider fill track line.")] + public Color TrackFillLineColor { get; set; } + + /// + /// Gets the size of the track. + /// + private float TrackWidth => Width; + + /// + /// Gets or sets the brush used for slider track drawing. + /// + [EditorDisplay("Track Style"), EditorOrder(2012), Tooltip("The brush used for slider track drawing.")] + public IBrush TrackBrush { get; set; } + + /// + /// Gets or sets the brush used for slider fill track drawing. + /// + [EditorDisplay("Track Style"), EditorOrder(2013), VisibleIf(nameof(FillTrack)), Tooltip("The brush used for slider fill track drawing.")] + public IBrush FillTrackBrush { get; set; } + + /// + /// The color of the slider thumb when it's not selected + /// + [EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups] + public Color ThumbColor { get; set; } + + /// + /// The color of the slider thumb when it's selected + /// + [EditorDisplay("Thumb Style"), EditorOrder(2031), Tooltip("The color of the slider thumb when it's selected.")] + public Color ThumbColorSelected { get; set; } + + /// + /// Gets or sets the brush used for slider thumb drawing. + /// + [EditorDisplay("Thumb Style"), EditorOrder(2032), Tooltip("The brush of the slider thumb.")] + public IBrush ThumbBrush { get; set; } + + /// + /// Gets a value indicating whether user is using a slider. + /// + [HideInEditor] + public bool IsSliding => _isSliding; + + /// + /// Occurs when sliding starts. + /// + public event Action SlidingStart; + + /// + /// Occurs when sliding ends. + /// + public event Action SlidingEnd; + + /// + /// Occurs when value gets changed. + /// + public event Action ValueChanged; + + /// + /// Initializes a new instance of the class. + /// + public Slider() + : this(120, 30) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The height. + public Slider(float width, float height) + : base(0, 0, width, height) + { + var style = Style.Current; + TrackLineColor = style.BackgroundHighlighted; + TrackFillLineColor = style.LightBackground; + ThumbColor = style.BackgroundNormal; + ThumbColorSelected = style.BackgroundSelected; + UpdateThumb(); + } + + private void UpdateThumb() + { + // Cache data + float trackSize = TrackWidth; + float range = Maximum - Minimum; + float pixelRange = trackSize - _thumbSize.X; + float perc = (_value - Minimum) / range; + float thumbPosition = (int)(perc * pixelRange); + _thumbCenter = thumbPosition + _thumbSize.X / 2; + _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + } + + private void EndSliding() + { + _isSliding = false; + EndMouseCapture(); + SlidingEnd?.Invoke(); + } + + /// + public override void Draw() + { + base.Draw(); + + // Draw track line + //var lineRect = new Rectangle(4, (Height - TrackHeight) / 2, Width - 8, TrackHeight); + var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight) / 2, Width - _thumbSize.X, TrackHeight); + if (TrackBrush != null) + TrackBrush.Draw(lineRect, TrackLineColor); + else + Render2D.FillRectangle(lineRect, TrackLineColor); + + // Draw track fill + if (FillTrack) + { + var fillLineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); + Render2D.PushClip(ref fillLineRect); + if (FillTrackBrush != null) + FillTrackBrush.Draw(lineRect, TrackFillLineColor); + else + Render2D.FillRectangle(lineRect, TrackFillLineColor); + Render2D.PopClip(); + } + + // Draw thumb + var thumbColor = _isSliding ? ThumbColorSelected : ThumbColor; + if (ThumbBrush != null) + ThumbBrush.Draw(_thumbRect, thumbColor); + else + Render2D.FillRectangle(_thumbRect, thumbColor); + } + + /// + public override void OnLostFocus() + { + if (_isSliding) + { + EndSliding(); + } + + base.OnLostFocus(); + } + + /// + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (button == MouseButton.Left) + { + Focus(); + float mousePosition = location.X; + + if (_thumbRect.Contains(ref location)) + { + // Start sliding + _isSliding = true; + StartMouseCapture(); + SlidingStart?.Invoke(); + return true; + } + else + { + // Click change + Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; + } + } + + return base.OnMouseDown(location, button); + } + + /// + public override void OnMouseMove(Float2 location) + { + if (_isSliding) + { + // Update sliding + var slidePosition = location + Root.TrackingMouseOffset; + Value = Mathf.Remap(slidePosition.X, 4, TrackWidth - 4, Minimum, Maximum); + } + else + { + base.OnMouseMove(location); + } + } + + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (button == MouseButton.Left && _isSliding) + { + EndSliding(); + return true; + } + + return base.OnMouseUp(location, button); + } + + /// + public override void OnEndMouseCapture() + { + // Check if was sliding + if (_isSliding) + { + EndSliding(); + } + else + { + base.OnEndMouseCapture(); + } + } + + /// + protected override void OnSizeChanged() + { + base.OnSizeChanged(); + + UpdateThumb(); + } +}