From 9690c5570a989035da4f517039fb22704e844813 Mon Sep 17 00:00:00 2001 From: Marco van den Oever Date: Sun, 19 Aug 2018 22:36:55 +0200 Subject: [PATCH] Renderer - FNA (#62) * Workspace * Assembly name "ImGui.NET.FNA" * Removed directives * FNA ImGuiRenderer cleanup * Comments * More comments * Even more comments * Removed unnecessary changes to IO.cs * Worked in review comments * Cleanup and readme * Corrected minVertexIndex and numVertices * Removed png, now generating sample image on runtime * SampleProgram.FNA - Added cimgui.dll as content * Added MonoGame support * Updated readme * Suppress warning on the DrawIndexedPrimitives method --- .../DrawVertDeclaration.cs | 29 ++ .../ImGui.NET.SampleProgram.XNA.csproj | 26 ++ .../ImGuiRenderer.cs | 377 ++++++++++++++++++ src/ImGui.NET.SampleProgram.XNA/Program.cs | 10 + src/ImGui.NET.SampleProgram.XNA/README.md | 12 + src/ImGui.NET.SampleProgram.XNA/SampleGame.cs | 134 +++++++ src/ImGui.NET.sln | 14 + src/ImGui.NET/IO.cs | 9 + 8 files changed, 611 insertions(+) create mode 100644 src/ImGui.NET.SampleProgram.XNA/DrawVertDeclaration.cs create mode 100644 src/ImGui.NET.SampleProgram.XNA/ImGui.NET.SampleProgram.XNA.csproj create mode 100644 src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs create mode 100644 src/ImGui.NET.SampleProgram.XNA/Program.cs create mode 100644 src/ImGui.NET.SampleProgram.XNA/README.md create mode 100644 src/ImGui.NET.SampleProgram.XNA/SampleGame.cs diff --git a/src/ImGui.NET.SampleProgram.XNA/DrawVertDeclaration.cs b/src/ImGui.NET.SampleProgram.XNA/DrawVertDeclaration.cs new file mode 100644 index 0000000..633d640 --- /dev/null +++ b/src/ImGui.NET.SampleProgram.XNA/DrawVertDeclaration.cs @@ -0,0 +1,29 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace ImGuiNET.SampleProgram.XNA +{ + public static class DrawVertDeclaration + { + public static readonly VertexDeclaration Declaration; + + public static readonly int Size; + + static DrawVertDeclaration() + { + unsafe { Size = sizeof(DrawVert); } + + Declaration = new VertexDeclaration( + Size, + + // Position + new VertexElement(0, VertexElementFormat.Vector2, VertexElementUsage.Position, 0), + + // UV + new VertexElement(8, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + + // Color + new VertexElement(16, VertexElementFormat.Color, VertexElementUsage.Color, 0) + ); + } + } +} \ No newline at end of file diff --git a/src/ImGui.NET.SampleProgram.XNA/ImGui.NET.SampleProgram.XNA.csproj b/src/ImGui.NET.SampleProgram.XNA/ImGui.NET.SampleProgram.XNA.csproj new file mode 100644 index 0000000..d360c32 --- /dev/null +++ b/src/ImGui.NET.SampleProgram.XNA/ImGui.NET.SampleProgram.XNA.csproj @@ -0,0 +1,26 @@ + + + + true + false + ImGuiNET.SampleProgram.XNA + ImGuiNET.SampleProgram.XNA + net462 + Exe + + + + + + + + + + + + + + + + + diff --git a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs new file mode 100644 index 0000000..aaba78a --- /dev/null +++ b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs @@ -0,0 +1,377 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ImGuiNET.SampleProgram.XNA +{ + /// + /// ImGui renderer for use with XNA-likes (FNA & MonoGame) + /// + public class ImGuiRenderer + { + private Game _game; + + // Graphics + private GraphicsDevice _graphicsDevice; + + private BasicEffect _effect; + private RasterizerState _rasterizerState; + + private byte[] _vertexData; + private VertexBuffer _vertexBuffer; + private int _vertexBufferSize; + + private byte[] _indexData; + private IndexBuffer _indexBuffer; + private int _indexBufferSize; + + // Textures + private Dictionary _loadedTextures; + + private int _textureId; + private IntPtr? _fontTextureId; + + // Input + private int _scrollWheelValue; + + private List _keys = new List(); + + public ImGuiRenderer(Game game) + { + _game = game ?? throw new ArgumentNullException(nameof(game)); + _graphicsDevice = game.GraphicsDevice; + + _loadedTextures = new Dictionary(); + + _rasterizerState = new RasterizerState() + { + CullMode = CullMode.None, + DepthBias = 0, + FillMode = FillMode.Solid, + MultiSampleAntiAlias = false, + ScissorTestEnable = true, + SlopeScaleDepthBias = 0 + }; + + SetupInput(); + } + + #region ImGuiRenderer + + /// + /// Creates a texture and loads the font data from ImGui. Should be called when the is initialized but before any rendering is done + /// + public virtual void RebuildFontAtlas() + { + // Get font texture from ImGui + var io = ImGui.GetIO(); + var texData = io.FontAtlas.GetTexDataAsRGBA32(); + + // Copy the data to a managed array + var pixels = new byte[texData.Width * texData.Height * texData.BytesPerPixel]; + unsafe { Marshal.Copy(new IntPtr(texData.Pixels), pixels, 0, pixels.Length); } + + // Create and register the texture as an XNA texture + var tex2d = new Texture2D(_graphicsDevice, texData.Width, texData.Height, false, SurfaceFormat.Color); + tex2d.SetData(pixels); + + // Should a texture already have been build previously, unbind it first so it can be deallocated + if (_fontTextureId.HasValue) UnbindTexture(_fontTextureId.Value); + + // Bind the new texture to an ImGui-friendly id + _fontTextureId = BindTexture(tex2d); + + // Let ImGui know where to find the texture + io.FontAtlas.SetTexID(_fontTextureId.Value); + io.FontAtlas.ClearTexData(); // Clears CPU side texture data + } + + /// + /// Creates a pointer to a texture, which can be passed through ImGui calls such as . That pointer is then used by ImGui to let us know what texture to draw + /// + public virtual IntPtr BindTexture(Texture2D texture) + { + var id = new IntPtr(_textureId++); + + _loadedTextures.Add(id, texture); + + return id; + } + + /// + /// Removes a previously created texture pointer, releasing its reference and allowing it to be deallocated + /// + public virtual void UnbindTexture(IntPtr textureId) + { + _loadedTextures.Remove(textureId); + } + + /// + /// Sets up ImGui for a new frame, should be called at frame start + /// + public virtual void BeforeLayout(GameTime gameTime) + { + ImGui.GetIO().DeltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; + + UpdateInput(); + + ImGui.NewFrame(); + } + + /// + /// Asks ImGui for the generated geometry data and sends it to the graphics pipeline, should be called after the UI is drawn using ImGui.** calls + /// + public virtual void AfterLayout() + { + ImGui.Render(); + + unsafe { RenderDrawData(ImGui.GetDrawData()); } + } + + #endregion ImGuiRenderer + + #region Setup & Update + + /// + /// Maps ImGui keys to XNA keys. We use this later on to tell ImGui what keys were pressed + /// + protected virtual void SetupInput() + { + var io = ImGui.GetIO(); + + _keys.Add(io.KeyMap[GuiKey.Tab] = (int)Keys.Tab); + _keys.Add(io.KeyMap[GuiKey.LeftArrow] = (int)Keys.Left); + _keys.Add(io.KeyMap[GuiKey.RightArrow] = (int)Keys.Right); + _keys.Add(io.KeyMap[GuiKey.UpArrow] = (int)Keys.Up); + _keys.Add(io.KeyMap[GuiKey.DownArrow] = (int)Keys.Down); + _keys.Add(io.KeyMap[GuiKey.PageUp] = (int)Keys.PageUp); + _keys.Add(io.KeyMap[GuiKey.PageDown] = (int)Keys.PageDown); + _keys.Add(io.KeyMap[GuiKey.Home] = (int)Keys.Home); + _keys.Add(io.KeyMap[GuiKey.End] = (int)Keys.End); + _keys.Add(io.KeyMap[GuiKey.Delete] = (int)Keys.Delete); + _keys.Add(io.KeyMap[GuiKey.Backspace] = (int)Keys.Back); + _keys.Add(io.KeyMap[GuiKey.Enter] = (int)Keys.Enter); + _keys.Add(io.KeyMap[GuiKey.Escape] = (int)Keys.Escape); + _keys.Add(io.KeyMap[GuiKey.A] = (int)Keys.A); + _keys.Add(io.KeyMap[GuiKey.C] = (int)Keys.C); + _keys.Add(io.KeyMap[GuiKey.V] = (int)Keys.V); + _keys.Add(io.KeyMap[GuiKey.X] = (int)Keys.X); + _keys.Add(io.KeyMap[GuiKey.Y] = (int)Keys.Y); + _keys.Add(io.KeyMap[GuiKey.Z] = (int)Keys.Z); + + // MonoGame-specific ////////////////////// + _game.Window.TextInput += (s, a) => + { + if (a.Character == '\t') return; + + ImGui.AddInputCharacter(a.Character); + }; + /////////////////////////////////////////// + + // FNA-specific /////////////////////////// + //TextInputEXT.TextInput += c => + //{ + // if (c == '\t') return; + + // ImGui.AddInputCharacter(c); + //}; + /////////////////////////////////////////// + + ImGui.GetIO().FontAtlas.AddDefaultFont(); + } + + /// + /// Updates the to the current matrices and texture + /// + protected virtual Effect UpdateEffect(Texture2D texture) + { + _effect = _effect ?? new BasicEffect(_graphicsDevice); + + var io = ImGui.GetIO(); + + // MonoGame-specific ////////////////////// + var offset = .5f; + /////////////////////////////////////////// + + // FNA-specific /////////////////////////// + //var offset = 0f; + /////////////////////////////////////////// + + _effect.World = Matrix.Identity; + _effect.View = Matrix.Identity; + _effect.Projection = Matrix.CreateOrthographicOffCenter(offset, io.DisplaySize.X + offset, io.DisplaySize.Y + offset, offset, -1f, 1f); + _effect.TextureEnabled = true; + _effect.Texture = texture; + _effect.VertexColorEnabled = true; + + return _effect; + } + + /// + /// Sends XNA input state to ImGui + /// + protected virtual void UpdateInput() + { + var io = ImGui.GetIO(); + + var mouse = Mouse.GetState(); + var keyboard = Keyboard.GetState(); + + for (int i = 0; i < _keys.Count; i++) + { + io.KeysDown[_keys[i]] = keyboard.IsKeyDown((Keys)_keys[i]); + } + + io.ShiftPressed = keyboard.IsKeyDown(Keys.LeftShift) || keyboard.IsKeyDown(Keys.RightShift); + io.CtrlPressed = keyboard.IsKeyDown(Keys.LeftControl) || keyboard.IsKeyDown(Keys.RightControl); + io.AltPressed = keyboard.IsKeyDown(Keys.LeftAlt) || keyboard.IsKeyDown(Keys.RightAlt); + io.SuperPressed = keyboard.IsKeyDown(Keys.LeftWindows) || keyboard.IsKeyDown(Keys.RightWindows); + + io.DisplaySize = new System.Numerics.Vector2(_graphicsDevice.PresentationParameters.BackBufferWidth, _graphicsDevice.PresentationParameters.BackBufferHeight); + io.DisplayFramebufferScale = new System.Numerics.Vector2(1f, 1f); + + io.MousePosition = new System.Numerics.Vector2(mouse.X, mouse.Y); + + io.MouseDown[0] = mouse.LeftButton == ButtonState.Pressed; + io.MouseDown[1] = mouse.RightButton == ButtonState.Pressed; + io.MouseDown[2] = mouse.MiddleButton == ButtonState.Pressed; + + var scrollDelta = mouse.ScrollWheelValue - _scrollWheelValue; + io.MouseWheel = scrollDelta > 0 ? 1 : scrollDelta < 0 ? -1 : 0; + _scrollWheelValue = mouse.ScrollWheelValue; + } + + #endregion Setup & Update + + #region Internals + + /// + /// Gets the geometry as set up by ImGui and sends it to the graphics device + /// + private unsafe void RenderDrawData(DrawData* drawData) + { + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers + var lastViewport = _graphicsDevice.Viewport; + var lastScissorBox = _graphicsDevice.ScissorRectangle; + + _graphicsDevice.BlendFactor = Color.White; + _graphicsDevice.BlendState = BlendState.NonPremultiplied; + _graphicsDevice.RasterizerState = _rasterizerState; + _graphicsDevice.DepthStencilState = DepthStencilState.DepthRead; + + // Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays) + ImGui.ScaleClipRects(drawData, ImGui.GetIO().DisplayFramebufferScale); + + // Setup projection + _graphicsDevice.Viewport = new Viewport(0, 0, _graphicsDevice.PresentationParameters.BackBufferWidth, _graphicsDevice.PresentationParameters.BackBufferHeight); + + UpdateBuffers(drawData); + + RenderCommandLists(drawData); + + // Restore modified state + _graphicsDevice.Viewport = lastViewport; + _graphicsDevice.ScissorRectangle = lastScissorBox; + } + + private unsafe void UpdateBuffers(DrawData* drawData) + { + // Expand buffers if we need more room + if (drawData->TotalVtxCount > _vertexBufferSize) + { + _vertexBuffer?.Dispose(); + + _vertexBufferSize = (int)(drawData->TotalVtxCount * 1.5f); + _vertexBuffer = new VertexBuffer(_graphicsDevice, DrawVertDeclaration.Declaration, _vertexBufferSize, BufferUsage.None); + _vertexData = new byte[_vertexBufferSize * DrawVertDeclaration.Size]; + } + + if (drawData->TotalIdxCount > _indexBufferSize) + { + _indexBuffer?.Dispose(); + + _indexBufferSize = (int)(drawData->TotalIdxCount * 1.5f); + _indexBuffer = new IndexBuffer(_graphicsDevice, IndexElementSize.SixteenBits, _indexBufferSize, BufferUsage.None); + _indexData = new byte[_indexBufferSize * sizeof(ushort)]; + } + + // Copy ImGui's vertices and indices to a set of managed byte arrays + int vtxOffset = 0; + int idxOffset = 0; + + for (int n = 0; n < drawData->CmdListsCount; n++) + { + var cmdList = drawData->CmdLists[n]; + + fixed (void* vtxDstPtr = &_vertexData[vtxOffset * DrawVertDeclaration.Size]) + fixed (void* idxDstPtr = &_indexData[idxOffset * sizeof(ushort)]) + { + Buffer.MemoryCopy(cmdList->VtxBuffer.Data, vtxDstPtr, _vertexData.Length, cmdList->VtxBuffer.Size * DrawVertDeclaration.Size); + Buffer.MemoryCopy(cmdList->IdxBuffer.Data, idxDstPtr, _indexData.Length, cmdList->IdxBuffer.Size * sizeof(ushort)); + } + + vtxOffset += cmdList->VtxBuffer.Size; + idxOffset += cmdList->IdxBuffer.Size; + } + + // Copy the managed byte arrays to the gpu vertex- and index buffers + _vertexBuffer.SetData(_vertexData, 0, drawData->TotalVtxCount * DrawVertDeclaration.Size); + _indexBuffer.SetData(_indexData, 0, drawData->TotalIdxCount * sizeof(ushort)); + } + + private unsafe void RenderCommandLists(DrawData* drawData) + { + _graphicsDevice.SetVertexBuffer(_vertexBuffer); + _graphicsDevice.Indices = _indexBuffer; + + int vtxOffset = 0; + int idxOffset = 0; + + for (int n = 0; n < drawData->CmdListsCount; n++) + { + var cmdList = drawData->CmdLists[n]; + + for (int cmdi = 0; cmdi < cmdList->CmdBuffer.Size; cmdi++) + { + var drawCmd = &(((DrawCmd*)cmdList->CmdBuffer.Data)[cmdi]); + + if (!_loadedTextures.ContainsKey(drawCmd->TextureId)) throw new InvalidOperationException($"Could not find a texture with id '{drawCmd->TextureId}', please check your bindings"); + + _graphicsDevice.ScissorRectangle = new Rectangle( + (int)drawCmd->ClipRect.X, + (int)drawCmd->ClipRect.Y, + (int)(drawCmd->ClipRect.Z - drawCmd->ClipRect.X), + (int)(drawCmd->ClipRect.W - drawCmd->ClipRect.Y) + ); + + var effect = UpdateEffect(_loadedTextures[drawCmd->TextureId]); + + foreach (var pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + +#pragma warning disable CS0618 // // FNA does not expose an alternative method. + _graphicsDevice.DrawIndexedPrimitives( + primitiveType: PrimitiveType.TriangleList, + baseVertex: vtxOffset, + minVertexIndex: 0, + numVertices: cmdList->VtxBuffer.Size, + startIndex: idxOffset, + primitiveCount: (int)drawCmd->ElemCount / 3 + ); +#pragma warning restore CS0618 + } + + idxOffset += (int)drawCmd->ElemCount; + } + + vtxOffset += cmdList->VtxBuffer.Size; + } + } + + #endregion Internals + } +} \ No newline at end of file diff --git a/src/ImGui.NET.SampleProgram.XNA/Program.cs b/src/ImGui.NET.SampleProgram.XNA/Program.cs new file mode 100644 index 0000000..4e1aeee --- /dev/null +++ b/src/ImGui.NET.SampleProgram.XNA/Program.cs @@ -0,0 +1,10 @@ +namespace ImGuiNET.SampleProgram.XNA +{ + public static class Program + { + public static void Main(string[] args) + { + new SampleGame().Run(); + } + } +} \ No newline at end of file diff --git a/src/ImGui.NET.SampleProgram.XNA/README.md b/src/ImGui.NET.SampleProgram.XNA/README.md new file mode 100644 index 0000000..c64b329 --- /dev/null +++ b/src/ImGui.NET.SampleProgram.XNA/README.md @@ -0,0 +1,12 @@ +# ImGui.NET renderer for XNA-likes (FNA & MonoGame) + +To run this sample program: + +## MonoGame (default, NuGet package for "DesktopGL"-version already installed) +- Download the SDL2 dll from https://www.libsdl.org/download-2.0.php and copy it to the project output directory + +## FNA +- Remove the MonoGame.Framework.DesktopGL NuGet package +- Download FNA from https://github.com/FNA-XNA/FNA/releases and add a reference to it +- Download the native FNA dependencies from https://github.com/FNA-XNA/FNA/wiki/3:-Distributing-FNA-Games and copy them to the project output directory +- Replace the marked MonoGame-specific parts of the code with their commented FNA counterparts (search for "FNA-specific") diff --git a/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs b/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs new file mode 100644 index 0000000..0596007 --- /dev/null +++ b/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs @@ -0,0 +1,134 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.IO; +using Num = System.Numerics; + +namespace ImGuiNET.SampleProgram.XNA +{ + /// + /// Simple FNA + ImGui example + /// + public class SampleGame : Game + { + private GraphicsDeviceManager _graphics; + private ImGuiRenderer _imGuiRenderer; + + private Texture2D _xnaTexture; + private IntPtr _imGuiTexture; + + public SampleGame() + { + _graphics = new GraphicsDeviceManager(this); + _graphics.PreferredBackBufferWidth = 1024; + _graphics.PreferredBackBufferHeight = 768; + _graphics.PreferMultiSampling = true; + + IsMouseVisible = true; + } + + protected override void Initialize() + { + _imGuiRenderer = new ImGuiRenderer(this); + _imGuiRenderer.RebuildFontAtlas(); + + base.Initialize(); + } + + protected override void LoadContent() + { + // Texture loading example + + // First, load the texture as a Texture2D (can also be done using the XNA/FNA content pipeline) + _xnaTexture = Texture2D.FromStream(GraphicsDevice, GenerateImage(300, 150)); + + // Then, bind it to an ImGui-friendly pointer, that we can use during regular ImGui.** calls (see below) + _imGuiTexture = _imGuiRenderer.BindTexture(_xnaTexture); + + base.LoadContent(); + } + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(new Color(clear_color.X, clear_color.Y, clear_color.Z)); + + // Call BeforeLayout first to set things up + _imGuiRenderer.BeforeLayout(gameTime); + + // Draw our UI + ImGuiLayout(); + + // Call AfterLayout now to finish up and draw all the things + _imGuiRenderer.AfterLayout(); + + base.Draw(gameTime); + } + + // Direct port of the example at https://github.com/ocornut/imgui/blob/master/examples/sdl_opengl2_example/main.cpp + private float f = 0.0f; + + private bool show_test_window = false; + private bool show_another_window = false; + private Num.Vector3 clear_color = new Num.Vector3(114f / 255f, 144f / 255f, 154f / 255f); + private byte[] _textBuffer = new byte[100]; + + protected virtual void ImGuiLayout() + { + // 1. Show a simple window + // Tip: if we don't call ImGui.Begin()/ImGui.End() the widgets appears in a window automatically called "Debug" + { + ImGui.Text("Hello, world!"); + ImGui.SliderFloat("float", ref f, 0.0f, 1.0f, null, 1f); + ImGui.ColorEdit3("clear color", ref clear_color); + if (ImGui.Button("Test Window")) show_test_window = !show_test_window; + if (ImGui.Button("Another Window")) show_another_window = !show_another_window; + ImGui.Text(string.Format("Application average {0:F3} ms/frame ({1:F1} FPS)", 1000f / ImGui.GetIO().Framerate, ImGui.GetIO().Framerate)); + + ImGui.InputText("Text input", _textBuffer, 100, InputTextFlags.Default, null); + + ImGui.Text("Texture sample"); + ImGui.Image(_imGuiTexture, new Num.Vector2(300, 150), Num.Vector2.Zero, Num.Vector2.One, Num.Vector4.One, Num.Vector4.One); // Here, the previously loaded texture is used + } + + // 2. Show another simple window, this time using an explicit Begin/End pair + if (show_another_window) + { + ImGui.SetNextWindowSize(new Num.Vector2(200, 100), Condition.FirstUseEver); + ImGui.BeginWindow("Another Window", ref show_another_window, WindowFlags.Default); + ImGui.Text("Hello"); + ImGui.EndWindow(); + } + + // 3. Show the ImGui test window. Most of the sample code is in ImGui.ShowTestWindow() + if (show_test_window) + { + ImGui.SetNextWindowPos(new Num.Vector2(650, 20), Condition.FirstUseEver); + ImGuiNative.igShowDemoWindow(ref show_test_window); + } + } + + private static Stream GenerateImage(int width, int height) + { + var stream = new MemoryStream(); + var random = new Random(42); + + var bmp = new System.Drawing.Bitmap(width, height); + var graphics = System.Drawing.Graphics.FromImage(bmp); + graphics.Clear(System.Drawing.Color.Black); + + for (int i = 0; i < 100; i++) + { + var size = random.Next(10, 50); + var pen = new System.Drawing.Pen(System.Drawing.Color.FromArgb(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)), random.Next(1, 4)); + + graphics.DrawEllipse(pen, random.Next(0, width), random.Next(0, height), size, size); + } + + bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); + + stream.Position = 0; + + return stream; + } + } +} \ No newline at end of file diff --git a/src/ImGui.NET.sln b/src/ImGui.NET.sln index 770d4ff..1a34b50 100644 --- a/src/ImGui.NET.sln +++ b/src/ImGui.NET.sln @@ -6,6 +6,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET", "ImGui.NET\ImGu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET.SampleProgram", "ImGui.NET.SampleProgram\ImGui.NET.SampleProgram.csproj", "{D6F62D30-7A37-43A1-BD5B-BE9A3D6F964C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET.SampleProgram.XNA", "ImGui.NET.SampleProgram.XNA\ImGui.NET.SampleProgram.XNA.csproj", "{3024336E-9A19-475F-A95D-60A60C0B1C0D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,6 +42,18 @@ Global {D6F62D30-7A37-43A1-BD5B-BE9A3D6F964C}.Release|x64.Build.0 = Release|Any CPU {D6F62D30-7A37-43A1-BD5B-BE9A3D6F964C}.Release|x86.ActiveCfg = Release|Any CPU {D6F62D30-7A37-43A1-BD5B-BE9A3D6F964C}.Release|x86.Build.0 = Release|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Debug|x64.ActiveCfg = Debug|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Debug|x64.Build.0 = Debug|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Debug|x86.ActiveCfg = Debug|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Debug|x86.Build.0 = Debug|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Release|Any CPU.Build.0 = Release|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Release|x64.ActiveCfg = Release|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Release|x64.Build.0 = Release|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Release|x86.ActiveCfg = Release|Any CPU + {3024336E-9A19-475F-A95D-60A60C0B1C0D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ImGui.NET/IO.cs b/src/ImGui.NET/IO.cs index 546ad3f..774586d 100644 --- a/src/ImGui.NET/IO.cs +++ b/src/ImGui.NET/IO.cs @@ -121,6 +121,15 @@ namespace ImGuiNET set { _nativePtr->KeyAlt = value ? (byte)1 : (byte)0; } } + /// + /// Keyboard modifier pressed: Cmd/Super/Windows + /// + public bool SuperPressed + { + get { return _nativePtr->KeySuper == 1; } + set { _nativePtr->KeySuper = value ? (byte)1 : (byte)0; } + } + public bool WantCaptureMouse { get { return _nativePtr->WantCaptureMouse == 1; }