Skip to content

Instantly share code, notes, and snippets.

@Jjagg
Last active August 25, 2021 01:51
Show Gist options
  • Save Jjagg/31de963c579a2118a3b7c0764d8abe45 to your computer and use it in GitHub Desktop.
Save Jjagg/31de963c579a2118a3b7c0764d8abe45 to your computer and use it in GitHub Desktop.
MonoGame backend sample for ImGui.NET (https://github.com/mellinoe/ImGui.NET)
using System;
using System.Runtime.InteropServices;
using ImGuiNET;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Vector2 = ImGuiNET.Vector2;
using Vector3 = ImGuiNET.Vector3;
using Vector4 = ImGuiNET.Vector4;
namespace ImGuiMG
{
public class Game1 : Game
{
private readonly GraphicsDeviceManager _graphics;
private ImGuiMg _imgui;
private int _pressCount;
private Vector4 _buttonColor = new Vector4(55f / 255f, 155f / 255f, 1f, 1f);
private bool _mainWindowOpened;
private float _sliderVal;
private Vector3 _positionValue = new Vector3(500);
public readonly IntPtr TextInputBuffer;
public readonly int TextInputBufferLength;
private readonly MemoryEditor _memoryEditor = new MemoryEditor();
private readonly byte[] _memoryEditorData;
public unsafe Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
Window.AllowUserResizing = true;
IsFixedTimeStep = false;
_graphics.SynchronizeWithVerticalRetrace = false;
_graphics.PreferredBackBufferWidth = 960;
_graphics.PreferredBackBufferHeight = 540;
TextInputBufferLength = 1024;
TextInputBuffer = Marshal.AllocHGlobal(TextInputBufferLength);
var ptr = (long*)TextInputBuffer.ToPointer();
for (var i = 0; i < 1024 / sizeof(long); i++)
ptr[i] = 0;
_memoryEditorData = new byte[1024];
var rnd = new Random();
for (var i = 0; i < _memoryEditorData.Length; i++)
_memoryEditorData[i] = (byte) rnd.Next(255);
}
protected override void LoadContent()
{
_imgui = new ImGuiMg(Window, GraphicsDevice);
/*var frc = new FrameRateCounter(this);
frc.Position = new Microsoft.Xna.Framework.Vector2(10f, 500f);
frc.Font = Content.Load<SpriteFont>("font");
Components.Add(frc);*/
}
protected override void Dispose(bool disposing)
{
_imgui.Dispose();
base.Dispose(disposing);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
_imgui.Update(gameTime);
SubmitImGuiStuff();
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
_imgui.Draw();
base.Draw(gameTime);
}
private unsafe void SubmitImGuiStuff()
{
ImGui.GetStyle().WindowRounding = 0;
var pp = GraphicsDevice.PresentationParameters;
ImGui.SetNextWindowSize(new Vector2(pp.BackBufferWidth - 20, pp.BackBufferHeight - 50), SetCondition.Once);
ImGui.SetNextWindowPosCenter(SetCondition.Always);
ImGui.BeginWindow("Hello from ImGui!", ref _mainWindowOpened, WindowFlags.Default);
ImGui.BeginMainMenuBar();
if (ImGui.BeginMenu("Help"))
{
if (ImGui.MenuItem("About", "Ctrl-Alt-A", false, true))
{
}
ImGui.EndMenu();
}
ImGui.EndMainMenuBar();
var pos = ImGui.GetIO().MousePosition;
var leftPressed = ImGui.GetIO().MouseDown[0];
ImGui.Text("Current mouse position: " + pos + ". Pressed=" + leftPressed);
if (ImGui.Button("Increment the counter."))
_pressCount += 1;
ImGui.Text($"Button pressed {_pressCount} times.", new Vector4(0, 1, 1, 1));
ImGui.Text("Input some text:");
ImGui.InputTextMultiline(string.Empty,
TextInputBuffer, (uint) TextInputBufferLength,
new Vector2(360, 240),
InputTextFlags.Default,
OnTextEdited);
ImGui.SliderFloat("SlidableValue", ref _sliderVal, -50f, 100f, $"{_sliderVal:##0.00}", 1.0f);
ImGui.DragVector3("Vector3", ref _positionValue, -100, 100);
if (ImGui.TreeNode("First Item"))
{
ImGui.Text("Word!");
ImGui.TreePop();
}
if (ImGui.TreeNode("Second Item"))
{
ImGui.ColorButton(_buttonColor, false, true);
if (ImGui.Button("Push me to change color", new Vector2(120, 30)))
{
_buttonColor = new Vector4(_buttonColor.Y + .25f, _buttonColor.Z, _buttonColor.X, _buttonColor.W);
if (_buttonColor.X > 1.0f)
{
_buttonColor.X -= 1.0f;
}
}
ImGui.TreePop();
}
if (ImGui.Button("Press me!", new Vector2(100, 30)))
ImGuiNative.igOpenPopup("SmallButtonPopup");
if (ImGui.BeginPopup("SmallButtonPopup"))
{
ImGui.Text("Here's a popup menu.");
ImGui.Text("With two lines.");
ImGui.EndPopup();
}
if (ImGui.Button("Open Modal window"))
ImGui.OpenPopup("ModalPopup");
if (ImGui.BeginPopupModal("ModalPopup"))
{
ImGui.Text("You can't press on anything else right now.");
ImGui.Text("You are stuck here.");
if (ImGui.Button("OK", new Vector2(0, 0))) { }
ImGui.SameLine();
ImGui.Dummy(100f, 0f);
ImGui.SameLine();
if (ImGui.Button("Please go away", new Vector2(0, 0))) { ImGui.CloseCurrentPopup(); }
ImGui.EndPopup();
}
ImGui.Text("I have a context menu.");
if (ImGui.BeginPopupContextItem("ItemContextMenu"))
{
if (ImGui.Selectable("How's this for a great menu?")) { }
ImGui.Selectable("Just click somewhere to get rid of me.");
ImGui.EndPopup();
}
ImGui.EndWindow();
_memoryEditor.Draw("Memory editor", _memoryEditorData, _memoryEditorData.Length);
}
public unsafe int OnTextEdited(TextEditCallbackData* data)
{
var currentEventChar = (char)data->EventChar;
return 0;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace ImGuiNET
{
public sealed class ImGuiMg : IDisposable
{
private readonly GraphicsDevice _graphicsDevice;
private readonly List<Texture2D> _textures;
private readonly RasterizerState _rasterizerState;
private readonly SpriteEffect _spriteEffect;
private KeyboardState _prevKeyState;
private int _sFontTexture;
private float _wheelPosition;
private readonly float _scaleFactor;
public ImGuiMg(GameWindow gw, GraphicsDevice gd)
{
_graphicsDevice = gd;
_textures = new List<Texture2D>();
_rasterizerState = new RasterizerState
{
CullMode = CullMode.None,
ScissorTestEnable = true
};
_spriteEffect = new SpriteEffect(gd);
int desiredWidth = 960, desiredHeight = 540;
_scaleFactor = (float) gd.PresentationParameters.BackBufferWidth / desiredWidth;
ImGui.GetIO().FontAtlas.AddDefaultFont();
SetKeyMappings();
gw.TextInput += OnKeyPress;
CreateDeviceObjects();
}
#region Keys
private void OnKeyPress(object sender, TextInputEventArgs e)
{
Console.Write("Char typed: " + e.Character);
ImGui.AddInputCharacter(e.Character);
}
private static unsafe void SetKeyMappings()
{
var io = ImGui.GetIO();
io.KeyMap[GuiKey.Tab] = (int)Keys.Tab;
io.KeyMap[GuiKey.LeftArrow] = (int)Keys.Left;
io.KeyMap[GuiKey.RightArrow] = (int)Keys.Right;
io.KeyMap[GuiKey.UpArrow] = (int)Keys.Up;
io.KeyMap[GuiKey.DownArrow] = (int)Keys.Down;
io.KeyMap[GuiKey.PageUp] = (int)Keys.PageUp;
io.KeyMap[GuiKey.PageDown] = (int)Keys.PageDown;
io.KeyMap[GuiKey.Home] = (int)Keys.Home;
io.KeyMap[GuiKey.End] = (int)Keys.End;
io.KeyMap[GuiKey.Delete] = (int)Keys.Delete;
io.KeyMap[GuiKey.Backspace] = (int)Keys.Back;
io.KeyMap[GuiKey.Enter] = (int)Keys.Enter;
io.KeyMap[GuiKey.Escape] = (int)Keys.Escape;
io.KeyMap[GuiKey.A] = (int)Keys.A;
io.KeyMap[GuiKey.C] = (int)Keys.C;
io.KeyMap[GuiKey.V] = (int)Keys.V;
io.KeyMap[GuiKey.X] = (int)Keys.X;
io.KeyMap[GuiKey.Y] = (int)Keys.Y;
io.KeyMap[GuiKey.Z] = (int)Keys.Z;
}
private static void UpdateKeys(KeyboardState prev, KeyboardState current)
{
// Keys down
foreach (var k in current.GetPressedKeys())
ImGui.GetIO().KeysDown[(int) k] = true;
// Keys up
foreach (var k in prev.GetPressedKeys())
{
if (!current.GetPressedKeys().Contains(k))
ImGui.GetIO().KeysDown[(int) k] = false;
}
UpdateModifiers(current);
}
private static void UpdateModifiers(KeyboardState state)
{
var io = ImGui.GetIO();
io.AltPressed = state.IsKeyDown(Keys.LeftAlt) || state.IsKeyDown(Keys.RightAlt);
io.CtrlPressed = state.IsKeyDown(Keys.LeftControl) || state.IsKeyDown(Keys.RightControl);
io.ShiftPressed = state.IsKeyDown(Keys.LeftShift) || state.IsKeyDown(Keys.RightShift);
}
#endregion
private unsafe void CreateDeviceObjects()
{
var io = ImGui.GetIO();
// Build texture atlas
var texData = io.FontAtlas.GetTexDataAsRGBA32();
var tex = new Texture2D(_graphicsDevice, texData.Width, texData.Height);
if (texData.BytesPerPixel != 4)
throw new Exception();
var pixelCount = texData.Width * texData.Height;
var pixelData = new Color[pixelCount];
for (var i = 0; i < pixelCount; i++)
pixelData[i] = new Color((int) texData.Pixels[4 * i], (int) texData.Pixels[4 * i + 1],
(int) texData.Pixels[4 * i + 2], (int) texData.Pixels[4 * i + 3]);
tex.SetData(pixelData);
_sFontTexture = _textures.Count;
_textures.Add(tex);
io.FontAtlas.SetTexID(_sFontTexture);
// Store the texture identifier in the ImFontAtlas substructure.
io.FontAtlas.SetTexID(_sFontTexture);
// Cleanup (don't clear the input data if you want to append new fonts later)
//io.Fonts->ClearInputData();
io.FontAtlas.ClearTexData();
}
public void Update(GameTime gameTime)
{
var io = ImGui.GetIO();
io.DeltaTime = (float) gameTime.ElapsedGameTime.TotalSeconds;
UpdateImGuiInput(io);
var pp = _graphicsDevice.PresentationParameters;
io.DisplaySize = new Vector2(pp.BackBufferWidth, pp.BackBufferHeight);
io.DisplayFramebufferScale = new Vector2(_scaleFactor);
ImGui.NewFrame();
}
public unsafe void Draw()
{
ImGui.Render();
var data = ImGui.GetDrawData();
RenderImDrawData(data);
}
private void UpdateImGuiInput(IO io)
{
var kbState = Keyboard.GetState();
UpdateKeys(_prevKeyState, kbState);
var mouseState = Mouse.GetState();
var windowPoint = mouseState.Position;
io.MousePosition = new Vector2(windowPoint.X / io.DisplayFramebufferScale.X, windowPoint.Y / io.DisplayFramebufferScale.Y);
io.MouseDown[0] = mouseState.LeftButton == ButtonState.Pressed;
io.MouseDown[1] = mouseState.RightButton == ButtonState.Pressed;
io.MouseDown[2] = mouseState.MiddleButton == ButtonState.Pressed;
float newWheelPos = mouseState.ScrollWheelValue;
var delta = newWheelPos - _wheelPosition;
_wheelPosition = newWheelPos;
io.MouseWheel = delta;
_prevKeyState = kbState;
}
private unsafe void RenderImDrawData(DrawData* drawData)
{
if (drawData == null)
{
Console.WriteLine("No frame rendered for ImGUI.NET, but draw was called.");
return;
}
var pp = _graphicsDevice.PresentationParameters;
// Rendering
var displayW = pp.BackBufferWidth;
var displayH = pp.BackBufferHeight;
var clearColor = new Vector4(114f / 255f, 144f / 255f, 154f / 255f, 1.0f);
_graphicsDevice.Viewport = new Viewport(0, 0, displayW, displayH);
_graphicsDevice.Clear(new Color(clearColor.X, clearColor.Y, clearColor.Z, clearColor.W));
// We are using the OpenGL fixed pipeline to make the example code simpler to read!
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers.
_graphicsDevice.BlendState = BlendState.NonPremultiplied;
_graphicsDevice.RasterizerState = _rasterizerState;
_graphicsDevice.DepthStencilState = DepthStencilState.None;
// Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays)
var io = ImGui.GetIO();
ImGui.ScaleClipRects(drawData, io.DisplayFramebufferScale);
// Render command lists
for (var n = 0; n < drawData->CmdListsCount; n++)
{
var cmdList = drawData->CmdLists[n];
var vtxBuffer = (DrawVert*)cmdList->VtxBuffer.Data;
var idxBuffer = (ushort*)cmdList->IdxBuffer.Data;
var vertices = new VertexPositionColorTexture[cmdList->VtxBuffer.Size];
for (var i = 0; i < vertices.Length; i++)
{
vertices[i] = new VertexPositionColorTexture(
new Microsoft.Xna.Framework.Vector3(vtxBuffer[i].pos.X, vtxBuffer[i].pos.Y, 0),
new Color(vtxBuffer[i].col),
new Microsoft.Xna.Framework.Vector2(vtxBuffer[i].uv.X, vtxBuffer[i].uv.Y));
}
_spriteEffect.CurrentTechnique.Passes[0].Apply();
for (var cmdI = 0; cmdI < cmdList->CmdBuffer.Size; cmdI++)
{
var pcmd = &(((DrawCmd*)cmdList->CmdBuffer.Data)[cmdI]);
if (pcmd->UserCallback != IntPtr.Zero)
{
throw new NotImplementedException();
}
else
{
_graphicsDevice.Textures[0] = _textures[pcmd->TextureId.ToInt32()];
_graphicsDevice.ScissorRectangle = new Rectangle(
(int)pcmd->ClipRect.X,
(int)pcmd->ClipRect.Y,
(int)(pcmd->ClipRect.Z - pcmd->ClipRect.X),
(int)(pcmd->ClipRect.W - pcmd->ClipRect.Y));
var indices = new short[pcmd->ElemCount];
for (var i = 0; i < indices.Length; i++) { indices[i] = (short) idxBuffer[i]; }
_graphicsDevice.DrawUserIndexedPrimitives(
PrimitiveType.TriangleList, vertices, 0,
vertices.Length, indices, 0, (int) pcmd->ElemCount / 3);
}
idxBuffer += pcmd->ElemCount;
}
}
}
public void Dispose()
{
_rasterizerState.Dispose();
_spriteEffect.Dispose();
}
}
}
using System;
using System.Globalization;
namespace ImGuiNET
{
// C# port of ocornut's imgui_memory_editor.h - https://gist.github.com/ocornut/0673e37e54aff644298b
// Mini memory editor for ImGui (to embed in your game/tools)
// v0.10
// Animated gif: https://cloud.githubusercontent.com/assets/8225057/9028162/3047ef88-392c-11e5-8270-a54f8354b208.gif
//
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity! If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before caling this.
//
// Usage:
// static MemoryEditor memory_editor; // save your state somewhere
// memory_editor.Draw("Memory Editor", mem_block, mem_block_size, (size_t)mem_block); // run
public class MemoryEditor
{
readonly bool _allowEdits;
int _rows;
int _dataEditingAddr;
bool _dataEditingTakeFocus;
readonly byte[] _dataInput = new byte[32];
readonly byte[] _addrInput = new byte[32];
public MemoryEditor()
{
_rows = 16;
_dataEditingAddr = -1;
_dataEditingTakeFocus = false;
_allowEdits = true;
}
private static string FixedHex(int v, int count)
{
return v.ToString("X").PadLeft(count, '0');
}
private static bool TryHexParse(byte[] bytes, out int result)
{
string input = System.Text.Encoding.UTF8.GetString(bytes).ToString();
return int.TryParse(input, NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out result);
}
private static void ReplaceChars(byte[] bytes, string input)
{
var address = System.Text.Encoding.ASCII.GetBytes(input);
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (i < address.Length) ? address[i] : (byte)0;
}
}
public unsafe void Draw(string title, byte[] memData, int memSize, int baseDisplayAddr = 0)
{
if (!ImGui.BeginWindow(title))
{
ImGui.EndWindow();
return;
}
float lineHeight = ImGuiNative.igGetTextLineHeight();
int lineTotalCount = (memSize + _rows - 1) / _rows;
ImGuiNative.igSetNextWindowContentSize(new Vector2(0.0f, lineTotalCount * lineHeight));
ImGui.BeginChild("##scrolling", new Vector2(0, -ImGuiNative.igGetItemsLineHeightWithSpacing()), false, 0);
ImGui.PushStyleVar(StyleVar.FramePadding, new Vector2(0, 0));
ImGui.PushStyleVar(StyleVar.ItemSpacing, new Vector2(0, 0));
int addrDigitsCount = 0;
for (int n = baseDisplayAddr + memSize - 1; n > 0; n >>= 4)
addrDigitsCount++;
float glyphWidth = ImGui.GetTextSize("F").X;
float cellWidth = glyphWidth * 3; // "FF " we include trailing space in the width to easily catch clicks everywhere
var clipper = new ImGuiListClipper(lineTotalCount, lineHeight);
int visibleStartAddr = clipper.DisplayStart * _rows;
int visibleEndAddr = clipper.DisplayEnd * _rows;
bool dataNext = false;
if (!_allowEdits || _dataEditingAddr >= memSize)
_dataEditingAddr = -1;
int dataEditingAddrBackup = _dataEditingAddr;
if (_dataEditingAddr != -1)
{
if (ImGui.IsKeyPressed(ImGui.GetKeyIndex(GuiKey.UpArrow)) && _dataEditingAddr >= _rows) { _dataEditingAddr -= _rows; _dataEditingTakeFocus = true; }
else if (ImGui.IsKeyPressed(ImGui.GetKeyIndex(GuiKey.DownArrow)) && _dataEditingAddr < memSize - _rows) { _dataEditingAddr += _rows; _dataEditingTakeFocus = true; }
else if (ImGui.IsKeyPressed(ImGui.GetKeyIndex(GuiKey.LeftArrow)) && _dataEditingAddr > 0) { _dataEditingAddr -= 1; _dataEditingTakeFocus = true; }
else if (ImGui.IsKeyPressed(ImGui.GetKeyIndex(GuiKey.RightArrow)) && _dataEditingAddr < memSize - 1) { _dataEditingAddr += 1; _dataEditingTakeFocus = true; }
}
if ((_dataEditingAddr / _rows) != (dataEditingAddrBackup / _rows))
{
// Track cursor movements
float scrollOffset = ((_dataEditingAddr / _rows) - (dataEditingAddrBackup / _rows)) * lineHeight;
bool scrollDesired = (scrollOffset < 0.0f && _dataEditingAddr < visibleStartAddr + _rows * 2) || (scrollOffset > 0.0f && _dataEditingAddr > visibleEndAddr - _rows * 2);
if (scrollDesired)
ImGuiNative.igSetScrollY(ImGuiNative.igGetScrollY() + scrollOffset);
}
for (int lineI = clipper.DisplayStart; lineI < clipper.DisplayEnd; lineI++) // display only visible items
{
int addr = lineI * _rows;
ImGui.Text(FixedHex(baseDisplayAddr + addr, addrDigitsCount) + ": ");
ImGui.SameLine();
// Draw Hexadecimal
float lineStartX = ImGuiNative.igGetCursorPosX();
for (int n = 0; n < _rows && addr < memSize; n++, addr++)
{
ImGui.SameLine(lineStartX + cellWidth * n);
if (_dataEditingAddr == addr)
{
// Display text input on current byte
ImGui.PushID(addr);
// FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious.
TextEditCallback callback = (data) =>
{
int* pCursorPos = (int*)data->UserData;
if (!data->HasSelection())
*pCursorPos = data->CursorPos;
return 0;
};
int cursorPos = -1;
bool dataWrite = false;
if (_dataEditingTakeFocus)
{
ImGui.SetKeyboardFocusHere();
ReplaceChars(_dataInput, FixedHex(memData[addr], 2));
ReplaceChars(_addrInput, FixedHex(baseDisplayAddr + addr, addrDigitsCount));
}
ImGui.PushItemWidth(ImGui.GetTextSize("FF").X);
var flags = InputTextFlags.CharsHexadecimal | InputTextFlags.EnterReturnsTrue | InputTextFlags.AutoSelectAll | InputTextFlags.NoHorizontalScroll | InputTextFlags.AlwaysInsertMode | InputTextFlags.CallbackAlways;
if (ImGui.InputText("##data", _dataInput, 32, flags, callback, new IntPtr(&cursorPos)))
dataWrite = dataNext = true;
else if (!_dataEditingTakeFocus && !ImGui.IsLastItemActive())
_dataEditingAddr = -1;
_dataEditingTakeFocus = false;
ImGui.PopItemWidth();
if (cursorPos >= 2)
dataWrite = dataNext = true;
if (dataWrite)
{
int data;
if (TryHexParse(_dataInput, out data))
memData[addr] = (byte)data;
}
ImGui.PopID();
}
else
{
ImGui.Text(FixedHex(memData[addr], 2));
if (_allowEdits && ImGui.IsLastItemHovered() && ImGui.IsMouseClicked(0))
{
_dataEditingTakeFocus = true;
_dataEditingAddr = addr;
}
}
}
ImGui.SameLine(lineStartX + cellWidth * _rows + glyphWidth * 2);
//separator line drawing replaced by printing a pipe char
// Draw ASCII values
addr = lineI * _rows;
var asciiVal = new System.Text.StringBuilder(2 + _rows);
asciiVal.Append("| ");
for (int n = 0; n < _rows && addr < memSize; n++, addr++)
{
int c = memData[addr];
asciiVal.Append((c >= 32 && c < 128) ? Convert.ToChar(c) : '.');
}
ImGui.TextUnformatted(asciiVal.ToString()); //use unformatted, so string can contain the '%' character
}
//clipper.End(); //not implemented
ImGui.PopStyleVar(2);
ImGui.EndChild();
if (dataNext && _dataEditingAddr < memSize)
{
_dataEditingAddr = _dataEditingAddr + 1;
_dataEditingTakeFocus = true;
}
ImGui.Separator();
ImGuiNative.igAlignFirstTextHeightToWidgets();
ImGui.PushItemWidth(50);
ImGuiNative.igPushAllowKeyboardFocus(false);
int rowsBackup = _rows;
if (ImGui.DragInt("##rows", ref _rows, 0.2f, 4, 32, "%.0f rows"))
{
if (_rows <= 0) _rows = 4;
Vector2 newWindowSize = ImGui.GetWindowSize();
newWindowSize.X += (_rows - rowsBackup) * (cellWidth + glyphWidth);
ImGui.SetWindowSize(newWindowSize);
}
ImGuiNative.igPopAllowKeyboardFocus();
ImGui.PopItemWidth();
ImGui.SameLine();
ImGui.Text(string.Format(" Range {0}..{1} ", FixedHex(baseDisplayAddr, addrDigitsCount),
FixedHex(baseDisplayAddr + memSize - 1, addrDigitsCount)));
ImGui.SameLine();
ImGui.PushItemWidth(70);
if (ImGui.InputText("##addr", _addrInput, 32, InputTextFlags.CharsHexadecimal | InputTextFlags.EnterReturnsTrue, null))
{
int gotoAddr;
if (TryHexParse(_addrInput, out gotoAddr))
{
gotoAddr -= baseDisplayAddr;
if (gotoAddr >= 0 && gotoAddr < memSize)
{
ImGui.BeginChild("##scrolling");
ImGuiNative.igSetScrollFromPosY(ImGui.GetCursorStartPos().Y + (gotoAddr / _rows) * ImGuiNative.igGetTextLineHeight());
ImGui.EndChild();
_dataEditingAddr = gotoAddr;
_dataEditingTakeFocus = true;
}
}
}
ImGui.PopItemWidth();
ImGui.EndWindow();
}
}
//Not a proper translation, because ImGuiListClipper uses imgui's internal api.
//Thus SetCursorPosYAndSetupDummyPrevLine isn't reimplemented, but SetCursorPosY + SetNextWindowContentSize seems to be working well instead.
//TODO expose clipper through newer cimgui version
internal class ImGuiListClipper
{
public float StartPosY;
public float ItemsHeight;
public int ItemsCount, StepNo, DisplayStart, DisplayEnd;
public ImGuiListClipper(int itemsCount = -1, float itemsHeight = -1.0f)
{
Begin(itemsCount, itemsHeight);
}
public void Begin(int count, float itemsHeight = -1.0f)
{
StartPosY = ImGuiNative.igGetCursorPosY();
ItemsHeight = itemsHeight;
ItemsCount = count;
StepNo = 0;
DisplayEnd = DisplayStart = -1;
if (ItemsHeight > 0.0f)
{
ImGui.CalcListClipping(ItemsCount, ItemsHeight, ref DisplayStart, ref DisplayEnd); // calculate how many to clip/display
if (DisplayStart > 0)
//SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
ImGuiNative.igSetCursorPosY(StartPosY + DisplayStart * ItemsHeight);
StepNo = 2;
}
}
}
}
@moolicc
Copy link

moolicc commented Jul 11, 2018

Thanks for this!
I forked and updated this to the latest imgui.net nuget package (0.47), btw.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment