From 41bbce56f6935d9066579a6b1d1e5d1b03c7fcc6 Mon Sep 17 00:00:00 2001 From: ExMatics HydrogenC <33123710+HydrogenC@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:41:45 +0800 Subject: [PATCH] Add multifont rendering to editor --- .../Content/Create/CreateFilesDialog.cs | 2 +- .../Content/Import/ImportFilesDialog.cs | 2 +- Source/Editor/Content/Tree/ContentTreeNode.cs | 4 +- .../CustomEditors/Dedicated/RagdollEditor.cs | 2 +- .../CustomEditors/Dedicated/ScriptsEditor.cs | 2 +- .../Dedicated/UIControlEditor.cs | 6 +- .../Editors/ActorTransformEditor.cs | 2 +- .../Editor/CustomEditors/Editors/TagEditor.cs | 2 +- .../CustomEditors/LayoutElementsContainer.cs | 4 +- Source/Editor/EditorAssets.cs | 2 +- Source/Editor/GUI/ColumnDefinition.cs | 2 +- Source/Editor/GUI/ComboBox.cs | 6 +- .../GUI/ContextMenu/ContextMenuButton.cs | 6 +- Source/Editor/GUI/CurveEditor.cs | 4 +- Source/Editor/GUI/Docking/DockWindow.cs | 2 +- Source/Editor/GUI/ItemsListContextMenu.cs | 4 +- Source/Editor/GUI/MainMenu.cs | 8 +- Source/Editor/GUI/MainMenuButton.cs | 4 +- Source/Editor/GUI/NavigationButton.cs | 4 +- Source/Editor/GUI/Row.cs | 4 +- Source/Editor/GUI/Table.cs | 2 +- .../Editor/GUI/Timeline/GUI/PositionHandle.cs | 21 +- .../Editor/GUI/Timeline/Tracks/MemberTrack.cs | 2 +- Source/Editor/GUI/ToolStripButton.cs | 4 +- Source/Editor/GUI/Tree/TreeNode.cs | 8 +- Source/Editor/Options/InterfaceOptions.cs | 63 ++- Source/Editor/Options/OptionsModule.cs | 17 +- Source/Editor/SceneGraph/GUI/ActorTreeNode.cs | 4 +- .../Archetypes/Animation.TransitionEditor.cs | 2 +- .../Editor/Surface/Archetypes/BehaviorTree.cs | 2 +- Source/Editor/Surface/Archetypes/Function.cs | 2 +- Source/Editor/Surface/AttributesEditor.cs | 2 +- .../Editor/Surface/ContextMenu/VisjectCM.cs | 2 +- .../Surface/ContextMenu/VisjectCMItem.cs | 14 +- Source/Editor/Surface/Elements/InputBox.cs | 4 +- Source/Editor/Surface/SurfaceNode.cs | 4 +- Source/Editor/Tools/Foliage/FoliageTab.cs | 2 +- Source/Editor/Tools/Terrain/CarveTab.cs | 2 +- .../Tools/Terrain/CreateTerrainDialog.cs | 2 +- Source/Editor/Viewport/EditorViewport.cs | 6 +- .../Viewport/Widgets/ViewportWidgetButton.cs | 4 +- Source/Editor/Windows/AboutDialog.cs | 4 +- Source/Editor/Windows/Assets/FontWindow.cs | 2 +- Source/Editor/Windows/ContentWindow.Search.cs | 2 +- Source/Editor/Windows/OutputLogWindow.cs | 74 +-- Source/Editor/Windows/PluginsWindow.cs | 9 +- Source/Editor/Windows/Profiler/Timeline.cs | 4 +- Source/Editor/Windows/ToolboxWindow.cs | 4 +- Source/Engine/Render2D/Font.cpp | 249 +--------- Source/Engine/Render2D/Font.h | 56 +-- Source/Engine/Render2D/MultiFont.cpp | 430 ++++++++++++++++++ Source/Engine/Render2D/MultiFont.h | 296 +++++++++++- Source/Engine/Render2D/MultiFontReference.cs | 74 +++ Source/Engine/Render2D/Render2D.cpp | 72 +-- Source/Engine/Render2D/Render2D.cs | 7 +- Source/Engine/Render2D/Render2D.h | 9 +- Source/Engine/Scripting/Scripting.cs | 15 +- Source/Engine/UI/GUI/Common/Button.cs | 8 +- Source/Engine/UI/GUI/Common/Dropdown.cs | 6 +- Source/Engine/UI/GUI/Common/Label.cs | 12 +- .../UI/GUI/Common/RichTextBox.Parsing.cs | 34 +- .../Engine/UI/GUI/Common/RichTextBox.Tags.cs | 72 +-- Source/Engine/UI/GUI/Common/RichTextBox.cs | 2 +- .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 28 +- Source/Engine/UI/GUI/Common/TextBox.cs | 16 +- Source/Engine/UI/GUI/Panels/DropPanel.cs | 6 +- Source/Engine/UI/GUI/Style.cs | 40 +- Source/Engine/UI/GUI/TextBlockStyle.cs | 2 +- Source/Engine/UI/GUI/Tooltip.cs | 5 +- 69 files changed, 1132 insertions(+), 647 deletions(-) create mode 100644 Source/Engine/Render2D/MultiFontReference.cs diff --git a/Source/Editor/Content/Create/CreateFilesDialog.cs b/Source/Editor/Content/Create/CreateFilesDialog.cs index d48e878bc..48e4920bb 100644 --- a/Source/Editor/Content/Create/CreateFilesDialog.cs +++ b/Source/Editor/Content/Create/CreateFilesDialog.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Content.Create AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new FontReference(Style.Current.FontTitle) + Font = new MultiFontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/Content/Import/ImportFilesDialog.cs b/Source/Editor/Content/Import/ImportFilesDialog.cs index 967583cf6..5a142d0f6 100644 --- a/Source/Editor/Content/Import/ImportFilesDialog.cs +++ b/Source/Editor/Content/Import/ImportFilesDialog.cs @@ -60,7 +60,7 @@ namespace FlaxEditor.Content.Import AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new FontReference(Style.Current.FontTitle) + Font = new MultiFontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/Content/Tree/ContentTreeNode.cs b/Source/Editor/Content/Tree/ContentTreeNode.cs index 37c4e8dbb..ee9b463f7 100644 --- a/Source/Editor/Content/Tree/ContentTreeNode.cs +++ b/Source/Editor/Content/Tree/ContentTreeNode.cs @@ -151,8 +151,8 @@ namespace FlaxEditor.Content var textRect = TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.First().GetCharPosition(text, ranges[i].StartIndex); - var end = font.First().GetCharPosition(text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.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/RagdollEditor.cs b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs index c4b334b3a..b6d14d81e 100644 --- a/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/RagdollEditor.cs @@ -81,7 +81,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new FontReference(FlaxEngine.GUI.Style.Current.FontLarge), + Font = new MultiFontReference(FlaxEngine.GUI.Style.Current.FontLarge), Text = "Ragdoll Options", Parent = this }; diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index 1f69074ce..fd56422cb 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Add script button var buttonText = "Add script"; - var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); + var textSize = Style.Current.FontMedium.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 5b5c2f90d..9627edfd8 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -239,7 +239,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Title var title = new Label(2, 2, DialogWidth - 4, TitleHeight) { - Font = new FontReference(style.FontLarge), + Font = new MultiFontReference(style.FontLarge), Text = "Anchor Presets", Parent = this }; @@ -247,7 +247,7 @@ namespace FlaxEditor.CustomEditors.Dedicated // Info var info = new Label(0, title.Bottom, DialogWidth, InfoHeight) { - Font = new FontReference(style.FontSmall.First()), + Font = new MultiFontReference(style.FontSmall), 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.First().MeasureText(buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.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 d7059f712..0cad200fd 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -101,7 +101,7 @@ namespace FlaxEditor.CustomEditors.Editors _linkButton.Clicked += ToggleLink; ToggleEnabled(); SetLinkStyle(); - var textSize = FlaxEngine.GUI.Style.Current.FontMedium.First().MeasureText(LinkedLabel.Text.Value); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.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 a174d02e4..dbd5d124c 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.First().MeasureText(buttonText); + var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(buttonText); if (textSize.Y > button.Width) button.Width = textSize.Y + 2; diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 936851b15..1584e88de 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -276,7 +276,7 @@ namespace FlaxEditor.CustomEditors public LabelElement Header(string text) { var element = Label(text); - element.Label.Font = new FontReference(Style.Current.FontLarge); + element.Label.Font = new MultiFontReference(Style.Current.FontLarge); return element; } @@ -284,7 +284,7 @@ namespace FlaxEditor.CustomEditors { var element = Header(header.Text); if (header.FontSize != -1) - element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize); + element.Label.Font = new MultiFontReference(element.Label.Font, header.FontSize); if (header.Color != 0) element.Label.TextColor = Color.FromRGBA(header.Color); return element; diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index 0fe5ee47e..c894abe6b 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -54,7 +54,7 @@ namespace FlaxEditor /// public static string PrimaryFont = "Editor/Fonts/Roboto-Regular"; - public static string CJKFont = "Editor/Fonts/NotoSansSC-Regular"; + public static string CjkFont = "Editor/Fonts/NotoSansSC-Regular"; /// /// The Inconsolata Regular font. diff --git a/Source/Editor/GUI/ColumnDefinition.cs b/Source/Editor/GUI/ColumnDefinition.cs index aff1817c3..c6e8f2889 100644 --- a/Source/Editor/GUI/ColumnDefinition.cs +++ b/Source/Editor/GUI/ColumnDefinition.cs @@ -35,7 +35,7 @@ namespace FlaxEditor.GUI /// /// The title font. /// - public Font TitleFont; + public MultiFont TitleFont; /// /// The column title text color. diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index 7dc407698..8e6cf39a0 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -191,7 +191,7 @@ namespace FlaxEditor.GUI /// Gets or sets the font used to draw text. /// [EditorDisplay("Style"), EditorOrder(2000)] - public FontReference Font { get; set; } + public MultiFontReference Font { get; set; } /// /// Gets or sets the color of the text. @@ -273,7 +273,7 @@ namespace FlaxEditor.GUI MaximumItemsInViewCount = 20; var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; @@ -554,7 +554,7 @@ namespace FlaxEditor.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + Render2D.DrawText(Font.GetMultiFont(), text, textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); } diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs index 872700a9b..ba3326412 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuButton.cs @@ -234,11 +234,11 @@ namespace FlaxEditor.GUI.ContextMenu { var style = Style.Current; float width = 20; - if (style.FontMedium.First()) + if (style.FontMedium) { - width += style.FontMedium.First().MeasureText(Text).X; + width += style.FontMedium.MeasureText(Text).X; if (!string.IsNullOrEmpty(ShortKeys)) - width += 40 + style.FontMedium.First().MeasureText(ShortKeys).X; + width += 40 + style.FontMedium.MeasureText(ShortKeys).X; } return Mathf.Max(width, base.MinimumWidth); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index f71409b20..ff14cdb24 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -317,7 +317,7 @@ namespace FlaxEditor.GUI private Color _contentsColor; private Color _linesColor; private Color _labelsColor; - private Font _labelsFont; + private MultiFont _labelsFont; /// /// The keyframe UI points. @@ -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.First(); + _labelsFont = style.FontSmall; _mainPanel = new Panel(ScrollBars.Both) { diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 8601b9c53..374885f01 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -489,7 +489,7 @@ namespace FlaxEditor.GUI.Docking { var style = Style.Current; if (style?.FontMedium != null) - _titleSize = style.FontMedium.First().MeasureText(_title); + _titleSize = style.FontMedium.MeasureText(_title); } base.PerformLayoutBeforeChildren(); diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index ce69f3544..b823cc907 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -87,8 +87,8 @@ namespace FlaxEditor.GUI var font = style.FontSmall; for (int i = 0; i < ranges.Length; i++) { - var start = font.First().GetCharPosition(Name, ranges[i].StartIndex); - var end = font.First().GetCharPosition(Name, ranges[i].EndIndex); + var start = font.GetCharPosition(Name, ranges[i].StartIndex); + var end = font.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/MainMenu.cs b/Source/Editor/GUI/MainMenu.cs index b313fed9f..aadfbf0a7 100644 --- a/Source/Editor/GUI/MainMenu.cs +++ b/Source/Editor/GUI/MainMenu.cs @@ -76,7 +76,7 @@ namespace FlaxEditor.GUI var windowIcon = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIcon); FontAsset windowIconsFont = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WindowIconsFont); - Font iconFont = windowIconsFont?.CreateFont(9); + MultiFont iconFont = new MultiFontReference([windowIconsFont], 9).GetMultiFont(); _window = mainWindow.RootWindow.Window; _window.HitTest += OnHitTest; @@ -108,7 +108,7 @@ namespace FlaxEditor.GUI _closeButton = new Button { Text = ((char)EditorAssets.SegMDL2Icons.ChromeClose).ToString(), - Font = new FontReference(iconFont), + Font = new MultiFontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, @@ -124,7 +124,7 @@ namespace FlaxEditor.GUI _minimizeButton = new Button { Text = ((char)EditorAssets.SegMDL2Icons.ChromeMinimize).ToString(), - Font = new FontReference(iconFont), + Font = new MultiFontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, @@ -139,7 +139,7 @@ namespace FlaxEditor.GUI _maximizeButton = new Button { Text = ((char)(_window.IsMaximized ? EditorAssets.SegMDL2Icons.ChromeRestore : EditorAssets.SegMDL2Icons.ChromeMaximize)).ToString(), - Font = new FontReference(iconFont), + Font = new MultiFontReference(iconFont), BackgroundColor = Color.Transparent, BorderColor = Color.Transparent, BorderColorHighlighted = Color.Transparent, diff --git a/Source/Editor/GUI/MainMenuButton.cs b/Source/Editor/GUI/MainMenuButton.cs index 76687f8b9..3bc57479c 100644 --- a/Source/Editor/GUI/MainMenuButton.cs +++ b/Source/Editor/GUI/MainMenuButton.cs @@ -102,8 +102,8 @@ namespace FlaxEditor.GUI var style = Style.Current; float width = 18; - if (style.FontMedium.First()) - width += style.FontMedium.First().MeasureText(Text).X; + if (style.FontMedium) + width += style.FontMedium.MeasureText(Text).X; Width = width; } diff --git a/Source/Editor/GUI/NavigationButton.cs b/Source/Editor/GUI/NavigationButton.cs index 77f8a7656..6fd17332c 100644 --- a/Source/Editor/GUI/NavigationButton.cs +++ b/Source/Editor/GUI/NavigationButton.cs @@ -66,9 +66,9 @@ namespace FlaxEditor.GUI { var style = Style.Current; - if (style.FontMedium.First()) + if (style.FontMedium) { - Width = style.FontMedium.First().MeasureText(Text).X + 2 * DefaultMargin; + Width = style.FontMedium.MeasureText(Text).X + 2 * DefaultMargin; } } } diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index 458138aea..7533dfb17 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -39,8 +39,8 @@ namespace FlaxEditor.GUI { Depth = -1; - if (Height < Style.Current.FontMedium.First().Height) - Height = Style.Current.FontMedium.First().Height + 4; + if (Height < Style.Current.FontMedium.MaxHeight) + Height = Style.Current.FontMedium.MaxHeight + 4; } /// diff --git a/Source/Editor/GUI/Table.cs b/Source/Editor/GUI/Table.cs index fed33a336..54656a6c2 100644 --- a/Source/Editor/GUI/Table.cs +++ b/Source/Editor/GUI/Table.cs @@ -130,7 +130,7 @@ namespace FlaxEditor.GUI Render2D.FillRectangle(rect, column.TitleBackgroundColor); var style = Style.Current; - var font = column.TitleFont ?? style.FontMedium.First(); + var font = column.TitleFont ?? style.FontMedium; Render2D.DrawText(font, column.Title, rect, column.TitleColor, TextAlignment.Center, TextAlignment.Center); if (columnIndex < _columns.Length - 1) diff --git a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs index bedb61a5e..791fb7133 100644 --- a/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs +++ b/Source/Editor/GUI/Timeline/GUI/PositionHandle.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; +using System.Linq; using FlaxEngine; using FlaxEngine.GUI; @@ -36,16 +37,16 @@ namespace FlaxEditor.GUI.Timeline.GUI string labelText; switch (_timeline.TimeShowMode) { - case Timeline.TimeShowModes.Frames: - labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture); - break; - case Timeline.TimeShowModes.Seconds: - labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture); - break; - case Timeline.TimeShowModes.Time: - labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g"); - break; - default: throw new ArgumentOutOfRangeException(); + case Timeline.TimeShowModes.Frames: + labelText = _timeline.CurrentFrame.ToString("###0", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Seconds: + labelText = _timeline.CurrentTime.ToString("###0.##'s'", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Time: + labelText = TimeSpan.FromSeconds(_timeline.CurrentTime).ToString("g"); + break; + default: throw new ArgumentOutOfRangeException(); } var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f); Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1); diff --git a/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs b/Source/Editor/GUI/Timeline/Tracks/MemberTrack.cs index 928129917..63787df2c 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.First().MeasureText(Title ?? Name).X; + var left = _xOffset + 16 + Style.Current.FontSmall.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 b74c7c19f..d21fd5689 100644 --- a/Source/Editor/GUI/ToolStripButton.cs +++ b/Source/Editor/GUI/ToolStripButton.cs @@ -151,8 +151,8 @@ namespace FlaxEditor.GUI if (hasSprite) width += iconSize; - if (!string.IsNullOrEmpty(_text) && style.FontMedium.First()) - width += style.FontMedium.First().MeasureText(_text).X + (hasSprite ? DefaultMargin : 0); + if (!string.IsNullOrEmpty(_text) && style.FontMedium) + width += style.FontMedium.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 f26929993..acb67ea8f 100644 --- a/Source/Editor/GUI/Tree/TreeNode.cs +++ b/Source/Editor/GUI/Tree/TreeNode.cs @@ -115,7 +115,7 @@ namespace FlaxEditor.GUI.Tree /// Gets or sets the font used to render text. /// [EditorDisplay("Style"), EditorOrder(2000)] - public FontReference TextFont { get; set; } + public MultiFontReference TextFont { get; set; } /// /// Gets or sets the color of the background when tree node is selected. @@ -318,7 +318,7 @@ namespace FlaxEditor.GUI.Tree BackgroundColorSelected = style.BackgroundSelected; BackgroundColorHighlighted = style.BackgroundHighlighted; BackgroundColorSelectedUnfocused = style.LightBackground; - TextFont = new FontReference(style.FontSmall.First()); + TextFont = new MultiFontReference(style.FontSmall); } /// @@ -573,7 +573,7 @@ namespace FlaxEditor.GUI.Tree { if (_textChanged) { - var font = TextFont.GetFont(); + var font = TextFont.GetMultiFont(); if (font) { _textWidth = font.MeasureText(_text).X; @@ -657,7 +657,7 @@ namespace FlaxEditor.GUI.Tree } // Draw text - Render2D.DrawText(TextFont.GetFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(TextFont.GetMultiFont(), _text, textRect, _cachedTextColor, TextAlignment.Near, TextAlignment.Center); // Draw drag and drop effect if (IsDragOver && _tree.DraggedOverNode == this) diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index c96a52fb1..e43751fd1 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -166,15 +166,13 @@ namespace FlaxEditor.Options /// Gets or sets the output log text font. /// [EditorDisplay("Output Log", "Text Font"), EditorOrder(320), Tooltip("The output log text font.")] - public FontReference OutputLogTextFont + public MultiFontReference OutputLogTextFont { get => _outputLogFont; set { - if (value == null) - _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); - else if (!value.Font) - _outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); + if (value == null || !value.Verify()) + _outputLogFont = new MultiFontReference(ConsoleFonts, 10); else _outputLogFont = value; } @@ -236,32 +234,31 @@ namespace FlaxEditor.Options [EditorDisplay("Cook & Run"), EditorOrder(500)] 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); + private static FontAsset[] DefaultFonts => + [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont), + FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; + + private static FontAsset[] ConsoleFonts => [FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont), + FlaxEngine.Content.LoadAsyncInternal(EditorAssets.CjkFont)]; + + private MultiFontReference _titleFont = new MultiFontReference(DefaultFonts, 18); + private MultiFontReference _largeFont = new MultiFontReference(DefaultFonts, 14); + private MultiFontReference _mediumFont = new MultiFontReference(DefaultFonts, 9); + private MultiFontReference _smallFont = new MultiFontReference(DefaultFonts, 9); + private MultiFontReference _outputLogFont = new MultiFontReference(ConsoleFonts, 10); - public FontReference CJKFont - { - get => new FontReference(_cjkFont, 9); - } /// /// Gets or sets the title font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(600), Tooltip("The title font for editor UI.")] - public FontReference TitleFont + public MultiFontReference TitleFont { get => _titleFont; set { - if (value == null) - _titleFont = new FontReference(DefaultFont, 18); - else if (!value.Font) - _titleFont.Font = DefaultFont; + if (value == null || !value.Verify()) + _titleFont = new MultiFontReference(DefaultFonts, 18); else _titleFont = value; } @@ -271,15 +268,13 @@ namespace FlaxEditor.Options /// Gets or sets the large font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(610), Tooltip("The large font for editor UI.")] - public FontReference LargeFont + public MultiFontReference LargeFont { get => _largeFont; set { - if (value == null) - _largeFont = new FontReference(DefaultFont, 14); - else if (!value.Font) - _largeFont.Font = DefaultFont; + if (value == null || !value.Verify()) + _largeFont = new MultiFontReference(DefaultFonts, 14); else _largeFont = value; } @@ -289,15 +284,13 @@ namespace FlaxEditor.Options /// Gets or sets the medium font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(620), Tooltip("The medium font for editor UI.")] - public FontReference MediumFont + public MultiFontReference MediumFont { get => _mediumFont; set { - if (value == null) - _mediumFont = new FontReference(DefaultFont, 9); - else if (!value.Font) - _mediumFont.Font = DefaultFont; + if (value == null || !value.Verify()) + _mediumFont = new MultiFontReference(DefaultFonts, 9); else _mediumFont = value; } @@ -307,15 +300,13 @@ namespace FlaxEditor.Options /// Gets or sets the small font for editor UI. /// [EditorDisplay("Fonts"), EditorOrder(630), Tooltip("The small font for editor UI.")] - public FontReference SmallFont + public MultiFontReference SmallFont { get => _smallFont; set { - if (value == null) - _smallFont = new FontReference(DefaultFont, 9); - else if (!value.Font) - _smallFont.Font = DefaultFont; + if (value == null || !value.Verify()) + _smallFont = new MultiFontReference(DefaultFonts, 9); else _smallFont = value; } diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 15059bd56..d138d7d0d 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -259,11 +259,10 @@ namespace FlaxEditor.Options }, // Fonts - FontTitle = options.Interface.TitleFont.GetFont(), - FontLarge = options.Interface.LargeFont.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(), + FontTitle = options.Interface.TitleFont.GetMultiFont(), + FontLarge = options.Interface.LargeFont.GetMultiFont(), + FontMedium = options.Interface.MediumFont.GetMultiFont(), + FontSmall = options.Interface.SmallFont.GetMultiFont(), // Icons ArrowDown = Editor.Icons.ArrowDown12, @@ -313,10 +312,10 @@ namespace FlaxEditor.Options ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f), // Fonts - FontTitle = options.Interface.TitleFont.GetFont(), - FontLarge = options.Interface.LargeFont.GetFont(), - FontMedium = new Font[] { options.Interface.MediumFont.GetFont(), options.Interface.CJKFont.GetFont() }, - FontSmall = new Font[] { options.Interface.SmallFont.GetFont(), options.Interface.CJKFont.GetFont() }, + FontTitle = options.Interface.TitleFont.GetMultiFont(), + FontLarge = options.Interface.LargeFont.GetMultiFont(), + FontMedium = options.Interface.MediumFont.GetMultiFont(), + FontSmall = options.Interface.SmallFont.GetMultiFont(), // Icons ArrowDown = Editor.Icons.ArrowDown12, diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs index 9a6fe4fd2..f64e46385 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.First().GetCharPosition(text, ranges[i].StartIndex); - var end = font.First().GetCharPosition(text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.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/Animation.TransitionEditor.cs b/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs index 84c2144b2..e1fdb0a42 100644 --- a/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs +++ b/Source/Editor/Surface/Archetypes/Animation.TransitionEditor.cs @@ -39,7 +39,7 @@ namespace FlaxEditor.Surface.Archetypes // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new FontReference(Style.Current.FontLarge), + Font = new MultiFontReference(Style.Current.FontLarge), Text = transition.SurfaceName, Parent = this }; diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index 38dd28e62..cca6856ae 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -101,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.First().MeasureText(_debugInfo); + _debugInfoSize = Style.Current.FontSmall.MeasureText(_debugInfo); } } diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 53950dad2..bc982a510 100644 --- a/Source/Editor/Surface/Archetypes/Function.cs +++ b/Source/Editor/Surface/Archetypes/Function.cs @@ -1407,7 +1407,7 @@ namespace FlaxEditor.Surface.Archetypes // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new FontReference(Style.Current.FontLarge), + Font = new MultiFontReference(Style.Current.FontLarge), Text = "Edit function signature", Parent = this }; diff --git a/Source/Editor/Surface/AttributesEditor.cs b/Source/Editor/Surface/AttributesEditor.cs index 81b11bb68..c9e32e23a 100644 --- a/Source/Editor/Surface/AttributesEditor.cs +++ b/Source/Editor/Surface/AttributesEditor.cs @@ -83,7 +83,7 @@ namespace FlaxEditor.Surface // Title var title = new Label(2, 2, width - 4, 23.0f) { - Font = new FontReference(Style.Current.FontLarge), + Font = new MultiFontReference(Style.Current.FontLarge), Text = "Edit attributes", Parent = this }; diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 930741807..0624d44b3 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -141,7 +141,7 @@ namespace FlaxEditor.Surface.ContextMenu }; // Title bar - var titleFontReference = new FontReference(Style.Current.FontLarge.Asset, 10); + var titleFontReference = new MultiFontReference(Style.Current.FontLarge); var titleLabel = new Label { Width = Width * 0.5f - 8f, diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 812bec5d2..207875a92 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.First().GetCharPosition(_archetype.Title, ranges[i].StartIndex); - var end = font.First().GetCharPosition(_archetype.Title, ranges[i].EndIndex); + var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); + var end = font.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.First().GetCharPosition(_archetype.Title, 0); - var end = font.First().GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.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.First().GetCharPosition(_archetype.Title, 0); - var end = font.First().GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.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.First().MeasureText(_archetype.Title).X; + var titleLength = style.FontSmall.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 611160611..2047bcd1a 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.First().MeasureText(Text).X, Y); + _defaultValueEditor.Location = new Float2(X + Width + 8 + Style.Current.FontSmall.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.First().MeasureText(Text).X, Y, 90, Height); + var bounds = new Rectangle(X + Width + 8 + Style.Current.FontSmall.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 82a9ab2bd..b6436e542 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -200,7 +200,7 @@ namespace FlaxEditor.Surface continue; if (child is InputBox inputBox) { - var boxWidth = boxLabelFont.First().MeasureText(inputBox.Text).X + 20; + var boxWidth = boxLabelFont.MeasureText(inputBox.Text).X + 20; if (inputBox.DefaultValueEditor != null) boxWidth += inputBox.DefaultValueEditor.Width + 4; leftWidth = Mathf.Max(leftWidth, boxWidth); @@ -208,7 +208,7 @@ namespace FlaxEditor.Surface } else if (child is OutputBox outputBox) { - rightWidth = Mathf.Max(rightWidth, boxLabelFont.First().MeasureText(outputBox.Text).X + 20); + rightWidth = Mathf.Max(rightWidth, boxLabelFont.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 e88972b5c..1badb8c0c 100644 --- a/Source/Editor/Tools/Foliage/FoliageTab.cs +++ b/Source/Editor/Tools/Foliage/FoliageTab.cs @@ -148,7 +148,7 @@ namespace FlaxEditor.Tools.Foliage Parent = _noFoliagePanel, Enabled = false }; - var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); + var textSize = Style.Current.FontMedium.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 0a43cd2d2..4ff85ca23 100644 --- a/Source/Editor/Tools/Terrain/CarveTab.cs +++ b/Source/Editor/Tools/Terrain/CarveTab.cs @@ -106,7 +106,7 @@ namespace FlaxEditor.Tools.Terrain Parent = _noTerrainPanel, Enabled = false }; - var textSize = Style.Current.FontMedium.First().MeasureText(buttonText); + var textSize = Style.Current.FontMedium.MeasureText(buttonText); if (_createTerrainButton.Width < textSize.X) { _createTerrainButton.LocalX -= (textSize.X - _createTerrainButton.Width) / 2; diff --git a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs index 252891d44..cba52283d 100644 --- a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs +++ b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs @@ -96,7 +96,7 @@ namespace FlaxEditor.Tools.Terrain AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = new Margin(0, 0, 0, 40), Parent = this, - Font = new FontReference(Style.Current.FontTitle) + Font = new MultiFontReference(Style.Current.FontTitle) }; var infoLabel = new Label { diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index f4ebfb51b..c49392d01 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.First().MeasureText(largestText); + var textSize = Style.Current.FontMedium.MeasureText(largestText); var xLocationForExtras = textSize.X + 5; - var cameraSpeedTextWidth = Style.Current.FontMedium.First().MeasureText("0.00").X; + var cameraSpeedTextWidth = Style.Current.FontMedium.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.First().MeasureText(largestText); + textSize = Style.Current.FontMedium.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 a58350ded..fe73048fd 100644 --- a/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs +++ b/Source/Editor/Viewport/Widgets/ViewportWidgetButton.cs @@ -163,8 +163,8 @@ namespace FlaxEditor.Viewport.Widgets { var style = Style.Current; - if (style != null && style.FontMedium.First()) - Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.First().MeasureText(_text).X, Icon.IsValid); + if (style != null && style.FontMedium) + Width = CalculateButtonWidth(_forcedTextWidth > 0.0f ? _forcedTextWidth : style.FontMedium.MeasureText(_text).X, Icon.IsValid); } } } diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs index 63ad44f29..1a81a9421 100644 --- a/Source/Editor/Windows/AboutDialog.cs +++ b/Source/Editor/Windows/AboutDialog.cs @@ -41,7 +41,7 @@ namespace FlaxEditor.Windows var nameLabel = new Label(icon.Right + 10, icon.Top, 200, 34) { Text = "Flax Engine", - Font = new FontReference(Style.Current.FontTitle), + Font = new MultiFontReference(Style.Current.FontTitle), HorizontalAlignment = TextAlignment.Near, VerticalAlignment = TextAlignment.Center, Parent = this @@ -54,7 +54,7 @@ namespace FlaxEditor.Windows Parent = this }; var buttonText = "Copy version info"; - var fontSize = Style.Current.FontMedium.First().MeasureText(buttonText); + var fontSize = Style.Current.FontMedium.MeasureText(buttonText); var copyVersionButton = new Button(Width - fontSize.X - 8, 6, fontSize.X + 4, 20) { Text = buttonText, diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index ff4135165..2e2e14d99 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets protected override void OnAssetLinked() { Asset.WaitForLoaded(); - _textPreview.Font = new FontReference(Asset.CreateFont(30)); + _textPreview.Font = new MultiFontReference([Asset], 30); _inputText.Text = string.Format("This is a sample text using font {0}.", Asset.FamilyName); var options = Asset.Options; _proxy.Set(ref options); diff --git a/Source/Editor/Windows/ContentWindow.Search.cs b/Source/Editor/Windows/ContentWindow.Search.cs index a1072d158..f29dc0bd6 100644 --- a/Source/Editor/Windows/ContentWindow.Search.cs +++ b/Source/Editor/Windows/ContentWindow.Search.cs @@ -57,7 +57,7 @@ namespace FlaxEditor.Windows var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); + Render2D.DrawText(Font.GetMultiFont(), "View", textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, textScale); Render2D.PopClip(); // Arrow diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 6526d7c8a..ac4fea5ba 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -470,9 +470,9 @@ namespace FlaxEditor.Windows var wasEmpty = _output.TextLength == 0; // Cache fonts - _output.DefaultStyle.Font.GetFont(); - _output.WarningStyle.Font.GetFont(); - _output.ErrorStyle.Font.GetFont(); + _output.DefaultStyle.Font.GetMultiFont(); + _output.WarningStyle.Font.GetMultiFont(); + _output.ErrorStyle.Font.GetMultiFont(); // Generate the output log Span entries = CollectionsMarshal.AsSpan(_entries); @@ -536,7 +536,7 @@ namespace FlaxEditor.Windows } var prevBlockBottom = _textBlocks.Count == 0 ? 0.0f : _textBlocks[_textBlocks.Count - 1].Bounds.Bottom; var entryText = _textBuffer.ToString(startIndex, endIndex - startIndex); - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) continue; var style = textBlock.Style; @@ -544,46 +544,52 @@ namespace FlaxEditor.Windows for (int j = 0; j < lines.Length; j++) { ref var line = ref lines[j]; - textBlock.Range.StartIndex = startIndex + line.FirstCharIndex; - textBlock.Range.EndIndex = startIndex + line.LastCharIndex + 1; - textBlock.Bounds = new Rectangle(new Float2(0.0f, prevBlockBottom), line.Size); - - if (textBlock.Range.Length > 0) + for (int k = 0; k < line.Blocks.Length; k++) { - // Parse compilation error/warning - var regexStart = line.FirstCharIndex; - if (j == 0) - regexStart += prefixLength; - var regexLength = line.LastCharIndex + 1 - regexStart; - if (regexLength > 0) + ref var block = ref line.Blocks[k]; + + textBlock.Range.StartIndex = startIndex + block.FirstCharIndex; + textBlock.Range.EndIndex = startIndex + block.LastCharIndex + 1; + textBlock.Bounds = new Rectangle(new Float2(block.Location.X, prevBlockBottom), block.Size); + + if (textBlock.Range.Length > 0) { - var match = _compileRegex.Match(entryText, regexStart, regexLength); - if (match.Success) + // Parse compilation error/warning + var regexStart = block.FirstCharIndex; + if (j == 0) + regexStart += prefixLength; + var regexLength = block.LastCharIndex + 1 - regexStart; + if (regexLength > 0) { - switch (match.Groups["level"].Value) + var match = _compileRegex.Match(entryText, regexStart, regexLength); + if (match.Success) { - case "error": - textBlock.Style = _output.ErrorStyle; - break; - case "warning": - textBlock.Style = _output.WarningStyle; - break; + switch (match.Groups["level"].Value) + { + case "error": + textBlock.Style = _output.ErrorStyle; + break; + case "warning": + textBlock.Style = _output.WarningStyle; + break; + } + textBlock.Tag = new TextBlockTag + { + Type = TextBlockTag.Types.CodeLocation, + Url = match.Groups["path"].Value, + Line = int.Parse(match.Groups["line"].Value), + }; } - textBlock.Tag = new TextBlockTag - { - Type = TextBlockTag.Types.CodeLocation, - Url = match.Groups["path"].Value, - Line = int.Parse(match.Groups["line"].Value), - }; + // TODO: parsing hyperlinks with link + // TODO: parsing file paths with link } - // TODO: parsing hyperlinks with link - // TODO: parsing file paths with link } + + _textBlocks.Add(textBlock); + textBlock.Style = style; } prevBlockBottom += line.Size.Y; - _textBlocks.Add(textBlock); - textBlock.Style = style; } } diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index 372505795..d6c14f4e4 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -14,6 +14,7 @@ using FlaxEditor.GUI.Tabs; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Json; +using FlaxEngine.Utilities; namespace FlaxEditor.Windows { @@ -78,7 +79,7 @@ namespace FlaxEditor.Windows HorizontalAlignment = TextAlignment.Near, AnchorPreset = AnchorPresets.HorizontalStretchTop, Text = desc.Name, - Font = new FontReference(Style.Current.FontLarge), + Font = new MultiFontReference(Style.Current.FontLarge), Parent = this, Bounds = new Rectangle(tmp1, margin, Width - tmp1 - margin, 28), }; @@ -119,8 +120,8 @@ namespace FlaxEditor.Windows url = desc.HomepageUrl; else if (!string.IsNullOrEmpty(desc.RepositoryUrl)) url = desc.RepositoryUrl; - versionLabel.Font.Font.WaitForLoaded(); - var font = versionLabel.Font.GetFont(); + versionLabel.Font.ForEach(x => x.Font.WaitForLoaded()); + var font = versionLabel.Font.GetMultiFont(); var authorWidth = font.MeasureText(desc.Author).X + 8; var authorLabel = new ClickableLabel { @@ -391,7 +392,7 @@ namespace FlaxEditor.Windows } Editor.Log("Plugin project has been cloned."); - + try { // Start git submodule clone diff --git a/Source/Editor/Windows/Profiler/Timeline.cs b/Source/Editor/Windows/Profiler/Timeline.cs index 88019b329..917647f23 100644 --- a/Source/Editor/Windows/Profiler/Timeline.cs +++ b/Source/Editor/Windows/Profiler/Timeline.cs @@ -85,8 +85,8 @@ namespace FlaxEditor.Windows.Profiler Render2D.FillRectangle(bounds, color); Render2D.DrawRectangle(bounds, color * 0.5f); - if (_nameLength < 0 && style.FontMedium.First()) - _nameLength = style.FontMedium.First().MeasureText(_name).X; + if (_nameLength < 0 && style.FontMedium) + _nameLength = style.FontMedium.MeasureText(_name).X; if (_nameLength < bounds.Width + 4) { diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs index e8fc1d56c..16b9165e1 100644 --- a/Source/Editor/Windows/ToolboxWindow.cs +++ b/Source/Editor/Windows/ToolboxWindow.cs @@ -273,8 +273,8 @@ namespace FlaxEditor.Windows var textRect = item.TextRect; for (int i = 0; i < ranges.Length; i++) { - var start = font.First().GetCharPosition(text, ranges[i].StartIndex); - var end = font.First().GetCharPosition(text, ranges[i].EndIndex); + var start = font.GetCharPosition(text, ranges[i].StartIndex); + var end = font.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 277ad8ddd..b33b10899 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -293,249 +293,6 @@ 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; - MultiFontLineCache tmpLine; - MultiFontSegmentCache tmpSegment; - FontCharacterEntry entry; - FontCharacterEntry previous; - int32 textLength = text.Length(); - float scale = layout.Scale / FontManager::FontScale; - float boundsWidth = layout.Bounds.GetWidth(); - float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; - - tmpSegment.Location = Float2::Zero; - tmpSegment.Height = 0; - tmpSegment.FirstCharIndex = 0; - tmpSegment.LastCharIndex = -1; - - tmpLine.Location = Float2::Zero; - tmpLine.Size = Float2::Zero; - tmpLine.Segments = Array(); - - if (textLength == 0) { - return; - } - - int32 lastWrapCharIndex = INVALID_INDEX; - float lastWrapCharX = 0; - bool lastMoveLine = false; - // The index of the font used by the current segment - int32 currentFontIndex = GetCharFontIndex(fonts, text[0], 0); - // The maximum font height of the current line - float maxHeight = 0; - float maxAscender = 0; - - // Process each character to split text into single lines - for (int32 currentIndex = 0; currentIndex < textLength;) - { - bool moveLine = false; - bool moveSegment = false; - float xAdvance = 0; - int32 nextCharIndex = currentIndex + 1; - - // Submit line and segment if text ends - if (nextCharIndex == textLength) { - moveLine = moveSegment = true; - } - - // 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) || (currentChar >= 0x3040 && currentChar <= 0x9FFF); - if (isWrapChar && currentIndex != 0) - { - lastWrapCharIndex = currentIndex; - lastWrapCharX = cursorX; - } - - int32 nextFontIndex = currentFontIndex; - // Check if it's a newline character - if (currentChar == '\n') - { - // Break line - moveLine = moveSegment = true; - tmpSegment.LastCharIndex++; - } - else - { - // Get character entry - if (nextCharIndex < textLength) { - nextFontIndex = GetCharFontIndex(fonts, text[nextCharIndex], currentFontIndex); - } - - // Get character entry - fonts[currentFontIndex]->GetCharacter(currentChar, entry); - maxHeight = Math::Max(maxHeight, static_cast(fonts[currentFontIndex]->GetHeight())); - maxAscender = Math::Max(maxAscender, static_cast(fonts[currentFontIndex]->GetAscender())); - - // Move segment if the font changes or text ends - if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) { - moveSegment = true; - } - - // Get kerning, only when the font hasn't changed - if (!isWhitespace && previous.IsValid && !moveSegment) - { - kerning = fonts[currentFontIndex]->GetKerning(previous.Character, entry.Character); - } - else - { - kerning = 0; - } - previous = entry; - 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; - tmpSegment.LastCharIndex++; - } - else if (layout.TextWrapping == TextWrapping::WrapWords) - { - if (lastWrapCharIndex != INVALID_INDEX) - { - // Skip moving twice for the same character - int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Segments.HasItems() ? outputLines.Last().Segments.Last().LastCharIndex : -10000; - if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) - { - currentIndex = nextCharIndex; - lastMoveLine = moveLine; - continue; - } - - // Move line - const Char wrapChar = text[lastWrapCharIndex]; - moveLine = true; - moveSegment = tmpSegment.FirstCharIndex < lastWrapCharIndex; - - cursorX = lastWrapCharX; - if (StringUtils::IsWhitespace(wrapChar)) - { - // Skip whitespaces - tmpSegment.LastCharIndex = lastWrapCharIndex - 1; - nextCharIndex = currentIndex = lastWrapCharIndex + 1; - } - else - { - tmpSegment.LastCharIndex = lastWrapCharIndex - 1; - nextCharIndex = currentIndex = lastWrapCharIndex; - } - } - } - else if (layout.TextWrapping == TextWrapping::WrapChars) - { - // Move line - moveLine = true; - moveSegment = tmpSegment.FirstCharIndex < currentChar; - nextCharIndex = currentIndex; - - // Skip moving twice for the same character - if (lastMoveLine) - break; - } - } - - if (moveSegment) { - // Add segment - tmpSegment.Height = baseLinesDistanceScale * fonts[currentFontIndex]->GetHeight(); - tmpSegment.LastCharIndex = Math::Max(tmpSegment.LastCharIndex, tmpSegment.FirstCharIndex); - tmpSegment.FontIndex = currentFontIndex; - tmpLine.Segments.Add(tmpSegment); - - // Reset segment - tmpSegment.Location.X = cursorX; - tmpSegment.FirstCharIndex = nextCharIndex; - tmpSegment.LastCharIndex = nextCharIndex - 1; - - currentFontIndex = nextFontIndex; - } - - // Check if move to another line - if (moveLine) - { - // Add line - tmpLine.Size.X = cursorX; - tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - tmpLine.MaxAscender = maxAscender; - outputLines.Add(tmpLine); - - // Reset line - tmpLine.Segments.Clear(); - tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; - cursorX = 0; - tmpSegment.Location.X = cursorX; - lastWrapCharIndex = INVALID_INDEX; - lastWrapCharX = 0; - previous.IsValid = false; - - // Reset max font height - maxHeight = 0; - maxAscender = 0; - } - - currentIndex = nextCharIndex; - lastMoveLine = moveLine; - } - - // Check if an additional line should be created - if (text[textLength - 1] == '\n') - { - // Add line - tmpLine.Size.X = cursorX; - tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; - 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++) - { - MultiFontLineCache& 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; - - // Align all segments to center in case they have different heights - for (int32 j = 0; j < line.Segments.Count(); j++) - { - MultiFontSegmentCache& segment = line.Segments[j]; - segment.Location.Y += (line.MaxAscender - fonts[segment.FontIndex]->GetAscender()) / 2; - } - } -} - Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout) { // Check if there is no need to do anything @@ -643,7 +400,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo ASSERT(lines.HasItems()); float scale = layout.Scale / FontManager::FontScale; float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; - Float2 rootOffset = layout.Bounds.Location + lines.First().Location; + Float2 rootOffset = layout.Bounds.Location; // Find line with that position FontCharacterEntry previous; @@ -682,10 +439,10 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo } // Position after last character in the last line - return rootOffset + Float2(lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); + return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, static_cast((lines.Count() - 1) * baseLinesDistance)); } -bool Font::ContainsChar(Char c) +bool Font::ContainsChar(Char c) const { return FT_Get_Char_Index(GetAsset()->GetFTFace(), c) > 0; } diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 0425f2bc5..453872582 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -8,7 +8,6 @@ #include "Engine/Content/AssetReference.h" #include "Engine/Scripting/ScriptingObject.h" #include "TextLayoutOptions.h" -#include "MultiFont.h" class FontAsset; struct FontTextureAtlasSlot; @@ -341,15 +340,6 @@ 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. /// @@ -406,17 +396,6 @@ 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. /// @@ -504,18 +483,6 @@ 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. /// @@ -557,28 +524,7 @@ public: /// /// The char to test. /// True if the font contains the glyph of the char, otherwise false. - API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c); - - /// - /// Gets the index of the font that should be used to render the char - /// - /// The font list. - /// The char. - /// Number to return if char cannot be found. - /// - API_FUNCTION() FORCE_INLINE static int32 GetCharFontIndex(const Array& fonts, Char c, int32 missing = -1) { - int32 fontIndex = 0; - while (fontIndex < fonts.Count() && !fonts[fontIndex]->ContainsChar(c)) - { - fontIndex++; - } - - if (fontIndex == fonts.Count()) { - return missing; - } - - return fontIndex; - } + API_FUNCTION() FORCE_INLINE bool ContainsChar(Char c) const; /// /// Flushes the size of the face with the Free Type library backend. diff --git a/Source/Engine/Render2D/MultiFont.cpp b/Source/Engine/Render2D/MultiFont.cpp index 9844c12e0..4a510a9f8 100644 --- a/Source/Engine/Render2D/MultiFont.cpp +++ b/Source/Engine/Render2D/MultiFont.cpp @@ -1 +1,431 @@ #include "MultiFont.h" +#include "FontManager.h" +#include "Engine/Core/Math/Math.h" + +MultiFont::MultiFont(const Array& fonts) + : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)), + _fonts(fonts) +{ + +} + +void MultiFont::ProcessText(const StringView& text, Array& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout) +{ + float cursorX = 0; + int32 kerning; + MultiFontLineCache tmpLine; + MultiFontBlockCache tmpBlock; + FontCharacterEntry entry; + FontCharacterEntry previous; + int32 textLength = text.Length(); + float scale = layout.Scale / FontManager::FontScale; + float boundsWidth = layout.Bounds.GetWidth(); + float baseLinesDistanceScale = layout.BaseLinesGapScale * scale; + + tmpBlock.Location = Float2::Zero; + tmpBlock.Size = Float2::Zero; + tmpBlock.FirstCharIndex = 0; + tmpBlock.LastCharIndex = -1; + + tmpLine.Location = Float2::Zero; + tmpLine.Size = Float2::Zero; + tmpLine.Blocks = Array(); + + if (textLength == 0) { + return; + } + + int32 lastWrapCharIndex = INVALID_INDEX; + float lastWrapCharX = 0; + bool lastMoveLine = false; + // The index of the font used by the current block + int32 currentFontIndex = GetCharFontIndex(text[0], 0); + // The maximum font height of the current line + float maxHeight = 0; + float maxAscender = 0; + float lastCursorX = 0; + + // Process each character to split text into single lines + for (int32 currentIndex = 0; currentIndex < textLength;) + { + bool moveLine = false; + bool moveBlock = false; + float xAdvance = 0; + int32 nextCharIndex = currentIndex + 1; + + // Submit line and block if text ends + if (nextCharIndex == textLength) { + moveLine = moveBlock = true; + } + + // 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) || (currentChar >= 0x3040 && currentChar <= 0x9FFF); + if (isWrapChar && currentIndex != 0) + { + lastWrapCharIndex = currentIndex; + lastWrapCharX = cursorX; + } + + int32 nextFontIndex = currentFontIndex; + // Check if it's a newline character + if (currentChar == '\n') + { + // Break line + moveLine = moveBlock = true; + tmpBlock.LastCharIndex++; + } + else + { + // Get character entry + if (nextCharIndex < textLength) { + nextFontIndex = GetCharFontIndex(text[nextCharIndex], currentFontIndex); + } + + // Get character entry + _fonts[currentFontIndex]->GetCharacter(currentChar, entry); + maxHeight = Math::Max(maxHeight, static_cast(_fonts[currentFontIndex]->GetHeight())); + maxAscender = Math::Max(maxAscender, static_cast(_fonts[currentFontIndex]->GetAscender())); + + // Move block if the font changes or text ends + if (nextFontIndex != currentFontIndex || nextCharIndex == textLength) { + moveBlock = true; + } + + // Get kerning, only when the font hasn't changed + if (!isWhitespace && previous.IsValid && !moveBlock) + { + kerning = _fonts[currentFontIndex]->GetKerning(previous.Character, entry.Character); + } + else + { + kerning = 0; + } + previous = entry; + 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; + tmpBlock.LastCharIndex++; + } + else if (layout.TextWrapping == TextWrapping::WrapWords) + { + if (lastWrapCharIndex != INVALID_INDEX) + { + // Skip moving twice for the same character + int32 lastLineLastCharIndex = outputLines.HasItems() && outputLines.Last().Blocks.HasItems() ? outputLines.Last().Blocks.Last().LastCharIndex : -10000; + if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) + { + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + continue; + } + + // Move line + const Char wrapChar = text[lastWrapCharIndex]; + moveLine = true; + moveBlock = tmpBlock.FirstCharIndex < lastWrapCharIndex; + + cursorX = lastWrapCharX; + if (StringUtils::IsWhitespace(wrapChar)) + { + // Skip whitespaces + tmpBlock.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex + 1; + } + else + { + tmpBlock.LastCharIndex = lastWrapCharIndex - 1; + nextCharIndex = currentIndex = lastWrapCharIndex; + } + } + } + else if (layout.TextWrapping == TextWrapping::WrapChars) + { + // Move line + moveLine = true; + moveBlock = tmpBlock.FirstCharIndex < currentChar; + nextCharIndex = currentIndex; + + // Skip moving twice for the same character + if (lastMoveLine) + break; + } + } + + if (moveBlock) { + // Add block + tmpBlock.Size.X = lastCursorX - cursorX; + tmpBlock.Size.Y = baseLinesDistanceScale * _fonts[currentFontIndex]->GetHeight(); + tmpBlock.LastCharIndex = Math::Max(tmpBlock.LastCharIndex, tmpBlock.FirstCharIndex); + tmpBlock.FontIndex = currentFontIndex; + tmpLine.Blocks.Add(tmpBlock); + + // Reset block + tmpBlock.Location.X = cursorX; + tmpBlock.FirstCharIndex = nextCharIndex; + tmpBlock.LastCharIndex = nextCharIndex - 1; + + currentFontIndex = nextFontIndex; + lastCursorX = cursorX; + } + + // Check if move to another line + if (moveLine) + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + tmpLine.MaxAscender = maxAscender; + outputLines.Add(tmpLine); + + // Reset line + tmpLine.Blocks.Clear(); + tmpLine.Location.Y += baseLinesDistanceScale * maxHeight; + cursorX = 0; + tmpBlock.Location.X = cursorX; + lastWrapCharIndex = INVALID_INDEX; + lastWrapCharX = 0; + previous.IsValid = false; + + // Reset max font height + maxHeight = 0; + maxAscender = 0; + lastCursorX = 0; + } + + currentIndex = nextCharIndex; + lastMoveLine = moveLine; + } + + // Check if an additional line should be created + if (text[textLength - 1] == '\n') + { + // Add line + tmpLine.Size.X = cursorX; + tmpLine.Size.Y = baseLinesDistanceScale * maxHeight; + 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++) + { + MultiFontLineCache& 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; + + // Align all blocks to center in case they have different heights + for (int32 j = 0; j < line.Blocks.Count(); j++) + { + MultiFontBlockCache& block = line.Blocks[j]; + block.Location.Y += (line.MaxAscender - _fonts[block.FontIndex]->GetAscender()) / 2; + } + } +} + +Float2 MultiFont::GetCharPosition(const StringView& text, int32 index, const TextLayoutOptions& layout) +{ + // Check if there is no need to do anything + if (text.IsEmpty()) + return layout.Bounds.Location; + + // Process text + Array lines; + ProcessText(text, lines, layout); + ASSERT(lines.HasItems()); + float scale = layout.Scale / FontManager::FontScale; + float baseLinesDistance = layout.BaseLinesGapScale * scale; + Float2 rootOffset = layout.Bounds.Location; + + // Find line with that position + FontCharacterEntry previous; + FontCharacterEntry entry; + for (int32 lineIndex = 0; lineIndex < lines.Count(); lineIndex++) + { + const MultiFontLineCache& line = lines[lineIndex]; + for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) + { + const MultiFontBlockCache& block = line.Blocks[blockIndex]; + // Check if desire position is somewhere inside characters in line range + if (Math::IsInRange(index, block.FirstCharIndex, block.LastCharIndex)) + { + float x = line.Location.X + block.Location.X; + float y = line.Location.Y + block.Location.Y; + + // Check all characters in the line + for (int32 currentIndex = block.FirstCharIndex; currentIndex < index; currentIndex++) + { + // Cache current character + const Char currentChar = text[currentIndex]; + _fonts[block.FontIndex]->GetCharacter(currentChar, entry); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Apply kerning + if (!isWhitespace && previous.IsValid) + { + x += _fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); + } + previous = entry; + + // Move + x += entry.AdvanceX * scale; + } + + // Upper left corner of the character + return rootOffset + Float2(x, y); + } + } + } + + // Position after last character in the last line + return rootOffset + Float2(lines.Last().Location.X + lines.Last().Size.X, lines.Last().Location.Y); +} + +int32 MultiFont::HitTestText(const StringView& text, const Float2& location, const TextLayoutOptions& layout) +{ + // Check if there is no need to do anything + if (text.Length() <= 0) + return 0; + + // Process text + Array lines; + ProcessText(text, lines, layout); + ASSERT(lines.HasItems()); + float scale = layout.Scale / FontManager::FontScale; + + // Offset position to match lines origin space + Float2 rootOffset = layout.Bounds.Location + lines.First().Location; + Float2 testPoint = location - rootOffset; + + // Get block which may intersect with the position (it's possible because lines have fixed height) + int32 lineIndex = 0; + while (lineIndex < lines.Count()) + { + if (lines[lineIndex].Location.Y + lines[lineIndex].Size.Y >= location.Y) { + break; + } + + lineIndex++; + } + lineIndex = Math::Clamp(lineIndex, 0, lines.Count() - 1); + const MultiFontLineCache& line = lines[lineIndex]; + + int32 blockIndex = 0; + while (blockIndex < line.Blocks.Count() - 1) + { + if (line.Location.X + line.Blocks[blockIndex + 1].Location.X >= location.X) { + break; + } + + blockIndex++; + } + const MultiFontBlockCache& block = line.Blocks[blockIndex]; + float x = line.Location.X; + + // Check all characters in the line to find hit point + FontCharacterEntry previous; + FontCharacterEntry entry; + int32 smallestIndex = INVALID_INDEX; + float dst, smallestDst = MAX_float; + for (int32 currentIndex = block.FirstCharIndex; currentIndex <= block.LastCharIndex; currentIndex++) + { + // Cache current character + const Char currentChar = text[currentIndex]; + + _fonts[block.FontIndex]->GetCharacter(currentChar, entry); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); + + // Apply kerning + if (!isWhitespace && previous.IsValid) + { + x += _fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); + } + previous = entry; + + // Test + dst = Math::Abs(testPoint.X - x); + if (dst < smallestDst) + { + // Found closer character + smallestIndex = currentIndex; + smallestDst = dst; + } + else if (dst > smallestDst) + { + // Current char is worse so return the best result + return smallestIndex; + } + + // Move + x += entry.AdvanceX * scale; + } + + // Test line end edge + dst = Math::Abs(testPoint.X - x); + if (dst < smallestDst) + { + // Pointer is behind the last character in the line + smallestIndex = block.LastCharIndex; + + // Fix for last line + if (lineIndex == lines.Count() - 1) + smallestIndex++; + } + + return smallestIndex; +} + +Float2 MultiFont::MeasureText(const StringView& text, const TextLayoutOptions& layout) +{ + // Check if there is no need to do anything + if (text.IsEmpty()) + return Float2::Zero; + + // Process text + Array lines; + ProcessText(text, lines, layout); + + // Calculate bounds + Float2 max = Float2::Zero; + for (int32 i = 0; i < lines.Count(); i++) + { + const MultiFontLineCache& line = lines[i]; + max = Float2::Max(max, line.Location + line.Size); + } + + return max; +} + diff --git a/Source/Engine/Render2D/MultiFont.h b/Source/Engine/Render2D/MultiFont.h index b1da832d5..870b8e668 100644 --- a/Source/Engine/Render2D/MultiFont.h +++ b/Source/Engine/Render2D/MultiFont.h @@ -3,23 +3,28 @@ #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Font.h" +#include "FontAsset.h" + +struct TextRange; +class Font; +class FontAsset; /// -/// The font segment info generated during text processing. +/// The font block info generated during text processing. /// -API_STRUCT(NoDefault) struct MultiFontSegmentCache +API_STRUCT(NoDefault) struct MultiFontBlockCache { - DECLARE_SCRIPTING_TYPE_MINIMAL(MultiFontSegmentCache); + DECLARE_SCRIPTING_TYPE_MINIMAL(MultiFontBlockCache); /// - /// The root position of the segment (upper left corner), relative to line. + /// The root position of the block (upper left corner), relative to line. /// API_FIELD() Float2 Location; /// - /// The height of the current segment + /// The height of the current block /// - API_FIELD() float Height; + API_FIELD() Float2 Size; /// /// The first character index (from the input text). @@ -38,13 +43,13 @@ API_STRUCT(NoDefault) struct MultiFontSegmentCache }; template<> -struct TIsPODType +struct TIsPODType { enum { Value = true }; }; /// -/// Line of font segments info generated during text processing. +/// Line of font blocks info generated during text processing. /// API_STRUCT(NoDefault) struct MultiFontLineCache { @@ -61,17 +66,288 @@ API_STRUCT(NoDefault) struct MultiFontLineCache API_FIELD() Float2 Size; /// - /// The maximum ascendent of the line. + /// The maximum ascender of the line. /// API_FIELD() float MaxAscender; /// /// The index of the font to render with /// - API_FIELD() Array Segments; + API_FIELD() Array Blocks; }; API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API MultiFont : public ManagedScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(MultiFont); +private: + Array _fonts; + +public: + MultiFont(const Array& fonts); + + API_FUNCTION() FORCE_INLINE static MultiFont* Create(const Array& fonts) { + return New(fonts); + } + + API_FUNCTION() FORCE_INLINE static MultiFont* Create(const Array& fontAssets, float size) { + Array fonts; + fonts.Resize(fontAssets.Count()); + for (int32 i = 0; i < fontAssets.Count(); i++) + { + fonts[i] = fontAssets[i]->CreateFont(size); + } + + return New(fonts); + } + + API_PROPERTY() FORCE_INLINE Array& GetFonts() { + return _fonts; + } + + API_PROPERTY() FORCE_INLINE void SetFonts(const Array& val) { + _fonts = val; + } + + API_PROPERTY() FORCE_INLINE int32 GetMaxHeight() { + int32 maxHeight = 0; + for (int32 i = 0; i < _fonts.Count(); i++) + { + if (_fonts[i]) { + maxHeight = Math::Max(maxHeight, _fonts[i]->GetHeight()); + } + } + + return maxHeight; + } + + API_PROPERTY() FORCE_INLINE int32 GetMaxAscender() { + int32 maxAsc = 0; + for (int32 i = 0; i < _fonts.Count(); i++) + { + if (_fonts[i]) { + maxAsc = Math::Max(maxAsc, _fonts[i]->GetAscender()); + } + } + + return maxAsc; + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The layout properties. + /// 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 input text. + /// The layout properties. + /// The output lines list. + API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout) + { + Array lines; + ProcessText(text, lines, layout); + return lines; + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The input text range (substring range of the input text parameter). + /// The layout properties. + /// The output lines list. + API_FUNCTION() Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + { + Array lines; + ProcessText(textRange.Substring(text), lines, layout); + return lines; + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The output lines list. + API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text) + { + return ProcessText(text, TextLayoutOptions()); + } + + /// + /// Processes text to get cached lines for rendering. + /// + /// The input text. + /// The input text range (substring range of the input text parameter). + /// The output lines list. + API_FUNCTION() FORCE_INLINE Array ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) + { + return ProcessText(textRange.Substring(text), TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text. + /// + /// The input text to test. + /// The layout properties. + /// 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 input text to test. + /// The input text range (substring range of the input text parameter). + /// The layout properties. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return MeasureText(textRange.Substring(text), layout); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text + /// . + /// The input text to test. + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text) + { + return MeasureText(text, TextLayoutOptions()); + } + + /// + /// Measures minimum size of the rectangle that will be needed to draw given text + /// . + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The minimum size for that text and fot to render properly. + API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange) + { + return MeasureText(textRange.Substring(text), TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return HitTestText(textRange.Substring(text), location, layout); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input location to test. + /// The text layout properties. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() int32 HitTestText(const StringView& text, const Float2& location, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, const Float2& location) + { + return HitTestText(text, location, TextLayoutOptions()); + } + + /// + /// Calculates hit character index at given location. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The input location to test. + /// The selected character position index (can be equal to text length if location is outside of the layout rectangle). + API_FUNCTION() FORCE_INLINE int32 HitTestText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Float2& location) + { + return HitTestText(textRange.Substring(text), location, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index. + /// + /// 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() Float2 GetCharPosition(const StringView& text, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout); + + /// + /// Calculates character position for given text and character index. + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// 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() Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index, API_PARAM(Ref) const TextLayoutOptions& layout) + { + return GetCharPosition(textRange.Substring(text), index, layout); + } + + /// + /// Calculates character position for given text and character index + /// + /// The input text to test. + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, int32 index) + { + return GetCharPosition(text, index, TextLayoutOptions()); + } + + /// + /// Calculates character position for given text and character index + /// + /// The input text to test. + /// The input text range (substring range of the input text parameter). + /// The text position to get coordinates of. + /// The character position (upper left corner which can be used for a caret position). + API_FUNCTION() FORCE_INLINE Float2 GetCharPosition(const StringView& text, API_PARAM(Ref) const TextRange& textRange, int32 index) + { + return GetCharPosition(textRange.Substring(text), index, TextLayoutOptions()); + } + + /// + /// Gets the index of the font that should be used to render the char + /// + /// The font list. + /// The char. + /// Number to return if char cannot be found. + /// + API_FUNCTION() FORCE_INLINE int32 GetCharFontIndex(Char c, int32 missing = -1) { + int32 fontIndex = 0; + while (fontIndex < _fonts.Count() && _fonts[fontIndex] && !_fonts[fontIndex]->ContainsChar(c)) + { + fontIndex++; + } + + if (fontIndex == _fonts.Count()) { + return missing; + } + + return fontIndex; + } + + API_FUNCTION() FORCE_INLINE bool Verify() { + for (int32 i = 0; i < _fonts.Count(); i++) + { + if (!_fonts[i]) { + return false; + } + } + + return true; + } }; diff --git a/Source/Engine/Render2D/MultiFontReference.cs b/Source/Engine/Render2D/MultiFontReference.cs new file mode 100644 index 000000000..dd3f4d179 --- /dev/null +++ b/Source/Engine/Render2D/MultiFontReference.cs @@ -0,0 +1,74 @@ + + +using System.Collections.Generic; +using System.Linq; + +namespace FlaxEngine +{ + /// + /// Reference to multiple font references + /// + public class MultiFontReference : List + { + public MultiFontReference() + { + _cachedFont = null; + } + + public MultiFontReference(IEnumerable other) + { + AddRange(other); + _cachedFont = null; + } + + public MultiFontReference(MultiFontReference other) + { + AddRange(other); + _cachedFont = other._cachedFont; + } + + public MultiFontReference(MultiFontReference other, float size) + { + AddRange(other.Select(x => new FontReference(x) { Size = size })); + _cachedFont = null; + } + + public MultiFontReference(MultiFont other) + { + AddRange(other.Fonts.Select(x => new FontReference(x))); + _cachedFont = other; + } + + public MultiFontReference(FontAsset[] assets, float size) + { + AddRange(assets.Select(x => new FontReference(x, size))); + _cachedFont = null; + } + + [EditorOrder(0), Tooltip("The font asset to use as characters source.")] + public MultiFont GetMultiFont() + { + if (_cachedFont) + return _cachedFont; + var fontList = this.Where(x => x.Font).Select(x => x.GetFont()).ToArray(); + _cachedFont = MultiFont.Create(fontList); + return _cachedFont; + } + + public bool Verify() + { + foreach (var i in this) + { + if (!i.Font) + { + return false; + } + } + + return true; + } + + [NoSerialize] + private MultiFont _cachedFont; + } +} diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 8a39d29be..c68c2445d 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -1370,10 +1370,11 @@ 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) +void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; + const Array& fonts = multiFont->GetFonts(); // Check if there is no need to do anything if (fonts.IsEmpty() || text.Length() < 0) return; @@ -1408,7 +1409,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) { if (text[currentIndex] != '\n') { - int32 fontIndex = Font::GetCharFontIndex(fonts, text[currentIndex], 0); + int32 fontIndex = multiFont->GetCharFontIndex(text[currentIndex], 0); maxAscenders[lineIndex] = Math::Max(maxAscenders[lineIndex], static_cast(fonts[fontIndex]->GetAscender())); } else { @@ -1418,12 +1419,12 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const } lineIndex = 0; - // The following code cut the text into segments, according to the font used to render + // The following code cut the text into blocks, according to the font used to render Float2 pointer = location; - // The starting index of the current segment + // The starting index of the current block int32 startIndex = 0; - // The index of the font used by the current segment - int32 currentFontIndex = Font::GetCharFontIndex(fonts, text[0], 0); + // The index of the font used by the current block + int32 currentFontIndex = multiFont->GetCharFontIndex(text[0], 0); // The maximum font height of the current line float maxHeight = 0; for (int32 currentIndex = 0; currentIndex < text.Length(); currentIndex++) @@ -1431,13 +1432,13 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const // Cache current character const Char currentChar = text[currentIndex]; int32 nextCharIndex = currentIndex + 1; - bool moveSegment = false; + bool moveBlock = false; bool moveLine = false; int32 nextFontIndex = currentFontIndex; - // Submit segment if text ends + // Submit block if text ends if (nextCharIndex == text.Length()) { - moveSegment = true; + moveBlock = true; } // Check if it isn't a newline character @@ -1445,21 +1446,21 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const { // Get character entry if (nextCharIndex < text.Length()) { - nextFontIndex = Font::GetCharFontIndex(fonts, text[nextCharIndex], currentFontIndex); + nextFontIndex = multiFont->GetCharFontIndex(text[nextCharIndex], currentFontIndex); } if (nextFontIndex != currentFontIndex) { - moveSegment = true; + moveBlock = true; } } else { // Move - moveLine = moveSegment = true; + moveLine = moveBlock = true; } - if (moveSegment) { - // Render the pending segment before beginning the new segment + if (moveBlock) { + // Render the pending block before beginning the new block auto fontHeight = fonts[currentFontIndex]->GetHeight(); maxHeight = Math::Max(maxHeight, static_cast(fontHeight)); auto fontDescender = fonts[currentFontIndex]->GetDescender(); @@ -1533,22 +1534,23 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const lineIndex++; } - // Start new segment + // Start new block startIndex = nextCharIndex; currentFontIndex = nextFontIndex; } } } -void Render2D::DrawText(const Array& fonts, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) +void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial) { - DrawText(fonts, textRange.Substring(text), color, location, customMaterial); + DrawText(multiFont, textRange.Substring(text), color, location, customMaterial); } -void Render2D::DrawText(const Array& fonts, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { RENDER2D_CHECK_RENDERING_STATE; + const Array& fonts = multiFont->GetFonts(); // Check if there is no need to do anything if (fonts.IsEmpty() || text.IsEmpty() || layout.Scale <= ZeroTolerance) return; @@ -1563,7 +1565,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const // Process text to get lines MultiFontLines.Clear(); - Font::ProcessText(fonts, text, MultiFontLines, layout); + multiFont->ProcessText(text, MultiFontLines, layout); // Render all lines FontCharacterEntry entry; @@ -1582,14 +1584,14 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const for (int32 lineIndex = 0; lineIndex < MultiFontLines.Count(); lineIndex++) { const MultiFontLineCache& line = MultiFontLines[lineIndex]; - for (int32 segmentIndex = 0; segmentIndex < line.Segments.Count(); segmentIndex++) + for (int32 blockIndex = 0; blockIndex < line.Blocks.Count(); blockIndex++) { - const MultiFontSegmentCache& segment = MultiFontLines[lineIndex].Segments[segmentIndex]; - auto fontHeight = fonts[segment.FontIndex]->GetHeight(); - auto fontDescender = fonts[segment.FontIndex]->GetDescender(); - Float2 pointer = line.Location + segment.Location; + const MultiFontBlockCache& block = MultiFontLines[lineIndex].Blocks[blockIndex]; + auto fontHeight = fonts[block.FontIndex]->GetHeight(); + auto fontDescender = fonts[block.FontIndex]->GetDescender(); + Float2 pointer = line.Location + block.Location; - for (int32 charIndex = segment.FirstCharIndex; charIndex <= segment.LastCharIndex; charIndex++) + for (int32 charIndex = block.FirstCharIndex; charIndex <= block.LastCharIndex; charIndex++) { Char c = text[charIndex]; if (c == '\n') @@ -1598,7 +1600,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const } // Get character entry - fonts[segment.FontIndex]->GetCharacter(c, entry); + fonts[block.FontIndex]->GetCharacter(c, 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) @@ -1623,7 +1625,7 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const const bool isWhitespace = StringUtils::IsWhitespace(c); if (!isWhitespace && previous.IsValid) { - kerning = fonts[segment.FontIndex]->GetKerning(previous.Character, entry.Character); + kerning = fonts[block.FontIndex]->GetKerning(previous.Character, entry.Character); } else { @@ -1659,9 +1661,9 @@ void Render2D::DrawText(const Array& fonts, const StringView& text, const } } -void Render2D::DrawText(const Array& fonts, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) +void Render2D::DrawText(MultiFont* multiFont, const StringView& text, const TextRange& textRange, const Color& color, const TextLayoutOptions& layout, MaterialBase* customMaterial) { - DrawText(fonts, textRange.Substring(text), color, layout, customMaterial); + DrawText(multiFont, textRange.Substring(text), color, layout, customMaterial); } FORCE_INLINE bool NeedAlphaWithTint(const Color& color) @@ -2165,22 +2167,22 @@ void Render2D::DrawBezier(const Float2& p1, const Float2& p2, const Float2& p3, { RENDER2D_CHECK_RENDERING_STATE; - // Find amount of segments to use + // Find amount of blocks to use const Float2 d1 = p2 - p1; const Float2 d2 = p3 - p2; const Float2 d3 = p4 - p3; const float len = d1.Length() + d2.Length() + d3.Length(); - const int32 segmentCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); - const float segmentCountInv = 1.0f / segmentCount; + const int32 blockCount = Math::Clamp(Math::CeilToInt(len * 0.05f), 1, 100); + const float blockCountInv = 1.0f / blockCount; - // Draw segmented curve + // Draw blocked curve Float2 p; AnimationUtils::Bezier(p1, p2, p3, p4, 0, p); Lines2.Clear(); Lines2.Add(p); - for (int32 i = 1; i <= segmentCount; i++) + for (int32 i = 1; i <= blockCount; i++) { - const float t = i * segmentCountInv; + const float t = i * blockCountInv; AnimationUtils::Bezier(p1, p2, p3, p4, t, p); Lines2.Add(p); } diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index a73556f7b..cad8380ab 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -164,7 +164,7 @@ namespace FlaxEngine /// 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) + public static void DrawText(MultiFont 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 { @@ -175,6 +175,8 @@ namespace FlaxEngine Scale = scale, BaseLinesGapScale = baseLinesGapScale, }; + + DrawText(fonts, text, color, ref layout); } @@ -191,7 +193,7 @@ namespace FlaxEngine /// 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) + public static void DrawText(MultiFont 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 { @@ -202,6 +204,7 @@ namespace FlaxEngine Scale = scale, BaseLinesGapScale = baseLinesGapScale, }; + DrawText(fonts, text, color, ref layout, customMaterial); } diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 0ec88edc0..5050171b2 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -15,6 +15,7 @@ struct Matrix3x3; struct Viewport; struct TextRange; class Font; +class MultiFont; class GPUPipelineState; class GPUTexture; class GPUTextureView; @@ -224,7 +225,7 @@ public: /// 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); + API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -234,7 +235,7 @@ public: /// 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); + API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Float2& location, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -245,7 +246,7 @@ public: /// 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); + API_FUNCTION() static void DrawText(MultiFont* multiFont, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr); /// /// Draws a text with formatting. @@ -256,7 +257,7 @@ public: /// 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); + API_FUNCTION() static void DrawText(MultiFont* multiFont, 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 8e71c9f31..bc800f88a 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -294,15 +294,12 @@ namespace FlaxEngine style.DragWindow = style.BackgroundSelected * 0.7f; // 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 = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) }; - style.FontSmall = new Font[] { defaultFont.CreateFont(9), cjkFont.CreateFont(9) }; - } + FontAsset[] defaultFont = [Content.LoadAsyncInternal("Editor/Fonts/Roboto-Regular"), Content.LoadAsyncInternal("Editor/Fonts/NotoSansSC-Regular")]; + + style.FontTitle = new MultiFontReference(defaultFont, 18).GetMultiFont(); + style.FontLarge = new MultiFontReference(defaultFont, 14).GetMultiFont(); + style.FontMedium = new MultiFontReference(defaultFont, 9).GetMultiFont(); + style.FontSmall = new MultiFontReference(defaultFont, 9).GetMultiFont(); Style.Current = style; } diff --git a/Source/Engine/UI/GUI/Common/Button.cs b/Source/Engine/UI/GUI/Common/Button.cs index 1b965b8d1..f21a29191 100644 --- a/Source/Engine/UI/GUI/Common/Button.cs +++ b/Source/Engine/UI/GUI/Common/Button.cs @@ -23,7 +23,7 @@ namespace FlaxEngine.GUI /// /// The font. /// - protected FontReference _font; + protected MultiFontReference _font; /// /// The text. @@ -44,7 +44,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to draw button text. /// [EditorDisplay("Text Style"), EditorOrder(2022), ExpandGroups] - public FontReference Font + public MultiFontReference Font { get => _font; set => _font = value; @@ -156,7 +156,7 @@ namespace FlaxEngine.GUI var style = Style.Current; if (style != null) { - _font = new FontReference(style.FontMedium.First()); + _font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BorderColor = style.BorderNormal; @@ -262,7 +262,7 @@ namespace FlaxEngine.GUI Render2D.DrawRectangle(clientRect, borderColor, BorderThickness); // Draw text - Render2D.DrawText(_font?.GetFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); + Render2D.DrawText(_font?.GetMultiFont(), TextMaterial, _text, clientRect, textColor, TextAlignment.Center, TextAlignment.Center); } /// diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 21ca9cbea..2681aac1b 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -278,7 +278,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to draw text. /// [EditorDisplay("Text Style"), EditorOrder(2021)] - public FontReference Font { get; set; } + public MultiFontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -359,7 +359,7 @@ namespace FlaxEngine.GUI : base(0, 0, 120, 18.0f) { var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; @@ -674,7 +674,7 @@ namespace FlaxEngine.GUI var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; - Render2D.DrawText(Font.GetFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(Font.GetMultiFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } diff --git a/Source/Engine/UI/GUI/Common/Label.cs b/Source/Engine/UI/GUI/Common/Label.cs index 5fa8ad21d..ccc1cc190 100644 --- a/Source/Engine/UI/GUI/Common/Label.cs +++ b/Source/Engine/UI/GUI/Common/Label.cs @@ -26,7 +26,7 @@ namespace FlaxEngine.GUI /// /// The font. /// - protected FontReference _font; + protected MultiFontReference _font; /// /// Gets or sets the text. @@ -86,7 +86,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font. /// [EditorDisplay("Text Style"), EditorOrder(2024)] - public FontReference Font + public MultiFontReference Font { get => _font; set @@ -192,7 +192,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -203,7 +203,7 @@ namespace FlaxEngine.GUI { AutoFocus = false; var style = Style.Current; - Font = new FontReference(style.FontMedium.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; TextColorHighlighted = style.Foreground; } @@ -235,7 +235,7 @@ namespace FlaxEngine.GUI } } - Render2D.DrawText([_font.GetFont(), Style.Current.FontCJK], Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); + Render2D.DrawText(_font.GetMultiFont(), Material, _text, rect, color, hAlignment, wAlignment, Wrapping, BaseLinesGapScale, scale); if (ClipText) Render2D.PopClip(); @@ -246,7 +246,7 @@ namespace FlaxEngine.GUI { if (_autoWidth || _autoHeight || _autoFitText) { - var font = _font.GetFont(); + var font = _font.GetMultiFont(); if (font) { // Calculate text size diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs index 2270136ae..9523ad30a 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using System; using System.Collections.Generic; using System.Runtime.InteropServices; using FlaxEngine.Utilities; @@ -185,7 +186,7 @@ namespace FlaxEngine.GUI }; // Process text into text blocks (handle newlines etc.) - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) return; var lines = font.ProcessText(_text, ref textBlock.Range); @@ -194,20 +195,27 @@ namespace FlaxEngine.GUI for (int i = 0; i < lines.Length; i++) { ref var line = ref lines[i]; - textBlock.Range = new TextRange - { - StartIndex = start + line.FirstCharIndex, - EndIndex = start + line.LastCharIndex, - }; + if (i != 0) { context.Caret.X = 0; OnLineAdded(ref context, textBlock.Range.StartIndex - 1); } - textBlock.Bounds = new Rectangle(context.Caret, line.Size); - textBlock.Bounds.X += line.Location.X; + for (int k = 0; k < line.Blocks.Length; k++) + { + ref var block = ref line.Blocks[k]; - context.AddTextBlock(ref textBlock); + textBlock.Range = new TextRange + { + StartIndex = start + block.FirstCharIndex, + EndIndex = start + block.LastCharIndex, + }; + + textBlock.Bounds = new Rectangle(context.Caret, block.Size); + textBlock.Bounds.X += block.Location.X; + + context.AddTextBlock(ref textBlock); + } } // Update the caret location @@ -236,9 +244,9 @@ namespace FlaxEngine.GUI var ascender = textBlock.Ascender; //if (ascender <= 0) { - var textBlockFont = textBlock.Style.Font.GetFont(); + var textBlockFont = textBlock.Style.Font.GetMultiFont(); if (textBlockFont) - ascender = textBlockFont.Ascender; + ascender = textBlockFont.MaxAscender; } lineAscender = Mathf.Max(lineAscender, ascender); lineSize = Float2.Max(lineSize, textBlockSize); @@ -259,9 +267,9 @@ namespace FlaxEngine.GUI var ascender = textBlock.Ascender; if (ascender <= 0) { - var textBlockFont = textBlock.Style.Font.GetFont(); + var textBlockFont = textBlock.Style.Font.GetMultiFont(); if (textBlockFont) - ascender = textBlockFont.Ascender; + ascender = textBlockFont.MaxAscender; } vOffset = lineAscender - ascender; textBlock.Bounds.Location.Y += vOffset; diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index 044c65044..08637918d 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using FlaxEngine.Utilities; +using System.Linq; namespace FlaxEngine.GUI { @@ -10,9 +11,9 @@ namespace FlaxEngine.GUI { context.Caret.X = 0; var style = context.StyleStack.Peek(); - var font = style.Font.GetFont(); + var font = style.Font.GetMultiFont(); if (font) - context.Caret.Y += font.Height; + context.Caret.Y += font.MaxHeight; } private static void ProcessColor(ref ParsingContext context, ref HtmlTag tag) @@ -86,15 +87,14 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = new FontReference(style.Font); - if (tag.Attributes.TryGetValue(string.Empty, out var fontName)) + style.Font = new MultiFontReference(style.Font); + if (tag.Attributes.TryGetValue("size", out var sizeText) && int.TryParse(sizeText, out var size) && tag.Attributes.TryGetValue(string.Empty, out var fontName)) { var font = (FontAsset)FindAsset(fontName, typeof(FontAsset)); if (font) - style.Font.Font = font; + style.Font = new MultiFontReference([font], size); } - if (tag.Attributes.TryGetValue("size", out var sizeText) && int.TryParse(sizeText, out var size)) - style.Font.Size = size; + context.StyleStack.Push(style); } } @@ -108,7 +108,7 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = style.Font.GetBold(); + // style.Font = style.Font.GetBold(); context.StyleStack.Push(style); } } @@ -122,7 +122,7 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = style.Font.GetItalic(); + // style.Font = style.Font.GetItalic(); context.StyleStack.Push(style); } } @@ -136,9 +136,9 @@ namespace FlaxEngine.GUI else { var style = context.StyleStack.Peek(); - style.Font = new FontReference(style.Font); - TryParseNumberTag(ref tag, string.Empty, style.Font.Size, out var size); - style.Font.Size = (int)size; + style.Font = new MultiFontReference(style.Font); + TryParseNumberTag(ref tag, string.Empty, style.Font.First().Size, out var size); + style.Font = new MultiFontReference(style.Font, (int)size); context.StyleStack.Push(style); } } @@ -173,9 +173,9 @@ namespace FlaxEngine.GUI imageBlock.Style.BackgroundBrush = image; // Setup size - var font = imageBlock.Style.Font.GetFont(); + var font = imageBlock.Style.Font.GetMultiFont(); if (font) - imageBlock.Bounds.Size = new Float2(font.Height); + imageBlock.Bounds.Size = new Float2(font.MaxHeight); imageBlock.Bounds.Size.X *= image.Size.X / image.Size.Y; // Keep original aspect ratio bool hasWidth = TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width); imageBlock.Bounds.Width = width; @@ -213,18 +213,18 @@ namespace FlaxEngine.GUI style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask; switch (valign) { - case "top": - style.Alignment = TextBlockStyle.Alignments.Top; - break; - case "bottom": - style.Alignment = TextBlockStyle.Alignments.Bottom; - break; - case "middle": - style.Alignment = TextBlockStyle.Alignments.Middle; - break; - case "baseline": - style.Alignment = TextBlockStyle.Alignments.Baseline; - break; + case "top": + style.Alignment = TextBlockStyle.Alignments.Top; + break; + case "bottom": + style.Alignment = TextBlockStyle.Alignments.Bottom; + break; + case "middle": + style.Alignment = TextBlockStyle.Alignments.Middle; + break; + case "baseline": + style.Alignment = TextBlockStyle.Alignments.Baseline; + break; } } context.StyleStack.Push(style); @@ -245,15 +245,15 @@ namespace FlaxEngine.GUI style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask; switch (valign) { - case "left": - style.Alignment = TextBlockStyle.Alignments.Left; - break; - case "right": - style.Alignment = TextBlockStyle.Alignments.Right; - break; - case "center": - style.Alignment = TextBlockStyle.Alignments.Center; - break; + case "left": + style.Alignment = TextBlockStyle.Alignments.Left; + break; + case "right": + style.Alignment = TextBlockStyle.Alignments.Right; + break; + case "center": + style.Alignment = TextBlockStyle.Alignments.Center; + break; } } context.StyleStack.Push(style); @@ -280,7 +280,7 @@ namespace FlaxEngine.GUI foreach (var id in ids) { var path = Content.GetEditorAssetPath(id); - if (!string.IsNullOrEmpty(path) && + if (!string.IsNullOrEmpty(path) && string.Equals(name, System.IO.Path.GetFileNameWithoutExtension(path), System.StringComparison.OrdinalIgnoreCase)) { return Content.LoadAsync(id, type); diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index e6a82c986..78da9621c 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -46,7 +46,7 @@ namespace FlaxEngine.GUI var style = Style.Current; _textStyle = new TextBlockStyle { - Font = new FontReference(style.FontMedium.First()), + Font = new MultiFontReference(style.FontMedium), Color = style.Foreground, BackgroundSelectedBrush = new SolidColorBrush(style.BackgroundSelected), }; diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 438a7e3d8..6247cb885 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -123,10 +123,10 @@ namespace FlaxEngine.GUI if (index <= 0) { ref TextBlock textBlock = ref textBlocks[0]; - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (font) { - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return textBlock.Bounds.UpperLeft; } } @@ -135,10 +135,10 @@ namespace FlaxEngine.GUI if (index >= _text.Length) { ref TextBlock textBlock = ref textBlocks[count - 1]; - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (font) { - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -150,10 +150,10 @@ namespace FlaxEngine.GUI if (textBlock.Range.Contains(index)) { - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) break; - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return textBlock.Bounds.Location + font.GetCharPosition(_text, ref textBlock.Range, index - textBlock.Range.StartIndex); } } @@ -165,10 +165,10 @@ namespace FlaxEngine.GUI if (index >= textBlock.Range.EndIndex) { - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) break; - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return textBlock.Bounds.UpperRight; } } @@ -193,7 +193,7 @@ namespace FlaxEngine.GUI if (containsY && (containsX || (i + 1 < count && textBlocks[i + 1].Bounds.Location.Y > textBlock.Bounds.Location.Y + 1.0f))) { - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font && textBlock.Range.Length > 0) break; return font.HitTestText(_text, ref textBlock.Range, location - textBlock.Bounds.Location) + textBlock.Range.StartIndex; @@ -281,7 +281,7 @@ namespace FlaxEngine.GUI } // Pick font - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) continue; @@ -290,7 +290,7 @@ namespace FlaxEngine.GUI { var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex); var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex); - float height = font.Height / DpiScale; + float height = font.MaxHeight / DpiScale; float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; Color selectionColor = Color.White * alpha; @@ -305,7 +305,7 @@ namespace FlaxEngine.GUI ref TextBlock textBlock = ref textBlocks[i]; // Pick font - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) continue; @@ -332,7 +332,7 @@ namespace FlaxEngine.GUI ref TextBlock textBlock = ref textBlocks[i]; // Pick font - var font = textBlock.Style.Font.GetFont(); + var font = textBlock.Style.Font.GetMultiFont(); if (!font) continue; @@ -340,7 +340,7 @@ namespace FlaxEngine.GUI if (textBlock.Style.UnderlineBrush != null) { var underLineHeight = 2.0f; - var height = font.Height / DpiScale; + var height = font.MaxHeight / DpiScale; var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight); textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color); } diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index 967617649..d9c7c1a80 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -40,7 +40,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font. /// [EditorDisplay("Text Style"), EditorOrder(2024)] - public FontReference Font { get; set; } + public MultiFontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -90,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.First()); + Font = new MultiFontReference(style.FontMedium); TextColor = style.Foreground; WatermarkTextColor = style.ForegroundDisabled; SelectionColor = style.BackgroundSelected; @@ -99,7 +99,7 @@ namespace FlaxEngine.GUI /// public override Float2 GetTextSize() { - var font = Font.GetFont(); + var font = Font.GetMultiFont(); if (font == null) { return Float2.Zero; @@ -111,21 +111,21 @@ namespace FlaxEngine.GUI /// public override Float2 GetCharPosition(int index, out float height) { - var font = Font.GetFont(); + var font = Font.GetMultiFont(); if (font == null) { height = Height; return Float2.Zero; } - height = font.Height / DpiScale; + height = font.MaxHeight / DpiScale; return font.GetCharPosition(_text, index, ref _layout); } /// public override int HitTestText(Float2 location) { - var font = Font.GetFont(); + var font = Font.GetMultiFont(); if (font == null) { return 0; @@ -148,7 +148,7 @@ namespace FlaxEngine.GUI // Cache data var rect = new Rectangle(Float2.Zero, Size); bool enabled = EnabledInHierarchy; - var font = Font.GetFont(); + var font = Font.GetMultiFont(); if (!font) return; @@ -172,7 +172,7 @@ namespace FlaxEngine.GUI { var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); - float fontHeight = font.Height / DpiScale; + float fontHeight = font.MaxHeight / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); diff --git a/Source/Engine/UI/GUI/Panels/DropPanel.cs b/Source/Engine/UI/GUI/Panels/DropPanel.cs index 33c2e1605..06256c9f8 100644 --- a/Source/Engine/UI/GUI/Panels/DropPanel.cs +++ b/Source/Engine/UI/GUI/Panels/DropPanel.cs @@ -130,7 +130,7 @@ namespace FlaxEngine.GUI /// Gets or sets the font used to render panel header text. /// [EditorDisplay("Header Text Style"), EditorOrder(2020), ExpandGroups] - public FontReference HeaderTextFont { get; set; } + public MultiFontReference HeaderTextFont { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. @@ -238,7 +238,7 @@ namespace FlaxEngine.GUI var style = Style.Current; HeaderColor = style.BackgroundNormal; HeaderColorMouseOver = style.BackgroundHighlighted; - HeaderTextFont = new FontReference(style.FontMedium.First()); + HeaderTextFont = new MultiFontReference(style.FontMedium); HeaderTextColor = style.Foreground; ArrowImageOpened = new SpriteBrush(style.ArrowDown); ArrowImageClosed = new SpriteBrush(style.ArrowRight); @@ -375,7 +375,7 @@ namespace FlaxEngine.GUI textColor *= 0.6f; } - Render2D.DrawText(HeaderTextFont.GetFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); + Render2D.DrawText(HeaderTextFont.GetMultiFont(), HeaderTextMaterial, HeaderText, textRect, textColor, TextAlignment.Near, TextAlignment.Center); if (!_isClosed && EnableContainmentLines) { diff --git a/Source/Engine/UI/GUI/Style.cs b/Source/Engine/UI/GUI/Style.cs index d3375a670..a8b87fb3a 100644 --- a/Source/Engine/UI/GUI/Style.cs +++ b/Source/Engine/UI/GUI/Style.cs @@ -14,68 +14,60 @@ 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; + private MultiFontReference _fontTitle; /// /// The font title. /// [NoSerialize] [EditorOrder(10)] - public Font FontTitle + public MultiFont FontTitle { - get => _fontTitle?.GetFont(); - set => _fontTitle = new FontReference(value); + get => _fontTitle?.GetMultiFont(); + set => _fontTitle = new MultiFontReference(value); } [Serialize] - private FontReference _fontLarge; + private MultiFontReference _fontLarge; /// /// The font large. /// [NoSerialize] [EditorOrder(20)] - public Font FontLarge + public MultiFont FontLarge { - get => _fontLarge?.GetFont(); - set => _fontLarge = new FontReference(value); + get => _fontLarge?.GetMultiFont(); + set => _fontLarge = new MultiFontReference(value); } [Serialize] - private FontReference[] _fontMedium; + private MultiFontReference _fontMedium; /// /// The font medium. /// [NoSerialize] [EditorOrder(30)] - public Font[] FontMedium + public MultiFont FontMedium { - get => _fontMedium?.Select((x)=>x.GetFont()).ToArray(); - set => _fontMedium = value.Select((x)=>new FontReference(x)).ToArray(); + get => _fontMedium?.GetMultiFont(); + set => _fontMedium = new MultiFontReference(value); } [Serialize] - private FontReference[] _fontSmall; + private MultiFontReference _fontSmall; /// /// The font small. /// [NoSerialize] [EditorOrder(40)] - public Font[] FontSmall + public MultiFont FontSmall { - get => _fontSmall?.Select((x) => x.GetFont()).ToArray(); - set => _fontSmall = value.Select((x) => new FontReference(x)).ToArray(); + get => _fontSmall?.GetMultiFont(); + set => _fontSmall = new MultiFontReference(value); } /// diff --git a/Source/Engine/UI/GUI/TextBlockStyle.cs b/Source/Engine/UI/GUI/TextBlockStyle.cs index c4c87715f..08f74e7c8 100644 --- a/Source/Engine/UI/GUI/TextBlockStyle.cs +++ b/Source/Engine/UI/GUI/TextBlockStyle.cs @@ -64,7 +64,7 @@ namespace FlaxEngine.GUI /// The text font. /// [EditorOrder(0)] - public FontReference Font; + public MultiFontReference Font; /// /// The custom material for the text rendering (must be GUI domain). diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index e17b754c4..7a1026e55 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -244,6 +244,7 @@ namespace FlaxEngine.GUI TextAlignment.Center, TextWrapping.WrapWords ); + } /// @@ -256,14 +257,14 @@ namespace FlaxEngine.GUI // Calculate size of the tooltip var size = Float2.Zero; - if (style != null && style.FontMedium.First() && !string.IsNullOrEmpty(_currentText)) + if (style != null && style.FontMedium && !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.First().ProcessText(_currentText, ref layout); + var items = style.FontMedium.ProcessText(_currentText, ref layout); for (int i = 0; i < items.Length; i++) { ref var item = ref items[i];