OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / src / extern / imgui_club / imgui_memory_editor.h
41.2 KiB · Objective-C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Mini memory editor for Dear ImGui (to embed in your game/tools)
2
// Get latest version at http://www.github.com/ocornut/imgui_club
3
// Licensed under The MIT License (MIT)
4
5
// Right-click anywhere to access the Options menu!
6
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
7
// The code assume a mono-space font for simplicity!
8
// If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before calling this.
9
//
10
// Usage:
11
//   // Create a window and draw memory editor inside it:
12
//   static MemoryEditor mem_edit_1;
13
//   static char data[0x10000];
14
//   size_t data_size = 0x10000;
15
//   mem_edit_1.DrawWindow("Memory Editor", data, data_size);
16
//
17
// Usage:
18
//   // If you already have a window, use DrawContents() instead:
19
//   static MemoryEditor mem_edit_2;
20
//   ImGui::Begin("MyWindow")
21
//   mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this);
22
//   ImGui::End();
23
//
24
// Changelog:
25
// - v0.10: initial version
26
// - v0.23 (2017/08/17): added to github. fixed right-arrow triggering a byte write.
27
// - v0.24 (2018/06/02): changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61).
28
// - v0.25 (2018/07/11): fixed wording: all occurrences of "Rows" renamed to "Columns".
29
// - v0.26 (2018/08/02): fixed clicking on hex region
30
// - v0.30 (2018/08/02): added data preview for common data types
31
// - v0.31 (2018/10/10): added OptUpperCaseHex option to select lower/upper casing display [@samhocevar]
32
// - v0.32 (2018/10/10): changed signatures to use void* instead of unsigned char*
33
// - v0.33 (2018/10/10): added OptShowOptions option to hide all the interactive option setting.
34
// - v0.34 (2019/05/07): binary preview now applies endianness setting [@nicolasnoble]
35
// - v0.35 (2020/01/29): using ImGuiDataType available since Dear ImGui 1.69.
36
// - v0.36 (2020/05/05): minor tweaks, minor refactor.
37
// - v0.40 (2020/10/04): fix misuse of ImGuiListClipper API, broke with Dear ImGui 1.79. made cursor position appears on left-side of edit box. option popup appears on mouse release. fix MSVC warnings where _CRT_SECURE_NO_WARNINGS wasn't working in recent versions.
38
// - v0.41 (2020/10/05): fix when using with keyboard/gamepad navigation enabled.
39
// - v0.42 (2020/10/14): fix for . character in ASCII view always being greyed out.
40
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel]
41
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
42
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
43
// - v0.51 (2024/02/22): fix for layout change in 1.89 when using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#34)
44
// - v0.52 (2024/03/08): removed unnecessary GetKeyIndex() calls, they are a no-op since 1.87.
45
// - v0.53 (2024/05/27): fixed right-click popup from not appearing when using DrawContents(). warning fixes. (#35)
46
// - v0.54 (2024/07/29): allow ReadOnly mode to still select and preview data. (#46) [@DeltaGW2])
47
// - v0.55 (2024/08/19): added BgColorFn to allow setting background colors independently from highlighted selection. (#27) [@StrikerX3]
48
//                       added MouseHoveredAddr public readable field. (#47, #27) [@StrikerX3]
49
//                       fixed a data preview crash with 1.91.0 WIP. fixed contiguous highlight color when using data preview.
50
//                       *BREAKING* added UserData field passed to all optional function handlers: ReadFn, WriteFn, HighlightFn, BgColorFn. (#50) [@silverweed]
51
// - v0.56 (2024/11/04): fixed MouseHovered, MouseHoveredAddr not being set when hovering a byte being edited. (#54)
52
// - v0.57 (2025/03/26): fixed warnings. using ImGui's ImSXX/ImUXX types instead of e.g. int32_t/uint32_t. (#56)
53
// - v0.58 (2025/03/31): fixed extraneous footer spacing (added in 0.51) breaking vertical auto-resize. (#53)
54
// - v0.59 (2025/04/08): fixed GotoAddrAndHighlight() not working if OptShowOptions is disabled.
55
//
56
// TODO:
57
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day.
58
// - PageUp/PageDown are not supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
59
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame.
60
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
61
62
#pragma once
63
64
#include <stdio.h>      // sprintf, scanf
65
#include <stdint.h>     // uint8_t, etc.
66
67
#if defined(_MSC_VER) && !defined(snprintf)
68
#define ImSnprintf  _snprintf
69
#else
70
#define ImSnprintf  snprintf
71
#endif
72
#if defined(_MSC_VER) && !defined(__clang__)
73
#define _PRISizeT   "I"
74
#else
75
#define _PRISizeT   "z"
76
#endif
77
78
#if defined(_MSC_VER) || defined(_UCRT)
79
#pragma warning (push)
80
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
81
#endif
82
83
struct MemoryEditor
84
{
85
    enum DataFormat
86
    {
87
        DataFormat_Bin = 0,
88
        DataFormat_Dec = 1,
89
        DataFormat_Hex = 2,
90
        DataFormat_COUNT
91
    };
92
93
    // Settings
94
    bool            Open;                                       // = true   // set to false when DrawWindow() was closed. ignore if not using DrawWindow().
95
    bool            ReadOnly;                                   // = false  // disable any editing.
96
    int             Cols;                                       // = 16     // number of columns to display.
97
    bool            OptShowOptions;                             // = true   // display options button/context menu. when disabled, options will be locked unless you provide your own UI for them.
98
    bool            OptShowDataPreview;                         // = false  // display a footer previewing the decimal/binary/hex/float representation of the currently selected bytes.
99
    bool            OptShowHexII;                               // = false  // display values in HexII representation instead of regular hexadecimal: hide null/zero bytes, ascii values as ".X".
100
    bool            OptShowAscii;                               // = true   // display ASCII representation on the right side.
101
    bool            OptGreyOutZeroes;                           // = true   // display null/zero bytes using the TextDisabled color.
102
    bool            OptUpperCaseHex;                            // = true   // display hexadecimal values as "FF" instead of "ff".
103
    int             OptMidColsCount;                            // = 8      // set to 0 to disable extra spacing between every mid-cols.
104
    int             OptAddrDigitsCount;                         // = 0      // number of addr digits to display (default calculated based on maximum displayed addr).
105
    float           OptFooterExtraHeight;                       // = 0      // space to reserve at the bottom of the widget to add custom widgets
106
    ImU32           HighlightColor;                             //          // background color of highlighted bytes.
107
108
    // Function handlers
109
    ImU8            (*ReadFn)(const ImU8* mem, size_t off, void* user_data);      // = 0      // optional handler to read bytes.
110
    void            (*WriteFn)(ImU8* mem, size_t off, ImU8 d, void* user_data);   // = 0      // optional handler to write bytes.
111
    bool            (*HighlightFn)(const ImU8* mem, size_t off, void* user_data); // = 0      // optional handler to return Highlight property (to support non-contiguous highlighting).
112
    ImU32           (*BgColorFn)(const ImU8* mem, size_t off, void* user_data);   // = 0      // optional handler to return custom background color of individual bytes.
113
    void*           UserData;                                                     // = NULL   // user data forwarded to the function handlers
114
115
    // Public read-only data
116
    bool            MouseHovered;                               // set when mouse is hovering a value.
117
    size_t          MouseHoveredAddr;                           // the address currently being hovered if MouseHovered is set.
118
119
    // [Internal State]
120
    bool            ContentsWidthChanged;
121
    size_t          DataPreviewAddr;
122
    size_t          DataEditingAddr;
123
    bool            DataEditingTakeFocus;
124
    char            DataInputBuf[32];
125
    char            AddrInputBuf[32];
126
    size_t          GotoAddr;
127
    size_t          HighlightMin, HighlightMax;
128
    int             PreviewEndianness;
129
    ImGuiDataType   PreviewDataType;
130
131
    MemoryEditor()
132
    {
133
        // Settings
134
        Open = true;
135
        ReadOnly = false;
136
        Cols = 16;
137
        OptShowOptions = true;
138
        OptShowDataPreview = false;
139
        OptShowHexII = false;
140
        OptShowAscii = true;
141
        OptGreyOutZeroes = true;
142
        OptUpperCaseHex = true;
143
        OptMidColsCount = 8;
144
        OptAddrDigitsCount = 0;
145
        OptFooterExtraHeight = 0.0f;
146
        HighlightColor = IM_COL32(255, 255, 255, 50);
147
        ReadFn = nullptr;
148
        WriteFn = nullptr;
149
        HighlightFn = nullptr;
150
        BgColorFn = nullptr;
151
        UserData = nullptr;
152
153
        // State/Internals
154
        ContentsWidthChanged = false;
155
        DataPreviewAddr = DataEditingAddr = (size_t)-1;
156
        DataEditingTakeFocus = false;
157
        memset(DataInputBuf, 0, sizeof(DataInputBuf));
158
        memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
159
        GotoAddr = (size_t)-1;
160
        MouseHovered = false;
161
        MouseHoveredAddr = 0;
162
        HighlightMin = HighlightMax = (size_t)-1;
163
        PreviewEndianness = 0;
164
        PreviewDataType = ImGuiDataType_S32;
165
    }
166
167
    void GotoAddrAndHighlight(size_t addr_min, size_t addr_max)
168
    {
169
        GotoAddr = addr_min;
170
        HighlightMin = addr_min;
171
        HighlightMax = addr_max;
172
    }
173
174
    struct Sizes
175
    {
176
        int     AddrDigitsCount;            // Number of digits required to represent maximum address.
177
        float   LineHeight;                 // Height of each line (no spacing).
178
        float   GlyphWidth;                 // Glyph width (assume mono-space).
179
        float   HexCellWidth;               // Width of a hex edit cell ~2.5f * GlypHWidth.
180
        float   SpacingBetweenMidCols;      // Spacing between each columns section (OptMidColsCount).
181
        float   OffsetHexMinX;
182
        float   OffsetHexMaxX;
183
        float   OffsetAsciiMinX;
184
        float   OffsetAsciiMaxX;
185
        float   WindowWidth;                // Ideal window width.
186
187
        Sizes() { memset(this, 0, sizeof(*this)); }
188
    };
189
190
    void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr)
191
    {
192
        ImGuiStyle& style = ImGui::GetStyle();
193
        s.AddrDigitsCount = OptAddrDigitsCount;
194
        if (s.AddrDigitsCount == 0)
195
            for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4)
196
                s.AddrDigitsCount++;
197
        s.LineHeight = ImGui::GetTextLineHeight();
198
        s.GlyphWidth = ImGui::CalcTextSize("F").x + 1;                  // We assume the font is mono-space
199
        s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f);             // "FF " we include trailing space in the width to easily catch clicks everywhere
200
        s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
201
        s.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
202
        s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * Cols);
203
        s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
204
        if (OptShowAscii)
205
        {
206
            s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
207
            if (OptMidColsCount > 0)
208
                s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
209
            s.OffsetAsciiMaxX = s.OffsetAsciiMinX + Cols * s.GlyphWidth;
210
        }
211
        s.WindowWidth = s.OffsetAsciiMaxX + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
212
    }
213
214
    // Standalone Memory Editor window
215
    void DrawWindow(const char* title, void* mem_data, size_t mem_size, size_t base_display_addr = 0x0000)
216
    {
217
        Sizes s;
218
        CalcSizes(s, mem_size, base_display_addr);
219
        ImGui::SetNextWindowSize(ImVec2(s.WindowWidth, s.WindowWidth * 0.60f), ImGuiCond_FirstUseEver);
220
        ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX));
221
222
        Open = true;
223
        if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
224
        {
225
            DrawContents(mem_data, mem_size, base_display_addr);
226
            if (ContentsWidthChanged)
227
            {
228
                CalcSizes(s, mem_size, base_display_addr);
229
                ImGui::SetWindowSize(ImVec2(s.WindowWidth, ImGui::GetWindowSize().y));
230
            }
231
        }
232
        ImGui::End();
233
    }
234
235
    // Memory Editor contents only
236
    void DrawContents(void* mem_data_void, size_t mem_size, size_t base_display_addr = 0x0000)
237
    {
238
        if (Cols < 1)
239
            Cols = 1;
240
241
        ImU8* mem_data = (ImU8*)mem_data_void;
242
        Sizes s;
243
        CalcSizes(s, mem_size, base_display_addr);
244
        ImGuiStyle& style = ImGui::GetStyle();
245
246
        const ImVec2 contents_pos_start = ImGui::GetCursorScreenPos();
247
248
        // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
249
        // This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
250
        const float height_separator = style.ItemSpacing.y;
251
        float footer_height = OptFooterExtraHeight;
252
        if (OptShowOptions)
253
            footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
254
        if (OptShowDataPreview)
255
            footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
256
        ImGui::BeginChild("##scrolling", ImVec2(-FLT_MIN, -footer_height), ImGuiChildFlags_None, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
257
        ImDrawList* draw_list = ImGui::GetWindowDrawList();
258
259
        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
260
        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
261
262
        // We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
263
        const ImVec2 avail_size = ImGui::GetContentRegionAvail();
264
        const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
265
        ImGuiListClipper clipper;
266
        clipper.Begin(line_total_count, s.LineHeight);
267
268
        bool data_next = false;
269
270
        if (DataEditingAddr >= mem_size)
271
            DataEditingAddr = (size_t)-1;
272
        if (DataPreviewAddr >= mem_size)
273
            DataPreviewAddr = (size_t)-1;
274
275
        size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0;
276
277
        size_t data_editing_addr_next = (size_t)-1;
278
        if (DataEditingAddr != (size_t)-1)
279
        {
280
            // Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
281
            if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols)                 { data_editing_addr_next = DataEditingAddr - Cols; }
282
            else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols){ data_editing_addr_next = DataEditingAddr + Cols; }
283
            else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0)              { data_editing_addr_next = DataEditingAddr - 1; }
284
            else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1)  { data_editing_addr_next = DataEditingAddr + 1; }
285
        }
286
287
        // Draw vertical separator
288
        ImVec2 window_pos = ImGui::GetWindowPos();
289
        if (OptShowAscii)
290
            draw_list->AddLine(ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
291
292
        const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
293
        const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
294
295
        const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: ";
296
        const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x";
297
        const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
298
        const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
299
300
        MouseHovered = false;
301
        MouseHoveredAddr = 0;
302
303
        while (clipper.Step())
304
            for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
305
            {
306
                size_t addr = (size_t)line_i * Cols;
307
                ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
308
309
                // Draw Hexadecimal
310
                for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
311
                {
312
                    float byte_pos_x = s.OffsetHexMinX + s.HexCellWidth * n;
313
                    if (OptMidColsCount > 0)
314
                        byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
315
                    ImGui::SameLine(byte_pos_x);
316
317
                    // Draw highlight or custom background color
318
                    const bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
319
                    const bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, UserData));
320
                    const bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
321
322
                    ImU32 bg_color = 0;
323
                    bool is_next_byte_highlighted = false;
324
                    if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
325
                    {
326
                        is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1, UserData)) || (addr + 1 < DataPreviewAddr + preview_data_type_size));
327
                        bg_color = HighlightColor;
328
                    }
329
                    else if (BgColorFn != nullptr)
330
                    {
331
                        is_next_byte_highlighted = (addr + 1 < mem_size) && ((BgColorFn(mem_data, addr + 1, UserData) & IM_COL32_A_MASK) != 0);
332
                        bg_color = BgColorFn(mem_data, addr, UserData);
333
                    }
334
                    if (bg_color != 0)
335
                    {
336
                        float bg_width = s.GlyphWidth * 2;
337
                        if (is_next_byte_highlighted || (n + 1 == Cols))
338
                        {
339
                            bg_width = s.HexCellWidth;
340
                            if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0)
341
                                bg_width += s.SpacingBetweenMidCols;
342
                        }
343
                        ImVec2 pos = ImGui::GetCursorScreenPos();
344
                        draw_list->AddRectFilled(pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color);
345
                    }
346
347
                    if (DataEditingAddr == addr)
348
                    {
349
                        // Display text input on current byte
350
                        bool data_write = false;
351
                        ImGui::PushID((void*)addr);
352
                        if (DataEditingTakeFocus)
353
                        {
354
                            ImGui::SetKeyboardFocusHere(0);
355
                            ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount, base_display_addr + addr);
356
                            ImSnprintf(DataInputBuf, 32, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
357
                        }
358
                        struct InputTextUserData
359
                        {
360
                            // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
361
                            static int Callback(ImGuiInputTextCallbackData* data)
362
                            {
363
                                InputTextUserData* user_data = (InputTextUserData*)data->UserData;
364
                                if (!data->HasSelection())
365
                                    user_data->CursorPos = data->CursorPos;
366
#if IMGUI_VERSION_NUM < 19102
367
                                if (data->Flags & ImGuiInputTextFlags_ReadOnly)
368
                                    return 0;
369
#endif
370
                                if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
371
                                {
372
                                    // When not editing a byte, always refresh its InputText content pulled from underlying memory data
373
                                    // (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there)
374
                                    data->DeleteChars(0, data->BufTextLen);
375
                                    data->InsertChars(0, user_data->CurrentBufOverwrite);
376
                                    data->SelectionStart = 0;
377
                                    data->SelectionEnd = 2;
378
                                    data->CursorPos = 0;
379
                                }
380
                                return 0;
381
                            }
382
                            char   CurrentBufOverwrite[3];  // Input
383
                            int    CursorPos;               // Output
384
                        };
385
                        InputTextUserData input_text_user_data;
386
                        input_text_user_data.CursorPos = -1;
387
                        ImSnprintf(input_text_user_data.CurrentBufOverwrite, 3, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
388
                        ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
389
                        if (ReadOnly)
390
                            flags |= ImGuiInputTextFlags_ReadOnly;
391
                        flags |= ImGuiInputTextFlags_AlwaysOverwrite; // was ImGuiInputTextFlags_AlwaysInsertMode
392
                        ImGui::SetNextItemWidth(s.GlyphWidth * 2);
393
                        if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, InputTextUserData::Callback, &input_text_user_data))
394
                            data_write = data_next = true;
395
                        else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
396
                            DataEditingAddr = data_editing_addr_next = (size_t)-1;
397
                        DataEditingTakeFocus = false;
398
                        if (input_text_user_data.CursorPos >= 2)
399
                            data_write = data_next = true;
400
                        if (data_editing_addr_next != (size_t)-1)
401
                            data_write = data_next = false;
402
                        unsigned int data_input_value = 0;
403
                        if (!ReadOnly && data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
404
                        {
405
                            if (WriteFn)
406
                                WriteFn(mem_data, addr, (ImU8)data_input_value, UserData);
407
                            else
408
                                mem_data[addr] = (ImU8)data_input_value;
409
                        }
410
                        if (ImGui::IsItemHovered())
411
                        {
412
                            MouseHovered = true;
413
                            MouseHoveredAddr = addr;
414
                        }
415
                        ImGui::PopID();
416
                    }
417
                    else
418
                    {
419
                        // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
420
                        ImU8 b = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
421
422
                        if (OptShowHexII)
423
                        {
424
                            if ((b >= 32 && b < 128))
425
                                ImGui::Text(".%c ", b);
426
                            else if (b == 0xFF && OptGreyOutZeroes)
427
                                ImGui::TextDisabled("## ");
428
                            else if (b == 0x00)
429
                                ImGui::Text("   ");
430
                            else
431
                                ImGui::Text(format_byte_space, b);
432
                        }
433
                        else
434
                        {
435
                            if (b == 0 && OptGreyOutZeroes)
436
                                ImGui::TextDisabled("00 ");
437
                            else
438
                                ImGui::Text(format_byte_space, b);
439
                        }
440
                        if (ImGui::IsItemHovered())
441
                        {
442
                            MouseHovered = true;
443
                            MouseHoveredAddr = addr;
444
                            if (ImGui::IsMouseClicked(0))
445
                            {
446
                                DataEditingTakeFocus = true;
447
                                data_editing_addr_next = addr;
448
                            }
449
                        }
450
                    }
451
                }
452
453
                if (OptShowAscii)
454
                {
455
                    // Draw ASCII values
456
                    ImGui::SameLine(s.OffsetAsciiMinX);
457
                    ImVec2 pos = ImGui::GetCursorScreenPos();
458
                    addr = (size_t)line_i * Cols;
459
460
                    const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x;
461
                    const size_t mouse_addr = (mouse_off_x >= 0.0f && mouse_off_x < s.OffsetAsciiMaxX - s.OffsetAsciiMinX) ? addr + (size_t)(mouse_off_x / s.GlyphWidth) : (size_t)-1;
462
463
                    ImGui::PushID(line_i);
464
                    if (ImGui::InvisibleButton("ascii", ImVec2(s.OffsetAsciiMaxX - s.OffsetAsciiMinX, s.LineHeight)))
465
                    {
466
                        DataEditingAddr = DataPreviewAddr = mouse_addr;
467
                        DataEditingTakeFocus = true;
468
                    }
469
                    if (ImGui::IsItemHovered())
470
                    {
471
                        MouseHovered = true;
472
                        MouseHoveredAddr = mouse_addr;
473
                    }
474
                    ImGui::PopID();
475
                    for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
476
                    {
477
                        if (addr == DataEditingAddr)
478
                        {
479
                            draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
480
                            draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
481
                        }
482
                        else if (BgColorFn)
483
                        {
484
                            draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), BgColorFn(mem_data, addr, UserData));
485
                        }
486
                        unsigned char c = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
487
                        char display_c = (c < 32 || c >= 128) ? '.' : c;
488
                        draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
489
                        pos.x += s.GlyphWidth;
490
                    }
491
                }
492
            }
493
        ImGui::PopStyleVar(2);
494
        const float child_width = ImGui::GetWindowSize().x;
495
        ImGui::EndChild();
496
497
        // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
498
        ImVec2 backup_pos = ImGui::GetCursorScreenPos();
499
        ImGui::SetCursorPosX(s.WindowWidth);
500
        ImGui::Dummy(ImVec2(0.0f, 0.0f));
501
        ImGui::SetCursorScreenPos(backup_pos);
502
503
        if (data_next && DataEditingAddr + 1 < mem_size)
504
        {
505
            DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1;
506
            DataEditingTakeFocus = true;
507
        }
508
        else if (data_editing_addr_next != (size_t)-1)
509
        {
510
            DataEditingAddr = DataPreviewAddr = data_editing_addr_next;
511
            DataEditingTakeFocus = true;
512
        }
513
514
        const bool lock_show_data_preview = OptShowDataPreview;
515
        if (OptShowOptions)
516
        {
517
            ImGui::Separator();
518
            DrawOptionsLine(s, mem_data, mem_size, base_display_addr);
519
        }
520
521
        if (lock_show_data_preview)
522
        {
523
            ImGui::Separator();
524
            DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
525
        }
526
527
        if (GotoAddr != (size_t)-1)
528
        {
529
            if (GotoAddr < mem_size)
530
            {
531
                ImGui::BeginChild("##scrolling");
532
                ImGui::SetScrollY((GotoAddr / Cols) * ImGui::GetTextLineHeight() - avail_size.y * 0.5f);
533
                ImGui::EndChild();
534
                DataEditingAddr = DataPreviewAddr = GotoAddr;
535
                DataEditingTakeFocus = true;
536
            }
537
            GotoAddr = (size_t)-1;
538
        }
539
540
        const ImVec2 contents_pos_end(contents_pos_start.x + child_width, ImGui::GetCursorScreenPos().y);
541
        //ImGui::GetForegroundDrawList()->AddRect(contents_pos_start, contents_pos_end, IM_COL32(255, 0, 0, 255));
542
        if (OptShowOptions)
543
            if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end))
544
                if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
545
                    ImGui::OpenPopup("OptionsPopup");
546
547
        if (ImGui::BeginPopup("OptionsPopup"))
548
        {
549
            ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
550
            if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; }
551
            ImGui::Checkbox("Show Data Preview", &OptShowDataPreview);
552
            ImGui::Checkbox("Show HexII", &OptShowHexII);
553
            if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; }
554
            ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes);
555
            ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex);
556
557
            ImGui::EndPopup();
558
        }
559
    }
560
561
    void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
562
    {
563
        IM_UNUSED(mem_data);
564
        ImGuiStyle& style = ImGui::GetStyle();
565
        const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
566
567
        // Options menu
568
        if (ImGui::Button("Options"))
569
            ImGui::OpenPopup("OptionsPopup");
570
571
        ImGui::SameLine();
572
        ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
573
        ImGui::SameLine();
574
        ImGui::SetNextItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f);
575
        if (ImGui::InputText("##addr", AddrInputBuf, IM_ARRAYSIZE(AddrInputBuf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue))
576
        {
577
            size_t goto_addr;
578
            if (sscanf(AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1)
579
            {
580
                GotoAddr = goto_addr - base_display_addr;
581
                HighlightMin = HighlightMax = (size_t)-1;
582
            }
583
        }
584
585
        //if (MouseHovered)
586
        //{
587
        //    ImGui::SameLine();
588
        //    ImGui::Text("Hovered: %p", MouseHoveredAddr);
589
        //}
590
    }
591
592
    void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
593
    {
594
        IM_UNUSED(base_display_addr);
595
        ImU8* mem_data = (ImU8*)mem_data_void;
596
        ImGuiStyle& style = ImGui::GetStyle();
597
        ImGui::AlignTextToFramePadding();
598
        ImGui::Text("Preview as:");
599
        ImGui::SameLine();
600
        ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
601
602
        static const ImGuiDataType supported_data_types[] = { ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16, ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64, ImGuiDataType_Float, ImGuiDataType_Double };
603
        if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
604
        {
605
            for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
606
            {
607
                ImGuiDataType data_type = supported_data_types[n];
608
                if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
609
                    PreviewDataType = data_type;
610
            }
611
            ImGui::EndCombo();
612
        }
613
        ImGui::SameLine();
614
        ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
615
        ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0");
616
617
        char buf[128] = "";
618
        float x = s.GlyphWidth * 6.0f;
619
        bool has_value = DataPreviewAddr != (size_t)-1;
620
        if (has_value)
621
            DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf));
622
        ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
623
        if (has_value)
624
            DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf));
625
        ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
626
        if (has_value)
627
            DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf));
628
        buf[IM_ARRAYSIZE(buf) - 1] = 0;
629
        ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
630
    }
631
632
    // Utilities for Data Preview (since we don't access imgui_internal.h)
633
    // FIXME: This technically depends on ImGuiDataType order.
634
    const char* DataTypeGetDesc(ImGuiDataType data_type) const
635
    {
636
        const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
637
        IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs));
638
        return descs[data_type];
639
    }
640
641
    size_t DataTypeGetSize(ImGuiDataType data_type) const
642
    {
643
        const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
644
        IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes));
645
        return sizes[data_type];
646
    }
647
648
    const char* DataFormatGetDesc(DataFormat data_format) const
649
    {
650
        const char* descs[] = { "Bin", "Dec", "Hex" };
651
        IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT);
652
        return descs[data_format];
653
    }
654
655
    bool IsBigEndian() const
656
    {
657
        ImU16 x = 1;
658
        char c[2];
659
        memcpy(c, &x, 2);
660
        return c[0] != 0;
661
    }
662
663
    static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
664
    {
665
        if (is_little_endian)
666
        {
667
            ImU8* dst = (ImU8*)_dst;
668
            ImU8* src = (ImU8*)_src + s - 1;
669
            for (int i = 0, n = (int)s; i < n; ++i)
670
                memcpy(dst++, src--, 1);
671
            return _dst;
672
        }
673
        else
674
        {
675
            return memcpy(_dst, _src, s);
676
        }
677
    }
678
679
    static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
680
    {
681
        if (is_little_endian)
682
        {
683
            return memcpy(_dst, _src, s);
684
        }
685
        else
686
        {
687
            ImU8* dst = (ImU8*)_dst;
688
            ImU8* src = (ImU8*)_src + s - 1;
689
            for (int i = 0, n = (int)s; i < n; ++i)
690
                memcpy(dst++, src--, 1);
691
            return _dst;
692
        }
693
    }
694
695
    void* EndiannessCopy(void* dst, void* src, size_t size) const
696
    {
697
        static void* (*fp)(void*, void*, size_t, int) = nullptr;
698
        if (fp == nullptr)
699
            fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
700
        return fp(dst, src, size, PreviewEndianness);
701
    }
702
703
    const char* FormatBinary(const ImU8* buf, int width) const
704
    {
705
        IM_ASSERT(width <= 64);
706
        size_t out_n = 0;
707
        static char out_buf[64 + 8 + 1];
708
        int n = width / 8;
709
        for (int j = n - 1; j >= 0; --j)
710
        {
711
            for (int i = 0; i < 8; ++i)
712
                out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0';
713
            out_buf[out_n++] = ' ';
714
        }
715
        IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf));
716
        out_buf[out_n] = 0;
717
        return out_buf;
718
    }
719
720
    // [Internal]
721
    void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
722
    {
723
        ImU8 buf[8];
724
        size_t elem_size = DataTypeGetSize(data_type);
725
        size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
726
        if (ReadFn)
727
            for (int i = 0, n = (int)size; i < n; ++i)
728
                buf[i] = ReadFn(mem_data, addr + i, UserData);
729
        else
730
            memcpy(buf, mem_data + addr, size);
731
732
        if (data_format == DataFormat_Bin)
733
        {
734
            ImU8 binbuf[8];
735
            EndiannessCopy(binbuf, buf, size);
736
            ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
737
            return;
738
        }
739
740
        out_buf[0] = 0;
741
        switch (data_type)
742
        {
743
        case ImGuiDataType_S8:
744
        {
745
            ImS8 data = 0;
746
            EndiannessCopy(&data, buf, size);
747
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", data); return; }
748
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
749
            break;
750
        }
751
        case ImGuiDataType_U8:
752
        {
753
            ImU8 data = 0;
754
            EndiannessCopy(&data, buf, size);
755
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", data); return; }
756
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
757
            break;
758
        }
759
        case ImGuiDataType_S16:
760
        {
761
            ImS16 data = 0;
762
            EndiannessCopy(&data, buf, size);
763
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", data); return; }
764
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
765
            break;
766
        }
767
        case ImGuiDataType_U16:
768
        {
769
            ImU16 data = 0;
770
            EndiannessCopy(&data, buf, size);
771
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", data); return; }
772
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
773
            break;
774
        }
775
        case ImGuiDataType_S32:
776
        {
777
            ImS32 data = 0;
778
            EndiannessCopy(&data, buf, size);
779
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", data); return; }
780
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
781
            break;
782
        }
783
        case ImGuiDataType_U32:
784
        {
785
            ImU32 data = 0;
786
            EndiannessCopy(&data, buf, size);
787
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", data); return; }
788
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
789
            break;
790
        }
791
        case ImGuiDataType_S64:
792
        {
793
            ImS64 data = 0;
794
            EndiannessCopy(&data, buf, size);
795
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data); return; }
796
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
797
            break;
798
        }
799
        case ImGuiDataType_U64:
800
        {
801
            ImU64 data = 0;
802
            EndiannessCopy(&data, buf, size);
803
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data); return; }
804
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
805
            break;
806
        }
807
        case ImGuiDataType_Float:
808
        {
809
            float data = 0.0f;
810
            EndiannessCopy(&data, buf, size);
811
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
812
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
813
            break;
814
        }
815
        case ImGuiDataType_Double:
816
        {
817
            double data = 0.0;
818
            EndiannessCopy(&data, buf, size);
819
            if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
820
            if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
821
            break;
822
        }
823
        default:
824
        case ImGuiDataType_COUNT:
825
            break;
826
        } // Switch
827
        IM_ASSERT(0); // Shouldn't reach
828
    }
829
};
830
831
#undef _PRISizeT
832
#undef ImSnprintf
833
834
#ifdef _MSC_VER
835
#pragma warning (pop)
836
#endif