diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index 0c0fc6a51..47159c6c9 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI; using FlaxEditor.GUI.Drag; using FlaxEditor.GUI.Tree; @@ -140,8 +141,8 @@ namespace FlaxEditor.Content var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = font.First().GetCharPosition(text, ranges[i].StartIndex); + var end = font.First().GetCharPosition(text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index d7bfbbad7..1f69074ce 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; @@ -42,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Add script button var buttonText = "Add script"; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); float addScriptButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var buttonHeight = (textSize.Y < 18) ? 18 : textSize.Y + 4; _addScriptsButton = new Button diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index 296560507..5b5c2f90d 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -247,7 +247,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Info var info = new Label(0, title.Bottom, DialogWidth, InfoHeight) { - Font = new FontReference(style.FontSmall), + Font = new FontReference(style.FontSmall.First()), Text = "Shift: also set bounds\nControl: also set pivot", Parent = this }; @@ -423,7 +423,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Set control type button var space = layout.Space(20); var buttonText = "Set Type"; - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(buttonText); float setTypeButtonWidth = (textSize.X < 60.0f) ? 60.0f : textSize.X + 4; var setTypeButton = new Button { diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 4c153e759..d7059f712 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -2,6 +2,7 @@ using FlaxEngine; using FlaxEngine.GUI; +using System.Linq; namespace FlaxEditor.CustomEditors.Editors { @@ -100,7 +101,7 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(LinkedLabel.Text.Value); _linkButton.LocalX += textSize.X + 10; LinkedLabel.SetupContextMenu += (label, menu, editor) => { diff --git a/Source/Editor/CustomEditors/Editors/TagEditor.cs b/Source/Editor/CustomEditors/Editors/TagEditor.cs index dbd5d124c..a174d02e4 100644 --- a/Source/Editor/CustomEditors/Editors/TagEditor.cs +++ b/Source/Editor/CustomEditors/Editors/TagEditor.cs @@ -631,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors TooltipText = "Edit...", Parent = _label, }; - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(buttonText); if (textSize.Y > button.Width) button.Width = textSize.Y + 2; diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index eb2f21356..237908a0c 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -54,6 +54,8 @@ namespace FlaxEditor /// public static string PrimaryFont = "Editor/Fonts/Roboto-Regular"; + public static string CJKFont = "Editor/Fonts/NotoSansSC-Medium"; + /// /// The Inconsolata Regular font. /// diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 0417cc7e3..7dc407698 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -273,7 +273,7 @@ namespace FlaxEditor.GUI MaximumItemsInViewCount = 20; var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index e371f7c4b..872700a9b 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -233,11 +234,11 @@ namespace FlaxEditor.GUI.ContextMenu { var style = Style.Current; float width = 20; - if (style.FontMedium) + if (style.FontMedium.First()) { - width += style.FontMedium.MeasureText(Text).X; + width += style.FontMedium.First().MeasureText(Text).X; if (!string.IsNullOrEmpty(ShortKeys)) - width += 40 + style.FontMedium.MeasureText(ShortKeys).X; + width += 40 + style.FontMedium.First().MeasureText(ShortKeys).X; } return Mathf.Max(width, base.MinimumWidth); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 22deec120..f71409b20 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -437,7 +437,7 @@ namespace FlaxEditor.GUI _contentsColor = style.Background.RGBMultiplied(0.7f); _linesColor = style.ForegroundDisabled.RGBMultiplied(0.7f); _labelsColor = style.ForegroundDisabled; - _labelsFont = style.FontSmall; + _labelsFont = style.FontSmall.First(); _mainPanel = new Panel(ScrollBars.Both) { diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index dd4e39ce2..8601b9c53 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -6,6 +6,7 @@ using FlaxEngine; using FlaxEngine.Assertions; using FlaxEngine.GUI; using FlaxEditor.Options; +using System.Linq; namespace FlaxEditor.GUI.Docking { @@ -488,7 +489,7 @@ namespace FlaxEditor.GUI.Docking { var style = Style.Current; if (style?.FontMedium != null) - _titleSize = style.FontMedium.MeasureText(_title); + _titleSize = style.FontMedium.First().MeasureText(_title); } base.PerformLayoutBeforeChildren(); diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 42d236991..ce69f3544 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Utilities; @@ -86,8 +87,8 @@ namespace FlaxEditor.GUI var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(Name, ranges[i].StartIndex); - var end = font.GetCharPosition(Name, ranges[i].EndIndex); + var start = font.First().GetCharPosition(Name, ranges[i].StartIndex); + var end = font.First().GetCharPosition(Name, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + 2, 0, end.X - start.X, Height)); } Visible = true; diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs index 117922361..76687f8b9 100644 --- a/Source/Editor/GUI/MainMenuButton.cs +++ b/Source/Editor/GUI/MainMenuButton.cs @@ -2,6 +2,7 @@ using FlaxEngine; using FlaxEngine.GUI; +using System.Linq; namespace FlaxEditor.GUI { @@ -101,8 +102,8 @@ namespace FlaxEditor.GUI var style = Style.Current; float width = 18; - if (style.FontMedium) - width += style.FontMedium.MeasureText(Text).X; + if (style.FontMedium.First()) + width += style.FontMedium.First().MeasureText(Text).X; Width = width; } diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 18e862304..77f8a7656 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -2,6 +2,7 @@ using FlaxEngine; using FlaxEngine.GUI; +using System.Linq; namespace FlaxEditor.GUI { @@ -65,9 +66,9 @@ namespace FlaxEditor.GUI { var style = Style.Current; - if (style.FontMedium) + if (style.FontMedium.First()) { - Width = style.FontMedium.MeasureText(Text).X + 2 * DefaultMargin; + Width = style.FontMedium.First().MeasureText(Text).X + 2 * DefaultMargin; } } } diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index f6bd5b02a..458138aea 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -38,8 +39,8 @@ namespace FlaxEditor.GUI { Depth = -1; - if (Height < Style.Current.FontMedium.Height) - Height = Style.Current.FontMedium.Height + 4; + if (Height < Style.Current.FontMedium.First().Height) + Height = Style.Current.FontMedium.First().Height + 4; } /// diff --git a/Source/Editor/GUI/Table.cs b/Source/Editor/GUI/Table.cs index 1d22ddb15..fed33a336 100644 --- a/Source/Editor/GUI/Table.cs +++ b/Source/Editor/GUI/Table.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using System.Runtime.CompilerServices; using FlaxEngine; using FlaxEngine.GUI; @@ -129,7 +130,7 @@ namespace FlaxEditor.GUI Render2D.FillRectangle(rect, column.TitleBackgroundColor); var style = Style.Current; - var font = column.TitleFont ?? style.FontMedium; + var font = column.TitleFont ?? style.FontMedium.First(); Render2D.DrawText(font, column.Title, rect, column.TitleColor, TextAlignment.Center, TextAlignment.Center); if (columnIndex < _columns.Length - 1) diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index 63787df2c..928129917 100644 --- a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs @@ -345,7 +345,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (_previewValue != null) { // Based on Track.Draw for track text placement - var left = _xOffset + 16 + Style.Current.FontSmall.MeasureText(Title ?? Name).X; + var left = _xOffset + 16 + Style.Current.FontSmall.First().MeasureText(Title ?? Name).X; if (Icon.IsValid) left += 18; if (IsExpanded) diff --git a/Source/Editor/GUI/ToolStripButton.cs b/Source/Editor/GUI/ToolStripButton.cs index e839a7356..b74c7c19f 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -150,8 +151,8 @@ namespace FlaxEditor.GUI if (hasSprite) width += iconSize; - if (!string.IsNullOrEmpty(_text) && style.FontMedium) - width += style.FontMedium.MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); + if (!string.IsNullOrEmpty(_text) && style.FontMedium.First()) + width += style.FontMedium.First().MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); Width = width; } diff --git a/Source/Editor/GUI/Tree/TreeNode.cs b/Source/Editor/GUI/Tree/TreeNode.cs index 703079469..f26929993 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -317,7 +318,7 @@ namespace FlaxEditor.GUI.Tree BackgroundColorSelected = style.BackgroundSelected; BackgroundColorHighlighted = style.BackgroundHighlighted; BackgroundColorSelectedUnfocused = style.LightBackground; - TextFont = new FontReference(style.FontSmall); + TextFont = new FontReference(style.FontSmall.First()); } /// diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 95a273f19..c96a52fb1 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -237,12 +237,18 @@ namespace FlaxEditor.Options public int NumberOfGameClientsToLaunch = 1; private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); + private static FontAsset _cjkFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CJKFont); private FontReference _titleFont = new FontReference(DefaultFont, 18); private FontReference _largeFont = new FontReference(DefaultFont, 14); private FontReference _mediumFont = new FontReference(DefaultFont, 9); private FontReference _smallFont = new FontReference(DefaultFont, 9); private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + public FontReference CJKFont + { + get => new FontReference(_cjkFont, 9); + } + /// /// Gets or sets the title font for editor UI. /// diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 1137e4c37..15059bd56 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -261,8 +261,9 @@ namespace FlaxEditor.Options // Fonts FontTitle = options.Interface.TitleFont.GetFont(), FontLarge = options.Interface.LargeFont.GetFont(), - FontMedium = options.Interface.MediumFont.GetFont(), - FontSmall = options.Interface.SmallFont.GetFont(), + FontMedium = new Font[] { options.Interface.MediumFont.GetFont(), options.Interface.CJKFont.GetFont() }, + FontSmall = new Font[] { options.Interface.SmallFont.GetFont(), options.Interface.CJKFont.GetFont() }, + FontCJK = options.Interface.CJKFont.GetFont(), // Icons ArrowDown = Editor.Icons.ArrowDown12, @@ -314,8 +315,8 @@ namespace FlaxEditor.Options // Fonts FontTitle = options.Interface.TitleFont.GetFont(), FontLarge = options.Interface.LargeFont.GetFont(), - FontMedium = options.Interface.MediumFont.GetFont(), - FontSmall = options.Interface.SmallFont.GetFont(), + FontMedium = new Font[] { options.Interface.MediumFont.GetFont(), options.Interface.CJKFont.GetFont() }, + FontSmall = new Font[] { options.Interface.SmallFont.GetFont(), options.Interface.CJKFont.GetFont() }, // Icons ArrowDown = Editor.Icons.ArrowDown12, diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index f64e46385..9a6fe4fd2 100644 --- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs +++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs @@ -142,8 +142,8 @@ namespace FlaxEditor.SceneGraph.GUI var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = font.First().GetCharPosition(text, ranges[i].StartIndex); + var end = font.First().GetCharPosition(text, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } isThisVisible = true; diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index f8cd7bb5a..38dd28e62 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.CustomEditors.Dedicated; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Drag; @@ -100,7 +101,7 @@ namespace FlaxEditor.Surface.Archetypes _debugRelevant = Behavior.GetNodeDebugRelevancy(instance, behavior); _debugInfo = Behavior.GetNodeDebugInfo(instance, behavior); if (!string.IsNullOrEmpty(_debugInfo)) - _debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo); + _debugInfoSize = Style.Current.FontSmall.First().MeasureText(_debugInfo); } } diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 207875a92..812bec5d2 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -200,8 +200,8 @@ namespace FlaxEditor.Surface.ContextMenu var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); - var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); + var start = font.First().GetCharPosition(_archetype.Title, ranges[i].StartIndex); + var end = font.First().GetCharPosition(_archetype.Title, ranges[i].EndIndex); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); if (ranges[i].StartIndex <= 0) @@ -222,8 +222,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = font.First().GetCharPosition(_archetype.Title, 0); + var end = font.First().GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); _isFullMatch = true; Visible = true; @@ -237,8 +237,8 @@ namespace FlaxEditor.Surface.ContextMenu _highlights.Clear(); var style = Style.Current; var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = font.First().GetCharPosition(_archetype.Title, 0); + var end = font.First().GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); Visible = true; @@ -286,7 +286,7 @@ namespace FlaxEditor.Surface.ContextMenu Render2D.DrawText(style.FontSmall, _archetype.Title, textRect, Enabled ? style.Foreground : style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); if (_archetype.SubTitle != null) { - var titleLength = style.FontSmall.MeasureText(_archetype.Title).X; + var titleLength = style.FontSmall.First().MeasureText(_archetype.Title).X; var subTitleRect = new Rectangle(textRect.X + titleLength, textRect.Y, textRect.Width - titleLength, textRect.Height); Render2D.DrawText(style.FontSmall, _archetype.SubTitle, subTitleRect, style.ForegroundDisabled, TextAlignment.Near, TextAlignment.Center); } diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs index 2047bcd1a..611160611 100644 --- a/Source/Editor/Surface/Elements/InputBox.cs +++ b/Source/Editor/Surface/Elements/InputBox.cs @@ -1428,7 +1428,7 @@ namespace FlaxEditor.Surface.Elements if (_defaultValueEditor != null) { - _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y); + _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.First().MeasureText(Text).X, Y); } } @@ -1635,7 +1635,7 @@ namespace FlaxEditor.Surface.Elements { if (DefaultValueEditors[i].CanUse(this, ref _currentType)) { - var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.MeasureText(Text).X, Y, 90, Height); + var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.First().MeasureText(Text).X, Y, 90, Height); _editor = DefaultValueEditors[i]; try { diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index 780ef81f0..82a9ab2bd 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.Scripting; using FlaxEditor.Surface.Elements; using FlaxEditor.Surface.Undo; @@ -199,7 +200,7 @@ namespace FlaxEditor.Surface continue; if (child is InputBox inputBox) { - var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; + var boxWidth = boxLabelFont.First().MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) boxWidth += inputBox.DefaultValueEditor.Width + 4; leftWidth = Mathf.Max(leftWidth, boxWidth); @@ -207,7 +208,7 @@ namespace FlaxEditor.Surface } else if (child is OutputBox outputBox) { - rightWidth = Mathf.Max(rightWidth, boxLabelFont.MeasureText(outputBox.Text).X + 20); + rightWidth = Mathf.Max(rightWidth, boxLabelFont.First().MeasureText(outputBox.Text).X + 20); rightHeight = Mathf.Max(rightHeight, outputBox.Archetype.Position.Y - Constants.NodeMarginY - Constants.NodeHeaderSize + 20.0f); } else if (child is Control control) diff --git a/Source/Editor/Tools/Foliage/FoliageTab.cs b/Source/Editor/Tools/Foliage/FoliageTab.cs index 1b46a42be..e88972b5c 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph.Actors; @@ -147,7 +148,7 @@ namespace FlaxEditor.Tools.Foliage Parent = _noFoliagePanel, Enabled = false }; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); if (_createNewFoliage.Width < textSize.X) { _createNewFoliage.LocalX -= (textSize.X - _createNewFoliage.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CarveTab.cs b/Source/Editor/Tools/Terrain/CarveTab.cs index d51915acf..0a43cd2d2 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph.Actors; @@ -105,7 +106,7 @@ namespace FlaxEditor.Tools.Terrain Parent = _noTerrainPanel, Enabled = false }; - var textSize = Style.Current.FontMedium.MeasureText(buttonText); + var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); if (_createTerrainButton.Width < textSize.X) { _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index c49392d01..f4ebfb51b 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -548,9 +548,9 @@ namespace FlaxEditor.Viewport #region Camera settings widget var largestText = "Relative Panning"; - var textSize = Style.Current.FontMedium.MeasureText(largestText); + var textSize = Style.Current.FontMedium.First().MeasureText(largestText); var xLocationForExtras = textSize.X + 5; - var cameraSpeedTextWidth = Style.Current.FontMedium.MeasureText("0.00").X; + var cameraSpeedTextWidth = Style.Current.FontMedium.First().MeasureText("0.00").X; // Camera Settings Widget _cameraWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); @@ -801,7 +801,7 @@ namespace FlaxEditor.Viewport #region View mode widget largestText = "Brightness"; - textSize = Style.Current.FontMedium.MeasureText(largestText); + textSize = Style.Current.FontMedium.First().MeasureText(largestText); xLocationForExtras = textSize.X + 5; var viewMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperLeft); diff --git a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs index 77e94ae53..a58350ded 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEngine; using FlaxEngine.GUI; @@ -162,8 +163,8 @@ namespace FlaxEditor.Viewport.Widgets { var style = Style.Current; - if (style != null && style.FontMedium) - Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid); + if (style != null && style.FontMedium.First()) + Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.First().MeasureText(_text).X, Icon.IsValid); } } } diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index b059dadcb..63ad44f29 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -2,6 +2,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.Dialogs; using FlaxEngine; using FlaxEngine.GUI; @@ -53,7 +54,7 @@ namespace FlaxEditor.Windows Parent = this }; var buttonText = "Copy version info"; - var fontSize = Style.Current.FontMedium.MeasureText(buttonText); + var fontSize = Style.Current.FontMedium.First().MeasureText(buttonText); var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { Text = buttonText, diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index 59a7a0e26..88019b329 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -2,6 +2,7 @@ using FlaxEngine; using FlaxEngine.GUI; +using System.Linq; namespace FlaxEditor.Windows.Profiler { @@ -84,8 +85,8 @@ namespace FlaxEditor.Windows.Profiler Render2D.FillRectangle(bounds, color); Render2D.DrawRectangle(bounds, color * 0.5f); - if (_nameLength < 0 && style.FontMedium) - _nameLength = style.FontMedium.MeasureText(_name).X; + if (_nameLength < 0 && style.FontMedium.First()) + _nameLength = style.FontMedium.First().MeasureText(_name).X; if (_nameLength < bounds.Width + 4) { diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index e2b04669b..e8fc1d56c 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Tabs; using FlaxEditor.GUI.Tree; @@ -272,8 +273,8 @@ namespace FlaxEditor.Windows var textRect = item.TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.GetCharPosition(text, ranges[i].StartIndex); - var end = font.GetCharPosition(text, ranges[i].EndIndex); + var start = font.First().GetCharPosition(text, ranges[i].StartIndex); + var end = font.First().GetCharPosition(text, ranges[i].EndIndex); highlights.Add(new Rectangle(start.X + textRect.X, textRect.Y, end.X - start.X, textRect.Height)); } item.SetHighlights(highlights); diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 90423f5ce..13b17197c 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -283,6 +283,206 @@ void Font::ProcessText(const StringView& text, Array& outputLines } } +void Font::ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) +{ + float cursorX = 0; + int32 kerning; + FontLineCache tmpLine; + FontCharacterEntry entry; + FontCharacterEntry previous; + int32 textLength = text.Length(); + float scale = layout.Scale / FontManager::FontScale; + float boundsWidth = layout.Bounds.GetWidth(); + float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; + tmpLine.Location = Float2::Zero; + tmpLine.Size = Float2::Zero; + tmpLine.FirstCharIndex = 0; + tmpLine.LastCharIndex = -1; + + int32 lastWrapCharIndex = INVALID_INDEX; + float lastWrapCharX = 0; + bool lastMoveLine = false; + + int32 previousFontIndex = -1; + // The maximum font height of the current line + float maxHeight = 0; + // Process each character to split text into single lines + for (int32 currentIndex = 0; currentIndex < textLength;) + { + bool moveLine = false; + float xAdvance = 0; + int32 nextCharIndex = currentIndex + 1; + + // Cache current character + const Char currentChar = text[currentIndex]; + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Check if character can wrap words + const bool isWrapChar = !StringUtils::IsAlnum(currentChar) || isWhitespace || StringUtils::IsUpper(currentChar); + if (isWrapChar && currentIndex != 0) + { + lastWrapCharIndex = currentIndex; + lastWrapCharX = cursorX; + } + + // Check if it's a newline character + if (currentChar == '\n') + { + // Break line + moveLine = true; + currentIndex++; + tmpLine.LastCharIndex++; + } + else + { + // Get character entry + int32 fontIndex = 0; + while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(currentChar)) + { + fontIndex++; + } + + // If no font can match the char, then use the first font + if (fontIndex == fonts.Count()) { + fontIndex = 0; + } + // Get character entry + fonts[fontIndex]->GetCharacter(currentChar, entry); + maxHeight = Math::Max(maxHeight, static_cast(fonts[fontIndex]->GetHeight())); + + // Get kerning, only when the font hasn't changed + if (!isWhitespace && previous.IsValid && previousFontIndex == fontIndex) + { + kerning = fonts[fontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + previous = entry; + previousFontIndex = fontIndex; + xAdvance = (kerning + entry.AdvanceX) * scale; + + // Check if character fits the line or skip wrapping + if (cursorX + xAdvance <= boundsWidth || layout.TextWrapping == TextWrapping::NoWrap) + { + // Move character + cursorX += xAdvance; + tmpLine.LastCharIndex++; + } + else if (layout.TextWrapping == TextWrapping::WrapWords) + { + if (lastWrapCharIndex != INVALID_INDEX) + { + // Skip moving twice for the same character + int32 lastLineLasCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; + if (lastLineLasCharIndex == lastWrapCharIndex || lastLineLasCharIndex == lastWrapCharIndex - 1 || lastLineLasCharIndex == lastWrapCharIndex - 2) + { + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + continue; + } + + // Move line + const Char wrapChar = text[lastWrapCharIndex]; + moveLine = true; + cursorX = lastWrapCharX; + if (StringUtils::IsWhitespace(wrapChar)) + { + // Skip whitespaces + tmpLine.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex + 1; + } + else + { + tmpLine.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex; + } + } + } + else if (layout.TextWrapping == TextWrapping::WrapChars) + { + // Move line + moveLine = true; + nextCharIndex = currentIndex; + + // Skip moving twice for the same character + if (lastMoveLine) + break; + } + } + + // Check if move to another line + if (moveLine) + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + tmpLine.LastCharIndex = Math::Max(tmpLine.LastCharIndex, tmpLine.FirstCharIndex); + outputLines.Add(tmpLine); + + // Reset line + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + tmpLine.FirstCharIndex = currentIndex; + tmpLine.LastCharIndex = currentIndex - 1; + cursorX = 0; + lastWrapCharIndex = INVALID_INDEX; + lastWrapCharX = 0; + previous.IsValid = false; + + // Reset max font height + maxHeight = 0; + } + + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + } + + if (textLength != 0 && (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n')) + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + tmpLine.LastCharIndex = textLength - 1; + outputLines.Add(tmpLine); + + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + } + + // Check amount of lines + if (outputLines.IsEmpty()) + return; + + float totalHeight = tmpLine.Location.Y; + + Float2 offset = Float2::Zero; + if (layout.VerticalAlignment == TextAlignment::Center) + { + offset.Y += (layout.Bounds.GetHeight() - totalHeight) * 0.5f; + } + else if (layout.VerticalAlignment == TextAlignment::Far) + { + offset.Y += layout.Bounds.GetHeight() - totalHeight; + } + for (int32 i = 0; i < outputLines.Count(); i++) + { + FontLineCache& line = outputLines[i]; + Float2 rootPos = line.Location + offset; + + // Fix upper left line corner to match desire text alignment + if (layout.HorizontalAlignment == TextAlignment::Center) + { + rootPos.X += (layout.Bounds.GetWidth() - line.Size.X) * 0.5f; + } + else if (layout.HorizontalAlignment == TextAlignment::Far) + { + rootPos.X += layout.Bounds.GetWidth() - line.Size.X; + } + + line.Location = rootPos; + } +} + Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything @@ -432,6 +632,11 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo return rootOffset + Float2(lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); } +bool Font::ContainsChar(Char c) +{ + return FT_Get_Char_Index(GetAsset()->GetFTFace(), c) > 0; +} + void Font::FlushFaceSize() const { // Set the character size diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 90f723cd8..d8ac1b708 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -339,6 +339,15 @@ public: /// The output lines list. void ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); + /// + /// Processes text to get cached lines for rendering. + /// + /// The font list. + /// The input text. + /// The layout properties. + /// The output lines list. + static void ProcessText(const Array& fonts, const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout); + /// /// Processes text to get cached lines for rendering. /// @@ -395,6 +404,15 @@ public: /// The minimum size for that text and fot to render properly. API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + /// + /// Measures minimum size of the rectangle that will be needed to draw given text. + /// + /// The fonts to render with. + /// The input text to test. + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() static Float2 MeasureText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout); + /// /// Measures minimum size of the rectangle that will be needed to draw given text. /// @@ -482,6 +500,16 @@ public: /// The character position (upper left corner which can be used for a caret position). API_FUNCTION() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + /// + /// Calculates character position for given text and character index. + /// + /// The fonts to use. + /// The input text to test. + /// The text position to get coordinates of. + /// The text layout properties. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() static Float2 GetCharPosition(const Array& fonts, const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + /// /// Calculates character position for given text and character index. /// @@ -518,6 +546,13 @@ public: return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions()); } + /// + /// Check if the font contains the glyph of a char + /// + /// The char to test. + /// True if the font contains the glyph of the char, otherwise false. + API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c); + /// /// Flushes the size of the face with the Free Type library backend. /// diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index bb7edf974..5b08c1d87 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -155,6 +155,9 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) // Get the index to the glyph in the font face const FT_UInt glyphIndex = FT_Get_Char_Index(face, c); + if (glyphIndex == 0) { + LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c); + } // Load the glyph const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags); diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index b56c3e1a2..eedfbeffe 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -27,11 +27,11 @@ #if USE_EDITOR #define RENDER2D_CHECK_RENDERING_STATE \ - if (!Render2D::IsRendering()) \ - { \ - LOG(Error, "Calling Render2D is only valid during rendering."); \ - return; \ - } + if (!Render2D::IsRendering()) \ + { \ + LOG(Error, "Calling Render2D is only valid during rendering."); \ + return; \ + } #else #define RENDER2D_CHECK_RENDERING_STATE #endif @@ -54,7 +54,7 @@ const bool DownsampleForBlur = false; PACK_STRUCT(struct Data { Matrix ViewProjection; - }); +}); PACK_STRUCT(struct BlurData { Float2 InvBufferSize; @@ -62,7 +62,7 @@ PACK_STRUCT(struct BlurData { float Dummy0; Float4 Bounds; Float4 WeightAndOffsets[RENDER2D_BLUR_MAX_SAMPLES / 2]; - }); +}); enum class DrawCallType : byte { @@ -1174,7 +1174,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, drawCall.AsChar.Mat = nullptr; } Float2 pointer = location; - for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++) + for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) { // Cache current character const Char currentChar = text[currentIndex]; @@ -1368,6 +1368,318 @@ void Render2D::DrawText(Font* font, const StringView& text, const TextRange& tex DrawText(font, textRange.Substring(text), color, layout, customMaterial); } +void Render2D::DrawText(const Array& fonts, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) +{ + RENDER2D_CHECK_RENDERING_STATE; + + // Check if there is no need to do anything + if (fonts.IsEmpty() || text.Length() < 0) + return; + + // Temporary data + uint32 fontAtlasIndex = 0; + FontTextureAtlas* fontAtlas = nullptr; + Float2 invAtlasSize = Float2::One; + FontCharacterEntry previous; + int32 kerning; + float scale = 1.0f / FontManager::FontScale; + + // Render all characters + FontCharacterEntry entry; + Render2DDrawCall drawCall; + if (customMaterial) + { + drawCall.Type = DrawCallType::DrawCharMaterial; + drawCall.AsChar.Mat = customMaterial; + } + else + { + drawCall.Type = DrawCallType::DrawChar; + drawCall.AsChar.Mat = nullptr; + } + + // The following code cut the text into segments, according to the font used to render + Float2 pointer = location; + // The starting index of the current segment + int32 startIndex = 0; + // The index of the font used by the current segment + int32 segmentFontIndex = 0; + // The maximum font height of the current line + float maxHeight = 0; + for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++) + { + // Cache current character + const Char currentChar = currentIndex < text.Length() ? text[currentIndex] : 0; + + // Check if it isn't a newline character + if (currentChar != '\n') + { + int32 fontIndex = 0; + if (currentIndex < text.Length()) { + while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(currentChar)) + { + fontIndex++; + } + + // If no font can match the char, then use the segment font + if (fontIndex == fonts.Count()) { + fontIndex = segmentFontIndex; + } + + // Do nothing if the char still belongs to the current segment + if (fontIndex == segmentFontIndex) { + continue; + } + } + + // Render the pending segment before beginning the new segment + renderText:auto fontHeight = fonts[segmentFontIndex]->GetHeight(); + maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); + auto fontDescender = fonts[segmentFontIndex]->GetDescender(); + for (int32 renderIndex = startIndex; renderIndex < currentIndex; renderIndex++) + { + // Get character entry + fonts[segmentFontIndex]->GetCharacter(text[renderIndex], entry); + + // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) + if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) + { + // Get texture atlas that contains current character + fontAtlasIndex = entry.TextureIndex; + fontAtlas = FontManager::GetAtlas(fontAtlasIndex); + if (fontAtlas) + { + fontAtlas->EnsureTextureCreated(); + drawCall.AsChar.Tex = fontAtlas->GetTexture(); + invAtlasSize = 1.0f / fontAtlas->GetSize(); + } + else + { + drawCall.AsChar.Tex = nullptr; + invAtlasSize = 1.0f; + } + } + + // Check if character is a whitespace + const bool isWhitespace = StringUtils::IsWhitespace(text[renderIndex]); + + // Get kerning + if (!isWhitespace && previous.IsValid) + { + kerning = fonts[segmentFontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + pointer.X += kerning * scale; + previous = entry; + + // Omit whitespace characters + if (!isWhitespace) + { + // Calculate character size and atlas coordinates + const float x = pointer.X + entry.OffsetX * scale; + const float y = pointer.Y + (fontHeight + fontDescender - entry.OffsetY) * scale; + + Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + + Float2 upperLeftUV = entry.UV * invAtlasSize; + Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; + + // Add draw call + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6; + DrawCalls.Add(drawCall); + WriteRect(charRect, color, upperLeftUV, rightBottomUV); + } + + // Move + pointer.X += entry.AdvanceX * scale; + } + + // Start new segment + startIndex = currentIndex; + segmentFontIndex = fontIndex; + + if (currentIndex == text.Length() - 1) { + currentIndex++; + goto renderText; + } + } + else + { + // Move + pointer.X = location.X; + pointer.Y += maxHeight * scale; + // Clear max height + maxHeight = 0; + } + } +} + +void Render2D::DrawText(const Array& fonts, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) +{ + DrawText(fonts, textRange.Substring(text), color, location, customMaterial); +} + +void Render2D::DrawText(const Array& fonts, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +{ + RENDER2D_CHECK_RENDERING_STATE; + + // Check if there is no need to do anything + if (fonts.IsEmpty() || text.IsEmpty() || layout.Scale <= ZeroTolerance) + return; + + // Temporary data + uint32 fontAtlasIndex = 0; + FontTextureAtlas* fontAtlas = nullptr; + Float2 invAtlasSize = Float2::One; + FontCharacterEntry previous; + int32 kerning; + float scale = layout.Scale / FontManager::FontScale; + + // Process text to get lines + Lines.Clear(); + Font::ProcessText(fonts, text, Lines, layout); + + // Render all lines + FontCharacterEntry entry; + Render2DDrawCall drawCall; + if (customMaterial) + { + drawCall.Type = DrawCallType::DrawCharMaterial; + drawCall.AsChar.Mat = customMaterial; + } + else + { + drawCall.Type = DrawCallType::DrawChar; + drawCall.AsChar.Mat = nullptr; + } + + for (int32 lineIndex = 0; lineIndex < Lines.Count(); lineIndex++) + { + const FontLineCache& line = Lines[lineIndex]; + + // The following code cut the text into segments, according to the font used to render + Float2 pointer = line.Location; + // The starting index of the current segment + int32 startIndex = line.FirstCharIndex; + // The index of the font used by the current segment + int32 segmentFontIndex = 0; + // The maximum font height of the current line + float maxHeight = 0; + + // Partition and render all characters from the line + for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex + 1; charIndex++) + { + const Char c = charIndex <= line.LastCharIndex ? text[charIndex] : 0; + + if (c != '\n') + { + int32 fontIndex = 0; + if (charIndex <= line.LastCharIndex) { + while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(c)) + { + fontIndex++; + } + + // If no font can match the char, then use the segment font + if (fontIndex == fonts.Count()) { + fontIndex = segmentFontIndex; + } + + + // Do nothing if the char still belongs to the current segment + if (fontIndex == segmentFontIndex) { + continue; + } + } + + + // Render the pending segment before beginning the new segment + auto fontHeight = fonts[segmentFontIndex]->GetHeight(); + maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); + auto fontDescender = fonts[segmentFontIndex]->GetDescender(); + + const Char* pred = L"Type"; + if (text.Substring(0, Math::Min(4, text.Length())) == pred) { + // __debugbreak(); + } + for (int32 renderIndex = startIndex; renderIndex < charIndex; renderIndex++) + { + // Get character entry + fonts[segmentFontIndex]->GetCharacter(text[renderIndex], entry); + + // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) + if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) + { + // Get texture atlas that contains current character + fontAtlasIndex = entry.TextureIndex; + fontAtlas = FontManager::GetAtlas(fontAtlasIndex); + if (fontAtlas) + { + fontAtlas->EnsureTextureCreated(); + invAtlasSize = 1.0f / fontAtlas->GetSize(); + drawCall.AsChar.Tex = fontAtlas->GetTexture(); + } + else + { + invAtlasSize = 1.0f; + drawCall.AsChar.Tex = nullptr; + } + } + + // Get kerning + const bool isWhitespace = StringUtils::IsWhitespace(text[renderIndex]); + if (!isWhitespace && previous.IsValid) + { + kerning = fonts[segmentFontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + pointer.X += (float)kerning * scale; + previous = entry; + + // Omit whitespace characters + if (!isWhitespace) + { + // Calculate character size and atlas coordinates + const float x = pointer.X + entry.OffsetX * scale; + const float y = pointer.Y - entry.OffsetY * scale + Math::Ceil((fontHeight + fontDescender) * scale); + + Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale); + charRect.Offset(layout.Bounds.Location); + + Float2 upperLeftUV = entry.UV * invAtlasSize; + Float2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize; + + // Add draw call + drawCall.StartIB = IBIndex; + drawCall.CountIB = 6; + DrawCalls.Add(drawCall); + WriteRect(charRect, color, upperLeftUV, rightBottomUV); + } + + // Move + pointer.X += entry.AdvanceX * scale; + } + + // Start new segment + startIndex = charIndex; + segmentFontIndex = fontIndex; + } + } + } +} + +void Render2D::DrawText(const Array& fonts, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +{ + DrawText(fonts, textRange.Substring(text), color, layout, customMaterial); +} + FORCE_INLINE bool NeedAlphaWithTint(const Color& color) { return (color.A * TintLayersStack.Peek().A) < 1.0f; @@ -1931,7 +2243,7 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs) { RENDER2D_CHECK_RENDERING_STATE; - CHECK(vertices.Length() == uvs.Length()) + CHECK(vertices.Length() == uvs.Length()); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexture; @@ -1977,7 +2289,7 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& indices, drawCall.StartIB = IBIndex; drawCall.CountIB = indices.Length(); drawCall.AsTexture.Ptr = t; - + for (int32 i = 0; i < indices.Length();) { const uint16 i0 = indices.Get()[i++]; diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index c4d9e81b4..a73556f7b 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -152,6 +152,59 @@ namespace FlaxEngine DrawText(font, text, color, ref layout, customMaterial); } + /// + /// Draws a text. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The size and position of the area in which the text is drawn. + /// The text color. + /// The horizontal alignment of the text in a layout rectangle. + /// The vertical alignment of the text in a layout rectangle. + /// Describes how wrap text inside a layout rectangle. + /// The scale for distance one baseline from another. Default is 1. + /// The text drawing scale. Default is 1. + public static void DrawText(Font[] fonts, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + DrawText(fonts, text, color, ref layout); + } + + /// + /// Draws a text using a custom material shader. Given material must have GUI domain and a public parameter named Font (texture parameter used for a font atlas sampling). + /// + /// The fonts to use, ordered by priority. + /// Custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + /// The text to render. + /// The size and position of the area in which the text is drawn. + /// The text color. + /// The horizontal alignment of the text in a layout rectangle. + /// The vertical alignment of the text in a layout rectangle. + /// Describes how wrap text inside a layout rectangle. + /// The scale for distance one baseline from another. Default is 1. + /// The text drawing scale. Default is 1. + public static void DrawText(Font[] fonts, MaterialBase customMaterial, string text, Rectangle layoutRect, Color color, TextAlignment horizontalAlignment = TextAlignment.Near, TextAlignment verticalAlignment = TextAlignment.Near, TextWrapping textWrapping = TextWrapping.NoWrap, float baseLinesGapScale = 1.0f, float scale = 1.0f) + { + var layout = new TextLayoutOptions + { + Bounds = layoutRect, + HorizontalAlignment = horizontalAlignment, + VerticalAlignment = verticalAlignment, + TextWrapping = textWrapping, + Scale = scale, + BaseLinesGapScale = baseLinesGapScale, + }; + DrawText(fonts, text, color, ref layout, customMaterial); + } + /// /// Calls drawing GUI to the texture. /// diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 2b890ced9..0ec88edc0 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -33,7 +33,7 @@ API_CLASS(Static) class FLAXENGINE_API Render2D /// /// The rendering features and options flags. /// - API_ENUM(Attributes="Flags") enum class RenderingFeatures + API_ENUM(Attributes = "Flags") enum class RenderingFeatures { /// /// The none. @@ -215,6 +215,49 @@ public: /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. API_FUNCTION() static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + /// + /// Draws a text. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The input text range (substring range of the input text parameter). + /// The text color. + /// The text location. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + + /// + /// Draws a text with formatting. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The text color. + /// The text layout properties. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); + + /// + /// Draws a text with formatting. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The input text range (substring range of the input text parameter). + /// The text color. + /// The text layout properties. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + + /// + /// Draws a text with formatting. + /// + /// The fonts to use, ordered by priority. + /// The text to render. + /// The input text range (substring range of the input text parameter). + /// The text color. + /// The text layout properties. + /// The custom material for font characters rendering. It must contain texture parameter named Font used to sample font texture. + API_FUNCTION() static void DrawText(const Array& fonts, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); + /// /// Fills a rectangle area. /// diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 188333ff1..8e71c9f31 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -295,12 +295,13 @@ namespace FlaxEngine // Use optionally bundled default font (matches Editor) var defaultFont = Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"); + var cjkFont = Content.LoadAsyncInternal("NotoSansSC-Medium"); if (defaultFont) { style.FontTitle = defaultFont.CreateFont(18); style.FontLarge = defaultFont.CreateFont(14); - style.FontMedium = defaultFont.CreateFont(9); - style.FontSmall = defaultFont.CreateFont(9); + style.FontMedium = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) }; + style.FontSmall = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) }; } Style.Current = style; diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 02337411b..1b965b8d1 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; namespace FlaxEngine.GUI { @@ -155,7 +156,7 @@ namespace FlaxEngine.GUI var style = Style.Current; if (style != null) { - _font = new FontReference(style.FontMedium); + _font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BorderColor = style.BorderNormal; diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index ecca2978f..21ca9cbea 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace FlaxEngine.GUI { @@ -358,7 +359,7 @@ namespace FlaxEngine.GUI : base(0, 0, 120, 18.0f) { var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 3c7c04fb2..b92e762e1 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -1,6 +1,8 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using FlaxEditor.Options; using System.ComponentModel; +using System.Linq; namespace FlaxEngine.GUI { @@ -190,7 +192,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -201,7 +203,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -233,7 +235,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText(_font.GetFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + Render2D.DrawText(new Font[] { _font.GetFont(), Style.Current.FontCJK }, Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index ab922d93d..e6a82c986 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System.Collections.Generic; +using System.Linq; namespace FlaxEngine.GUI { @@ -45,7 +46,7 @@ namespace FlaxEngine.GUI var style = Style.Current; _textStyle = new TextBlockStyle { - Font = new FontReference(style.FontMedium), + Font = new FontReference(style.FontMedium.First()), Color = style.Foreground, BackgroundSelectedBrush = new SolidColorBrush(style.BackgroundSelected), }; diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index ee4f744a6..967617649 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Linq; + namespace FlaxEngine.GUI { /// @@ -88,7 +90,7 @@ namespace FlaxEngine.GUI _layout.Bounds = new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2); var style = Style.Current; - Font = new FontReference(style.FontMedium); + Font = new FontReference(style.FontMedium.First()); TextColor = style.Foreground; WatermarkTextColor = style.ForegroundDisabled; SelectionColor = style.BackgroundSelected; diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index 66e7413eb..33c2e1605 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; namespace FlaxEngine.GUI { @@ -237,7 +238,7 @@ namespace FlaxEngine.GUI var style = Style.Current; HeaderColor = style.BackgroundNormal; HeaderColorMouseOver = style.BackgroundHighlighted; - HeaderTextFont = new FontReference(style.FontMedium); + HeaderTextFont = new FontReference(style.FontMedium.First()); HeaderTextColor = style.Foreground; ArrowImageOpened = new SpriteBrush(style.ArrowDown); ArrowImageClosed = new SpriteBrush(style.ArrowRight); diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index 22b8f52af..d3375a670 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System.Linq; + namespace FlaxEngine.GUI { /// @@ -12,6 +14,14 @@ namespace FlaxEngine.GUI /// public static Style Current { get; set; } + public Font FontCJK + { + get => _fontCJK?.GetFont(); + set => _fontCJK = new FontReference(value); + } + + private FontReference _fontCJK; + [Serialize] private FontReference _fontTitle; @@ -41,31 +51,31 @@ namespace FlaxEngine.GUI } [Serialize] - private FontReference _fontMedium; + private FontReference[] _fontMedium; /// /// The font medium. /// [NoSerialize] [EditorOrder(30)] - public Font FontMedium + public Font[] FontMedium { - get => _fontMedium?.GetFont(); - set => _fontMedium = new FontReference(value); + get => _fontMedium?.Select((x)=>x.GetFont()).ToArray(); + set => _fontMedium = value.Select((x)=>new FontReference(x)).ToArray(); } [Serialize] - private FontReference _fontSmall; + private FontReference[] _fontSmall; /// /// The font small. /// [NoSerialize] [EditorOrder(40)] - public Font FontSmall + public Font[] FontSmall { - get => _fontSmall?.GetFont(); - set => _fontSmall = new FontReference(value); + get => _fontSmall?.Select((x) => x.GetFont()).ToArray(); + set => _fontSmall = value.Select((x) => new FontReference(x)).ToArray(); } /// diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index 734fb078f..e17b754c4 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Linq; namespace FlaxEngine.GUI { @@ -255,14 +256,14 @@ namespace FlaxEngine.GUI // Calculate size of the tooltip var size = Float2.Zero; - if (style != null && style.FontMedium && !string.IsNullOrEmpty(_currentText)) + if (style != null && style.FontMedium.First() && !string.IsNullOrEmpty(_currentText)) { var layout = TextLayoutOptions.Default; layout.Bounds = new Rectangle(0, 0, MaxWidth, 10000000); layout.HorizontalAlignment = TextAlignment.Center; layout.VerticalAlignment = TextAlignment.Center; layout.TextWrapping = TextWrapping.WrapWords; - var items = style.FontMedium.ProcessText(_currentText, ref layout); + var items = style.FontMedium.First().ProcessText(_currentText, ref layout); for (int i = 0; i < items.Length; i++) { ref var item = ref items[i];