Skip to content

Gum styling update 2025 8 #164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 10, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Gum addresses these challenges with ready-made solutions, allowing us to focus o
> [!IMPORTANT]
> This tutorial uses the Gum NuGet package to help with layout and responding to user interactions. This tutorial does not require the use of the Gum editor, we will be doing everything in code.
>
> Keep in mind, that while it is possible to build a full UI system without any external dependencies, creating a layout engine is complicated and beyond the scope of this tutorial. Instead, we will be taking advantage of the Gum NuGet package.
> Keep in mind that while it is possible to build a full UI system without any external dependencies, creating a layout engine is complicated and beyond the scope of this tutorial. Instead, we will be taking advantage of the Gum NuGet package.
>
> Gum is a powerful system enabling the creation of virtually any game UI, and we will be covering some of the basics of its use in this tutorial. The full Gum documentation can be found here: [https://docs.flatredball.com/gum/code/monogame](https://docs.flatredball.com/gum/code/monogame)

Expand Down Expand Up @@ -321,7 +321,7 @@ To add the Gum NuGet package in Visual Studio Code:
2. Choose `Add NuGet Package` from the context menu.
3. Enter `Gum.MonoGame` in the `Add NuGet Package` search prompt and press Enter.
4. When the search finishes, select the `Gum.MonoGame` package in the results
5. When prompted for a version choose version `2025.5.1.1`.
5. When prompted for a version choose version `2025.8.3.3`.

#### [Visual Studio 2022](#tab/vs2022)

Expand All @@ -332,7 +332,7 @@ To Add the Gum NuGet package in Visual Studio 2022:
3. In the NuGet Package Manager window, select the `Browse` tab if it is not already selected.
4. In the search box, enter `Gum.MonoGame`.
5. Select the "Gum.MonoGame" package from the search results.
6. On the right, in the version dropdown, select version `2025.5.1.1` and click the "Install" button.
6. On the right, in the version dropdown, select version `2025.8.3.3` and click the "Install" button.

#### [dotnet CLI](#tab/dotnetcli)

Expand All @@ -342,7 +342,7 @@ To add the Gum NuGet package using the dotnet CLI:
2. Enter the following command:

```sh
dotnet add DungeonSlime.csproj package Gum.MonoGame --version 2025.5.1.1
dotnet add DungeonSlime.csproj package Gum.MonoGame --version 2025.8.3.3
```

---
Expand All @@ -351,11 +351,11 @@ To add the Gum NuGet package using the dotnet CLI:
> You can verify the package was successfully added by examining your `DungeonSlime.csproj` file, which should now contain a reference like:
>
> ```xml
> <PackageReference Include="Gum.MonoGame" Version="2025.5.1.1" />
> <PackageReference Include="Gum.MonoGame" Version="2025.8.3.3" />
> ```

> [!IMPORTANT]
> This tutorial uses version `2025.5.1.1` of Gum, which is the latest version of Gum as of this writing. That exact version is specified to use in the section above when installing the NuGet package to ensure compatibility throughout this tutorial. If there are newer versions of Gum available, please consult the [Gum documentation](https://docs.flatredball.com/gum/gum-tool/breaking-changes) before updating in case there are any breaking changes from the code that is presented in this tutorial.
> This tutorial uses version `2025.8.3.3` of Gum, which is the latest version of Gum as of this writing. That exact version is specified to use in the section above when installing the NuGet package to ensure compatibility throughout this tutorial. If there are newer versions of Gum available, please consult the [Gum documentation](https://docs.flatredball.com/gum/gum-tool/upgrading) before updating in case there are any breaking changes from the code that is presented in this tutorial.

### Adding UI Sound Effect

Expand Down Expand Up @@ -385,7 +385,7 @@ With the Gum NuGet package added to our project, we need to initialize Gum in ou

First, open the `Game1.cs` file and add the following new using statements to the top:

[!code-csharp[](./snippets/game1/usings.cs?highlight=4-5)]
[!code-csharp[](./snippets/game1/usings.cs?highlight=2-5)]

Next, add the following method to the `Game1` class to encapsulate the initializations of the Gum UI service:

Expand All @@ -397,10 +397,10 @@ Finally, update the [**Initialize**](xref:Microsoft.Xna.Framework.Game.Initializ

The following is a breakdown of this initialization process:

1. **Basic Initialization**: `GumService.Default.Initialize(this)` sets up the Gum system with our game instance. This is required for any gum project.
1. **Basic Initialization**: `GumService.Default.Initialize(this, DefaultVisualsVersion.V2)` sets up the Gum system with our game instance. This is required for any gum project. The second parameter specifies the default visual styling. V2 is the latest version which makes it easy to style the default controls.

> [!NOTE]
> We only need to pass our [**Game**](xref:Microsoft.Xna.Framework.Game) instance since we are using Gum as a code-first approach. Gum also offers a visual editor that creates Gum project files. When using the editor, you will need to also pass the Gum Project file here.
> We only need to pass our [**Game**](xref:Microsoft.Xna.Framework.Game) instance and the visuals version since we are using Gum as a code-first approach. Gum also offers a visual editor that creates Gum project files. When using the editor, you will need to also pass the Gum Project file to `Initialize`.

2. **Content Loading**: Gum needs to be made aware of which content manager to use to load assets through the content pipeline. By setting `GumService.Default.ContentLoader.XnaContentManager = Core.Content`, we tell Gum to use our game's content manager when loading assets. By using the game's existing content manager, Gum also gets the benefit of the caching that the content manager performs when loading assets.
3. **Input Configuration**:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
private void InitializeGum()
{
// Initialize the Gum service
GumService.Default.Initialize(this);
// Initialize the Gum service. The second parameter specifies
// the version of the default visuals to use. V2 is the latest
// version.
GumService.Default.Initialize(this, DefaultVisualsVersion.V2);

// Tell the Gum service which content manager to use. We will tell it to
// use the global content manager from our Core.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using DungeonSlime.Scenes;
using Microsoft.Xna.Framework.Media;
using Gum.Forms;
using Gum.Forms.Controls;
using MonoGameLibrary;
using MonoGameGum;
using MonoGameGum.Forms.Controls;
using Microsoft.Xna.Framework.Media;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoGameGum;
using MonoGameGum.Forms.Controls;
using Gum.Forms.Controls;
using MonoGameGum.GueDeriving;
using MonoGameLibrary;
using MonoGameLibrary.Graphics;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoGameGum;
using MonoGameGum.Forms.Controls;
using Gum.Forms.Controls;
using MonoGameGum.GueDeriving;
using MonoGameLibrary;
using MonoGameLibrary.Scenes;
50 changes: 31 additions & 19 deletions articles/tutorials/building_2d_games/21_customizing_gum_ui/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,29 +182,41 @@ Now that we have all our resources prepared, we can create custom versions of th

### The AnimatedButton Class

Our first custom component will be an `AnimatedButton` that inherits from Gum's base `Button` class. This button will use the game's existing texture atlas for its visual appearance and provide animation when focused.
Our first custom component is an `AnimatedButton` that inherits from Gum's base `Button` class. This button uses the game's existing texture atlas for its visual appearance and animates when focused.

By default all Gum components provide a Visual property which can be casted to a type specific to the particular control. By convention the visual type is named the same as the component with the word `Visual` appened. For example, we will be casting the AnimatedButton's Visual property to `ButtonVisual` to access button-specific properties.

This new `AnimatedButton` class casts the Visual property to `ButtonVisual` and modifies the button-specific properties such as background and text.

First, in the *DungeonSlime* project (your main game project), create a new folder named `UI` to store our custom UI components. Next, in that `UI` folder, create a new file called `AnimatedButton.cs` and add the following code to it:

[!code-csharp[](./snippets/animatedbutton.cs)]

Next, we will examine the key aspects of this new `AnimatedButton` implementation:

#### Top-level Container
#### ButtonVisual

Every customized control needs a top-level container to hold all visual elements. For our button, we create a `ContainerRuntime` that manages the button's size and contains all other visual elements:
As mentioned earlier, we first access the Visual object and cast it to a ButtonVisual. Doing so gives us access to button-specific proerpties including individual elements (such as the text and background visuals) as well as the states that are applied when the button is hovered or pressed.

[!code-csharp[](./snippets/animatedbutton.cs?start=26&end=32)]
We can modify the Visual to give it the appropriate size.

The `WidthUnits` property set to `RelativeToChildren` means the container will automatically size itself based on its child elements, with 21 pixels of additional space. This allows the button to adapt its size depending on the text content.
[!code-csharp[](./snippets/animatedbutton.cs?start=30&end=35)]

The `WidthUnits` property set to `RelativeToChildren` means the container automatically sizes itself based on its child elements, with 21 pixels of additional space. This allows the button to adapt its size depending on the text content.

#### Nine-slice Background

We use a `NineSliceRuntime` for the button's background. A nine-slice is a special graphic that can be stretch while preserving its corners and edges:
`ButtonVisual` provides a `Background` which we can modify. This is of type `NineSliceRuntime` which is a special graphic that can be stretch while preserving its corners and edges:

[!code-csharp[](./snippets/animatedbutton.cs?start=39&end=42)]

[!code-csharp[](./snippets/animatedbutton.cs?start=34&end=41)]
The `TextureAddress` property is set to `Custom` so we can specify exactly which portion of the atlas texture to use, while `Dock(Dock.Fill)` ensure the background fills the entire button area. The portion of the atlas is assigned using AnimationChains, which are discussed later in this tutorial.

The `TextureAddress` property is set to `Custom` so we can specify exactly which portion of the atlas texture to use, while `Dock(Dock.Fill)` ensure the background fills the entire button area.
#### Text

`ButtonVisual` also provides a customizable `Text` property. In this case we assign the font, color, and size.

[!code-csharp[](./snippets/animatedbutton.cs?start=45&end=55)]

#### Animated Chains

Expand All @@ -215,44 +227,44 @@ The most distinctive feature of our animated button is its ability to change app

Each animation frame specifies the coordinates within our texture atlas to display:

[!code-csharp[](./snippets/animatedbutton.cs?start=62&end=102)]
[!code-csharp[](./snippets/animatedbutton.cs?start=58&end=93)]

#### States and Categories

In Gum, each control type has a specific category name that identifies its state collection. For buttons we use `Button.ButtonCategoryName`:
In Gum, each control type has a specific category name that identifies its state collection. `ButtonVisual` provides access to ready-made states and catgories which we can modify. Before we speicfy how a state should modify the button's appearance, we clear out all existing functionality so that we can fully control the states:

[!code-csharp[](./snippets/animatedbutton.cs?start=104&end=107)]
[!code-csharp[](./snippets/animatedbutton.cs?start=104&end=104)]

Within this category, we define how the button appears in different states by creating `StateSave` objects with specific state names:
Each of the button's states can be accessed through `ButtonVisual`. Since the states were cleared previously, the code assigns only the necessary property assignments in the `Apply` delegate. In our case, we switch between animation chains to create the desired visual effect.

[!code-csharp[](./snippets/animatedbutton.cs?start=109&end=140)]

Each state's `Apply` action defines what visual changes occur when the state becomes active. In our case, we switch between animation chains to create the desired visual effect.
[!code-csharp[](./snippets/animatedbutton.cs?start=107&end=130)]

#### Custom Input Handling

We add custom keyboard navigation to our button by handling the `KeyDown` event:

[!code-csharp[](./snippets/animatedbutton.cs?start=142&end=143)]
[!code-csharp[](./snippets/animatedbutton.cs?start=133&end=133)]

[!code-csharp[](./snippets/animatedbutton.cs?start=152&end=167)]
[!code-csharp[](./snippets/animatedbutton.cs?start=142&end=154)]

This allows players to navigate between buttons using the left and right arrow keys, providing additional control options beyond the default tab navigation.

#### Focus Management

We also add a `RollOn` event handler to ensure the button gets focus when the mouse hovers over it:

[!code-csharp[](./snippets/animatedbutton.cs?start=145&end=146)]
[!code-csharp[](./snippets/animatedbutton.cs?start=136&end=136)]

[!code-csharp[](./snippets/animatedbutton.cs?start=169&end=175)]
[!code-csharp[](./snippets/animatedbutton.cs?start=159&end=162)]

This creates a more responsive interface by immediately focusing elements that the player interacts with using the mouse.

### The OptionsSlider Class

Now we will create a custom `OptionsSlider` class to style the volume sliders. This class inherits from Gum's base `Slider` class and provides a styled appearance consistent with the game's visual theme.

Unlike `AnimatedButton`, the `OptionsSlider` creates a Visual completely from scratch. This class provides an example for how to completely customize a Forms control by recreating its Visual object entirely. We do this because the desired appearance and behavior of our `OptionsSlider` is differs enough from the existing Slider that it is easier to replace its `Visual` entirely.

In the `UI` folder of the *DungeonSlime* project (your main game project), create a new file called `OptionsSlider.cs` and add the following code to it:

[!code-csharp[](./snippets/optionsslider.cs)]
Expand Down
Loading