diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs
index b0ce8c251..4f00acd1a 100644
--- a/Source/Editor/GUI/Tree/TreeNode.cs
+++ b/Source/Editor/GUI/Tree/TreeNode.cs
@@ -508,9 +508,9 @@ namespace FlaxEditor.GUI.Tree
/// True if event has been handled.
protected virtual bool OnMouseDoubleClickHeader(ref Float2 location, MouseButton button)
{
- if (HasAnyVisibleChild)
+ if (HasAnyVisibleChild && _animationProgress >= 1.0f)
{
- // Toggle open state
+ // Toggle open state (ignored while an expand/collapse animation is running)
if (_opened)
Collapse();
else
@@ -966,8 +966,8 @@ namespace FlaxEditor.GUI.Tree
}
}
- // Check if mouse hits arrow
- if (_mouseOverArrow && HasAnyVisibleChild)
+ // Check if mouse hits arrow (ignored while an expand/collapse animation is running)
+ if (_mouseOverArrow && HasAnyVisibleChild && _animationProgress >= 1.0f)
{
if (ParentTree.Root.GetKey(KeyboardKeys.Alt))
{
@@ -1322,7 +1322,19 @@ namespace FlaxEditor.GUI.Tree
// Skip full layout if it's fully collapsed
if (_opened || _animationProgress < 1.0f)
{
- y -= _cachedHeight * (_opened ? 1.0f - _animationProgress : _animationProgress);
+ // Measure the total children height this frame instead of relying on the previously cached one.
+ // Using the stale _cachedHeight causes a visible 3-frame flicker on first expand (down -> up -> down).
+ // Frame 1 uses the collapsed cache (~header height) so children snap to fully-open.
+ // Frame 2 uses the just-refreshed full height so they snap behind the header.
+ // At the end subsequent frames slide back down.
+ float childrenHeight = 0.0f;
+ for (int i = 0; i < _children.Count; i++)
+ {
+ if (_children[i] is TreeNode node && node.Visible)
+ childrenHeight += node.Height + DefaultNodeOffsetY;
+ }
+
+ y -= childrenHeight * (_opened ? 1.0f - _animationProgress : _animationProgress);
for (int i = 0; i < _children.Count; i++)
{
if (_children[i] is TreeNode node && node.Visible)
diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs
index 49e30a53d..77fb116f9 100644
--- a/Source/Editor/Options/OptionsModule.cs
+++ b/Source/Editor/Options/OptionsModule.cs
@@ -129,10 +129,13 @@ namespace FlaxEditor.Options
float prevInterfaceScale = Options.Interface.InterfaceScale;
Options = options;
- OnOptionsChanged();
- // Scale interface relative to the current value (eg. when using system-provided Dpi Scale)
+ // Scale interface relative to the current value (eg. when using system-provided Dpi Scale).
+ // Apply this before OnOptionsChanged so newly created fonts get rasterized at the target DPI scale (avoids blurry text).
Platform.CustomDpiScale *= Options.Interface.InterfaceScale / prevInterfaceScale;
+ Font.SetGlobalScale(Platform.DpiScale);
+
+ OnOptionsChanged();
}
else
{
diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp
index b2e6e8b75..ea098af2c 100644
--- a/Source/Engine/Render2D/Font.cpp
+++ b/Source/Engine/Render2D/Font.cpp
@@ -3,12 +3,30 @@
#include "Font.h"
#include "FontAsset.h"
#include "FontManager.h"
+#include "Engine/Content/Content.h"
#include "Engine/Core/Log.h"
#include "Engine/Threading/Threading.h"
#include "IncludeFreeType.h"
Array, HeapAllocation> Font::FallbackFonts;
+float Font::GetGlobalScale()
+{
+ return FontManager::FontScale;
+}
+
+void Font::SetGlobalScale(float scale)
+{
+ if (scale <= 0.0f || Math::NearEqual(FontManager::FontScale, scale)) return;
+ FontManager::FontScale = scale;
+
+ // Invalidate all loaded font assets so cached glyphs are re-rasterized and Font metrics are recomputed at the new scale
+ auto assets = Content::GetAssets();
+ for (auto* asset : assets) if (asset) asset->Invalidate();
+
+ FontManager::Flush();
+}
+
Font::Font(FontAsset* parentAsset, float size)
: ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer))
, _asset(parentAsset)
diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h
index fc40cc16c..b2365c0f8 100644
--- a/Source/Engine/Render2D/Font.h
+++ b/Source/Engine/Render2D/Font.h
@@ -261,6 +261,18 @@ public:
///
API_FIELD() static Array, HeapAllocation> FallbackFonts;
+ ///
+ /// Gets the global font scaling factor used to rasterize font glyphs at higher resolutions (eg. for high-DPI displays or UI scaling).
+ ///
+ API_PROPERTY() static float GetGlobalScale();
+
+ ///
+ /// Sets the global font scaling factor used to rasterize font glyphs at higher resolutions (eg. for high-DPI displays or UI scaling).
+ /// Calling this rebuilds the cached glyphs and metrics of every loaded font so text rendered afterwards stays crisp at the new scale.
+ ///
+ /// The new font scale (must be greater than 0).
+ API_FUNCTION() static void SetGlobalScale(float scale);
+
///
/// Gets parent font asset that contains font family used by this font.
///