From 58cd3e82f69977aa0d10ecb5f4b23fd742fb7b7f Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 24 Oct 2025 14:55:38 -0500 Subject: [PATCH 01/22] Better rotation on UI handles which handles total parent rotation. --- Source/Editor/Gizmo/UIEditorGizmo.cs | 54 +++++++++++++++++++--------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index 765893bed..e6af16a46 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -635,22 +635,28 @@ namespace FlaxEditor if (_widgets == null) _widgets = new List(); var widgetSize = 10.0f; + var sideWidgetSize = new Float2(5.0f, 20.0f); + var topWidgetSize = new Float2(20.0f, 5.0f); var viewScale = ViewScale; if (viewScale < 0.7f) + { widgetSize *= viewScale; - var widgetHandleSize = new Float2(widgetSize); - DrawControlWidget(uiControl, ref ul, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, -1), CursorType.SizeNWSE); - DrawControlWidget(uiControl, ref ur, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, -1), CursorType.SizeNESW); - DrawControlWidget(uiControl, ref bl, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, 1), CursorType.SizeNESW); - DrawControlWidget(uiControl, ref br, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, 1), CursorType.SizeNWSE); + sideWidgetSize *= viewScale; + topWidgetSize *= viewScale; + } + var cornerWidgetHandleSize = new Float2(widgetSize); + DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerWidgetHandleSize, viewScale, new Float2(-1, -1), CursorType.SizeNWSE); + DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerWidgetHandleSize, viewScale, new Float2(1, -1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerWidgetHandleSize, viewScale, new Float2(-1, 1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerWidgetHandleSize, viewScale, new Float2(1, 1), CursorType.SizeNWSE); Float2.Lerp(ref ul, ref bl, 0.5f, out var el); Float2.Lerp(ref ur, ref br, 0.5f, out var er); Float2.Lerp(ref ul, ref ur, 0.5f, out var eu); Float2.Lerp(ref bl, ref br, 0.5f, out var eb); - DrawControlWidget(uiControl, ref el, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, 0), CursorType.SizeWE); - DrawControlWidget(uiControl, ref er, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, 0), CursorType.SizeWE); - DrawControlWidget(uiControl, ref eu, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, -1), CursorType.SizeNS); - DrawControlWidget(uiControl, ref eb, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, 1), CursorType.SizeNS); + DrawControlWidget(uiControl, ref el, ref mousePos, ref sideWidgetSize, viewScale, new Float2(-1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref er, ref mousePos, ref sideWidgetSize, viewScale, new Float2(1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref eu, ref mousePos, ref topWidgetSize, viewScale, new Float2(0, -1), CursorType.SizeNS); + DrawControlWidget(uiControl, ref eb, ref mousePos, ref topWidgetSize, viewScale, new Float2(0, 1), CursorType.SizeNS); // Draw pivot var pivotSize = 8.0f; @@ -685,7 +691,7 @@ namespace FlaxEditor anchorRectSize *= viewScale; // Make anchor rects and rotate if parent is rotated. - var parentRotation = controlParent.Rotation * Mathf.DegreesToRadians; + var parentRotation = GetTotalRotation(controlParent) * Mathf.DegreesToRadians; var rect1Axis = new Float2(-1, -1); var rect1 = new Rectangle(anchorUpperLeft + @@ -717,17 +723,24 @@ namespace FlaxEditor } } + private float GetTotalRotation(Control control) + { + if (control.Parent != null) + return control.Rotation + GetTotalRotation(control.Parent); + return control.Rotation; + } + private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, float scale, Float2 resizeAxis, CursorType cursor) { var style = Style.Current; var control = uiControl.Control; - var rotation = control.Rotation; + var rotation = GetTotalRotation(control); var rotationInRadians = rotation * Mathf.DegreesToRadians; - var rect = new Rectangle((pos + - new Float2(resizeAxis.X * Mathf.Cos(rotationInRadians) - resizeAxis.Y * Mathf.Sin(rotationInRadians), - resizeAxis.Y * Mathf.Cos(rotationInRadians) + resizeAxis.X * Mathf.Sin(rotationInRadians)) * 10 * scale) - size * 0.5f, - size); - + var position = (pos); + var halfSize = size * 0.5f; + // Keep at 0, 0 rect position until later to correctly render rotation. + var rect = new Rectangle(0, 0, size); + // Find more correct cursor at different angles var unwindRotation = Mathf.UnwindDegrees(rotation); if (unwindRotation is (>= 45 and < 135) or (> -135 and <= -45) ) @@ -749,6 +762,10 @@ namespace FlaxEditor default: break; } } + + Render2D.PushTransform(Matrix3x3.Translation2D(position)); + Render2D.PushTransform(Matrix3x3.RotationZ(rotationInRadians)); + Render2D.PushTransform(Matrix3x3.Translation2D(-1 * halfSize)); if (rect.Contains(ref mousePos)) { Render2D.FillRectangle(rect, style.Foreground); @@ -759,9 +776,14 @@ namespace FlaxEditor Render2D.FillRectangle(rect, style.ForegroundGrey); Render2D.DrawRectangle(rect, style.Foreground); } + Render2D.PopTransform(); + Render2D.PopTransform(); + Render2D.PopTransform(); + if (!_mouseMovesWidget && uiControl != null) { // Collect widget + rect.Location = position - halfSize; _widgets.Add(new Widget { UIControl = uiControl, From dd8e6bf694fba1cef4501b429fab50921ed44387 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 24 Oct 2025 15:40:45 -0500 Subject: [PATCH 02/22] Change handles back to outside of control. --- Source/Editor/Gizmo/UIEditorGizmo.cs | 29 ++++++++++++---------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index e6af16a46..a22d9f7ee 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -634,29 +634,23 @@ namespace FlaxEditor // Draw sizing widgets if (_widgets == null) _widgets = new List(); - var widgetSize = 10.0f; - var sideWidgetSize = new Float2(5.0f, 20.0f); - var topWidgetSize = new Float2(20.0f, 5.0f); + var widgetSize = 8.0f; var viewScale = ViewScale; if (viewScale < 0.7f) - { widgetSize *= viewScale; - sideWidgetSize *= viewScale; - topWidgetSize *= viewScale; - } - var cornerWidgetHandleSize = new Float2(widgetSize); - DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerWidgetHandleSize, viewScale, new Float2(-1, -1), CursorType.SizeNWSE); - DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerWidgetHandleSize, viewScale, new Float2(1, -1), CursorType.SizeNESW); - DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerWidgetHandleSize, viewScale, new Float2(-1, 1), CursorType.SizeNESW); - DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerWidgetHandleSize, viewScale, new Float2(1, 1), CursorType.SizeNWSE); + var widgetHandleSize = new Float2(widgetSize); + DrawControlWidget(uiControl, ref ul, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, -1), CursorType.SizeNWSE); + DrawControlWidget(uiControl, ref ur, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, -1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref bl, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, 1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref br, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, 1), CursorType.SizeNWSE); Float2.Lerp(ref ul, ref bl, 0.5f, out var el); Float2.Lerp(ref ur, ref br, 0.5f, out var er); Float2.Lerp(ref ul, ref ur, 0.5f, out var eu); Float2.Lerp(ref bl, ref br, 0.5f, out var eb); - DrawControlWidget(uiControl, ref el, ref mousePos, ref sideWidgetSize, viewScale, new Float2(-1, 0), CursorType.SizeWE); - DrawControlWidget(uiControl, ref er, ref mousePos, ref sideWidgetSize, viewScale, new Float2(1, 0), CursorType.SizeWE); - DrawControlWidget(uiControl, ref eu, ref mousePos, ref topWidgetSize, viewScale, new Float2(0, -1), CursorType.SizeNS); - DrawControlWidget(uiControl, ref eb, ref mousePos, ref topWidgetSize, viewScale, new Float2(0, 1), CursorType.SizeNS); + DrawControlWidget(uiControl, ref el, ref mousePos, ref widgetHandleSize, viewScale, new Float2(-1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref er, ref mousePos, ref widgetHandleSize, viewScale, new Float2(1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref eu, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, -1), CursorType.SizeNS); + DrawControlWidget(uiControl, ref eb, ref mousePos, ref widgetHandleSize, viewScale, new Float2(0, 1), CursorType.SizeNS); // Draw pivot var pivotSize = 8.0f; @@ -736,7 +730,8 @@ namespace FlaxEditor var control = uiControl.Control; var rotation = GetTotalRotation(control); var rotationInRadians = rotation * Mathf.DegreesToRadians; - var position = (pos); + var position = (pos + new Float2(resizeAxis.X * Mathf.Cos(rotationInRadians) - resizeAxis.Y * Mathf.Sin(rotationInRadians), + resizeAxis.Y * Mathf.Cos(rotationInRadians) + resizeAxis.X * Mathf.Sin(rotationInRadians)) * 4 * (scale < 0.7f ? scale : 1)); var halfSize = size * 0.5f; // Keep at 0, 0 rect position until later to correctly render rotation. var rect = new Rectangle(0, 0, size); From 09964df198f43e4bea3ece4d4ea16a5f4488edfe Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 24 Oct 2025 16:19:33 -0500 Subject: [PATCH 03/22] Removing moving the control when trying to resize it. --- Source/Editor/Gizmo/UIEditorGizmo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index 765893bed..66bcd7634 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -398,7 +398,6 @@ namespace FlaxEditor var moveLocation = _mouseMovesPos + delta; var control = _activeWidget.UIControl.Control; var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation); - control.LocalLocation += uiControlDelta * resizeAxisNeg; control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg; // Don't move if layout doesn't allow it From 7b3e41efae5f024e18295bbf8f31b2faf7e10063 Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 31 Oct 2025 18:02:22 +0100 Subject: [PATCH 04/22] change how to the actor transform editor looks --- .../Editors/ActorTransformEditor.cs | 43 ++++++++++--------- Source/Editor/GUI/Input/ValueBox.cs | 11 +++++ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 02f5ca7ec..7dfea232c 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -26,11 +26,6 @@ namespace FlaxEditor.CustomEditors.Editors /// public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f); - /// - /// The axes colors grey out scale when input field is not focused. - /// - public static float AxisGreyOutFactor = 0.6f; - /// /// Custom editor for actor position property. /// @@ -43,18 +38,20 @@ namespace FlaxEditor.CustomEditors.Editors base.Initialize(layout); if (XElement.ValueBox.Parent is UniformGridPanel ug) + { + ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f); CheckLayout(ug); + } // Override colors - var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; - XElement.ValueBox.Category = Utils.ValueCategory.Distance; - YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; - YElement.ValueBox.Category = Utils.ValueCategory.Distance; - ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; + XElement.ValueBox.HighlightColor = AxisColorX; + XElement.ValueBox.Category = Utils.ValueCategory.Distance; + YElement.ValueBox.HighlightColor = AxisColorY; + YElement.ValueBox.Category = Utils.ValueCategory.Distance; + ZElement.ValueBox.HighlightColor = AxisColorZ; ZElement.ValueBox.Category = Utils.ValueCategory.Distance; } } @@ -71,18 +68,20 @@ namespace FlaxEditor.CustomEditors.Editors base.Initialize(layout); if (XElement.ValueBox.Parent is UniformGridPanel ug) + { + ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f); CheckLayout(ug); + } // Override colors - var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; - XElement.ValueBox.Category = Utils.ValueCategory.Angle; - YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; - YElement.ValueBox.Category = Utils.ValueCategory.Angle; - ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; + XElement.ValueBox.HighlightColor = AxisColorX; + XElement.ValueBox.Category = Utils.ValueCategory.Angle; + YElement.ValueBox.HighlightColor = AxisColorY; + YElement.ValueBox.Category = Utils.ValueCategory.Angle; + ZElement.ValueBox.HighlightColor = AxisColorZ; ZElement.ValueBox.Category = Utils.ValueCategory.Angle; } } @@ -129,17 +128,19 @@ namespace FlaxEditor.CustomEditors.Editors } if (XElement.ValueBox.Parent is UniformGridPanel ug) + { + ug.SlotPadding = new Margin(3.0f, 0.0f, 0.0f, 0.0f); CheckLayout(ug); + } // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - var grayOutFactor = 0.6f; - XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; - YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; - ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; + XElement.ValueBox.HighlightColor = AxisColorX; + YElement.ValueBox.HighlightColor = AxisColorY; + ZElement.ValueBox.HighlightColor = AxisColorZ; } /// diff --git a/Source/Editor/GUI/Input/ValueBox.cs b/Source/Editor/GUI/Input/ValueBox.cs index 674ee0697..e619ac2e3 100644 --- a/Source/Editor/GUI/Input/ValueBox.cs +++ b/Source/Editor/GUI/Input/ValueBox.cs @@ -89,6 +89,11 @@ namespace FlaxEditor.GUI.Input /// public bool IsSliding => _isSliding; + /// + /// The color of the highlight to the left of the value box. + /// + public Color HighlightColor; + /// /// Occurs when sliding starts. /// @@ -206,6 +211,12 @@ namespace FlaxEditor.GUI.Input Render2D.DrawRectangle(bounds, style.SelectionBorder); } } + + if (HighlightColor != Color.Transparent) + { + var highlightRect = new Rectangle(-3.0f, 0.0f, 3.0f, Height); + Render2D.FillRectangle(highlightRect, HighlightColor); + } } /// From c1738bcb0a4cab0e16cd4652519573bf765a509a Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 31 Oct 2025 18:02:35 +0100 Subject: [PATCH 05/22] make the ui editor look like the actor transform editor --- Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 614d2c160..90873e2b2 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -701,8 +701,7 @@ namespace FlaxEditor.CustomEditors.Dedicated { valueBox = floatEditorElement.ValueBox; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - valueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor); - valueBox.BorderSelectedColor = borderColor; + valueBox.HighlightColor = borderColor; } return grid; } From 316e5b2845da6b9d92305a532986be1b8811d05d Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 31 Oct 2025 18:10:00 +0100 Subject: [PATCH 06/22] also apply color to border if focused/ selected in ui editor --- Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 90873e2b2..1628f69ec 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -690,7 +690,7 @@ namespace FlaxEditor.CustomEditors.Dedicated return grid; } - private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor, out FloatValueBox valueBox) + private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color highlightColor, out FloatValueBox valueBox) { valueBox = null; var grid = UniformGridTwoByOne(el); @@ -701,7 +701,8 @@ namespace FlaxEditor.CustomEditors.Dedicated { valueBox = floatEditorElement.ValueBox; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - valueBox.HighlightColor = borderColor; + valueBox.HighlightColor = highlightColor; + valueBox.BorderSelectedColor = highlightColor; } return grid; } From b6696564f56aa8c07ab04306fd660dc0c701e969 Mon Sep 17 00:00:00 2001 From: Saas Date: Fri, 31 Oct 2025 18:22:30 +0100 Subject: [PATCH 07/22] tweak colors to be less bright --- Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 7dfea232c..c1179e393 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -14,17 +14,17 @@ namespace FlaxEditor.CustomEditors.Editors /// /// The X axis color. /// - public static Color AxisColorX = new Color(1.0f, 0.0f, 0.02745f, 1.0f); + public static Color AxisColorX = new Color(0.8f, 0.0f, 0.027f, 1.0f); /// /// The Y axis color. /// - public static Color AxisColorY = new Color(0.239215f, 1.0f, 0.047058f, 1.0f); + public static Color AxisColorY = new Color(0.239215f, 0.65f, 0.047058f, 1.0f); /// /// The Z axis color. /// - public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f); + public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 0.9f, 1.0f); /// /// Custom editor for actor position property. From ab22b88a53acf7dec3830967b47abe485d017af1 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 29 Nov 2025 15:57:35 -0600 Subject: [PATCH 08/22] Better calculation for moving ui by the widgets. --- Source/Editor/Gizmo/UIEditorGizmo.cs | 39 ++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index 66bcd7634..45a44d572 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -387,18 +387,47 @@ namespace FlaxEditor if (_mouseMovesWidget && _activeWidget.UIControl) { // Calculate transform delta - var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute; - var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One); - var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One); var delta = location - _mouseMovesPos; // TODO: scale/size snapping? - delta *= resizeAxisAbs; // Resize control via widget var moveLocation = _mouseMovesPos + delta; var control = _activeWidget.UIControl.Control; var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation); - control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg; + + // Transform delta to control local space + var rotation = control.Rotation * Mathf.DegreesToRadians; // TODO: use total parent rotation + var cos = Mathf.Cos(rotation); + var sin = Mathf.Sin(rotation); + var localDeltaX = uiControlDelta.X * cos + uiControlDelta.Y * sin; + var localDeltaY = uiControlDelta.Y * cos - uiControlDelta.X * sin; + var localDelta = new Float2(localDeltaX, localDeltaY); + localDelta *= _activeWidget.ResizeAxis.Absolute; + + // Calculate size change + var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One); + var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One); + var dSizeScaled = localDelta * resizeAxisPos - localDelta * resizeAxisNeg; + var scale = control.Scale; + var dSize = new Float2( + Mathf.Abs(scale.X) > Mathf.Epsilon ? dSizeScaled.X / scale.X : 0, + Mathf.Abs(scale.Y) > Mathf.Epsilon ? dSizeScaled.Y / scale.Y : 0); + + // Calculate pivot offset to keep the opposite edge stationary + var pivotOffset = Float2.Zero; + if (Mathf.Abs(dSizeScaled.X) > Mathf.Epsilon) + pivotOffset.X = (_activeWidget.ResizeAxis.X < 0 ? control.Pivot.X - 1.0f : control.Pivot.X) * dSizeScaled.X; + if (Mathf.Abs(dSizeScaled.Y) > Mathf.Epsilon) + pivotOffset.Y = (_activeWidget.ResizeAxis.Y < 0 ? control.Pivot.Y - 1.0f : control.Pivot.Y) * dSizeScaled.Y; + + // Transform pivot offset back to parent space + var dLocationX = pivotOffset.X * cos - pivotOffset.Y * sin; + var dLocationY = pivotOffset.X * sin + pivotOffset.Y * cos; + var dLocation = new Float2(dLocationX, dLocationY); + + // Apply changes + control.Size += dSize; + control.LocalLocation += dLocation; // Don't move if layout doesn't allow it if (control.Parent != null) From bbaa2dfc73ec5cecd2625a0059d4e9b7bac41ad1 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sat, 29 Nov 2025 16:13:46 -0600 Subject: [PATCH 09/22] Fix pibot relative movement. --- Source/Editor/Gizmo/UIEditorGizmo.cs | 34 ++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index 45a44d572..7ae34f590 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -413,21 +413,27 @@ namespace FlaxEditor Mathf.Abs(scale.X) > Mathf.Epsilon ? dSizeScaled.X / scale.X : 0, Mathf.Abs(scale.Y) > Mathf.Epsilon ? dSizeScaled.Y / scale.Y : 0); - // Calculate pivot offset to keep the opposite edge stationary - var pivotOffset = Float2.Zero; - if (Mathf.Abs(dSizeScaled.X) > Mathf.Epsilon) - pivotOffset.X = (_activeWidget.ResizeAxis.X < 0 ? control.Pivot.X - 1.0f : control.Pivot.X) * dSizeScaled.X; - if (Mathf.Abs(dSizeScaled.Y) > Mathf.Epsilon) - pivotOffset.Y = (_activeWidget.ResizeAxis.Y < 0 ? control.Pivot.Y - 1.0f : control.Pivot.Y) * dSizeScaled.Y; - - // Transform pivot offset back to parent space - var dLocationX = pivotOffset.X * cos - pivotOffset.Y * sin; - var dLocationY = pivotOffset.X * sin + pivotOffset.Y * cos; - var dLocation = new Float2(dLocationX, dLocationY); - - // Apply changes + // Apply size change control.Size += dSize; - control.LocalLocation += dLocation; + + // Calculate location offset to keep the opposite edge stationary + // When PivotRelative is false, resizing keeps Top-Left (Location) constant, + // so we only need to slide back if we are resizing Left or Top edges. + if (!control.PivotRelative) + { + var pivotOffset = Float2.Zero; + if (_activeWidget.ResizeAxis.X < 0 && Mathf.Abs(dSize.X) > Mathf.Epsilon) + pivotOffset.X = -dSize.X * scale.X; + if (_activeWidget.ResizeAxis.Y < 0 && Mathf.Abs(dSize.Y) > Mathf.Epsilon) + pivotOffset.Y = -dSize.Y * scale.Y; + + // Transform offset back to parent space and apply + var dLocationX = pivotOffset.X * cos - pivotOffset.Y * sin; + var dLocationY = pivotOffset.X * sin + pivotOffset.Y * cos; + var dLocation = new Float2(dLocationX, dLocationY); + + control.LocalLocation += dLocation; + } // Don't move if layout doesn't allow it if (control.Parent != null) From 5d9d64e6e76701160bf3c860712d8145376307bd Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 27 Jan 2026 22:10:22 -0600 Subject: [PATCH 10/22] Fix missing scale for inverse squared setting for lights. --- Source/Shaders/LightingCommon.hlsl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/Shaders/LightingCommon.hlsl b/Source/Shaders/LightingCommon.hlsl index f09572310..5b15a0d4d 100644 --- a/Source/Shaders/LightingCommon.hlsl +++ b/Source/Shaders/LightingCommon.hlsl @@ -68,20 +68,25 @@ void GetRadialLightAttenuation( // Distance attenuation if (lightData.InverseSquared) { + // Convert cm-based scene units to meters for inverse-squared falloff. + const float distanceScale = 0.01f; + const float distanceScaleSqr = distanceScale * distanceScale; + float distanceSqrScaled = distanceSqr * distanceScaleSqr; + float distanceBiasSqrScaled = distanceBiasSqr * distanceScaleSqr; BRANCH if (lightData.SourceLength > 0) { - float3 l01 = lightData.Direction * lightData.SourceLength; - float3 l0 = toLight - 0.5 * l01; - float3 l1 = toLight + 0.5 * l01; + float3 l01 = lightData.Direction * (lightData.SourceLength * distanceScale); + float3 l0 = (toLight * distanceScale) - 0.5 * l01; + float3 l1 = (toLight * distanceScale) + 0.5 * l01; float lengthL0 = length(l0); float lengthL1 = length(l1); - attenuation = rcp((lengthL0 * lengthL1 + dot(l0, l1)) * 0.5 + distanceBiasSqr); + attenuation = rcp((lengthL0 * lengthL1 + dot(l0, l1)) * 0.5 + distanceBiasSqrScaled); NoL = saturate(0.5 * (dot(N, l0) / lengthL0 + dot(N, l1) / lengthL1)); } else { - attenuation = rcp(distanceSqr + distanceBiasSqr); + attenuation = rcp(distanceSqrScaled + distanceBiasSqrScaled); NoL = saturate(dot(N, L)); } attenuation *= Square(saturate(1 - Square(distanceSqr * Square(lightData.RadiusInv)))); From ace84ae29faa235d2a7f1c32132b5812fc007f3f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Mar 2026 23:31:44 +0100 Subject: [PATCH 11/22] Add limit to max DDGI cascade updates per-frame to `2` #3963 --- .../Renderer/GI/DynamicDiffuseGlobalIllumination.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 25550ecd8..37d268991 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -90,6 +90,7 @@ public: Float3 ProbesOrigin; float ProbesSpacing = 0.0f; Int3 ProbeScrollOffsets; + bool PendingUpdate = true; Int3 ProbeScrollClears; void Clear() @@ -97,6 +98,7 @@ public: ProbesOrigin = Float3::Zero; ProbeScrollOffsets = Int3::Zero; ProbeScrollClears = Int3::Zero; + PendingUpdate = true; } } Cascades[4]; @@ -457,9 +459,12 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; //const uint64 cascadeFrequencies[] = { 10, 10, 10, 10 }; bool cascadeSkipUpdate[4]; + int32 maxCascadesPerFrame = renderContext.View.IsSingleFrame ? cascadesCount : 2; for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { - cascadeSkipUpdate[cascadeIndex] = !clear && (ddgiData.LastFrameUsed % cascadeFrequencies[cascadeIndex]) != 0 && GPU_SPREAD_WORKLOAD; + auto& cascade = ddgiData.Cascades[cascadeIndex]; + cascade.PendingUpdate |= !clear && (ddgiData.LastFrameUsed % cascadeFrequencies[cascadeIndex]) != 0 && GPU_SPREAD_WORKLOAD; + cascadeSkipUpdate[cascadeIndex] = !cascade.PendingUpdate || maxCascadesPerFrame-- <= 0; } // Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement) @@ -468,6 +473,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont if (cascadeSkipUpdate[cascadeIndex]) continue; auto& cascade = ddgiData.Cascades[cascadeIndex]; + cascade.PendingUpdate = false; // Calculate the count of grid cells between the view origin and the scroll anchor const Float3 volumeOrigin = cascade.ProbesOrigin + Float3(cascade.ProbeScrollOffsets) * cascade.ProbesSpacing; From 6d9d606085574521f98c86e7e9d53528a0297585 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 11 Mar 2026 23:56:47 +0100 Subject: [PATCH 12/22] Add `UNITS_TO_METERS_SCALE` to shader sources for world units scale #3907 --- Source/Shaders/Common.hlsl | 1 + Source/Shaders/LightingCommon.hlsl | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Shaders/Common.hlsl b/Source/Shaders/Common.hlsl index 66303546a..2052214b2 100644 --- a/Source/Shaders/Common.hlsl +++ b/Source/Shaders/Common.hlsl @@ -131,6 +131,7 @@ SamplerComparisonState ShadowSamplerLinear : register(s5); #define SAMPLE_RT_LINEAR(rt, texCoord) rt.SampleLevel(SamplerLinearClamp, texCoord, 0) #define HDR_CLAMP_MAX 65472.0 #define PI 3.1415926535897932 +#define UNITS_TO_METERS_SCALE 0.01f // Structure that contains information about GBuffer struct GBufferData diff --git a/Source/Shaders/LightingCommon.hlsl b/Source/Shaders/LightingCommon.hlsl index 5b15a0d4d..734b9b709 100644 --- a/Source/Shaders/LightingCommon.hlsl +++ b/Source/Shaders/LightingCommon.hlsl @@ -68,8 +68,8 @@ void GetRadialLightAttenuation( // Distance attenuation if (lightData.InverseSquared) { - // Convert cm-based scene units to meters for inverse-squared falloff. - const float distanceScale = 0.01f; + // Convert scene units to meters for inverse-squared falloff + const float distanceScale = UNITS_TO_METERS_SCALE; const float distanceScaleSqr = distanceScale * distanceScale; float distanceSqrScaled = distanceSqr * distanceScaleSqr; float distanceBiasSqrScaled = distanceBiasSqr * distanceScaleSqr; From 895dcf4aa6d856f672a09f37a7c02cb7f0c8608f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 00:00:28 +0100 Subject: [PATCH 13/22] Add auto-converting old point/spot lights brightness when using `Inverse Squared Falloff` to match new shader math #3907 --- Flax.flaxproj | 2 +- Source/Engine/Level/Actors/PointLight.cpp | 9 +++++++++ Source/Engine/Level/Actors/SpotLight.cpp | 10 ++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index c9c27281b..78c03fce6 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 11, "Revision": 0, - "Build": 6807 + "Build": 6808 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.", diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 607bf1bc4..b5801166e 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -1,6 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. #include "PointLight.h" +#include "Engine/Content/Deprecated.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderView.h" @@ -196,6 +197,14 @@ void PointLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modi DESERIALIZE(UseInverseSquaredFalloff); DESERIALIZE(UseIESBrightness); DESERIALIZE(IESBrightnessScale); + + // [Deprecated on 12.03.2026, expires on 12.03.2028] + if (modifier->EngineBuild <= 6807 && SERIALIZE_FIND_MEMBER(stream, "UseInverseSquaredFalloff") != stream.MemberEnd() && UseInverseSquaredFalloff) + { + // Convert old non-physical brightness value that was used for Inverse Squared Falloff which wasn't based on proper cm/m units calculations + MARK_CONTENT_DEPRECATED(); + Brightness = Math::Sqrt(Brightness * 0.01f); + } } bool PointLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index 85b77647a..38502cf1b 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -1,6 +1,8 @@ // Copyright (c) Wojciech Figat. All rights reserved. #include "SpotLight.h" + +#include "Engine/Content/Deprecated.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Content/Assets/IESProfile.h" @@ -282,6 +284,14 @@ void SpotLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modif DESERIALIZE(UseInverseSquaredFalloff); DESERIALIZE(UseIESBrightness); DESERIALIZE(IESBrightnessScale); + + // [Deprecated on 12.03.2026, expires on 12.03.2028] + if (modifier->EngineBuild <= 6807 && SERIALIZE_FIND_MEMBER(stream, "UseInverseSquaredFalloff") != stream.MemberEnd() && UseInverseSquaredFalloff) + { + // Convert old non-physical brightness value that was used for Inverse Squared Falloff which wasn't based on proper cm/m units calculations + MARK_CONTENT_DEPRECATED(); + Brightness = Math::Sqrt(Brightness * 0.01f); + } } bool SpotLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) From e736048fab2b28e7ae3ee1accb54044a49e51445 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 08:41:06 +0100 Subject: [PATCH 14/22] Ensure probe count is never out of bounds #3963 --- Content/Shaders/GI/DDGI.flax | 4 ++-- Source/Shaders/GI/DDGI.shader | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 257953bf9..98cfbb80a 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b017cf857f443553020e4bc7c8c8c5da3a826a2514322664a023ffa6005f7a5 -size 38217 +oid sha256:50753dbd37cb2a6fd6f48a597e90c8b8b689c7e433e66a5f21bc4ffa94244895 +size 38260 diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index b080efc0b..bade984fc 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -351,6 +351,7 @@ META_CS(true, FEATURE_LEVEL_SM5) void CS_UpdateProbesInitArgs() { uint activeProbesCount = ActiveProbes.Load(0); // Counter at 0 + activeProbesCount = min(activeProbesCount, ProbesCount); uint arg = 0; for (uint probesOffset = 0; probesOffset < activeProbesCount; probesOffset += DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT) { From 30d7588d9cc3d44ac920d3b12acc8a29b3384053 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 08:53:30 +0100 Subject: [PATCH 15/22] Fix UI panel controls missing in Editor --- Source/Engine/UI/GUI/ScrollableControl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Engine/UI/GUI/ScrollableControl.cs b/Source/Engine/UI/GUI/ScrollableControl.cs index 4977ac89b..7caa211cf 100644 --- a/Source/Engine/UI/GUI/ScrollableControl.cs +++ b/Source/Engine/UI/GUI/ScrollableControl.cs @@ -6,7 +6,6 @@ namespace FlaxEngine.GUI /// Base class for container controls that can offset controls in a view (eg. scroll panels). /// /// - [HideInEditor] public class ScrollableControl : ContainerControl { /// From 62e492452dec984851c654fc15bd385ea82bbbe3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 09:26:23 +0100 Subject: [PATCH 16/22] Add UI editor grid toggle --- Source/Editor/Gizmo/UIEditorGizmo.cs | 30 ++++++++++++------- .../Editor/Viewport/PrefabWindowViewport.cs | 3 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index 765893bed..8307ec8de 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -180,6 +180,11 @@ namespace FlaxEditor /// public bool EnableCamera => _view != null && EnableBackground; + /// + /// True if enable grid drawing. + /// + public bool ShowGrid { get; set; } = true; + /// /// Transform gizmo to use sync with (selection, snapping, transformation settings). /// @@ -492,17 +497,20 @@ namespace FlaxEditor // Draw background Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height); - // Draw grid - var viewRect = GetClientArea(); - var upperLeft = _view.PointFromParent(viewRect.Location); - var bottomRight = _view.PointFromParent(viewRect.Size); - var min = Float2.Min(upperLeft, bottomRight); - var max = Float2.Max(upperLeft, bottomRight); - var pixelRange = (max - min) * ViewScale; - Render2D.PushClip(ref viewRect); - DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X); - DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y); - Render2D.PopClip(); + if (ShowGrid) + { + // Draw grid + var viewRect = GetClientArea(); + var upperLeft = _view.PointFromParent(viewRect.Location); + var bottomRight = _view.PointFromParent(viewRect.Size); + var min = Float2.Min(upperLeft, bottomRight); + var max = Float2.Max(upperLeft, bottomRight); + var pixelRange = (max - min) * ViewScale; + Render2D.PushClip(ref viewRect); + DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X); + DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y); + Render2D.PopClip(); + } } base.Draw(); diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 7f2b1a471..a8bdc8f20 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -6,7 +6,6 @@ using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; -using FlaxEditor.GUI.Input; using FlaxEditor.Modules; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; @@ -15,7 +14,6 @@ using FlaxEditor.Viewport.Previews; using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; -using FlaxEngine.Json; using Utils = FlaxEditor.Utilities.Utils; namespace FlaxEditor.Viewport @@ -185,6 +183,7 @@ namespace FlaxEditor.Viewport showGridButton.Clicked += () => { _gridGizmo.Enabled = !_gridGizmo.Enabled; + _uiRoot.ShowGrid = _gridGizmo.Enabled; showGridButton.Checked = _gridGizmo.Enabled; }; showGridButton.Checked = true; From 7fa40eba8301fdf8d27b9a9b833462ee26e09e5e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 10:10:47 +0100 Subject: [PATCH 17/22] Fix `Render2D.DrawRectangle` to avoid alpha overdraw on the corners when thickness is large --- Source/Engine/Render2D/Render2D.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 5ae034bc5..689c1a3c1 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -1440,8 +1440,28 @@ void Render2D::DrawRectangle(const Rectangle& rect, const Color& color1, const C RENDER2D_CHECK_RENDERING_STATE; const auto& mask = ClipLayersStack.Peek().Mask; + float thick = thickness; thickness *= (TransformCached.M11 + TransformCached.M22 + TransformCached.M33) * 0.3333333f; + // When lines thickness is very large, don't use corner caps and place line ends to not overlap + if (thickness > 4.0f) + { + thick *= Math::Lerp(0.6f, 1.0f, Math::Saturate(thick - 4.0f)); // Smooth transition between soft LineAA and harsh FillRect + Float2 totalMin = rect.GetUpperLeft() - Float2(thick * 0.5f); + Float2 totalMax = rect.GetBottomRight() + Float2(thick * 0.5f); + Float2 size = totalMax - totalMin; + Render2DDrawCall& drawCall = DrawCalls.AddOne(); + drawCall.Type = NeedAlphaWithTint(color1, color2, color3, color4) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha; + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6 * 4; + // TODO: interpolate colors from corners to extended rectangle edges properly + WriteRect(Rectangle(totalMin.X, totalMin.Y, size.X, thick), color1, color2, color2, color1); + WriteRect(Rectangle(totalMin.X, totalMin.Y + rect.Size.Y, size.X, thick), color4, color3, color3, color4); + WriteRect(Rectangle(totalMin.X, totalMin.Y + thick, thick, rect.Size.Y - thick), color1, color1, color4, color4); + WriteRect(Rectangle(totalMax.X - thick, totalMin.Y + thick, thick, rect.Size.Y - thick), color2, color2, color3, color3); + return; + } + Float2 points[5]; ApplyTransform(rect.GetUpperLeft(), points[0]); ApplyTransform(rect.GetUpperRight(), points[1]); From b5d841ffc93596c0aff623f1a56868feeb65a838 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 11:06:42 +0100 Subject: [PATCH 18/22] Use total rotation from #3758 in #3760 --- Source/Editor/Gizmo/UIEditorGizmo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs index 3d0bc022b..61109556c 100644 --- a/Source/Editor/Gizmo/UIEditorGizmo.cs +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -401,7 +401,7 @@ namespace FlaxEditor var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation); // Transform delta to control local space - var rotation = control.Rotation * Mathf.DegreesToRadians; // TODO: use total parent rotation + var rotation = GetTotalRotation(control) * Mathf.DegreesToRadians; var cos = Mathf.Cos(rotation); var sin = Mathf.Sin(rotation); var localDeltaX = uiControlDelta.X * cos + uiControlDelta.Y * sin; From 8318a9c1d033fa5f559fa74cbfdabef5f8e183ae Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 13:39:01 +0100 Subject: [PATCH 19/22] Fix crash when unloading scene during tick of that scene --- Source/Engine/Level/Level.cpp | 3 -- Source/Engine/Level/Scene/Scene.cpp | 3 ++ Source/Engine/Level/Scene/SceneTicking.cpp | 40 +++++++++++----------- Source/Engine/Level/Scene/SceneTicking.h | 8 +++++ 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 956005f57..a46ee52f6 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -958,9 +958,6 @@ bool LevelImpl::unloadScene(Scene* scene) // Simple enqueue scene root object to be deleted scene->DeleteObject(); - // Force flush deleted objects so we actually delete unloaded scene objects (prevent from reloading their managed objects, etc.) - ObjectsRemovalService::Flush(); - return false; } diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index f60aa8d2f..b45424a3d 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -389,11 +389,14 @@ void Scene::BeginPlay(SceneBeginData* data) if (model == nullptr) CreateCsgModel(); } + + Ticking.SetTicking(true); } void Scene::EndPlay() { // Improve scene cleanup performance by removing all data from scene rendering and ticking containers + Ticking.SetTicking(false); Ticking.Clear(); Rendering.Clear(); Navigation.Clear(); diff --git a/Source/Engine/Level/Scene/SceneTicking.cpp b/Source/Engine/Level/Scene/SceneTicking.cpp index 30a551117..2b0ccc506 100644 --- a/Source/Engine/Level/Scene/SceneTicking.cpp +++ b/Source/Engine/Level/Scene/SceneTicking.cpp @@ -44,7 +44,7 @@ void SceneTicking::TickData::Tick() { TickScripts(Scripts); - for (int32 i = 0; i < Ticks.Count(); i++) + for (int32 i = 0; i < Ticks.Count() && _canTick; i++) Ticks.Get()[i].Call(); } @@ -66,7 +66,7 @@ void SceneTicking::TickData::TickExecuteInEditor() { TickScripts(ScriptsExecuteInEditor); - for (int32 i = 0; i < TicksExecuteInEditor.Count(); i++) + for (int32 i = 0; i < TicksExecuteInEditor.Count() && _canTick; i++) TicksExecuteInEditor.Get()[i].Call(); } @@ -89,10 +89,8 @@ SceneTicking::FixedUpdateTickData::FixedUpdateTickData() void SceneTicking::FixedUpdateTickData::TickScripts(const Array& scripts) { - for (auto* script : scripts) - { - script->OnFixedUpdate(); - } + for (int32 i = 0; i < scripts.Count() && _canTick; i++) + scripts.Get()[i]->OnFixedUpdate(); } SceneTicking::UpdateTickData::UpdateTickData() @@ -102,36 +100,30 @@ SceneTicking::UpdateTickData::UpdateTickData() void SceneTicking::UpdateTickData::TickScripts(const Array& scripts) { - for (auto* script : scripts) - { - script->OnUpdate(); - } + for (int32 i = 0; i < scripts.Count() && _canTick; i++) + scripts.Get()[i]->OnUpdate(); } SceneTicking::LateUpdateTickData::LateUpdateTickData() - : TickData(64) + : TickData(0) { } void SceneTicking::LateUpdateTickData::TickScripts(const Array& scripts) { - for (auto* script : scripts) - { - script->OnLateUpdate(); - } + for (int32 i = 0; i < scripts.Count() && _canTick; i++) + scripts.Get()[i]->OnLateUpdate(); } SceneTicking::LateFixedUpdateTickData::LateFixedUpdateTickData() - : TickData(64) + : TickData(0) { } void SceneTicking::LateFixedUpdateTickData::TickScripts(const Array& scripts) { - for (auto* script : scripts) - { - script->OnLateFixedUpdate(); - } + for (int32 i = 0; i < scripts.Count() && _canTick; i++) + scripts.Get()[i]->OnLateFixedUpdate(); } void SceneTicking::AddScript(Script* obj) @@ -167,3 +159,11 @@ void SceneTicking::Clear() LateUpdate.Clear(); LateFixedUpdate.Clear(); } + +void SceneTicking::SetTicking(bool enable) +{ + FixedUpdate._canTick = enable; + Update._canTick = enable; + LateUpdate._canTick = enable; + LateFixedUpdate._canTick = enable; +} diff --git a/Source/Engine/Level/Scene/SceneTicking.h b/Source/Engine/Level/Scene/SceneTicking.h index 78e028b8e..26040852b 100644 --- a/Source/Engine/Level/Scene/SceneTicking.h +++ b/Source/Engine/Level/Scene/SceneTicking.h @@ -46,6 +46,9 @@ public: /// class FLAXENGINE_API TickData { + protected: + friend SceneTicking; + bool _canTick = true; public: Array Scripts; Array Ticks; @@ -134,6 +137,11 @@ public: /// void Clear(); + /// + /// Changes the ability to tick. When disabled, the ticking functions won't be called. Can be called during ticking (eg. when scene is unloaded) to stp performing any more ticks. + /// + void SetTicking(bool enable); + public: /// /// The fixed update tick function. From e7016564b16e68dc5c103d6da5930f405735588d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 14:27:34 +0100 Subject: [PATCH 20/22] Add Git repository branch name and commit hash injection into generated code module metadata --- Source/Editor/Windows/AboutDialog.cs | 10 ++- Source/Engine/Engine/Engine.cpp | 4 ++ .../Bindings/BindingsGenerator.CSharp.cs | 3 +- .../Bindings/BindingsGenerator.Cpp.cs | 4 ++ Source/Tools/Flax.Build/ProjectInfo.cs | 66 +++++++++++++++++++ .../Tools/Flax.Build/Utilities/Utilities.cs | 4 +- 6 files changed, 88 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 1d9afbdfe..e8352fb1c 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -2,6 +2,7 @@ //#define USE_AUTODESK_FBX_SDK using System.Collections.Generic; +using System.Reflection; using FlaxEditor.GUI.Dialogs; using FlaxEngine; using FlaxEngine.GUI; @@ -45,9 +46,16 @@ namespace FlaxEditor.Windows VerticalAlignment = TextAlignment.Center, Parent = this }; + var assembly = typeof(Editor).Assembly; + var assemblyCopyright = assembly.GetCustomAttribute(); + var assemblyInformationalVersion = assembly.GetCustomAttribute(); + var versionParts = assemblyInformationalVersion.InformationalVersion.Split('+'); + string versionInfo = string.Empty; + if (versionParts.Length == 3) + versionInfo = $"\nBranch: {versionParts[1]}+{(versionParts[2].Length == 40 ? versionParts[2].Substring(0, 8) : versionParts[2])}"; new Label(nameLabel.Left, nameLabel.Bottom + 4, nameLabel.Width, 50) { - Text = string.Format("Version: {0}\nCopyright (c) 2012-2025 Wojciech Figat.\nAll rights reserved.", Globals.EngineVersion), + Text = $"Version: {Globals.EngineVersion}{versionInfo}\n{assemblyCopyright.Copyright.Replace(". ", ".\n")}", HorizontalAlignment = TextAlignment.Near, VerticalAlignment = TextAlignment.Near, Parent = this diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 5e8224b4e..15b3a1932 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -595,7 +595,11 @@ void EngineImpl::InitLog() #if COMPILE_WITH_DEV_ENV LOG(Info, "Compiled for Dev Environment"); #endif +#if defined(FLAXENGINE_BRANCH) && defined(FLAXENGINE_COMMIT) + LOG(Info, "Version " FLAXENGINE_VERSION_TEXT ", " FLAXENGINE_BRANCH ", " FLAXENGINE_COMMIT); +#else LOG(Info, "Version " FLAXENGINE_VERSION_TEXT); +#endif const Char* cpp = TEXT("?"); if (__cplusplus == 202101L) cpp = TEXT("C++23"); else if (__cplusplus == 202002L) cpp = TEXT("C++20"); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 034803d25..174c6de2c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -2429,7 +2429,7 @@ namespace Flax.Build.Bindings contents.AppendLine(); contents.AppendLine($"[assembly: AssemblyTitle(\"{binaryModuleName}\")]"); contents.AppendLine("[assembly: AssemblyDescription(\"\")]"); - contents.AppendLine("[assembly: AssemblyConfiguration(\"\")]"); + contents.AppendLine($"[assembly: AssemblyConfiguration(\"{buildData.Configuration}\")]"); contents.AppendLine($"[assembly: AssemblyCompany(\"{project.Company}\")]"); contents.AppendLine("[assembly: AssemblyProduct(\"FlaxEngine\")]"); contents.AppendLine($"[assembly: AssemblyCopyright(\"{project.Copyright}\")]"); @@ -2439,6 +2439,7 @@ namespace Flax.Build.Bindings contents.AppendLine($"[assembly: Guid(\"{id:d}\")]"); contents.AppendLine($"[assembly: AssemblyVersion(\"{project.Version}\")]"); contents.AppendLine($"[assembly: AssemblyFileVersion(\"{project.Version}\")]"); + contents.AppendLine($"[assembly: AssemblyInformationalVersion(\"{project.VersionControlInfo}\")]"); #if USE_NETCORE contents.AppendLine("[assembly: DisableRuntimeMarshalling]"); #endif diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index f1c4c6be8..ce557be20 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -3289,6 +3289,10 @@ namespace Flax.Build.Bindings contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_REVISION {version.Revision}"); contents.AppendLine($"#define {binaryModuleNameUpper}_COMPANY \"{project.Company}\""); contents.AppendLine($"#define {binaryModuleNameUpper}_COPYRIGHT \"{project.Copyright}\""); + if (project.VersionControlBranch.Length != 0) + contents.AppendLine($"#define {binaryModuleNameUpper}_BRANCH \"{project.VersionControlBranch}\""); + if (project.VersionControlCommit.Length != 0) + contents.AppendLine($"#define {binaryModuleNameUpper}_COMMIT \"{project.VersionControlCommit}\""); contents.AppendLine(); contents.AppendLine("class BinaryModule;"); contents.AppendLine($"extern \"C\" {binaryModuleNameUpper}_API BinaryModule* GetBinaryModule{binaryModuleName}();"); diff --git a/Source/Tools/Flax.Build/ProjectInfo.cs b/Source/Tools/Flax.Build/ProjectInfo.cs index 44065015d..00bdbf938 100644 --- a/Source/Tools/Flax.Build/ProjectInfo.cs +++ b/Source/Tools/Flax.Build/ProjectInfo.cs @@ -145,6 +145,7 @@ namespace Flax.Build public sealed class ProjectInfo { private static List _projectsCache; + private string _versionControlCommit, _versionControlBranch; /// /// The project reference. @@ -232,6 +233,51 @@ namespace Flax.Build [System.Text.Json.Serialization.JsonConverter(typeof(ConfigurationDictionaryConverter))] public Dictionary Configuration; + /// + /// Gets the name of the branch from Version Control System (VCS) used by the project. Empty when unused. + /// + public string VersionControlBranch + { + get + { + if (_versionControlBranch == null) + InitVersionControlInfo(); + return _versionControlBranch; + } + } + + /// + /// Gets the commit hash/changeset identifier from Version Control System (VCS) used by the project. Empty when unused. + /// + public string VersionControlCommit + { + get + { + if (_versionControlCommit == null) + InitVersionControlInfo(); + return _versionControlCommit; + } + } + + /// + /// Gets the informative version of the project including any Version Control System (VCS) information such as branch name, commit hash or changeset identifier. + /// + public string VersionControlInfo + { + + get + { + if (_versionControlCommit == null) + InitVersionControlInfo(); + var version = Version.ToString(); + if (_versionControlBranch.Length != 0) + version += "+" + _versionControlBranch; + if (_versionControlCommit.Length != 0) + version += "+" + _versionControlCommit; + return version; + } + } + /// /// True if project is using C#-only and no native toolsets is required to build and use scripts. /// @@ -267,6 +313,26 @@ namespace Flax.Build }); } + private void InitVersionControlInfo() + { + _versionControlBranch = string.Empty; + _versionControlCommit = string.Empty; + + // Git + if (Directory.Exists(Path.Combine(ProjectFolderPath, ".git"))) + { + try + { + _versionControlBranch = Utilities.ReadProcessOutput("git", "rev-parse --abbrev-ref HEAD", ProjectFolderPath); + _versionControlCommit = Utilities.ReadProcessOutput("git", "rev-parse HEAD", ProjectFolderPath); + } + catch (Exception) + { + // Ignored + } + } + } + /// /// Gets all projects including this project, it's references and their references (any deep level of references). /// diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 872b269ee..cb55b154a 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -587,8 +587,9 @@ namespace Flax.Build /// /// The executable file path. /// The custom arguments. + /// The custom folder to run program in it. /// Returned process output. - public static string ReadProcessOutput(string filename, string args = null) + public static string ReadProcessOutput(string filename, string args = null, string workspace = null) { Process p = new Process { @@ -599,6 +600,7 @@ namespace Flax.Build UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, + WorkingDirectory = workspace, } }; p.Start(); From 549ed76e7c71e584985048246520e85632db0c76 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 14:32:11 +0100 Subject: [PATCH 21/22] Add warning when importing texture that is non-power-of-two and cannot generate mip maps --- Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp | 4 ++++ Source/Engine/Tools/TextureTool/TextureTool.stb.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index d7199a470..be38f0015 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -722,6 +722,10 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path errorMsg = String::Format(TEXT("Imported texture has not full mip chain, loaded mips count: {0}, expected: {1}"), sourceMipLevels, mipLevels); return true; } + if (options.GenerateMipMaps && !isPowerOfTwo) + { + LOG(Warning, "Cannot generate mip maps for texture '{}' that size is not power of two. Use Resize or Max Size to change dimensions.", StringUtils::GetFileName(path), width, height); + } // Allocate memory for texture data auto& data = textureData.Items; diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index 7c74ef3f2..544877232 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -543,6 +543,10 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu errorMsg = String::Format(TEXT("Imported texture has not full mip chain, loaded mips count: {0}, expected: {1}"), sourceMipLevels, mipLevels); return true; } + if (options.GenerateMipMaps && !isPowerOfTwo) + { + LOG(Warning, "Cannot generate mip maps for texture '{}' that size is not power of two. Use Resize or Max Size to change dimensions.", StringUtils::GetFileName(path), width, height); + } // Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing) if (PixelFormatExtensions::IsCompressed(textureDataSrc->Format)) From 8b4f8de9888b72aa5b4504d04bb3314f22b8a651 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 12 Mar 2026 15:27:31 +0100 Subject: [PATCH 22/22] Fix `Control.LocalLocation` to use parent control `GetDesireClientArea` rather than `Bounds` #3889 --- Source/Engine/UI/GUI/Control.Bounds.cs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Source/Engine/UI/GUI/Control.Bounds.cs b/Source/Engine/UI/GUI/Control.Bounds.cs index 62bffd6f7..079530c50 100644 --- a/Source/Engine/UI/GUI/Control.Bounds.cs +++ b/Source/Engine/UI/GUI/Control.Bounds.cs @@ -166,8 +166,26 @@ namespace FlaxEngine.GUI [NoSerialize, HideInEditor] public Float2 LocalLocation { - get => _bounds.Location - (_parent != null ? _parent._bounds.Size * (_anchorMax + _anchorMin) * 0.5f : Float2.Zero) + _bounds.Size * _pivot; - set => Bounds = new Rectangle(value + (_parent != null ? _parent.Bounds.Size * (_anchorMax + _anchorMin) * 0.5f : Float2.Zero) - _bounds.Size * _pivot, _bounds.Size); + get + { + var anchor = Float2.Zero; + if (_parent != null) + { + _parent.GetDesireClientArea(out var parentBounds); + anchor = parentBounds.Location + parentBounds.Size * (_anchorMin + _anchorMax) * 0.5f; + } + return _bounds.Location - anchor + _bounds.Size * _pivot; + } + set + { + var anchor = Float2.Zero; + if (_parent != null) + { + _parent.GetDesireClientArea(out var parentBounds); + anchor = parentBounds.Location + parentBounds.Size * (_anchorMin + _anchorMax) * 0.5f; + } + Bounds = new Rectangle(value + anchor - _bounds.Size * _pivot, _bounds.Size); + } } ///