# RichText Rich text needs to parse and store parsed markup. Then the parsed markup needs to be processed and rendered. The current rich text implementation uses a flattened tree structure and the parser parses the start tag, the contained text and the end tag seperately. In addition to the single tag type `color` being hardcoded into the parser. I want to improve on that by making the parser more flexible and changing the process of rendering the markup while keeping changes to rich texts public api minimal. ## Current RichText Current RichText isn't that bad once you understand how to read [Pidgin](https://github.com/benjamin-hodgson/Pidgin) parsers. The biggest problem with current RichText is its inflexibility and the fact that it only supports one tag: `color`. ### Parsing [FormattedMessage.cs](https://github.com/space-wizards/RobustToolbox/blob/master/Robust.Shared/Utility/FormattedMessage.cs) & [FormattedMessage.MarkupParser.cs](https://github.com/space-wizards/RobustToolbox/blob/master/Robust.Shared/Utility/FormattedMessage.MarkupParser.cs) Parsing is done by parsing text and single opening and closing tags repeatedly. The results are saved in different structs all inheriting the `Tag` struct. - Text is parsed into `TagText` - Color opening tags are parsed into `TagColor` - Color closing tags are parsed into `TagPop` The result is a flatened tree structure as a list of tags going down a level with each `TagColor` and up a level with `TagPop`. #### Example Result ```csharp [TagText, TagColor, TagText, TagColor, TagText, TagPop, TagPop, TagText] 1 - | - - 2 - - | - | 3 - - | ``` ### Processing Calculating word wrap is done by iterating over every tag, ignoring tags that are not `TagText` and using the contained text and the supplied font to calculate the necessary line breaks. ### Drawing Drawing is done by iterating over every tag, pushing a color on a stack when encountering a `TagColor` and poping one from the stack when encountering a `TagPop`. Content of `TagText` tags is drawn using one font and in the color that's currently at the top of the color stack. ## RichText Proposal I want to make the parser more flexible and not hardcode the tag~~s~~ that are available. Content should be able to implement its own tags without having to modify the engine and processing and drawing should take the different tags into account. For that I want to add the `IMarkupTag` interface that can be implemented to provide new tags.[*](#notes) The interface contains method definitions for providing text before and after a tags children and for modifying a `MarkupDrawingContext` when entering and exiting a tag. Instead of a color stack when rendering the markup the `MarkupDrawingContext` will contain the context (color, font, etc.) for rendering text. Additionally I want to keep the current public api of RichText the same as much as possible. ### Parsing The parser parsers text, tags and self closing tags into a list of `MarkupNode` nodes.[**](#notes) The nodes have a name, a parameter and children boolean indicating wether it's representing an opening or closing tag. Text is also represented by nodes but text nodes don't have a name. The results is a list of nodes that represent a flat tree. Similiar to the current implementation. A tags parameter is stored as a `MarkupParameter` which basically just contains a nullable string, long and color field and some convenience methods. One of those fields is set depending on the parameter type that got parsed. #### Example ```csharp //Empty is an example for a self closing tag [tag/] [Text, (Color, false), Text, (Color, true), Text, (Empty, false), (Empty, true)] 1 - | - - | - 2 - - | - | ``` ### Processing For calculating word wrap the [`RichTextEntry#Update`](https://github.com/space-wizards/RobustToolbox/blob/2440eb168b75b02e70745431931a4b57833591e6/Robust.Client/UserInterface/RichTextEntry.cs#L50) method would iterate over every node and retreive an instance of its `IMarkupTag` implementation if it's not a text node. When the node has an implementation of `IMarkupTag` the update method calls specific methods from the interface depending on wether it's entering or exiting the node. For entering a node the methods are: - `string TextBefore(MarkupParameter parameter)` - `void PushToContext(MarkupParameter parameter, MarkupDrawContext context)` For exiting a node they are: - `string TextAfter(MarkupParameter parameter)` - `void PopFromContext(MarkupDrawContext context)` The update method adds the returned text to the text it's processing and uses the font at the top of the contexts font stack for calculating the required line breaks. Instances of `IMarkupTag` implementations would be collected and managed by the `MarkupTagManager` which retrieves and instantiates tag implementations using [`IReflectionManager`](https://github.com/space-wizards/RobustToolbox/blob/master/Robust.Shared/Reflection/IReflectionManager.cs) and [`ISandboxHelper`](https://github.com/space-wizards/RobustToolbox/blob/master/Robust.Shared/Sandboxing/SandboxHelper.cs). ### Rendering Rendering iterates over the tags like [`RichTextEntry#Update`](https://github.com/space-wizards/RobustToolbox/blob/2440eb168b75b02e70745431931a4b57833591e6/Robust.Client/UserInterface/RichTextEntry.cs#L50) will but instad of calculation line breaks it renders the text using the `MarkupDrawingContext` for setting things like font and color. ## Inline UI Controls > Links, Images, Buttons, etc. Tags should be able to return a control that gets put in line with the text. For that to work the word wrap calculation needs to be able to handle a width and height input and treat the control as one large character. The next line should just be rendered below the control by adjusting the y offset according to the controls height. Something I'm not quite sure how to do yet is positioning the control properly but I think in can just set the controls position according the the current x and y offset while rendering. Technically the control would be rendered after the text by the UI system and the text would just make space for it I think. ## Notes ~~For tags like link I would probably have to add a list of interactable areas inside [`RichTextEntry`](https://github.com/space-wizards/RobustToolbox/blob/2440eb168b75b02e70745431931a4b57833591e6/Robust.Client/UserInterface/RichTextEntry.cs) and work with mouse events. When a mouse event happens inside that area a method defined in `IMarkupTag` would be called for handling those events. The tag parameter would be passed into that method but I don't know if that's enough or if a link tag implementation of `IMarkupTag` would need to track some kind of state.~~ see: [Inline UI Controls](#Inline-UI-Controls) --- <a id="notes"/> **Using an interface was suggested to me and I also think that is a good way of doing it* ***I thought about using a tree for saving the parsed markup but using a flattened tree like the current implementation seems to make more sense*