1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-29 23:58:34 +01:00

Compare commits

...

2 commits

Author SHA1 Message Date
Ell
24a4c23be5 additional documentation article improvements 2023-03-05 20:31:09 +01:00
Ell
12af816a90 improved various documentation articles 2023-03-05 18:42:21 +01:00
6 changed files with 114 additions and 24 deletions

View file

@ -23,6 +23,12 @@ var split = spriteFont.SplitString("This is a really long line of text [...]", w
spriteFont.DrawString(this.SpriteBatch, split, new Vector2(10, 10), Color.White); spriteFont.DrawString(this.SpriteBatch, split, new Vector2(10, 10), Color.White);
``` ```
Alternatively, the `SplitStringSeparate` method returns a collection of strings, where each entry represents a place where a split has been introduced. Using this method, you can differentiate between pre-existing newline characters and newly introduced ones.
```cs
var split = spriteFont.SplitStringSeparate("This is a line of text that contains\nnewline characters!", width: 10, scale: 1);
// returns something like ["This is a line of ", "text that contains\nnewline characters!"]
```
## Truncating ## Truncating
Using generic fonts, a long line of text can also be truncated to fit a certain width in pixels. The remaining text that doesn't fit will simply be chopped off of the end (or start) of the string. Using generic fonts, a long line of text can also be truncated to fit a certain width in pixels. The remaining text that doesn't fit will simply be chopped off of the end (or start) of the string.
```cs ```cs
@ -30,4 +36,4 @@ Using generic fonts, a long line of text can also be truncated to fit a certain
var truncFront = spriteFont.TruncateString("This is a really long line of text [...]", width: 100, fromBack: false, scale: 1); var truncFront = spriteFont.TruncateString("This is a really long line of text [...]", width: 100, fromBack: false, scale: 1);
// Truncate from the back // Truncate from the back
var truncBack = spriteFont.TruncateString("This is a really long line of text [...]", width: 100, fromBack: true, scale: 1); var truncBack = spriteFont.TruncateString("This is a really long line of text [...]", width: 100, fromBack: true, scale: 1);
``` ```

View file

@ -4,10 +4,12 @@ The **MLEM** base package features an extended `InputHandler` class that allows
Rather than using an event-based structure, the MLEM input handler relies on the game's `Update` frames: To query input through the input handler, you have to query it every Update frame, and input information will only be available for a single update frame in most situations. Rather than using an event-based structure, the MLEM input handler relies on the game's `Update` frames: To query input through the input handler, you have to query it every Update frame, and input information will only be available for a single update frame in most situations.
The input handler makes use of the `GenericInput` struct, which is a MLEM wrapper around the three main types of input that MonoGame and FNA provide: `Keys`, `Buttons` and `MouseButton` (the latter of which is a MLEM abstraction of mouse buttons). Values of all of these types can be converted into `GenericInput` implicitly, and a `GenericInput` can be converted back implicitly as well, so you will rarely ever have to interact with the `GenericInput` type manually.
## Setting it up ## Setting it up
To set it up, all you have to do is create a new instance. The constructor optionally accepts parameters to enable or disable certain kinds of input. To set it up, all you have to do is create a new instance. The constructor optionally accepts parameters to enable or disable certain kinds of input.
```cs ```cs
this.InputHandler = new InputHandler(); this.InputHandler = new InputHandler(gameInstance);
``` ```
Additionally, you will have to call the input handler's `Update` method each update call of your game: Additionally, you will have to call the input handler's `Update` method each update call of your game:
```cs ```cs
@ -15,7 +17,7 @@ this.InputHandler.Update();
``` ```
## Querying pressed keys ## Querying pressed keys
A *pressed* key is a key that wasn't down the last update but is held down the current update. This behavior can be useful for things like ui buttons, where holding down the mouse button shouldn't constantly keep triggering the button. A *pressed* key is a key that wasn't down the last update but is held down the current update, which is essentially a positive-edge-triggered press. This behavior can be useful for things like ui buttons, where holding down the mouse button shouldn't constantly keep triggering the button.
You can query if any key, mouse button or gamepad button is pressed as follows: You can query if any key, mouse button or gamepad button is pressed as follows:
```cs ```cs
@ -28,20 +30,52 @@ var gamepad = this.InputHandler.IsPressed(Buttons.A);
var gamepad2 = this.InputHandler.IsPressed(Buttons.A, 2); var gamepad2 = this.InputHandler.IsPressed(Buttons.A, 2);
``` ```
Using the `InvertPressBehavior` flag, you can invert this behavior: If it is set to `true`, a key is considered pressed if it was down the last update, but is up in the current update. This is essentially a negative-edge-triggered press.
### Repeat events ### Repeat events
Keyboard and gamepad repeat events can be enabled or disabled through the `HandleKeyboardRepeats` and `HandleGamepadRepeats` properties in the input handler. Additionally, you can configure the time that it takes until the first repeat is triggered through the `KeyRepeatDelay` property, and you can configure the delay between repeat events through the `KeyRepeatRate` property. Keyboard and gamepad repeat events can be enabled or disabled through the `HandleKeyboardRepeats` and `HandleGamepadRepeats` properties in the input handler. Additionally, you can configure the time that it takes until the first repeat is triggered through the `KeyRepeatDelay` property, and you can configure the delay between repeat events through the `KeyRepeatRate` property.
When enabled, repeat events for *pressing* are automatically triggered. This means that calling `IsPressed` every update call would return `true` for a control that is being held down every `KeyRepeatRate` seconds after `KeyRepeatDelay` seconds have passed once. When enabled, repeat events for *pressing* are automatically triggered. This means that calling `IsPressed` every update call would return `true` for a control that is being held down every `KeyRepeatRate` seconds after `KeyRepeatDelay` seconds have passed once.
## Consuming inputs
Due to the fact that the input handler is query-based (rather than event-based), multiple pieces of code might query the same input each update, causing a single press to be misconstrued as multiple distinct inputs.
The input handler provides the methods `IsPressConsumed`, `IsPressedAvailable`, and `TryConsumePressed`. Calling `TryConsumePressed` on an input means that subsequent calls to `IsPressConsumed` and `IsPressedAvailable` will not return `true` until the next update frame:
```cs
// is this update frame's Up press consumed yet?
var consumed = this.InputHandler.IsPressConsumed(Keys.Up);
// is the Up key pressed, and its press not consumed yet?
var available = this.InputHandler.IsPressedAvailable(Keys.Up);
// check whether the Up key is pressed and its press is available, and consume it
if (this.InputHandler.TryConsumePressed(Keys.Up)) {
// the press has been consumed by us, now do something with the press!
}
```
## Input metrics
The input handler tracks additional data related to keyboard, gamepad, and mouse inputs, such as the amount of times that they have been down for. These metrics can be useful for implementing short-press and long-press behavior.
```cs
// how long has the A key been up (or down) for the last time it was up (or down)?
var upTime = this.InputHandler.GetUpTime(Keys.A);
var downTime = this.InputHandler.GetDownTime(Keys.A);
// how long has it been since the A key was pressed?
var timeSincePress = this.InputHandler.TryGetTimeSincePress(Keys.A);
```
## Gesture handling ## Gesture handling
MonoGame's default touch handling is not very library-friendly, so the input handler also provides a much more streamlined user experience for touch gesture input. MonoGame's default gesture handling (which is inherited from XNA) can be a little difficult to deal with. This is mainly due to the fact that gestures stay in the queue until they are queried (so they might be very old), and the fact that they can't be queried without being permanently removed from the queue.
Because of this, MLEM's input handler also provides a much more streamlined user experience for touch gesture input.
To enable touch input, the gestures you want to use first have to be enabled: To enable touch input, the gestures you want to use first have to be enabled:
```cs ```cs
InputHandler.EnableGestures(GestureType.Tap); InputHandler.EnableGestures(GestureType.Tap);
``` ```
When enabled, a `GestureSample` will be available for the requested gesture type *the frame it is finished*. It can be accessed like so: When enabled, a `GestureSample` will be available for the requested gesture type *the update frame it is finished*. It can be accessed like so:
```cs ```cs
if (this.InputHandler.GetGesture(GestureType.Tap, out var sample)) { if (this.InputHandler.GetGesture(GestureType.Tap, out var sample)) {
// The gesture happened this frame // The gesture happened this frame
@ -52,6 +86,21 @@ if (this.InputHandler.GetGesture(GestureType.Tap, out var sample)) {
``` ```
### External gesture handling ### External gesture handling
If your game already handles gestures through some other means, you might notice that one of the gesture handling methods stops working correctly. This is due to the fact that MonoGame's gesture querying system only supports each gesture being queried once before it is removed from the queue. If your game already handles gestures through some other means, you might notice that one of the gesture handling methods stops working correctly. This is due to the fact that MonoGame's gesture querying system only supports each gesture being queried once before it is removed from the queue, which causes any additional queries for that gesture to fail.
If you want to continue using your own gesture handling, but still allow the `InputHandler` to use gestures (for [MLEM.Ui](ui.md), for example), you can set `ExternalGestureHandling` to true in your `InputHandler`. Then, you can use `AddExternalGesture` to make the input handler aware of a gesture for the duration of the update frame that you added it on. The input handler's gesture handling does not have this problem, since gestures are kept around for an entire update frame no matter how many times they are queried, and gestures can be queried from multiple sources based on the expected gesture type. Because of this, it's generally recommended that you use the input handler's gesture system instead of the default one.
However, if you want to continue using your own gesture handling, but still allow the `InputHandler` to have access to gestures (for [MLEM.Ui](ui.md), for example), you can set `ExternalGestureHandling` to true in your `InputHandler`. Then, you can use `AddExternalGesture` to make the input handler aware of a gesture for the duration of the update frame that you added it on. As an example, you could modify your game's existing gesture handling like this:
```cs
while (TouchPanel.IsGestureAvailable) {
var gesture = TouchPanel.ReadGesture();
// your game's existing gesture handling ...
bool gestureConsumed = this.HandleGestureSomeWay(gesture);
if (gestureConsumed)
continue;
// pass the gesture onto the input handler if we didn't make use of it
this.InputHandler.AddExternalGesture(gesture);
}
```

View file

@ -56,4 +56,18 @@ namespace Test {
} }
} }
``` ```
As `RawContentManager` automatically collects all raw content readers in the loaded assemblies, you don't have to register your custom reader anywhere. As `RawContentManager` automatically collects all raw content readers in the loaded assemblies, you don't have to register your custom reader anywhere.
## Environments without reflection or with trimming
By default, the `RawContentManager` finds all types that extend `RawContentReader` in all loaded assemblies, so they don't have to be added manually. This won't work in environments like NativeAOT, where reflection isn't as readily available, or in assemblies that get trimmed.
If you're in an environment with this restriction, you can manually collect all the content readers that you plan on using and call the constructor that accepts a list of content readers instead:
```csharp
protected override void LoadContent() {
var neededReaders = new List<RawContentReader> {
new Texture2DReader(), new JsonReader() // ...
};
this.rawContent = new RawContentManager(this.Services, neededReaders);
this.Components.Add(this.rawContent);
}
```

View file

@ -2,24 +2,28 @@
The **MLEM** package contains a simple text formatting system that supports coloring, bold and italic font modifiers, in-text icons and text animations. The **MLEM** package contains a simple text formatting system that supports coloring, bold and italic font modifiers, in-text icons and text animations.
Text formatting makes use of [generic fonts](font_extensions.md). Text formatting makes use of [generic fonts](font_extensions.md), and [MLEM.Ui](ui.md)'s `Paragraph` supports text formatting out of the box, but using it for your own text rendering is very simple.
It should also be noted that [MLEM.Ui](ui.md)'s `Paragraph` supports text formatting out of the box. [The demo](https://github.com/Ellpeck/MLEM/blob/main/Demos/TextFormattingDemo.cs) features plenty of examples of the formatting codes that are available by default, as well as examples of the ability to add custom codes and interact with formatted text.
## Formatting codes ## Formatting codes
To format your text, you can insert *formatting codes* into it. Almost all of these codes are single letters surrounded by `<>`, and some formatting codes can accept additional parameters after their letter representation. To format your text, you can insert *formatting codes* into it. Almost all of these codes are single letters surrounded by `<>`, and some formatting codes can accept additional parameters after their letter representation.
By default, the following formatting options are available: By default, the following formatting options are available:
- Colors using `<c ColorName>`. All default MonoGame colors are supported, for example `<c CornflowerBlue>`. Reset using `</c>`. - **Colors** using `<c ColorName>`. All default MonoGame colors are supported, for example `<c CornflowerBlue>`. Reset using `</c>`.
- Bold and italic text using `<b>` and `<i>`, respectively. Reset using `</b>` and `</i>`. - **Bold** and *italic* text using `<b>` and `<i>`, respectively. Reset using `</b>` and `</i>`.
- Drop shadows using `<s>`. Optional parameters for the shadow's color and positional offset are accepted: `<s #AARRGGBB 2.5>`. Reset using `</s>`. - **Drop shadows** using `<s>`. Optional parameters for the shadow's color and positional offset are accepted: `<s #AARRGGBB 2.5>`. Reset using `</s>`.
- Underlined and strikethrough text using `<u>` and `<st>`, respectively. Reset using `</u>` and `</st>`. - **Underlined** and **strikethrough** text using `<u>` and `<st>`, respectively. Reset using `</u>` and `</st>`.
- A wobbly sine wave animation using `<a wobbly>`. Optional parameters for the wobble's intensity and height are accepted: `<a wobbly 10 0.25>`. Reset using `</a>`. - **Subscript** and **superscript** text using `<sub>` and `<sup>`, respectively. Reset using `</sub>` and `</sup>`.
- **Text outlines** using `<o>`. Optional parameters for the outlines' color and thickness are accepted as well: `<o #ff0000 4>`. Reset using `</o>`.
- A wobbly sine wave **animation** using `<a wobbly>`. Optional parameters for the wobble's intensity and height are accepted: `<a wobbly 10 0.25>`. Reset using `</a>`.
When using [MLEM.Ui](ui.md)'s `Paragraph`, these additional formatting options are available by default: When using [MLEM.Ui](ui.md)'s `Paragraph`, these additional formatting options are available by default:
- Hoverable and clickable links using `<l Url>`. Note that this code does not automatically change the color of the text. Reset using `</l>`. - Hoverable and clickable links using `<l Url>`. Reset using `</l>`.
- Inline font changes using `<f FontName>`, with custom fonts gathered from `UiStyle.AdditionalFonts`. Reset using `</f>`. - Inline font changes using `<f FontName>`, with custom fonts gathered from `UiStyle.AdditionalFonts`. Reset using `</f>`.
If you only want to use your own formatting codes in your text formatter, the constructor allows disabling some or all of the default ones.
## Getting your text ready ## Getting your text ready
To get your text ready for rendering with formatting codes, it has to be tokenized. For that, you need to create a new text formatter first. Additionally, you need to have a [generic font](font_extensions.md) ready: To get your text ready for rendering with formatting codes, it has to be tokenized. For that, you need to create a new text formatter first. Additionally, you need to have a [generic font](font_extensions.md) ready:
```cs ```cs
@ -34,15 +38,29 @@ Additionally, if you want your tokenized string to be split based on a certain m
```cs ```cs
tokenizedString.Split(font, maxWidth, scale); tokenizedString.Split(font, maxWidth, scale);
``` ```
## Drawing the formatted text ## Drawing the formatted text
To draw your tokenized text, all you have to do is call its `Draw` method like so: To draw your tokenized text, all you have to do is call its `Draw` method like so:
```cs ```cs
tokenizedString.Draw(gameTime, spriteBatch, position, font, color, scale, depth); tokenizedString.Draw(gameTime, spriteBatch, position, font, color, scale, depth);
``` ```
Note that, if your tokenized text contains any animations, you have to updated the tokenized string every `Update` call like so: Note that, if your tokenized text contains any animations, you have to update the tokenized string every `Update` call like so:
```cs ```cs
tokenizedString.Update(gameTime); tokenizedString.Update(gameTime);
``` ```
## Interacting with formatted text
The `TokenizedString` class also features several methods for querying and interacting with the drawing of formatted text:
```cs
// the token that is under queryPosition if the string is drawn at position
var tokenUnderPos = tokenizedString.GetTokenUnderPos(position, queryPosition, scale);
foreach (var token in tokenizedString.Tokens) {
// the area that the given token takes up
var area = token.GetArea(position, scale);
}
```
## Adding custom codes ## Adding custom codes
Adding custom formatting codes is easy! There are two things that a custom formatting code requires: Adding custom formatting codes is easy! There are two things that a custom formatting code requires:
- A class that extends `Code` that does what your formatting code should do (we'll use `MyCustomCode` in this case) - A class that extends `Code` that does what your formatting code should do (we'll use `MyCustomCode` in this case)
@ -61,12 +79,13 @@ formatter.AddImage("ImageName", new TextureRegion(texture, 0, 0, 8, 8));
After doing so, the image can be displayed using the code `<i ImageName>`. After doing so, the image can be displayed using the code `<i ImageName>`.
## Macros ## Macros
The text formatting system additionally supports macros: Regular expressions that cause the matched text to expand into a different string. Macros are resolved recursively, meaning that you can have macros that resolve into other macros, and so on. The text formatting system additionally supports macros: Regular expressions that cause the matched text to expand into a different string. Macros are resolved recursively (up to 64 times), meaning that you can have macros that resolve into other macros as well.
By default, the following macros are available: By default, the following macros are available:
- `~` expands into a non-breaking space, much like in LaTeX. - `~` expands into a non-breaking space, much like in LaTeX.
- `<n>` expands into a newline character, if you like visual consistency with the other codes.
Adding custom macros is very similar to adding custom formatting codes: Adding custom macros is very similar to adding custom formatting codes:
```cs ```cs
formatter.Macros.Add(new Regex("matchme"), (form, match, regex) => "replacement string"); formatter.Macros.Add(new Regex("matchme"), (form, match, regex) => "replacement string");
``` ```

View file

@ -32,8 +32,8 @@ This means that, to check if the tile at tile coordinate `6, 10` contains any co
```cs ```cs
var tiles = collisions.GetCollidingTiles(new RectangleF(6, 10, 1, 1)); var tiles = collisions.GetCollidingTiles(new RectangleF(6, 10, 1, 1));
``` ```
If the tile at that location is `16x16` pixels big and it has a single collision box at pixels `4, 4` that is `8x8` pixels big, then the following code prints out its percentaged coordinates: `X: 0.25, Y: 0.25, Width: 0.5, Height: 0.5`. If the tile at that location is `16x16` pixels big, and it has a single collision box at pixels `4, 4` that is `8x8` pixels big, then the following code prints out its percentaged coordinates: `X: 0.25, Y: 0.25, Width: 0.5, Height: 0.5`.
```cs ```cs
foreach (var tile in tiles) foreach (var tile in tiles)
Console.WriteLine(tile.Collisions[0]); Console.WriteLine(tile.Collisions[0]);
``` ```

View file

@ -53,7 +53,7 @@ MlemPlatform.Current = new MlemPlatform.None();
``` ```
Initializing the platform in this way also allows for links in paragraphs to be clickable, causing a browser or explorer window to be opened on desktop or mobile devices. Initializing the platform in this way also allows for links in paragraphs to be clickable, causing a browser or explorer window to be opened on desktop or mobile devices.
For more info on MLEM's platform-related code, you can also check out MlemPlatform's [documentation](https://mlem.ellpeck.de/api/MLEM.Misc.MlemPlatform). For more info on MLEM's platform-related code, you can also check out MlemPlatform's [documentation](xref:MLEM.Misc.MlemPlatform).
## Setting the style ## Setting the style
By default, MLEM.Ui's controls look pretty bland, since it doesn't ship with any fonts or textures for any of its controls. To change the style of your ui, simply expand your `new UntexturedStyle(this.SpriteBatch)` call to include fonts and textures of your choosing, for example: By default, MLEM.Ui's controls look pretty bland, since it doesn't ship with any fonts or textures for any of its controls. To change the style of your ui, simply expand your `new UntexturedStyle(this.SpriteBatch)` call to include fonts and textures of your choosing, for example:
@ -92,5 +92,7 @@ this.UiSystem.Add("InfoBox", box);
### About sizing ### About sizing
Note that, when setting the width and height of any element, there are some things to note: Note that, when setting the width and height of any element, there are some things to note:
- Each element has a `SetWidthBasedOnChildren` and a `SetHeightBasedOnChildren` property, which allow them to change their size automatically based on their content - Each element has a `SetWidthBasedOnChildren` and a `SetHeightBasedOnChildren` property, which allow them to change their size automatically based on their content
- When specifying a width or height *lower than or equal to 1*, it is seen as a percentage based on the parent's size instead. For example, a paragraph with a width of `0.5F` inside of a panel width a width of `200` will be `100` units wide. - When specifying a width or height *lower than or equal to 1*, it is seen as a percentage based on the parent's size instead. For example, a paragraph with a width of `0.5F` inside a panel width a width of `200` will be `100` units wide.
- When specifying a width *lower than 0*, it is seen as a percentage based on the element's height, and vice versa. For example, a panel with a width of `200` and a height of `-2` will be `400` units tall. - When specifying a width *lower than 0*, it is seen as a percentage based on the element's height, and vice versa. For example, a panel with a width of `200` and a height of `-2` will be `400` units tall.
A lot of other ways to modify the size of an object are available as well, including `TreatSizeAsMaximum`, `TreatSizeAsMinimum`, `PreventParentSpill` and more. For more information, the `Element` [documentation](xref:MLEM.Ui.Elements.Element) contains descriptions of all fields, properties, and methods.