2022-06-17 21:47:40 +02:00
using System ;
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
2022-12-06 16:49:19 +01:00
using Microsoft.Xna.Framework.Input ;
using MLEM.Extensions ;
2022-06-17 21:47:40 +02:00
using MLEM.Font ;
using MLEM.Formatting ;
using MLEM.Formatting.Codes ;
2022-12-07 13:35:57 +01:00
using MLEM.Misc ;
2022-06-17 21:47:40 +02:00
using MLEM.Startup ;
using MLEM.Textures ;
namespace Demos {
public class TextFormattingDemo : Demo {
private const string Text =
"MLEM's text formatting system allows for various <b>formatting codes</b> to be applied in the middle of a string. Here's a demonstration of some of them.\n\n" +
2024-03-17 21:43:53 +01:00
"You can write in <b>bold</b>, <i>italics</i>, <u>with an underline</u>, <st>strikethrough</st>, with a <s>drop shadow</s> whose <s #ff0000 4>color</s> and <s #000000 10>offset</s> you can modify in each application of the code, with an <o>outline</o> that you can also <o #ff0000 4>modify</o> <o #ff00ff 2>dynamically</o>, or with various types of <b>combined <c Pink>formatting</c> codes</b>.\n\n" +
2022-06-17 21:47:40 +02:00
"You can apply <c CornflowerBlue>custom</c> <c Yellow>colors</c> to text, including all default <c Orange>MonoGame colors</c> and <c #aabb00>inline custom colors</c>.\n\n" +
"You can also use animations like <a wobbly>a wobbly one</a>, as well as create custom ones using the <a wobbly>Code class</a>.\n\n" +
2023-01-07 22:02:22 +01:00
"You can also display <i grass> icons in your text, and use super<sup>script</sup> or sub<sub>script</sub> formatting!\n\n" +
2022-06-17 23:17:45 +02:00
"Additionally, the text formatter has various methods for interacting with the text, like custom behaviors when hovering over certain parts, and more." ;
2023-02-23 14:33:03 +01:00
private const float DefaultScale = 0.5F ;
private const float WidthMultiplier = 0.9F ;
2022-06-17 21:47:40 +02:00
private TextFormatter formatter ;
private TokenizedString tokenizedText ;
private GenericFont font ;
2022-12-06 16:49:19 +01:00
private bool drawBounds ;
2023-02-23 15:18:07 +01:00
private float Scale {
get {
// calculate our scale based on how much larger the window is, so that the text scales with the window
var viewport = new Rectangle ( 0 , 0 , this . Game . Window . ClientBounds . Width , this . Game . Window . ClientBounds . Height ) ;
return TextFormattingDemo . DefaultScale * Math . Min ( viewport . Width / 1280F , viewport . Height / 720F ) ;
}
}
2023-05-18 21:41:36 +02:00
private int startIndex ;
private int endIndex ;
2022-06-17 21:47:40 +02:00
public TextFormattingDemo ( MlemGame game ) : base ( game ) { }
public override void LoadContent ( ) {
this . Game . Window . ClientSizeChanged + = this . OnResize ;
// creating a new text formatter as well as a generic font to draw with
2023-02-23 15:18:07 +01:00
this . formatter = new TextFormatter {
DefaultShadowOffset = new Vector2 ( 4 ) ,
DefaultOutlineThickness = 4
} ;
2022-06-17 21:47:40 +02:00
// GenericFont and its subtypes are wrappers around various font classes, including SpriteFont, MonoGame.Extended's BitmapFont and FontStashSharp
// supplying a bold and italic version of the font here allows for the bold and italic formatting codes to be used
this . font = new GenericSpriteFont (
2023-02-23 14:33:03 +01:00
Demo . LoadContent < SpriteFont > ( "Fonts/Roboto" ) ,
Demo . LoadContent < SpriteFont > ( "Fonts/RobotoBold" ) ,
Demo . LoadContent < SpriteFont > ( "Fonts/RobotoItalic" ) ) ;
2022-06-17 21:47:40 +02:00
// adding the image code used in the example to it
var testTexture = Demo . LoadContent < Texture2D > ( "Textures/Test" ) ;
this . formatter . AddImage ( "grass" , new TextureRegion ( testTexture , 0 , 0 , 8 , 8 ) ) ;
// tokenizing our text and splitting it to fit the screen
// we specify our text alignment here too, so that all data is cached correctly for display
this . tokenizedText = this . formatter . Tokenize ( this . font , TextFormattingDemo . Text , TextAlignment . Center ) ;
2023-02-23 15:18:07 +01:00
this . tokenizedText . Split ( this . font , this . GraphicsDevice . Viewport . Width * TextFormattingDemo . WidthMultiplier , this . Scale , TextAlignment . Center ) ;
2023-05-18 21:41:36 +02:00
this . endIndex = this . tokenizedText . String . Length ;
2022-06-17 21:47:40 +02:00
}
public override void DoDraw ( GameTime time ) {
2022-06-17 23:17:45 +02:00
this . GraphicsDevice . Clear ( Color . DarkSlateGray ) ;
2022-08-20 11:39:28 +02:00
this . SpriteBatch . Begin ( SpriteSortMode . Deferred , null , SamplerState . PointClamp , DepthStencilState . None , RasterizerState . CullCounterClockwise ) ;
2022-06-17 21:47:40 +02:00
// we draw the tokenized text in the center of the screen
// since the text is already center-aligned, we only need to align it on the y axis here
2023-02-23 15:18:07 +01:00
var size = this . tokenizedText . GetArea ( Vector2 . Zero , this . Scale ) . Size ;
2022-06-17 21:47:40 +02:00
var pos = new Vector2 ( this . GraphicsDevice . Viewport . Width / 2 , ( this . GraphicsDevice . Viewport . Height - size . Y ) / 2 ) ;
2022-12-06 16:49:19 +01:00
// draw bounds, which can be toggled with B in this demo
if ( this . drawBounds ) {
2022-12-07 13:35:57 +01:00
var blank = this . SpriteBatch . GetBlankTexture ( ) ;
this . SpriteBatch . Draw ( blank , new RectangleF ( pos - new Vector2 ( size . X / 2 , 0 ) , size ) , Color . Red * 0.25F ) ;
2022-12-06 16:49:19 +01:00
foreach ( var token in this . tokenizedText . Tokens ) {
2023-02-23 15:18:07 +01:00
foreach ( var area in token . GetArea ( pos , this . Scale ) )
2022-12-07 13:35:57 +01:00
this . SpriteBatch . Draw ( blank , area , Color . Black * 0.25F ) ;
2022-12-06 16:49:19 +01:00
}
}
2023-05-18 21:41:36 +02:00
// draw the text itself (start and end indices are optional)
this . tokenizedText . Draw ( time , this . SpriteBatch , pos , this . font , Color . White , this . Scale , 0 , this . startIndex , this . endIndex ) ;
2022-06-17 21:47:40 +02:00
this . SpriteBatch . End ( ) ;
}
public override void Update ( GameTime time ) {
// update our tokenized string to animate the animation codes
this . tokenizedText . Update ( time ) ;
2023-05-18 21:41:36 +02:00
// change some demo showcase info based on keybinds
2022-12-06 16:49:19 +01:00
if ( this . InputHandler . IsPressed ( Keys . B ) )
this . drawBounds = ! this . drawBounds ;
2023-05-18 21:41:36 +02:00
if ( this . startIndex > 0 & & this . InputHandler . IsDown ( Keys . Left ) )
this . startIndex - - ;
if ( this . startIndex < this . tokenizedText . String . Length & & this . InputHandler . IsDown ( Keys . Right ) )
this . startIndex + + ;
if ( this . endIndex > 0 & & this . InputHandler . IsDown ( Keys . Down ) )
this . endIndex - - ;
if ( this . endIndex < this . tokenizedText . String . Length & & this . InputHandler . IsDown ( Keys . Up ) )
this . endIndex + + ;
2022-06-17 21:47:40 +02:00
}
public override void Clear ( ) {
base . Clear ( ) ;
this . Game . Window . ClientSizeChanged - = this . OnResize ;
}
private void OnResize ( object sender , EventArgs e ) {
// re-split our text if the window resizes, since it depends on the window size
// this doesn't require re-tokenization of the text, since TokenizedString also stores the un-split version
2023-02-23 15:18:07 +01:00
this . tokenizedText . Split ( this . font , this . GraphicsDevice . Viewport . Width * TextFormattingDemo . WidthMultiplier , this . Scale , TextAlignment . Center ) ;
2022-06-17 21:47:40 +02:00
}
}
}