From ad37b8361b9f3d66c18433991d1c11d33a13f6e3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 3 Aug 2022 12:57:22 +0200 Subject: [PATCH] Add more tags to Rich Text Box --- Source/Editor/Scripting/TypeUtils.cs | 2 +- Source/Engine/Render2D/Font.h | 2 +- Source/Engine/Render2D/FontAsset.cpp | 74 ++++++++++++++++--- Source/Engine/Render2D/FontAsset.h | 40 +++++++--- Source/Engine/Render2D/FontReference.cs | 18 +++++ .../UI/GUI/Common/RichTextBox.Parsing.cs | 10 +++ .../Engine/UI/GUI/Common/RichTextBox.Tags.cs | 74 +++++++++++++++++++ Source/Engine/UI/GUI/Common/RichTextBox.cs | 8 ++ .../Engine/UI/GUI/Common/RichTextBoxBase.cs | 2 +- 9 files changed, 205 insertions(+), 25 deletions(-) diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index 84d7340f9..ba0e9d778 100644 --- a/Source/Editor/Scripting/TypeUtils.cs +++ b/Source/Editor/Scripting/TypeUtils.cs @@ -141,7 +141,7 @@ namespace FlaxEditor.Scripting Utilities.Utils.InitDefaultValues(value); return value; } - throw new NotSupportedException("Cannot create default value for type " + type); + return null; } /// diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index d216223b4..00d151990 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -274,7 +274,7 @@ public: /// /// Gets the largest vertical distance above the baseline for any character in the font. /// - FORCE_INLINE int32 GetAscender() const + API_PROPERTY() FORCE_INLINE int32 GetAscender() const { return _ascender; } diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index a150bd14a..4cced556f 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -4,6 +4,7 @@ #include "Font.h" #include "FontManager.h" #include "Engine/Core/Log.h" +#include "Engine/Content/Content.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Content/Upgraders/FontAssetUpgrader.h" #include "Engine/Profiler/ProfilerCPU.h" @@ -13,7 +14,7 @@ #include "Engine/Platform/FileSystem.h" #endif -REGISTER_BINARY_ASSET_WITH_UPGRADER(FontAsset, "FlaxEngine.FontAsset", FontAssetUpgrader, false); +REGISTER_BINARY_ASSET_WITH_UPGRADER(FontAsset, "FlaxEngine.FontAsset", FontAssetUpgrader, true); FontAsset::FontAsset(const SpawnParams& params, const AssetInfo* info) : BinaryAsset(params, info) @@ -40,15 +41,7 @@ Asset::LoadResult FontAsset::load() _fontFile.Swap(chunk0->Data); // Create font face - const FT_Error error = FT_New_Memory_Face(FontManager::GetLibrary(), _fontFile.Get(), static_cast(_fontFile.Length()), 0, &_face); - if (error) - { - _face = nullptr; - LOG_FT_ERROR(error); - return LoadResult::Failed; - } - - return LoadResult::Ok; + return Init() ? LoadResult::Failed : LoadResult::Ok; } void FontAsset::unload(bool isReloading) @@ -81,6 +74,22 @@ AssetChunksFlag FontAsset::getChunksToPreload() const return GET_CHUNK_FLAG(0); } +bool FontAsset::Init() +{ + const FT_Error error = FT_New_Memory_Face(FontManager::GetLibrary(), _fontFile.Get(), static_cast(_fontFile.Length()), 0, &_face); + if (error) + { + _face = nullptr; + LOG_FT_ERROR(error); + } + return error; +} + +void FontAsset::SetOptions(const FontOptions& value) +{ + _options = value; +} + Font* FontAsset::CreateFont(int32 size) { PROFILE_CPU(); @@ -89,6 +98,8 @@ Font* FontAsset::CreateFont(int32 size) return nullptr; ScopeLock lock(Locker); + if (_face == nullptr) + return nullptr; // Check if font with that size has already been created for (auto font : _fonts) @@ -100,6 +111,46 @@ Font* FontAsset::CreateFont(int32 size) return New(this, size); } +FontAsset* FontAsset::GetBold() +{ + ScopeLock lock(Locker); + if (_options.Flags & FontFlags::Bold) + return this; + if (!_virtualBold) + { + _virtualBold = Content::CreateVirtualAsset(); + _virtualBold->Init(_fontFile); + auto options = _options; + options.Flags |= FontFlags::Bold; + _virtualBold->SetOptions(options); + } + return _virtualBold; +} + +FontAsset* FontAsset::GetItalic() +{ + ScopeLock lock(Locker); + if (_options.Flags & FontFlags::Italic) + return this; + if (!_virtualItalic) + { + _virtualItalic = Content::CreateVirtualAsset(); + _virtualItalic->Init(_fontFile); + auto options = _options; + options.Flags |= FontFlags::Italic; + _virtualItalic->SetOptions(options); + } + return _virtualItalic; +} + +bool FontAsset::Init(const BytesContainer& fontFile) +{ + ScopeLock lock(Locker); + unload(true); + _fontFile.Copy(fontFile); + return Init(); +} + #if USE_EDITOR bool FontAsset::Save(const StringView& path) @@ -148,6 +199,9 @@ void FontAsset::Invalidate() bool FontAsset::init(AssetInitData& initData) { + if (IsVirtual()) + return false; + // Validate if (initData.SerializedVersion != SerializedVersion) { diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index 80c0d651c..d5b21ea1a 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Content/BinaryAsset.h" +#include "Engine/Content/AssetReference.h" class Font; class FontManager; @@ -72,7 +73,7 @@ DECLARE_ENUM_OPERATORS(FontFlags); /// API_STRUCT() struct FontOptions { -DECLARE_SCRIPTING_TYPE_MINIMAL(FontOptions); + DECLARE_SCRIPTING_TYPE_MINIMAL(FontOptions); /// /// The hinting. @@ -90,17 +91,17 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FontOptions); /// API_CLASS(NoSpawn) class FLAXENGINE_API FontAsset : public BinaryAsset { -DECLARE_BINARY_ASSET_HEADER(FontAsset, 3); + DECLARE_BINARY_ASSET_HEADER(FontAsset, 3); friend Font; private: - FT_Face _face; FontOptions _options; BytesContainer _fontFile; Array> _fonts; + AssetReference _virtualBold; + AssetReference _virtualItalic; public: - /// /// Gets the font family name. /// @@ -130,13 +131,9 @@ public: /// /// Sets the font options. /// - API_PROPERTY() void SetOptions(const FontOptions& value) - { - _options = value; - } + API_PROPERTY() void SetOptions(const FontOptions& value); public: - /// /// Creates the font object of given characters size. /// @@ -144,15 +141,32 @@ public: /// The created font object. API_FUNCTION() Font* CreateFont(int32 size); -#if USE_EDITOR + /// + /// Gets the font with bold style. Returns itself or creates a new virtual font asset using this font but with bold option enabled. + /// + /// The virtual font or this. + API_FUNCTION() FontAsset* GetBold(); + /// + /// Gets the font with italic style. Returns itself or creates a new virtual font asset using this font but with italic option enabled. + /// + /// The virtual font or this. + API_FUNCTION() FontAsset* GetItalic(); + + /// + /// Initializes the font with a custom font file data. + /// + /// Raw bytes with font file data. + /// True if cannot init, otherwise false. + API_FUNCTION() bool Init(const BytesContainer& fontFile); + +#if USE_EDITOR /// /// Saves this asset to the file. Supported only in Editor. /// /// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset. /// True if cannot save data, otherwise false. API_FUNCTION() bool Save(const StringView& path = StringView::Empty); - #endif /// @@ -161,10 +175,12 @@ public: API_FUNCTION() void Invalidate(); protected: - // [BinaryAsset] bool init(AssetInitData& initData) override; LoadResult load() override; void unload(bool isReloading) override; AssetChunksFlag getChunksToPreload() const override; + +private: + bool Init(); }; diff --git a/Source/Engine/Render2D/FontReference.cs b/Source/Engine/Render2D/FontReference.cs index b92415d4d..5ad67f486 100644 --- a/Source/Engine/Render2D/FontReference.cs +++ b/Source/Engine/Render2D/FontReference.cs @@ -106,6 +106,24 @@ namespace FlaxEngine return _cachedFont; } + /// + /// Gets the bold font object described by the structure. + /// + /// The bold font asset. + public FontReference GetBold() + { + return new FontReference(_font?.GetBold(), _size); + } + + /// + /// Gets the italic font object described by the structure. + /// + /// The bold font asset. + public FontReference GetItalic() + { + return new FontReference(_font?.GetItalic(), _size); + } + /// /// Determines whether the specified is equal to this instance. /// diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs index c5c1af84b..53f1a9491 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Parsing.cs @@ -12,6 +12,11 @@ namespace FlaxEngine.GUI /// public struct ParsingContext { + /// + /// Text box control. + /// + public RichTextBox Control; + /// /// HTML tags parser. /// @@ -55,6 +60,10 @@ namespace FlaxEngine.GUI { { "br", ProcessBr }, { "color", ProcessColor }, + { "alpha", ProcessAlpha }, + { "style", ProcessStyle }, + { "b", ProcessBold }, + { "i", ProcessItalic }, }; private HtmlParser _parser = new HtmlParser(); @@ -75,6 +84,7 @@ namespace FlaxEngine.GUI _styleStack.Push(_textStyle); var context = new ParsingContext { + Control = this, Parser = _parser, StyleStack = _styleStack, }; diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index 1ce40d213..ead87ddc6 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -34,5 +34,79 @@ namespace FlaxEngine.GUI context.StyleStack.Push(style); } } + + private static void ProcessAlpha(ref ParsingContext context, ref HtmlTag tag) + { + if (tag.IsSlash) + { + context.StyleStack.Pop(); + } + else + { + var style = context.StyleStack.Peek(); + if (tag.Attributes.TryGetValue(string.Empty, out var alphaText)) + { + if (alphaText.Length == 3 && alphaText[0] == '#') + { + style.Color.A = ((StringUtils.HexDigit(alphaText[1]) << 4) + StringUtils.HexDigit(alphaText[2])) / 255.0f; + } + else if (alphaText.Length > 1 && alphaText[alphaText.Length - 1] == '%') + { + style.Color.A = float.Parse(alphaText.Substring(0, alphaText.Length - 1)) / 100.0f; + } + } + context.StyleStack.Push(style); + } + } + + private static void ProcessStyle(ref ParsingContext context, ref HtmlTag tag) + { + if (tag.IsSlash) + { + context.StyleStack.Pop(); + } + else + { + var style = context.StyleStack.Peek(); + if (tag.Attributes.TryGetValue(string.Empty, out var styleName)) + { + if (context.Control.Styles.TryGetValue(styleName, out var customStyle)) + { + if (customStyle.Font == null) + customStyle.Font = style.Font; + style = customStyle; + } + } + context.StyleStack.Push(style); + } + } + + private static void ProcessBold(ref ParsingContext context, ref HtmlTag tag) + { + if (tag.IsSlash) + { + context.StyleStack.Pop(); + } + else + { + var style = context.StyleStack.Peek(); + style.Font = style.Font.GetBold(); + context.StyleStack.Push(style); + } + } + + private static void ProcessItalic(ref ParsingContext context, ref HtmlTag tag) + { + if (tag.IsSlash) + { + context.StyleStack.Pop(); + } + else + { + var style = context.StyleStack.Peek(); + style.Font = style.Font.GetItalic(); + context.StyleStack.Push(style); + } + } } } diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.cs b/Source/Engine/UI/GUI/Common/RichTextBox.cs index 0b6edbe9b..6d066b0f7 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +using System.Collections.Generic; + namespace FlaxEngine.GUI { /// @@ -23,6 +25,12 @@ namespace FlaxEngine.GUI } } + /// + /// The collection of custom text styles to apply (named). + /// + [EditorOrder(30)] + public Dictionary Styles = new Dictionary(); + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs index 2a0e541f4..c56f0be30 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBoxBase.cs @@ -62,7 +62,7 @@ namespace FlaxEngine.GUI /// /// Updates the text blocks. /// - protected virtual void UpdateTextBlocks() + public virtual void UpdateTextBlocks() { Profiler.BeginEvent("RichTextBoxBase.UpdateTextBlocks");