MGUI is a UI framework for MonoGame (Src) that features a powerful layout engine and data binding engine similar to WPF, and a robust set of controls to build your UI with.
All control names are prefixed with 'MG' and many controls have similar names and properties to what you might find in WPF. Currently supported controls:
'Container'-like Controls that define their own logic for arranging their children:
MGUI can also parse and render your XAML markup at runtime using the MGXAMLDesigner control:
Getting Started:
Clone this repo
Use Visual Studio 2022 or later (since this project targets .NET 6.0, and makes use of some new-ish C# language features such as record structs (which may be unavailable in c# language version 8.0 or earlier))
In your MonoGame project:
In the Solution Explorer:
Right-click your Solution, Add -> Existing Project. Browse for MGUI.Shared.csproj, and MGUI.Core.csproj.
Right-click your Project, Add -> Project Reference. Add references to MGUI.Shared and MGUI.Core.
You may need to:
Right-click your game's Content folder, Add -> Existing Item. Browse for MGUI\MGUI.Shared\Content\MGUI.Shared.Content.mgcb and MGUI\MGUI.Core\Content\MGUI.Core.Content.mgcb and add them both as links (in the file browser dialog, click the dropdown arrow next to the Add button and choose Add as link). This step will ensure that MGUI's content .xnb files are copied to your project's bin\Content folder. This step might not be necessary.
In your Game class:
In the Initialize method:
Instantiate MGUI.Shared.Rendering.MainRenderer
Instantiate MGUI.Core.UI.MGDesktop
Anywhere in your code, instantiate 1 or more MGWindow and add them to your MGDesktop instance via MGDesktop.Windows
In the Update method: Call MGDesktop.Update()
In the Draw method: Call MGDesktop.Draw()
Example code for your Game class:
```c#
public class Game1 : Game, IObservableUpdate
{
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private MainRenderer MGUIRenderer { get; set; }
private MGDesktop Desktop { get; set; }
public event EventHandler PreviewUpdate;
public event EventHandler EndUpdate;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize()
{
this.MGUIRenderer = new(new GameRenderHost(this));
this.Desktop = new(MGUIRenderer);
MGWindow Window1 = new(Desktop, 50, 50, 500, 200);
Window1.TitleText = "Sample Window with a single [b]Button[/b]: [color=yellow]Click it![/color]";
Window1.BackgroundBrush.NormalValue = new MGSolidFillBrush(Color.Orange);
Window1.Padding = new(15);
MGButton Button1 = new(Window1, button => { button.SetContent("I've been clicked!"); });
Button1.SetContent("Click me!");
Window1.SetContent(Button1);
this.Desktop.Windows.Add(Window1);
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
PreviewUpdate?.Invoke(this, gameTime.TotalGameTime);
// Call Desktop.Update() at start of your update loop, so that the UI has the 1st-chance to receive and handle user-inputs
Desktop.Update();
// TODO: Add your game update logic here
base.Update(gameTime);
EndUpdate?.Invoke(this, EventArgs.Empty);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
// Call Desktop.Draw() at the end of your draw loop, so that the UI is rendered overtop of everything else
Desktop.Draw();
base.Draw(gameTime);
}
}
```
![window1.png](assets/samples/window1.png)
Multi-Platform
MGUI.Core targets net6.0-windows by default. If you wish to use MGUI on another OS, open MGUI\MGUI.Core\MGUI.Core.csproj and change <TargetFramework>net6.0-windows</TargetFramework> to <TargetFramework>net6.0</TargetFramework>. Some features have better implementations when targeting net6.0-windows, but everything probably™ works fine when targeting net6.0. In particular, targeting net6.0 breaks some intellisense related to MarkupExtensions (such as MGBinding) when using the Visual Studio Xaml designer (but it still works at runtime, don't ask me why).
Input Handling
MGUI uses its own framework to detect and respond to inputs.
InputTracker
The base-class for input-related logic
Uusually only 1 instance per program, automatically created when you create an instance of MGUI.Shared.Rendering.MainRenderer
Contains 2 child objects: MouseTracker and KeyboardTracker
MouseTracker
Detects changes to the MouseState and stores information about those changes in EventArg objects, most of which have an IsHandled property.
Manages 0 to many MouseHandlers
MouseHandler
Exposes several events that your code can subscribe to, to react to mouse events. Such as:
Prevents the same event from being invoked to several subscribers after one of the subscribers handles it (by setting IsHandled to true)
KeyboardTracker
Just like MouseTracker except for managing Keyboard-related inputs.
If you'd like to utilize this framework in your own code, get the MouseTracker and/or KeyboardTracker instance from the InputTracker. Call MouseTracker.CreateHandler(...)/KeyboardTracker.CreateHandler(...), and subscribe to the events in the returned handler instance.
EX: Creates a MouseHandler instance in Game1.Initialize and subscribes to an event
public class Game1 : Game, IObservableUpdate, IMouseHandlerHost
{
private MainRenderer MGUIRenderer { get; set; }
private MGDesktop Desktop { get; set; }
// IObservableUpdate implementation
public event EventHandler<TimeSpan> PreviewUpdate;
public event EventHandler<EventArgs> EndUpdate;
// IMouseHandlerHost implementation
bool IMouseViewport.IsInside(Vector2 Position) => Window.ClientBounds.Contains(Position);
Vector2 IMouseViewport.GetOffset() => Vector2.Zero;
private MouseHandler MH { get; set; }
protected override void Initialize()
{
this.MGUIRenderer = new MainRenderer(new GameRenderHost<Game1>(this));
this.Desktop = new MGDesktop(MGUIRenderer);
// Create a MouseHandler
this.MH = MGUIRenderer.Input.Mouse.CreateHandler(this, null);
// Subscribe to 1 or more mouse-related events
MH.MovedInside += (sender, e) =>
{
// TODO: Do something in response to the mouse moving
};
base.Initialize();
}
protected override void Update(GameTime gameTime)
{
PreviewUpdate?.Invoke(this, gameTime.TotalGameTime);
Desktop.Update();
MH.ManualUpdate();
// TODO: Add your update logic here
base.Update(gameTime);
EndUpdate?.Invoke(this, EventArgs.Empty);
}
// constructor and other functions ommitted for brevity...
}
Full example code:
Suppose you want to react to WASD keys to move your player, but you don't want to move the player if the WASD was handled by an `MGTextBox` on the UI:
```c#
public class Game1 : Game, IObservableUpdate, IKeyboardHandlerHost
{
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private MainRenderer MGUIRenderer { get; set; }
private MGDesktop Desktop { get; set; }
public event EventHandler PreviewUpdate;
public event EventHandler EndUpdate;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
private KeyboardHandler PlayerMovementHandler;
protected override void Initialize()
{
_graphics.PreferredBackBufferWidth = 400;
_graphics.PreferredBackBufferHeight = 300;
_graphics.ApplyChanges();
this.MGUIRenderer = new(new GameRenderHost(this));
this.Desktop = new(MGUIRenderer);
// Create a simple UI that may need to handle keyboard events
MGWindow Window1 = new(Desktop, 20, 20, 200, 100);
Window1.Padding = new(10);
MGTextBox TextBox = new(Window1);
Window1.SetContent(TextBox);
Desktop.Windows.Add(Window1);
// Create a KeyboardHandler instance that will respond to WASD key presses
PlayerMovementHandler = MGUIRenderer.Input.Keyboard.CreateHandler(this, null);
PlayerMovementHandler.Pressed += (sender, e) =>
{
if (e.Key is Keys.W or Keys.A or Keys.S or Keys.D)
{
//TODO Move the player
e.SetHandledBy(this, false);
}
};
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
PreviewUpdate?.Invoke(this, gameTime.TotalGameTime);
Desktop.Update();
// By updating this handler AFTER we've updated our UI,
// the handler won't receive events that were already handled by our UI's TextBox
PlayerMovementHandler.ManualUpdate();
// TODO: Add your update logic here
base.Update(gameTime);
EndUpdate?.Invoke(this, EventArgs.Empty);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
Desktop.Draw();
base.Draw(gameTime);
}
}
```
If you don't want to use MGUI's input framework, then you can just check if the `EventArgs` in `MouseTracker`/`KeyboardTracker` have already been handled before your code attempts to handle them:
```c#
BaseKeyPressedEventArgs W_PressEvent = MGUIRenderer.Input.Keyboard.CurrentKeyPressedEvents[Keys.W];
if (Keyboard.GetState().IsKeyDown(Keys.W) && (W_PressEvent == null || !W_PressEvent.IsHandled))
{
//TODO do something
}
```
DataBinding
MGUI has its own DataBinding engine to allow you to bind UI data to data in your DataContext. Set MGWindow.WindowDataContext or MGElement.DataContextOverride. Then you can use the MGBinding markup extension in XAML to configure your bindings.
{MGBinding Path=SomeSampleString, Mode=OneWay} creates a DataBinding that will automatically set TextBlock.Text = TextBlock.DataContext.SomeSampleString;
MGUI's DataBinding engine supports several features such as:
Binding to nested properties ({MGBinding Path=Foo.Bar})
Binding to specific named elements ({MGBinding ElementName=SomeNamedCheckBox, Path=IsChecked})
Binding with a Converter ({MGBinding Path=SomeValue, Converter={local:BoolToVisibilityConverter}})
Binding with a StringFormat ({MGBinding Path=SomeFloatValue, StringFormat={}{0:0.0}})
and more. Check the DataBinding window in the Samples project for detailed documentation:
Because MGUI uses its own DataBinding engine, DataBindings will work even on non-Windows platforms. (Though you will need to change MGUI.Core to target net6.0 instead of net6.0-windows, and some intellisense won't work in the Visual Studio Xaml designer.)