├── LICENSE ├── README.md ├── imgui_hex.cpp └── imgui_hex.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Teselka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | My version of hexadecimal editor for the Dear ImGui 2 | 3 | ![image](https://github.com/user-attachments/assets/6092f924-2959-4a68-91f5-963e931cecca) 4 | 5 | Features 6 | 1. Automatically adjust visible bytes count depending on the window width 7 | 2. Read-only mode 8 | 3. Optional ascii display 9 | 4. Separators support 10 | 5. Support for lowercase bytes 11 | 6. Keyboard navigation 12 | 7. Custom read/write/name callbacks 13 | 8. Render zeroes as disabled (idea from the ocronut's hex editor version) 14 | 9. Custom highlighting (with automatic text contrast selection) 15 | 10. Clipboard support 16 | 17 | Example: 18 | 19 | > [!NOTE] 20 | > Hex editor doesn't require you to implement callbacks at all. 21 | ```cpp 22 | static ImGuiHexEditorState hex_state; 23 | 24 | hex_state.ReadCallback = [](ImGuiHexEditorState* state, size_t offset, void* buf, size_t size) -> size_t { 25 | SIZE_T read; 26 | ReadProcessMemory(GetCurrentProcess(), (char*)state->Bytes + offset, buf, size, &read); 27 | return read; 28 | }; 29 | 30 | hex_state.WriteCallback = [](ImGuiHexEditorState* state, size_t offset, void* buf, size_t size) -> size_t { 31 | SIZE_T write; 32 | WriteProcessMemory(GetCurrentProcess(), (char*)state->Bytes + offset, buf, size, &write); 33 | return write; 34 | }; 35 | 36 | hex_state.GetAddressNameCallback = [](ImGuiHexEditorState* state, size_t offset, char* buf, size_t size) -> bool 37 | { 38 | if (offset >= 0 && offset < sizeof(ImGuiIO)) 39 | { 40 | snprintf(buf, size, "io+%0.*zX", 4, offset); 41 | return true; 42 | } 43 | 44 | return false; 45 | }; 46 | 47 | hex_state.SingleHighlightCallback = [](ImGuiHexEditorState* state, int offset, ImColor* color, ImColor* text_color, ImColor* border_color) -> ImGuiHexEditorHighlightFlags 48 | { 49 | if (offset >= 100 && offset <= 150) 50 | { 51 | *color = ImColor(user_highlight_color); 52 | return ImGuiHexEditorHighlightFlags_Apply | ImGuiHexEditorHighlightFlags_TextAutomaticContrast | ImGuiHexEditorHighlightFlags_Ascii 53 | | ImGuiHexEditorHighlightFlags_BorderAutomaticContrast; 54 | } 55 | 56 | return ImGuiHexEditorHighlightFlags_None; 57 | }; 58 | 59 | hex_state.HighlightRanges.clear(); 60 | 61 | { 62 | ImGuiHexEditorHighlightRange range; 63 | range.From = 200; 64 | range.To = 250; 65 | range.Color = ImColor(user_highlight_color); 66 | range.Flags = ImGuiHexEditorHighlightFlags_TextAutomaticContrast | ImGuiHexEditorHighlightFlags_FullSized 67 | | ImGuiHexEditorHighlightFlags_Ascii | ImGuiHexEditorHighlightFlags_Border | ImGuiHexEditorHighlightFlags_BorderAutomaticContrast; 68 | hex_state.HighlightRanges.push_back(range); 69 | } 70 | 71 | hex_state.Bytes = (void*)&ImGui::GetIO(); 72 | hex_state.MaxBytes = sizeof(ImGuiIO) + 0x1000; 73 | 74 | ImGui::BeginHexEditor("##HexEditor", &hex_state); 75 | ImGui::EndHexEditor(); 76 | ``` 77 | 78 | TODO: 79 | - [ ] Support for any amount of address chars for automatical bytes count selection. 80 | -------------------------------------------------------------------------------- /imgui_hex.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_hex.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static char HalfByteToPrintable(unsigned char half_byte, bool lower) 7 | { 8 | IM_ASSERT(!(half_byte & 0xf0)); 9 | return half_byte <= 9 ? '0' + half_byte : (lower ? 'a' : 'A') + half_byte - 10; 10 | } 11 | 12 | static unsigned char KeyToHalfByte(ImGuiKey key) 13 | { 14 | IM_ASSERT((key >= ImGuiKey_A && key <= ImGuiKey_F) || (key >= ImGuiKey_0 && key <= ImGuiKey_9)); 15 | return (key >= ImGuiKey_A && key <= ImGuiKey_F) ? (char)(key - ImGuiKey_A) + 10 : (char)(key - ImGuiKey_0); 16 | } 17 | 18 | static bool HasAsciiRepresentation(unsigned char byte) 19 | { 20 | return (byte >= '!' && byte <= '~'); 21 | } 22 | 23 | static int CalcBytesPerLine(float bytes_avail_x, const ImVec2& byte_size, const ImVec2& spacing, bool show_ascii, const ImVec2& char_size, int separators) 24 | { 25 | const float byte_width = byte_size.x + spacing.x + (show_ascii ? char_size.x : 0.f); 26 | int bytes_per_line = (int)(bytes_avail_x / byte_width); 27 | bytes_per_line = bytes_per_line <= 0 ? 1 : bytes_per_line; 28 | 29 | int actual_separators = separators > 0 ? (int)(bytes_per_line / separators) : 0; 30 | if (actual_separators != 0 && separators > 0 && bytes_per_line > actual_separators && (bytes_per_line - 1) % actual_separators == 0) 31 | --actual_separators; 32 | 33 | return separators > 0 ? CalcBytesPerLine(bytes_avail_x - (actual_separators * spacing.x), byte_size, spacing, show_ascii, char_size, 0) : bytes_per_line; 34 | } 35 | 36 | static ImColor CalcContrastColor(ImColor color) 37 | { 38 | #ifdef IMGUI_USE_BGRA_PACKED_COLOR 39 | const float l = (0.299f * (color.Value.z * 255.f) + 0.587f * (color.Value.y * 255.f) + 0.114f * (color.Value.x * 255.f)) / 255.f; 40 | #else 41 | const float l = (0.299f * (color.Value.x * 255.f) + 0.587f * (color.Value.y * 255.f) + 0.114f * (color.Value.z * 255.f)) / 255.f; 42 | #endif 43 | const int c = l > 0.5f ? 0 : 255; 44 | return IM_COL32(c, c, c, 255); 45 | } 46 | 47 | static bool RangeRangeIntersection(int a_min, int a_max, int b_min, int b_max, int* out_min, int* out_max) 48 | { 49 | if (a_max < b_min || b_max < a_min) 50 | return false; 51 | 52 | *out_min = ImMax(a_min, b_min); 53 | *out_max = ImMin(a_max, b_max); 54 | 55 | if (*out_min <= *out_max) 56 | return true; 57 | 58 | return false; 59 | } 60 | 61 | static void RenderRectCornerCalcRounding(const ImVec2& ra, const ImVec2& rb, float& rounding) 62 | { 63 | rounding = ImMin(rounding, ImFabs(rb.x - ra.x) * 0.5f); 64 | rounding = ImMin(rounding, ImFabs(rb.y - ra.y) * 0.5f); 65 | } 66 | 67 | static void RenderTopLeftCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImVec2& b, ImColor color, float rounding) 68 | { 69 | const ImVec2 ra = { a.x + 0.5f, a.y + 0.5f }; 70 | const ImVec2 rb = { b.x, b.y }; 71 | 72 | RenderRectCornerCalcRounding(ra, rb, rounding); 73 | 74 | draw_list->PathArcToFast({ ra.x, rb.y }, 0, 3, 6); 75 | draw_list->PathArcToFast({ ra.x + rounding, ra.y + rounding }, rounding, 6, 9); 76 | draw_list->PathArcToFast({ rb.x , ra.y }, 0, 9, 12); 77 | 78 | draw_list->PathStroke(color, ImDrawFlags_None, 1.f); 79 | } 80 | 81 | static void RenderBottomRightCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImVec2& b, ImColor color, float rounding) 82 | { 83 | const ImVec2 ra = { a.x, a.y + 0.5f }; 84 | const ImVec2 rb = { b.x - 0.5f, b.y + 0.5f }; 85 | 86 | RenderRectCornerCalcRounding(ra, rb, rounding); 87 | 88 | draw_list->PathArcToFast({ rb.x, ra.y }, 0, 9, 12); 89 | draw_list->PathArcToFast({ rb.x - rounding, rb.y - rounding }, rounding, 0, 3); 90 | draw_list->PathArcToFast({ ra.x, rb.y }, 0, 3, 6); 91 | 92 | draw_list->PathStroke(color, ImDrawFlags_None, 1.f); 93 | } 94 | 95 | static void RenderTopRightCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImVec2& b, ImColor color, float rounding) 96 | { 97 | const ImVec2 ra = { a.x + 0.5f, a.y + 0.5f }; 98 | const ImVec2 rb = { b.x - 0.5f, b.y }; 99 | 100 | RenderRectCornerCalcRounding(ra, rb, rounding); 101 | 102 | draw_list->PathArcToFast(ra, 0.f, 6, 9); 103 | draw_list->PathArcToFast({ rb.x - rounding, ra.y + rounding }, rounding, 9, 12); 104 | draw_list->PathArcToFast(rb, 0.f, 0, 3); 105 | 106 | draw_list->PathStroke(color, ImDrawFlags_None, 1.f); 107 | } 108 | 109 | static void RenderBottomLeftCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImVec2& b, ImColor color, float rounding) 110 | { 111 | const ImVec2 ra = { a.x + 0.5f, a.y + 0.5f }; 112 | const ImVec2 rb = { b.x + 0.5f, b.y + 0.5f }; 113 | 114 | RenderRectCornerCalcRounding(ra, rb, rounding); 115 | 116 | draw_list->PathArcToFast({ rb.x, rb.y }, 0.f, 0, 3); 117 | draw_list->PathArcToFast({ ra.x + rounding, rb.y - rounding }, rounding, 3, 6); 118 | draw_list->PathArcToFast({ ra.x, ra.y }, 0.f, 9, 12); 119 | 120 | draw_list->PathStroke(color, ImDrawFlags_None, 1.f); 121 | } 122 | 123 | static void RenderBottomCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImVec2& b, ImColor color, float rounding) 124 | { 125 | 126 | const ImVec2 ra = { a.x + 0.5f, a.y + 0.5f }; 127 | const ImVec2 rb = { b.x + 0.5f, b.y + 0.5f }; 128 | 129 | RenderRectCornerCalcRounding(ra, rb, rounding); 130 | 131 | draw_list->PathArcToFast({ rb.x, ra.y }, 0.f, 0, 3); 132 | draw_list->PathArcToFast({ rb.x - rounding, rb.y - rounding }, rounding, 0, 3); 133 | draw_list->PathArcToFast({ ra.x + rounding, rb.y - rounding }, rounding, 3, 6); 134 | draw_list->PathArcToFast({ ra.x, ra.y }, 0.f, 9, 12); 135 | 136 | draw_list->PathStroke(color, ImDrawFlags_None, 1.f); 137 | } 138 | 139 | static void RenderTopCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImVec2& b, ImColor color, float rounding) 140 | { 141 | const ImVec2 ra = { a.x + 0.5f, a.y + 0.5f }; 142 | const ImVec2 rb = { b.x - 0.5f, b.y + 0.5f }; 143 | 144 | RenderRectCornerCalcRounding(ra, rb, rounding); 145 | 146 | draw_list->PathArcToFast({ ra.x, rb.y }, 0.f, 3, 6); 147 | draw_list->PathArcToFast({ ra.x + rounding, ra.y + rounding }, rounding, 6, 9); 148 | draw_list->PathArcToFast({ rb.x - rounding, ra.y + rounding }, rounding, 9, 12); 149 | draw_list->PathArcToFast({ rb.x , rb.y }, 0.f, 0, 3); 150 | 151 | draw_list->PathStroke(color, ImDrawFlags_None, 1.f); 152 | } 153 | 154 | static void RenderByteDecorations(ImDrawList* draw_list, const ImRect& bb, ImColor bg_color, 155 | ImGuiHexEditorHighlightFlags flags, ImColor border_color, float rounding, 156 | int offset, int range_min, int range_max, int bytes_per_line, int i, int line_base) 157 | { 158 | const bool has_border = flags & ImGuiHexEditorHighlightFlags_Border; 159 | 160 | if (!has_border) 161 | { 162 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, 0.f); 163 | return; 164 | } 165 | 166 | if (range_min == range_max) 167 | { 168 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding); 169 | draw_list->AddRect(bb.Min, bb.Max, border_color, rounding); 170 | return; 171 | } 172 | 173 | const int start_line = range_min / bytes_per_line; 174 | const int end_line = range_max / bytes_per_line; 175 | const int current_line = line_base / bytes_per_line; 176 | 177 | const bool is_start_line = start_line == (line_base / bytes_per_line); 178 | const bool is_end_line = end_line == (line_base / bytes_per_line); 179 | const bool is_last_byte = i == (bytes_per_line - 1); 180 | 181 | bool rendered_bg = false; 182 | 183 | if (offset == range_min) 184 | { 185 | if (!is_last_byte) 186 | { 187 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersTopLeft); 188 | RenderTopLeftCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 189 | 190 | if (start_line == end_line) 191 | draw_list->AddLine({ bb.Min.x, bb.Max.y }, { bb.Max.x, bb.Max.y }, border_color); 192 | } 193 | else 194 | { 195 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersTop); 196 | RenderTopCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 197 | } 198 | 199 | rendered_bg = true; 200 | } 201 | else if (i == 0) 202 | { 203 | if (is_end_line) 204 | { 205 | if (offset == range_max) 206 | { 207 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersBottom); 208 | RenderBottomCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 209 | } 210 | else 211 | { 212 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersBottomLeft); 213 | RenderBottomLeftCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 214 | } 215 | 216 | rendered_bg = true; 217 | } 218 | else if (current_line == start_line + 1 && (range_min % bytes_per_line) != 0) 219 | { 220 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersTopLeft); 221 | RenderTopLeftCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 222 | rendered_bg = true; 223 | } 224 | else 225 | { 226 | if (!rendered_bg) 227 | { 228 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, 0.f); 229 | rendered_bg = true; 230 | } 231 | 232 | draw_list->AddLine({ bb.Min.x, bb.Min.y }, { bb.Min.x, bb.Max.y }, border_color); 233 | } 234 | } 235 | 236 | if (i != 0 && offset == range_max) 237 | { 238 | if (start_line == end_line) 239 | { 240 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersTopRight); 241 | RenderTopRightCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 242 | draw_list->AddLine({ bb.Min.x, bb.Max.y }, { bb.Max.x, bb.Max.y }, border_color); 243 | } 244 | else 245 | { 246 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersBottomRight); 247 | RenderBottomRightCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 248 | } 249 | 250 | rendered_bg = true; 251 | } 252 | else if (is_last_byte && offset != range_min) 253 | { 254 | if (is_start_line) 255 | { 256 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersTopRight); 257 | RenderTopRightCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 258 | rendered_bg = true; 259 | } 260 | else if (current_line == end_line - 1 && (range_max % bytes_per_line) != bytes_per_line - 1) 261 | { 262 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, rounding, ImDrawFlags_RoundCornersBottomRight); 263 | RenderBottomRightCornerRect(draw_list, bb.Min, bb.Max, border_color, rounding); 264 | rendered_bg = true; 265 | } 266 | else 267 | { 268 | if (!rendered_bg) 269 | { 270 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, 0.f); 271 | rendered_bg = true; 272 | } 273 | 274 | draw_list->AddLine({ bb.Max.x - 1.f, bb.Min.y }, { bb.Max.x - 1.f, bb.Max.y }, border_color); 275 | } 276 | } 277 | 278 | if ((is_start_line && offset != range_min && !is_last_byte && offset != range_max) 279 | || (current_line == start_line + 1 && (i < (range_min % bytes_per_line) && i != 0))) 280 | { 281 | if (!rendered_bg) 282 | { 283 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, 0.f); 284 | rendered_bg = true; 285 | } 286 | 287 | draw_list->AddLine({ bb.Min.x, bb.Min.y }, { bb.Max.x, bb.Min.y }, border_color); 288 | } 289 | 290 | if ((is_end_line && offset != range_max && i != 0) 291 | || (current_line == end_line - 1 && (i > (range_max % bytes_per_line) && !is_last_byte))) 292 | { 293 | if (!rendered_bg) 294 | { 295 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, 0.f); 296 | rendered_bg = true; 297 | } 298 | 299 | draw_list->AddLine({ bb.Min.x, bb.Max.y }, { bb.Max.x, bb.Max.y }, border_color); 300 | } 301 | 302 | if (!rendered_bg) 303 | draw_list->AddRectFilled(bb.Min, bb.Max, bg_color, 0.f); 304 | } 305 | 306 | bool ImGui::BeginHexEditor(const char* str_id, ImGuiHexEditorState* state, const ImVec2& size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags) 307 | { 308 | if (!ImGui::BeginChild(str_id, size, child_flags, window_flags)) 309 | return false; 310 | 311 | const ImVec2 char_size = ImGui::CalcTextSize("0"); 312 | const ImVec2 byte_size = { char_size.x * 2.f, char_size.y }; 313 | 314 | const ImGuiStyle& style = ImGui::GetStyle(); 315 | const ImVec2 spacing = style.ItemSpacing; 316 | 317 | ImVec2 content_avail = ImGui::GetContentRegionAvail(); 318 | 319 | float address_max_size; 320 | int address_max_chars; 321 | if (state->ShowAddress) 322 | { 323 | int address_chars = state->AddressChars; 324 | if (address_chars == -1) 325 | address_chars = ImFormatString(nullptr, 0, "%zX", (size_t)state->MaxBytes) + 1; 326 | 327 | address_max_chars = address_chars + 1; 328 | address_max_size = char_size.x * address_max_chars + spacing.x * 0.5f; 329 | } 330 | else 331 | { 332 | address_max_size = 0.f; 333 | address_max_chars = 0; 334 | } 335 | 336 | float bytes_avail_x = content_avail.x - address_max_size; 337 | if (ImGui::GetScrollMaxY() > 0.f) 338 | bytes_avail_x -= style.ScrollbarSize; 339 | 340 | const bool show_ascii = state->ShowAscii; 341 | 342 | if (show_ascii) 343 | bytes_avail_x -= char_size.x * 0.5f; 344 | 345 | bytes_avail_x = bytes_avail_x < 0.f ? 0.f : bytes_avail_x; 346 | 347 | int bytes_per_line; 348 | 349 | if (state->BytesPerLine == -1) 350 | { 351 | bytes_per_line = CalcBytesPerLine(bytes_avail_x, byte_size, spacing, show_ascii, char_size, state->Separators); 352 | } 353 | else 354 | { 355 | bytes_per_line = state->BytesPerLine; 356 | } 357 | 358 | int actual_separators = (int)(bytes_per_line / state->Separators); 359 | if (bytes_per_line % state->Separators == 0) 360 | --actual_separators; 361 | 362 | int lines_count; 363 | if (bytes_per_line != 0) 364 | { 365 | lines_count = state->MaxBytes / bytes_per_line; 366 | if (lines_count * bytes_per_line < state->MaxBytes) 367 | { 368 | ++lines_count; 369 | } 370 | } 371 | else 372 | lines_count = 0; 373 | 374 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 375 | ImGuiIO& io = ImGui::GetIO(); 376 | 377 | const ImColor text_color = ImGui::GetColorU32(ImGuiCol_Text); 378 | const ImColor text_disabled_color = ImGui::GetColorU32(ImGuiCol_TextDisabled); 379 | const ImColor text_selected_bg_color = ImGui::GetColorU32(ImGuiCol_TextSelectedBg); 380 | const ImColor separator_color = ImGui::GetColorU32(ImGuiCol_Separator); 381 | const ImColor border_color = ImGui::GetColorU32(ImGuiCol_FrameBgActive); 382 | 383 | const bool lowercase_bytes = state->LowercaseBytes; 384 | 385 | const int select_start_byte = state->SelectStartByte; 386 | const int select_start_subbyte = state->SelectStartSubByte; 387 | const int select_end_byte = state->SelectEndByte; 388 | const int select_end_subbyte = state->SelectEndSubByte; 389 | const int last_selected_byte = state->LastSelectedByte; 390 | const int select_drag_byte = state->SelectDragByte; 391 | const int select_drag_subbyte = state->SelectDragSubByte; 392 | 393 | int next_select_start_byte = select_start_byte; 394 | int next_select_start_subbyte = select_start_subbyte; 395 | int next_select_end_byte = select_end_byte; 396 | int next_select_end_subbyte = select_end_subbyte; 397 | int next_last_selected_byte = last_selected_byte; 398 | int next_select_drag_byte = select_drag_byte; 399 | int next_select_drag_subbyte = select_drag_subbyte; 400 | 401 | ImGuiKey hex_key_pressed = ImGuiKey_None; 402 | 403 | if (state->EnableClipboard && ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_C)) 404 | { 405 | if (state->SelectStartByte != -1) 406 | { 407 | const int bytes_count = (state->SelectEndByte + 1) - state->SelectStartByte; 408 | 409 | char* bytes = (char*)ImGui::MemAlloc((size_t)bytes_count); 410 | if (bytes) 411 | { 412 | int read_bytes; 413 | 414 | if (state->ReadCallback) 415 | read_bytes = state->ReadCallback(state, state->SelectStartByte, bytes, bytes_count); 416 | else 417 | { 418 | memcpy(bytes, (char*)state->Bytes + state->SelectStartByte, bytes_count); 419 | read_bytes = bytes_count; 420 | } 421 | 422 | if (read_bytes > 0) 423 | { 424 | ImGuiHexEditorClipboardFlags flags = state->ClipboardFlags; 425 | 426 | const int text_byte_size = 3; 427 | 428 | ImGui::LogToClipboard(); 429 | 430 | for (int i = 0, abs_i = state->SelectStartByte; i < bytes_count; i++, abs_i++) 431 | { 432 | const char byte = bytes[i]; 433 | 434 | char text[3]; 435 | text[0] = HalfByteToPrintable((byte & 0xf0) >> 4, lowercase_bytes); 436 | text[1] = HalfByteToPrintable(byte & 0x0f, lowercase_bytes); 437 | text[2] = '\0'; 438 | 439 | ImGui::LogText("%s", text); 440 | 441 | if (bytes_per_line != 0 && ((abs_i % bytes_per_line) == bytes_per_line - 1) && abs_i != 0) 442 | { 443 | ImGui::LogText(IM_NEWLINE); 444 | } 445 | else 446 | { 447 | ImGui::LogText(" "); 448 | } 449 | } 450 | 451 | ImGui::LogFinish(); 452 | } 453 | 454 | ImGui::MemFree(bytes); 455 | } 456 | } 457 | } 458 | else 459 | { 460 | 461 | if (last_selected_byte != -1) 462 | { 463 | bool any_pressed = false; 464 | if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) 465 | { 466 | if (!select_start_subbyte) 467 | { 468 | if (last_selected_byte == 0) 469 | { 470 | next_last_selected_byte = 0; 471 | } 472 | else 473 | { 474 | next_last_selected_byte = last_selected_byte - 1; 475 | next_select_start_subbyte = 1; 476 | } 477 | } 478 | else 479 | next_select_start_subbyte = 0; 480 | 481 | any_pressed = true; 482 | } 483 | else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) 484 | { 485 | if (select_start_subbyte) 486 | { 487 | if (last_selected_byte >= state->MaxBytes - 1) 488 | { 489 | next_last_selected_byte = state->MaxBytes - 1; 490 | } 491 | else 492 | { 493 | next_last_selected_byte = last_selected_byte + 1; 494 | next_select_start_subbyte = 0; 495 | } 496 | } 497 | else 498 | next_select_start_subbyte = 1; 499 | 500 | any_pressed = true; 501 | } 502 | else if (bytes_per_line != 0) 503 | { 504 | if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) 505 | { 506 | if (last_selected_byte >= bytes_per_line) 507 | { 508 | next_last_selected_byte = last_selected_byte - bytes_per_line; 509 | } 510 | 511 | any_pressed = true; 512 | } 513 | else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) 514 | { 515 | if (last_selected_byte < state->MaxBytes - bytes_per_line) 516 | { 517 | next_last_selected_byte = last_selected_byte + bytes_per_line; 518 | } 519 | 520 | any_pressed = true; 521 | } 522 | } 523 | 524 | if (any_pressed) 525 | { 526 | next_select_start_byte = next_last_selected_byte; 527 | next_select_end_byte = next_last_selected_byte; 528 | } 529 | } 530 | 531 | for (ImGuiKey key = ImGuiKey_A; key != ImGuiKey_G; key = (ImGuiKey)((int)key + 1)) 532 | { 533 | if (ImGui::IsKeyPressed(key)) 534 | { 535 | hex_key_pressed = key; 536 | break; 537 | } 538 | } 539 | 540 | if (hex_key_pressed == ImGuiKey_None) 541 | { 542 | for (ImGuiKey key = ImGuiKey_0; key != ImGuiKey_A; key = (ImGuiKey)((int)key + 1)) 543 | { 544 | if (ImGui::IsKeyPressed(key)) 545 | { 546 | hex_key_pressed = key; 547 | break; 548 | } 549 | } 550 | } 551 | } 552 | 553 | unsigned char stack_line_buf[128]; 554 | unsigned char* line_buf = bytes_per_line <= sizeof(stack_line_buf) ? stack_line_buf : (unsigned char*)ImGui::MemAlloc(bytes_per_line); 555 | if (!line_buf) 556 | return true; 557 | 558 | char stack_address_buf[32]; 559 | char* address_buf = address_max_chars <= sizeof(stack_address_buf) ? stack_address_buf : (char*)ImGui::MemAlloc(address_max_chars); 560 | if (!address_buf) 561 | { 562 | if (line_buf != stack_line_buf) 563 | ImGui::MemFree(line_buf); 564 | 565 | return true; 566 | } 567 | 568 | const ImVec2 mouse_pos = ImGui::GetMousePos(); 569 | const bool mouse_left_down = ImGui::IsMouseDown(ImGuiMouseButton_Left); 570 | 571 | ImGuiListClipper clipper; 572 | clipper.Begin(lines_count, byte_size.y + spacing.y); 573 | while (clipper.Step()) 574 | { 575 | const int clipper_lines = clipper.DisplayEnd - clipper.DisplayStart; 576 | 577 | ImVec2 cursor = ImGui::GetCursorScreenPos(); 578 | 579 | ImVec2 ascii_cursor = { cursor.x + address_max_size + (spacing.x * 0.5f) + (bytes_per_line * (byte_size.x + spacing.x)) + (actual_separators * spacing.x), cursor.y }; 580 | if (show_ascii) 581 | { 582 | draw_list->AddLine(ascii_cursor, { ascii_cursor.x, ascii_cursor.y + clipper_lines * (byte_size.y + spacing.y) }, separator_color); 583 | } 584 | 585 | { 586 | int count = clipper_lines * bytes_per_line * 2; 587 | if (show_ascii) 588 | count += clipper_lines * bytes_per_line; 589 | 590 | draw_list->IdxBuffer.reserve(draw_list->IdxBuffer.Size + (count * 6)); 591 | draw_list->VtxBuffer.reserve(draw_list->VtxBuffer.Size + (count * 4)); 592 | } 593 | 594 | for (int n = clipper.DisplayStart; n != clipper.DisplayEnd; n++) 595 | { 596 | const int line_base = n * bytes_per_line; 597 | if (state->ShowAddress) 598 | { 599 | if (!state->GetAddressNameCallback || !state->GetAddressNameCallback(state, line_base, address_buf, address_max_chars)) 600 | ImFormatString(address_buf, (size_t)address_max_chars, "%0.*zX", address_max_chars - 1, (size_t)line_base); 601 | 602 | const ImVec2 text_size = ImGui::CalcTextSize(address_buf); 603 | draw_list->AddText(cursor, text_color, address_buf); 604 | draw_list->AddText({ cursor.x + text_size.x, cursor.y }, text_disabled_color, ":"); 605 | cursor.x += address_max_size; 606 | } 607 | 608 | int max_bytes_per_line = line_base; 609 | max_bytes_per_line = max_bytes_per_line > state->MaxBytes ? max_bytes_per_line - state->MaxBytes : bytes_per_line; 610 | 611 | int bytes_read; 612 | if (!state->ReadCallback) 613 | { 614 | memcpy(line_buf, (char*)state->Bytes + line_base, max_bytes_per_line); 615 | bytes_read = max_bytes_per_line; 616 | } 617 | else 618 | bytes_read = state->ReadCallback(state, line_base, line_buf, max_bytes_per_line); 619 | 620 | cursor.x += spacing.x * 0.5f; 621 | 622 | for (int i = 0; i != bytes_per_line; i++) 623 | { 624 | const ImRect byte_bb = { { cursor.x, cursor.y }, { cursor.x + byte_size.x, cursor.y + byte_size.y } }; 625 | 626 | ImRect item_bb = byte_bb; 627 | 628 | item_bb.Min.x -= spacing.x * 0.5f; 629 | 630 | if (n != clipper.DisplayStart) 631 | item_bb.Min.y -= spacing.y * 0.5f; 632 | 633 | item_bb.Max.x += spacing.x * 0.5f; 634 | item_bb.Max.y += spacing.y * 0.5f; 635 | 636 | const int offset = bytes_per_line * n + i; 637 | unsigned char byte; 638 | 639 | ImVec2 byte_ascii = ascii_cursor; 640 | 641 | byte_ascii.x += (char_size.x * i) + spacing.x; 642 | byte_ascii.y += (char_size.y + spacing.y) * (n - clipper.DisplayStart); 643 | 644 | char text[3]; 645 | if (offset < state->MaxBytes && i < bytes_read) 646 | { 647 | byte = line_buf[i]; 648 | 649 | text[0] = HalfByteToPrintable((byte & 0xf0) >> 4, lowercase_bytes); 650 | text[1] = HalfByteToPrintable(byte & 0x0f, lowercase_bytes); 651 | text[2] = '\0'; 652 | } 653 | else 654 | { 655 | byte = 0x00; 656 | 657 | text[0] = '?'; 658 | text[1] = '?'; 659 | text[2] = '\0'; 660 | } 661 | 662 | const ImGuiID id = ImGui::GetID(offset); 663 | 664 | if (!ImGui::ItemAdd(item_bb, id, 0, ImGuiItemFlags_Inputable)) 665 | continue; 666 | 667 | ImColor byte_text_color = (offset >= state->MaxBytes || (state->RenderZeroesDisabled && byte == 0x00) || i >= bytes_read) ? text_disabled_color : text_color; 668 | 669 | if (offset >= select_start_byte && offset <= select_end_byte) 670 | { 671 | ImGuiHexEditorHighlightFlags flags = state->SelectionHighlightFlags; 672 | 673 | if (select_start_byte == select_end_byte) 674 | { 675 | flags &= ~ImGuiHexEditorHighlightFlags_FullSized; 676 | } 677 | 678 | ImRect bb = (flags & ImGuiHexEditorHighlightFlags_FullSized) ? item_bb : byte_bb; 679 | 680 | if (select_start_byte == select_end_byte) 681 | { 682 | if (select_start_subbyte) 683 | bb.Min.x = byte_bb.GetCenter().x; 684 | else 685 | bb.Max.x = byte_bb.GetCenter().x; 686 | } 687 | 688 | RenderByteDecorations(draw_list, bb, text_selected_bg_color, flags, border_color, 689 | style.FrameRounding, offset, select_start_byte, select_end_byte, bytes_per_line, i, line_base); 690 | 691 | if (flags & ImGuiHexEditorHighlightFlags_Ascii) 692 | { 693 | RenderByteDecorations(draw_list, { byte_ascii, { byte_ascii.x + char_size.x, byte_ascii.y + char_size.y } }, 694 | text_selected_bg_color, flags, border_color, style.FrameRounding, offset, offset, offset, bytes_per_line, i, line_base); 695 | } 696 | } 697 | else 698 | { 699 | bool single_highlight = false; 700 | 701 | if (state->SingleHighlightCallback) 702 | { 703 | ImColor color; 704 | ImColor custom_border_color; 705 | 706 | ImGuiHexEditorHighlightFlags flags = state->SingleHighlightCallback(state, offset, 707 | &color, &byte_text_color, &custom_border_color); 708 | 709 | if (flags & ImGuiHexEditorHighlightFlags_Apply) 710 | { 711 | ImColor highlight_border_color; 712 | 713 | if (flags & ImGuiHexEditorHighlightFlags_BorderAutomaticContrast) 714 | highlight_border_color = CalcContrastColor(color); 715 | else if (flags & ImGuiHexEditorHighlightFlags_OverrideBorderColor) 716 | highlight_border_color = custom_border_color; 717 | else 718 | highlight_border_color = border_color; 719 | 720 | single_highlight = true; 721 | 722 | RenderByteDecorations(draw_list, (flags & ImGuiHexEditorHighlightFlags_FullSized) ? item_bb : byte_bb, color, flags, highlight_border_color, 723 | style.FrameRounding, offset, offset, offset, bytes_per_line, i, line_base); 724 | 725 | if (flags & ImGuiHexEditorHighlightFlags_Ascii) 726 | { 727 | RenderByteDecorations(draw_list, { byte_ascii, { byte_ascii.x + char_size.x, byte_ascii.y + char_size.y } }, color, flags, highlight_border_color, 728 | style.FrameRounding, offset, offset, offset, bytes_per_line, i, line_base); 729 | } 730 | 731 | if (flags & ImGuiHexEditorHighlightFlags_TextAutomaticContrast) 732 | byte_text_color = CalcContrastColor(color); 733 | } 734 | } 735 | 736 | if (!single_highlight) 737 | { 738 | for (int j = 0; j != state->HighlightRanges.Size; j++) 739 | { 740 | ImGuiHexEditorHighlightRange& range = state->HighlightRanges[j]; 741 | 742 | if (line_base + i >= range.From && line_base + i <= range.To) 743 | { 744 | ImColor highlight_border_color; 745 | 746 | if (range.Flags & ImGuiHexEditorHighlightFlags_BorderAutomaticContrast) 747 | highlight_border_color = CalcContrastColor(range.Color); 748 | else if (range.Flags & ImGuiHexEditorHighlightFlags_OverrideBorderColor) 749 | highlight_border_color = range.BorderColor; 750 | else 751 | highlight_border_color = border_color; 752 | 753 | RenderByteDecorations(draw_list, (range.Flags & ImGuiHexEditorHighlightFlags_FullSized) ? item_bb : byte_bb, range.Color, range.Flags, highlight_border_color, 754 | style.FrameRounding, offset, range.From, range.To, bytes_per_line, i, line_base); 755 | 756 | if (range.Flags & ImGuiHexEditorHighlightFlags_Ascii) 757 | { 758 | RenderByteDecorations(draw_list, { byte_ascii, { byte_ascii.x + char_size.x, byte_ascii.y + char_size.y } }, range.Color, range.Flags, highlight_border_color, 759 | style.FrameRounding, offset, range.From, range.To, bytes_per_line, i, line_base); 760 | } 761 | 762 | if (range.Flags & ImGuiHexEditorHighlightFlags_TextAutomaticContrast) 763 | byte_text_color = CalcContrastColor(range.Color); 764 | } 765 | } 766 | } 767 | } 768 | 769 | draw_list->AddText(byte_bb.Min, byte_text_color, text); 770 | 771 | if (offset == select_start_byte) 772 | { 773 | state->SelectCursorAnimationTime += io.DeltaTime; 774 | 775 | if (!io.ConfigInputTextCursorBlink || ImFmod(state->SelectCursorAnimationTime, 1.20f) <= 0.80f) 776 | { 777 | ImVec2 pos; 778 | pos.x = byte_bb.Min.x; 779 | pos.y = byte_bb.Max.y; 780 | 781 | if (select_start_subbyte) 782 | pos.x += char_size.x; 783 | 784 | draw_list->AddLine({ pos.x, pos.y }, { pos.x + char_size.x, pos.y }, text_color); 785 | } 786 | } 787 | 788 | const bool hovered = ImGui::ItemHoverable(item_bb, id, ImGuiItemFlags_Inputable); 789 | 790 | if (select_drag_byte != -1 && offset == select_drag_byte && !mouse_left_down) 791 | { 792 | next_select_drag_byte = -1; 793 | } 794 | else 795 | { 796 | if (hovered) 797 | { 798 | const bool clicked = ImGui::IsItemClicked(); 799 | 800 | if (clicked) 801 | { 802 | next_select_start_byte = offset; 803 | next_select_end_byte = offset; 804 | next_select_drag_byte = offset; 805 | next_select_drag_subbyte = mouse_pos.x > byte_bb.GetCenter().x; 806 | next_select_start_subbyte = next_select_drag_subbyte; 807 | next_last_selected_byte = offset; 808 | 809 | ImGui::SetKeyboardFocusHere(); 810 | } 811 | else if (mouse_left_down && select_drag_byte != -1) 812 | { 813 | if (offset >= select_drag_byte) 814 | { 815 | next_select_end_byte = offset; 816 | } 817 | else 818 | { 819 | next_select_start_byte = offset; 820 | next_select_end_byte = select_drag_byte; 821 | next_select_start_subbyte = 0; 822 | } 823 | 824 | ImGui::SetKeyboardFocusHere(); 825 | } 826 | } 827 | } 828 | 829 | if (offset == next_last_selected_byte && last_selected_byte != next_last_selected_byte) 830 | { 831 | ImGui::SetKeyboardFocusHere(); 832 | } 833 | 834 | if (offset == last_selected_byte && !state->ReadOnly && hex_key_pressed != ImGuiKey_None) 835 | { 836 | IM_ASSERT(offset == select_start_byte || offset == select_end_byte); 837 | const int subbyte = offset == select_start_byte ? select_start_subbyte : select_end_subbyte; 838 | 839 | unsigned char wbyte; 840 | if (subbyte) 841 | wbyte = (byte & 0xf0) | KeyToHalfByte(hex_key_pressed); 842 | else 843 | wbyte = (KeyToHalfByte(hex_key_pressed) << 4) | (byte & 0x0f); 844 | 845 | if (!state->WriteCallback) 846 | *(unsigned char*)((char*)state->Bytes + n * bytes_per_line + i) = wbyte; 847 | else 848 | state->WriteCallback(state, n * bytes_per_line + i, &wbyte, sizeof(wbyte)); 849 | 850 | int* next_subbyte = (int*)(offset == select_start_byte ? &next_select_start_subbyte : &next_select_end_subbyte); 851 | if (!subbyte) 852 | { 853 | next_select_start_byte = offset; 854 | next_select_end_byte = offset; 855 | *next_subbyte = 1; 856 | } 857 | else 858 | { 859 | next_last_selected_byte = offset + 1; 860 | if (next_last_selected_byte >= state->MaxBytes - 1) 861 | next_last_selected_byte = state->MaxBytes - 1; 862 | else 863 | *next_subbyte = 0; 864 | 865 | next_select_start_byte = next_last_selected_byte; 866 | next_select_end_byte = next_last_selected_byte; 867 | } 868 | 869 | state->SelectCursorAnimationTime = 0.f; 870 | } 871 | 872 | cursor.x += byte_size.x + spacing.x; 873 | if (i > 0 && state->Separators > 0 && (i + 1) % state->Separators == 0 874 | && i != bytes_per_line - 1) 875 | cursor.x += spacing.x; 876 | 877 | if (show_ascii) 878 | { 879 | unsigned char byte; 880 | if (offset < state->MaxBytes) 881 | byte = line_buf[i]; 882 | else 883 | byte = 0x00; 884 | 885 | bool has_ascii = HasAsciiRepresentation(byte); 886 | 887 | const ImRect char_bb = { byte_ascii, { byte_ascii.x + char_size.x, byte_ascii.y + char_size.y } }; 888 | 889 | /*if (offset >= select_start_byte && offset <= select_end_byte) 890 | { 891 | draw_list->AddRectFilled(char_bb.Min, char_bb.Max, text_selected_bg_color); 892 | }*/ 893 | 894 | char text[2]; 895 | text[0] = has_ascii ? *(char*)&byte : '.'; 896 | text[1] = '\0'; 897 | 898 | draw_list->AddText(byte_ascii, byte_text_color, text); 899 | } 900 | 901 | ImGui::SetCursorScreenPos(cursor); 902 | } 903 | 904 | ImGui::NewLine(); 905 | cursor = ImGui::GetCursorScreenPos(); 906 | } 907 | } 908 | 909 | state->SelectStartByte = next_select_start_byte; 910 | state->SelectStartSubByte = next_select_start_subbyte; 911 | state->SelectEndByte = next_select_end_byte; 912 | state->SelectEndSubByte = next_select_end_subbyte; 913 | state->LastSelectedByte = next_last_selected_byte; 914 | state->SelectDragByte = next_select_drag_byte; 915 | state->SelectDragSubByte = next_select_drag_subbyte; 916 | 917 | if (line_buf != stack_line_buf) 918 | ImGui::MemFree(line_buf); 919 | 920 | if (address_buf != stack_address_buf) 921 | ImGui::MemFree(address_buf); 922 | 923 | return true; 924 | } 925 | 926 | void ImGui::EndHexEditor() 927 | { 928 | ImGui::EndChild(); 929 | } 930 | 931 | bool ImGui::CalcHexEditorRowRange(int row_offset, int row_bytes_count, int range_min, int range_max, int* out_min, int* out_max) 932 | { 933 | int abs_min; 934 | int abs_max; 935 | 936 | if (RangeRangeIntersection(row_offset, row_offset + row_bytes_count, range_min, range_max, &abs_min, &abs_max)) 937 | { 938 | *out_min = abs_min - row_offset; 939 | *out_max = abs_max - row_offset; 940 | return true; 941 | } 942 | 943 | return false; 944 | } -------------------------------------------------------------------------------- /imgui_hex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | enum ImGuiHexEditorHighlightFlags_ : int 5 | { 6 | ImGuiHexEditorHighlightFlags_None = 0, 7 | ImGuiHexEditorHighlightFlags_Apply = 1 << 0, 8 | ImGuiHexEditorHighlightFlags_TextAutomaticContrast = 1 << 1, 9 | ImGuiHexEditorHighlightFlags_FullSized = 1 << 2, // Highlight entire byte space including it's container, has no effect on ascii 10 | ImGuiHexEditorHighlightFlags_Ascii = 1 << 3, // Highlight ascii (doesn't affect single byte highlighting) 11 | ImGuiHexEditorHighlightFlags_Border = 1 << 4, 12 | ImGuiHexEditorHighlightFlags_OverrideBorderColor = 1 << 5, 13 | ImGuiHexEditorHighlightFlags_BorderAutomaticContrast = 1 << 6, 14 | }; 15 | 16 | typedef int ImGuiHexEditorHighlightFlags; // -> enum ImGuiHexEditorHighlightFlags_ // Flags: for ImGuiHexEditor callbacks 17 | 18 | struct ImGuiHexEditorHighlightRange 19 | { 20 | int From; 21 | int To; 22 | ImColor Color; 23 | ImColor BorderColor; 24 | ImGuiHexEditorHighlightFlags Flags; 25 | }; 26 | 27 | enum ImGuiHexEditorClipboardFlags_ : int 28 | { 29 | ImGuiHexEditorClipboardFlags_None = 0, 30 | ImGuiHexEditorClipboardFlags_Multiline = 1 << 0, // Separate resulting hex editor lines with carriage return 31 | }; 32 | 33 | typedef int ImGuiHexEditorClipboardFlags; // -> enum ImGuiHexEditorClipboardFlags_ 34 | 35 | struct ImGuiHexEditorState 36 | { 37 | void* Bytes; 38 | int MaxBytes; 39 | int BytesPerLine = -1; 40 | bool ShowPrintable = false; 41 | bool LowercaseBytes = false; 42 | bool RenderZeroesDisabled = true; 43 | bool ShowAddress = true; 44 | int AddressChars = -1; 45 | bool ShowAscii = true; 46 | bool ReadOnly = false; 47 | int Separators = 8; 48 | void* UserData = nullptr; 49 | ImVector HighlightRanges; 50 | bool EnableClipboard = true; 51 | ImGuiHexEditorClipboardFlags ClipboardFlags = ImGuiHexEditorClipboardFlags_Multiline; 52 | 53 | int(*ReadCallback)(ImGuiHexEditorState* state, int offset, void* buf, int size) = nullptr; 54 | int(*WriteCallback)(ImGuiHexEditorState* state, int offset, void* buf, int size) = nullptr; 55 | bool(*GetAddressNameCallback)(ImGuiHexEditorState* state, int offset, char* buf, int size) = nullptr; 56 | ImGuiHexEditorHighlightFlags(*SingleHighlightCallback)(ImGuiHexEditorState* state, int offset, ImColor* color, ImColor* text_color, ImColor* border_color) = nullptr; 57 | void(*HighlightRangesCallback)(ImGuiHexEditorState* state, int display_start, int display_end) = nullptr; 58 | 59 | int SelectStartByte = -1; 60 | int SelectStartSubByte = 0; 61 | int SelectEndByte = -1; 62 | int SelectEndSubByte = 0; 63 | int LastSelectedByte = -1; 64 | int SelectDragByte = -1; 65 | int SelectDragSubByte = 0; 66 | float SelectCursorAnimationTime = 0.f; 67 | 68 | ImGuiHexEditorHighlightFlags SelectionHighlightFlags = ImGuiHexEditorHighlightFlags_FullSized | ImGuiHexEditorHighlightFlags_Ascii; 69 | }; 70 | 71 | namespace ImGui 72 | { 73 | bool BeginHexEditor(const char* str_id, ImGuiHexEditorState* state, const ImVec2& size = { 0.f, 0.f }, ImGuiChildFlags child_flags = 0, ImGuiWindowFlags window_flags = 0); 74 | void EndHexEditor(); 75 | 76 | // Helpers 77 | 78 | bool CalcHexEditorRowRange(int row_offset, int row_bytes_count, int range_min, int range_max, int* out_min, int* out_max); 79 | } --------------------------------------------------------------------------------