Table of Contents

Myra UI - Draggable Window and Services

Myra, an external UI library, hosted inside Stride: a draggable window, two health bars - one declared in the view, one added while it runs - and service lookup through GetService to keep the UI from depending directly on the game. Hosting a foreign UI toolkit means giving it a scene renderer of its own and a place in the compositor, which is what MyraSceneRenderer does.

The Program.cs file shows how to:

  • Hosting an external UI library inside Stride
  • Rendering a foreign UI through a custom scene renderer
  • Building a draggable window from Myra widgets
  • Adding widgets statically and at runtime
  • Decoupling UI from the game with GetService()

Myra UI - Draggable Window and Services

View on GitHub.

using E04_Myra_DraggableWindow;
using Stride.CommunityToolkit.Bepu;
using Stride.CommunityToolkit.Engine;
using Stride.CommunityToolkit.Rendering.Compositing;
using Stride.CommunityToolkit.Skyboxes;
using Stride.Engine;
using Stride.Games;

// Whether the second health bar has been added yet
var healthBarAdded = false;

using var game = new Game();

game.Run(new SceneSetup3D().UseBepu().UseSkybox(), start: Start, update: Update);

void Start(Scene rootScene) => game.AddSceneRenderer(new MyraSceneRenderer());

// The second health bar is added while the game runs, once the renderer has put the view in the services
void Update(Scene rootScene, GameTime time)
{
    if (healthBarAdded || game.Services.GetService<MainView>() is not { } mainView) return;

    mainView.Widgets.Add(UIUtils.CreateHealthBar(-50, "#FFD961FF"));

    healthBarAdded = true;
}

MyraSceneRenderer.cs

The scene renderer that draws the Myra desktop over the scene.

using Myra;
using Myra.Graphics2D.UI;
using Stride.Engine;
using Stride.Games;
using Stride.Graphics;
using Stride.Rendering;
using Stride.Rendering.Compositing;

namespace E04_Myra_DraggableWindow;

/// <summary>
/// Draws a Myra user interface as a scene renderer in Stride's rendering pipeline.
/// </summary>
public class MyraSceneRenderer : SceneRendererBase
{
    /// <summary>Myra's root container, which every element hangs off.</summary>
    private Desktop? _desktop;

    /// <summary>Sets up Myra, the main view and the desktop that holds it.</summary>
    protected override void InitializeCore()
    {
        base.InitializeCore();

        // Myra needs the concrete Game by now: a null here would surface much later as a
        // NullReferenceException inside Myra.
        MyraEnvironment.Game = Services.GetService<IGame>() as Game
            ?? throw new InvalidOperationException("Myra needs the running Stride Game to be registered in Services.");

        // The view goes into the services, which is how Program.cs finds it to add a widget later
        var mainView = new MainView();

        Services.AddService(mainView);

        _desktop = new Desktop { Root = mainView };
    }

    protected override void DrawCore(RenderContext context, RenderDrawContext drawContext)
    {
        drawContext.CommandList.Clear(GraphicsDevice.Presenter.DepthStencilBuffer, DepthStencilClearOptions.DepthBuffer);

        _desktop?.Render();
    }
}

MainView.cs

The main view: a health bar, a HorizontalProgressBar, and the draggable window.

using Myra.Graphics2D.UI;

namespace E04_Myra_DraggableWindow;

/// <summary>
/// The main view: a health bar and a draggable test window.
/// </summary>
public sealed class MainView : Panel
{
    /// <summary>The draggable test window, with a sample label.</summary>
    public Window ExampleWindow { get; }

    public MainView()
    {
        Widgets.Add(UIUtils.CreateHealthBar(-20, "#4BD961FF"));

        ExampleWindow = new Window
        {
            Title = "Hello From Myra",
            Left = 590,
            Top = 200,
            Content = new Label
            {
                VerticalSpacing = 10,
                Text = "This is a Test! Hello from Myra! This is a draggable window and below two progress bars."
            }
        };

        Widgets.Add(ExampleWindow);
    }
}

UIUtils.cs

CreateHealthBar, which builds both health bars: the one in MainView and the one Program.cs adds at run time.

using Myra.Graphics2D.Brushes;
using Myra.Graphics2D.UI;

namespace E04_Myra_DraggableWindow;

/// <summary>
/// Creates custom UI widgets.
/// </summary>
public static class UIUtils
{
    /// <summary>
    /// Creates a <see cref="HorizontalProgressBar"/> with the given top position and filler colour.
    /// </summary>
    /// <param name="top">The top position of the progress bar.</param>
    /// <param name="filler">The filler colour of the progress bar.</param>
    /// <returns>A new <see cref="HorizontalProgressBar"/> instance.</returns>
    public static HorizontalProgressBar CreateHealthBar(int top, string filler) => new()
    {
        HorizontalAlignment = HorizontalAlignment.Left,
        VerticalAlignment = VerticalAlignment.Bottom,
        Value = 100,
        Filler = new SolidBrush(filler),
        Left = 20,
        Top = top,
        Width = 300,
        Height = 20,
        Background = new SolidBrush("#202020FF")
    };
}

See also