1337 lines
44 KiB
C++
1337 lines
44 KiB
C++
// dear imgui test engine
|
|
// (helpers/utilities. do NOT use this as a general purpose library)
|
|
|
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#include "imgui_te_utils.h"
|
|
#include "imgui.h"
|
|
#include "imgui_internal.h"
|
|
#define STR_IMPLEMENTATION
|
|
#include "thirdparty/Str/Str.h"
|
|
|
|
#if defined(_WIN32)
|
|
#if !defined(_WINDOWS_)
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
#include <shellapi.h> // ShellExecuteA()
|
|
#include <stdio.h>
|
|
#else
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifndef _MSC_VER
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h> // stat()
|
|
#endif
|
|
#ifdef __APPLE__
|
|
#include <sys/sysctl.h>
|
|
#endif
|
|
|
|
#if defined(__linux) || defined(__linux__) || defined(__MACH__) || defined(__MSL__) || defined(__MINGW32__)
|
|
#include <pthread.h> // pthread_setname_np()
|
|
#endif
|
|
#include <chrono> // high_resolution_clock::now()
|
|
#include <thread> // this_thread::sleep_for()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Hashing Helpers
|
|
//-----------------------------------------------------------------------------
|
|
// - ImHashDecoratedPathParseLiteral() [internal]
|
|
// - ImHashDecoratedPath()
|
|
// - ImFindNextDecoratedPartInPath()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// - Parse literals encoded as "$$xxxx/" and incorporate into our hash based on type.
|
|
// - $$ not passed by caller.
|
|
static ImGuiID ImHashDecoratedPathParseLiteral(ImGuiID crc, const unsigned char* str, const unsigned char* str_end, const unsigned char** out_str_remaining)
|
|
{
|
|
// Parse type (default to int)
|
|
ImGuiDataType type = ImGuiDataType_S32;
|
|
if (*str == '(')
|
|
{
|
|
// "$$(int)????" where ???? is s32 or u32
|
|
if (str + 5 < str_end && memcmp(str, "(int)", 5) == 0)
|
|
{
|
|
type = ImGuiDataType_S32;
|
|
str += 5;
|
|
}
|
|
// "$$(ptr)0x????" where ???? is ptr size
|
|
else if (str + 7 < str_end && memcmp(str, "(ptr)0x", 7) == 0)
|
|
{
|
|
type = ImGuiDataType_Pointer;
|
|
str += 7;
|
|
}
|
|
}
|
|
|
|
// Parse value
|
|
switch (type)
|
|
{
|
|
case ImGuiDataType_S32:
|
|
{
|
|
// e.g. "$$(int)123" for s32/u32/ImGuiID, same as PushID(int)
|
|
int v = 0;
|
|
{
|
|
int negative = 0;
|
|
if (str < str_end && *str == '-') { negative = 1; str++; }
|
|
if (str < str_end && *str == '+') { str++; }
|
|
for (char c = *str; str < str_end; c = *(++str))
|
|
{
|
|
if (c >= '0' && c <= '9') { v = (v * 10) + (c - '0'); }
|
|
else break;
|
|
}
|
|
if (negative)
|
|
v = -v;
|
|
}
|
|
crc = ~ImHashData(&v, sizeof(int), ~crc);
|
|
break;
|
|
}
|
|
case ImGuiDataType_Pointer:
|
|
{
|
|
// e.g. "$$(ptr)0x1234FFFF" for pointers, same as PushID(void*)
|
|
intptr_t v = 0;
|
|
{
|
|
for (char c = *str; str < str_end; c = *(++str))
|
|
{
|
|
if (c >= '0' && c <= '9') { v = (v << 4) + (c - '0'); }
|
|
else if (c >= 'A' && c <= 'F') { v = (v << 4) + 10 + (c - 'A'); }
|
|
else if (c >= 'a' && c <= 'f') { v = (v << 4) + 10 + (c - 'a'); }
|
|
else break;
|
|
}
|
|
}
|
|
crc = ~ImHashData(&v, sizeof(void*), ~crc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// "$$xxxx" must always be either end of string, either leading to a next section e.g. "$$xxxx/"
|
|
IM_ASSERT(str == str_end || *str == '/');
|
|
|
|
*out_str_remaining = str;
|
|
return crc;
|
|
}
|
|
|
|
// Hash "hello/world" as if it was "helloworld"
|
|
// To hash a forward slash we need to use "hello\\/world"
|
|
// IM_ASSERT(ImHashDecoratedPath("Hello/world") == ImHashStr("Helloworld", 0));
|
|
// IM_ASSERT(ImHashDecoratedPath("Hello\\/world") == ImHashStr("Hello/world", 0));
|
|
// IM_ASSERT(ImHashDecoratedPath("$$1") == (n = 1, ImHashData(&n, sizeof(int))));
|
|
// Adapted from ImHash(). Not particularly fast!
|
|
ImGuiID ImHashDecoratedPath(const char* str, const char* str_end, ImGuiID seed)
|
|
{
|
|
static ImU32 crc32_lut[256] = { 0 };
|
|
if (!crc32_lut[1])
|
|
{
|
|
const ImU32 polynomial = 0xEDB88320;
|
|
for (ImU32 i = 0; i < 256; i++)
|
|
{
|
|
ImU32 crc = i;
|
|
for (ImU32 j = 0; j < 8; j++)
|
|
crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial);
|
|
crc32_lut[i] = crc;
|
|
}
|
|
}
|
|
|
|
// Prefixing the string with / ignore the seed
|
|
if (str != str_end && str[0] == '/')
|
|
seed = 0;
|
|
|
|
seed = ~seed;
|
|
ImU32 crc = seed;
|
|
|
|
// Focus for non-zero terminated string for consistency
|
|
if (str_end == NULL)
|
|
str_end = str + strlen(str);
|
|
|
|
bool inhibit_one = false;
|
|
bool new_section = true;
|
|
const unsigned char* current = (const unsigned char*)str;
|
|
while (current < (const unsigned char*)str_end)
|
|
{
|
|
const unsigned char c = *current++;
|
|
|
|
// Backslash to inhibit special behavior of following character
|
|
if (c == '\\' && !inhibit_one)
|
|
{
|
|
inhibit_one = true;
|
|
continue;
|
|
}
|
|
|
|
// Forward slashes are ignored unless prefixed with a backward slash
|
|
if (c == '/' && !inhibit_one)
|
|
{
|
|
inhibit_one = false;
|
|
new_section = true;
|
|
seed = crc; // Set seed to the new path
|
|
continue;
|
|
}
|
|
|
|
// $$ at the beginning of a section to encode literals.
|
|
// - Currently: "$$????" = hash of 1 as int
|
|
// - May add pointers and other types.
|
|
if (c == '$' && current[0] == '$' && !inhibit_one && new_section)
|
|
{
|
|
crc = ImHashDecoratedPathParseLiteral(crc, current + 1, (const unsigned char*)str_end, ¤t);
|
|
continue;
|
|
}
|
|
|
|
// Reset the hash when encountering ###
|
|
if (c == '#' && current[0] == '#' && current[1] == '#')
|
|
crc = seed;
|
|
|
|
// Hash byte
|
|
crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
|
|
|
|
inhibit_one = new_section = false;
|
|
}
|
|
return ~crc;
|
|
}
|
|
|
|
// Returns a next element of decorated hash path.
|
|
// "//hello/world/child" --> "world/child"
|
|
// "world/child" --> "child"
|
|
// This is a helper for code needing to do some parsing of individual nodes in a path.
|
|
// Note: we need the (unsigned char*) stuff in order to keep code similar to ImHashDecoratedPath(). They are not really necessary in this function tho.
|
|
const char* ImFindNextDecoratedPartInPath(const char* str, const char* str_end)
|
|
{
|
|
const unsigned char* current = (const unsigned char*)str;
|
|
while (*current == '/')
|
|
current++;
|
|
|
|
bool inhibit_one = false;
|
|
while (true)
|
|
{
|
|
if (str_end != NULL && current == (const unsigned char*)str_end)
|
|
break;
|
|
|
|
const unsigned char c = *current++;
|
|
if (c == 0)
|
|
break;
|
|
if (c == '\\' && !inhibit_one)
|
|
{
|
|
inhibit_one = true;
|
|
continue;
|
|
}
|
|
|
|
// Forward slashes are ignored unless prefixed with a backward slash
|
|
if (c == '/' && !inhibit_one)
|
|
return (const char*)current;
|
|
|
|
inhibit_one = false;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// File/Directory Helpers
|
|
//-----------------------------------------------------------------------------
|
|
// - ImFileExist()
|
|
// - ImFileCreateDirectoryChain()
|
|
// - ImFileFindInParents()
|
|
// - ImFileLoadSourceBlurb()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#if _WIN32
|
|
static const char IM_DIR_SEPARATOR = '\\';
|
|
|
|
static void ImUtf8ToWideChar(const char* multi_byte, ImVector<wchar_t>* buf)
|
|
{
|
|
const int wsize = ::MultiByteToWideChar(CP_UTF8, 0, multi_byte, -1, NULL, 0);
|
|
buf->resize(wsize);
|
|
::MultiByteToWideChar(CP_UTF8, 0, multi_byte, -1, (wchar_t*)buf->Data, wsize);
|
|
}
|
|
#else
|
|
static const char IM_DIR_SEPARATOR = '/';
|
|
#endif
|
|
|
|
bool ImFileExist(const char* filename)
|
|
{
|
|
struct stat dir_stat;
|
|
int ret = stat(filename, &dir_stat);
|
|
return (ret == 0);
|
|
}
|
|
|
|
bool ImFileDelete(const char* filename)
|
|
{
|
|
#if _WIN32
|
|
ImVector<wchar_t> buf;
|
|
ImUtf8ToWideChar(filename, &buf);
|
|
return ::DeleteFileW(&buf[0]) == TRUE;
|
|
#else
|
|
unlink(filename);
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// Create directories for specified path. Slashes will be replaced with platform directory separators.
|
|
// e.g. ImFileCreateDirectoryChain("aaaa/bbbb/cccc.png")
|
|
// will try to create "aaaa/" then "aaaa/bbbb/".
|
|
bool ImFileCreateDirectoryChain(const char* path, const char* path_end)
|
|
{
|
|
IM_ASSERT(path != NULL);
|
|
IM_ASSERT(path[0] != 0);
|
|
|
|
if (path_end == NULL)
|
|
path_end = path + strlen(path);
|
|
|
|
// Copy in a local, zero-terminated buffer
|
|
size_t path_len = (size_t)(path_end - path);
|
|
char* path_local = (char*)IM_ALLOC(path_len + 1);
|
|
memcpy(path_local, path, path_len);
|
|
path_local[path_len] = 0;
|
|
|
|
#if defined(_WIN32)
|
|
ImVector<wchar_t> buf;
|
|
#endif
|
|
// Modification of passed file_name allows us to avoid extra temporary memory allocation.
|
|
// strtok() pokes \0 into places where slashes are, we create a directory using directory_name and restore slash.
|
|
for (char* token = strtok(path_local, "\\/"); token != NULL; token = strtok(NULL, "\\/"))
|
|
{
|
|
// strtok() replaces slashes with NULLs. Overwrite removed slashes here with the type of slashes the OS needs (win32 functions need backslashes).
|
|
if (token != path_local)
|
|
*(token - 1) = IM_DIR_SEPARATOR;
|
|
|
|
#if defined(_WIN32)
|
|
// Use ::CreateDirectoryW() because ::CreateDirectoryA() treat filenames in the local code-page instead of UTF-8
|
|
// We cannot use ImWchar, which can be 32bits if IMGUI_USE_WCHAR32 (and CreateDirectoryW require 16bits wchar)
|
|
int filename_wsize = MultiByteToWideChar(CP_UTF8, 0, path_local, -1, NULL, 0);
|
|
buf.resize(filename_wsize);
|
|
MultiByteToWideChar(CP_UTF8, 0, path_local, -1, &buf[0], filename_wsize);
|
|
if (!::CreateDirectoryW((wchar_t*)&buf[0], NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
|
|
#else
|
|
if (mkdir(path_local, S_IRWXU) != 0 && errno != EEXIST)
|
|
#endif
|
|
{
|
|
IM_FREE(path_local);
|
|
return false;
|
|
}
|
|
}
|
|
IM_FREE(path_local);
|
|
return true;
|
|
}
|
|
|
|
bool ImFileFindInParents(const char* sub_path, int max_parent_count, Str* output)
|
|
{
|
|
IM_ASSERT(sub_path != NULL);
|
|
IM_ASSERT(output != NULL);
|
|
for (int parent_level = 0; parent_level < max_parent_count; parent_level++)
|
|
{
|
|
output->clear();
|
|
for (int j = 0; j < parent_level; j++)
|
|
output->append("../");
|
|
output->append(sub_path);
|
|
if (ImFileExist(output->c_str()))
|
|
return true;
|
|
}
|
|
output->clear();
|
|
return false;
|
|
}
|
|
|
|
bool ImFileLoadSourceBlurb(const char* file_name, int line_no_start, int line_no_end, ImGuiTextBuffer* out_buf)
|
|
{
|
|
size_t file_size = 0;
|
|
char* file_begin = (char*)ImFileLoadToMemory(file_name, "rb", &file_size, 1);
|
|
if (file_begin == NULL)
|
|
return false;
|
|
|
|
char* file_end = file_begin + file_size;
|
|
int line_no = 0;
|
|
const char* test_src_begin = NULL;
|
|
const char* test_src_end = NULL;
|
|
for (const char* p = file_begin; p < file_end; )
|
|
{
|
|
line_no++;
|
|
const char* line_begin = p;
|
|
const char* line_end = ImStrchrRange(line_begin + 1, file_end, '\n');
|
|
if (line_end == NULL)
|
|
line_end = file_end;
|
|
if (line_no >= line_no_start && line_no <= line_no_end)
|
|
{
|
|
if (test_src_begin == NULL)
|
|
test_src_begin = line_begin;
|
|
test_src_end = ImMax(test_src_end, line_end);
|
|
}
|
|
p = line_end + 1;
|
|
}
|
|
|
|
if (test_src_begin != NULL)
|
|
out_buf->append(test_src_begin, test_src_end);
|
|
else
|
|
out_buf->clear();
|
|
|
|
ImGui::MemFree(file_begin);
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Path Helpers
|
|
//-----------------------------------------------------------------------------
|
|
// - ImPathFindFilename()
|
|
// - ImPathFindFileExt()
|
|
// - ImPathFixSeparatorsForCurrentOS()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const char* ImPathFindFilename(const char* path, const char* path_end)
|
|
{
|
|
IM_ASSERT(path != NULL);
|
|
if (!path_end)
|
|
path_end = path + strlen(path);
|
|
const char* p = path_end;
|
|
while (p > path)
|
|
{
|
|
if (p[-1] == '/' || p[-1] == '\\')
|
|
break;
|
|
p--;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
// "folder/filename" -> return pointer to "" (end of string)
|
|
// "folder/filename.png" -> return pointer to ".png"
|
|
// "folder/filename.png.bak" -> return pointer to ".png.bak"
|
|
const char* ImPathFindExtension(const char* path, const char* path_end)
|
|
{
|
|
if (!path_end)
|
|
path_end = path + strlen(path);
|
|
const char* filename = ImPathFindFilename(path, path_end);
|
|
const char* p = filename;
|
|
while (p < path_end)
|
|
{
|
|
if (p[0] == '.')
|
|
break;
|
|
p++;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void ImPathFixSeparatorsForCurrentOS(char* buf)
|
|
{
|
|
#ifdef _WIN32
|
|
for (char* p = buf; *p != 0; p++)
|
|
if (*p == '/')
|
|
*p = '\\';
|
|
#else
|
|
for (char* p = buf; *p != 0; p++)
|
|
if (*p == '\\')
|
|
*p = '/';
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// String Helpers
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static const char* ImStrStr(const char* haystack, size_t hlen, const char* needle, int nlen)
|
|
{
|
|
const char* end = haystack + hlen;
|
|
const char* p = haystack;
|
|
while ((p = (const char*)memchr(p, *needle, end - p)) != NULL)
|
|
{
|
|
if (end - p < nlen)
|
|
return NULL;
|
|
if (memcmp(p, needle, nlen) == 0)
|
|
return p;
|
|
p++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void ImStrReplace(Str* s, const char* find, const char* repl)
|
|
{
|
|
IM_ASSERT(find != NULL && *find);
|
|
IM_ASSERT(repl != NULL);
|
|
int find_len = (int)strlen(find);
|
|
int repl_len = (int)strlen(repl);
|
|
int repl_diff = repl_len - find_len;
|
|
|
|
// Estimate required length of new buffer if string size increases.
|
|
int need_capacity = s->capacity();
|
|
int num_matches = INT_MAX;
|
|
if (repl_diff > 0)
|
|
{
|
|
num_matches = 0;
|
|
need_capacity = s->length() + 1;
|
|
for (char* p = s->c_str(), *end = s->c_str() + s->length(); p != NULL && p < end;)
|
|
{
|
|
p = (char*)ImStrStr(p, end - p, find, find_len);
|
|
if (p)
|
|
{
|
|
need_capacity += repl_diff;
|
|
p += find_len;
|
|
num_matches++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (num_matches == 0)
|
|
return;
|
|
|
|
const char* not_owned_data = s->owned() ? NULL : s->c_str();
|
|
if (!s->owned() || need_capacity > s->capacity())
|
|
s->reserve(need_capacity);
|
|
if (not_owned_data != NULL)
|
|
s->set(not_owned_data);
|
|
|
|
// Replace data.
|
|
for (char* p = s->c_str(), *end = s->c_str() + s->length(); p != NULL && p < end && num_matches--;)
|
|
{
|
|
p = (char*)ImStrStr(p, end - p, find, find_len);
|
|
if (p)
|
|
{
|
|
memmove(p + repl_len, p + find_len, end - p - find_len + 1);
|
|
memcpy(p, repl, repl_len);
|
|
p += repl_len;
|
|
end += repl_diff;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* ImStrchrRangeWithEscaping(const char* str, const char* str_end, char find_c)
|
|
{
|
|
while (str < str_end)
|
|
{
|
|
const char c = *str;
|
|
if (c == '\\')
|
|
{
|
|
str += 2;
|
|
continue;
|
|
}
|
|
if (c == find_c)
|
|
return str;
|
|
str++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Suboptimal but ok for the data size we are dealing with (see commit on 2022/08/22 for a faster and more complicated version)
|
|
void ImStrXmlEscape(Str* s)
|
|
{
|
|
ImStrReplace(s, "&", "&");
|
|
ImStrReplace(s, "<", "<");
|
|
ImStrReplace(s, ">", ">");
|
|
ImStrReplace(s, "\"", """);
|
|
ImStrReplace(s, "\'", "'");
|
|
}
|
|
|
|
// Based on code from https://github.com/EddieBreeg/C_b64 by @EddieBreeg.
|
|
int ImStrBase64Encode(const unsigned char* src, char* dst, int length)
|
|
{
|
|
static const char* b64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
int i, j, k, l, encoded_len = 0;
|
|
|
|
while (length > 0)
|
|
{
|
|
switch (length)
|
|
{
|
|
case 1:
|
|
i = src[0] >> 2;
|
|
j = (src[0] & 3) << 4;
|
|
k = 64;
|
|
l = 64;
|
|
break;
|
|
case 2:
|
|
i = src[0] >> 2;
|
|
j = ((src[0] & 3) << 4) | (src[1] >> 4);
|
|
k = (src[1] & 15) << 2;
|
|
l = 64;
|
|
break;
|
|
default:
|
|
i = src[0] >> 2;
|
|
j = ((src[0] & 3) << 4) | (src[1] >> 4);
|
|
k = ((src[1] & 0xf) << 2) | (src[2] >> 6 & 3);
|
|
l = src[2] & 0x3f;
|
|
break;
|
|
}
|
|
dst[0] = b64Table[i];
|
|
dst[1] = b64Table[j];
|
|
dst[2] = b64Table[k];
|
|
dst[3] = b64Table[l];
|
|
src += 3;
|
|
dst += 4;
|
|
length -= 3;
|
|
encoded_len += 4;
|
|
}
|
|
return encoded_len;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Parsing Helpers
|
|
//-----------------------------------------------------------------------------
|
|
// - ImParseSplitCommandLine()
|
|
// - ImParseFindIniSection()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImParseExtractArgcArgvFromCommandLine(int* out_argc, char const*** out_argv, const char* cmd_line)
|
|
{
|
|
size_t cmd_line_len = strlen(cmd_line);
|
|
|
|
int n = 1;
|
|
{
|
|
const char* p = cmd_line;
|
|
while (*p != 0)
|
|
{
|
|
const char* arg = p;
|
|
while (*arg == ' ')
|
|
arg++;
|
|
const char* arg_end = strchr(arg, ' ');
|
|
if (arg_end == NULL)
|
|
p = arg_end = cmd_line + cmd_line_len;
|
|
else
|
|
p = arg_end + 1;
|
|
n++;
|
|
}
|
|
}
|
|
|
|
int argc = n;
|
|
char const** argv = (char const**)malloc(sizeof(char*) * ((size_t)argc + 1) + (cmd_line_len + 1));
|
|
IM_ASSERT(argv != NULL);
|
|
char* cmd_line_dup = (char*)argv + sizeof(char*) * ((size_t)argc + 1);
|
|
strcpy(cmd_line_dup, cmd_line);
|
|
|
|
{
|
|
argv[0] = "main.exe";
|
|
argv[argc] = NULL;
|
|
|
|
char* p = cmd_line_dup;
|
|
for (n = 1; n < argc; n++)
|
|
{
|
|
char* arg = p;
|
|
char* arg_end = strchr(arg, ' ');
|
|
if (arg_end == NULL)
|
|
p = arg_end = cmd_line_dup + cmd_line_len;
|
|
else
|
|
p = arg_end + 1;
|
|
argv[n] = arg;
|
|
arg_end[0] = 0;
|
|
}
|
|
}
|
|
|
|
*out_argc = argc;
|
|
*out_argv = argv;
|
|
}
|
|
|
|
bool ImParseFindIniSection(const char* ini_config, const char* header, ImVector<char>* result)
|
|
{
|
|
IM_ASSERT(ini_config != NULL);
|
|
IM_ASSERT(header != NULL);
|
|
IM_ASSERT(result != NULL);
|
|
|
|
size_t ini_len = strlen(ini_config);
|
|
size_t header_len = strlen(header);
|
|
|
|
IM_ASSERT(header_len > 0);
|
|
|
|
if (ini_len == 0)
|
|
return false;
|
|
|
|
const char* section_start = strstr(ini_config, header);
|
|
if (section_start == NULL)
|
|
return false;
|
|
|
|
const char* section_end = strstr(section_start + header_len, "\n[");
|
|
if (section_end == NULL)
|
|
section_end = section_start + ini_len;
|
|
|
|
// "\n[" matches next header start on all platforms, but it cuts new line marker in half on windows.
|
|
if (*(section_end - 1) == '\r')
|
|
--section_end;
|
|
|
|
size_t section_len = (size_t)(section_end - section_start);
|
|
result->resize((int)section_len + 1);
|
|
ImStrncpy(result->Data, section_start, section_len);
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Time Helpers
|
|
//-----------------------------------------------------------------------------
|
|
// - ImTimeGetInMicroseconds()
|
|
// - ImTimestampToISO8601()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
uint64_t ImTimeGetInMicroseconds()
|
|
{
|
|
// Trying std::chrono out of unfettered optimism that it may actually work..
|
|
using namespace std;
|
|
chrono::microseconds ms = chrono::duration_cast<chrono::microseconds>(chrono::high_resolution_clock::now().time_since_epoch());
|
|
return (uint64_t)ms.count();
|
|
}
|
|
|
|
void ImTimestampToISO8601(uint64_t timestamp, Str* out_date)
|
|
{
|
|
time_t unix_time = (time_t)(timestamp / 1000000); // Convert to seconds.
|
|
tm* time = gmtime(&unix_time);
|
|
const char* time_format = "%Y-%m-%dT%H:%M:%S";
|
|
size_t size_req = strftime(out_date->c_str(), out_date->capacity(), time_format, time);
|
|
if (size_req >= (size_t)out_date->capacity())
|
|
{
|
|
out_date->reserve((int)size_req);
|
|
strftime(out_date->c_str(), out_date->capacity(), time_format, time);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Threading Helpers
|
|
//-----------------------------------------------------------------------------
|
|
// - ImThreadSleepInMilliseconds()
|
|
// - ImThreadSetCurrentThreadDescription()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImThreadSleepInMilliseconds(int ms)
|
|
{
|
|
using namespace std;
|
|
this_thread::sleep_for(chrono::milliseconds(ms));
|
|
}
|
|
|
|
#if defined(_MSC_VER)
|
|
// Helper function for setting thread name on Win32
|
|
// This is a separate function because __try cannot coexist with local objects that need destructors called on stack unwind
|
|
static void ImThreadSetCurrentThreadDescriptionWin32OldStyle(const char* description)
|
|
{
|
|
// Old-style Win32 thread name setting method
|
|
// See https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code
|
|
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
|
#pragma pack(push,8)
|
|
typedef struct tagTHREADNAME_INFO
|
|
{
|
|
DWORD dwType; // Must be 0x1000.
|
|
LPCSTR szName; // Pointer to name (in user addr space).
|
|
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
|
DWORD dwFlags; // Reserved for future use, must be zero.
|
|
} THREADNAME_INFO;
|
|
#pragma pack(pop)
|
|
|
|
THREADNAME_INFO info;
|
|
info.dwType = 0x1000;
|
|
info.szName = description;
|
|
info.dwThreadID = (DWORD)-1;
|
|
info.dwFlags = 0;
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 6320 6322)
|
|
__try
|
|
{
|
|
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
}
|
|
#pragma warning(pop)
|
|
}
|
|
#endif // #ifdef _WIN32
|
|
|
|
// Set the description (name) of the current thread for debugging purposes
|
|
void ImThreadSetCurrentThreadDescription(const char* description)
|
|
{
|
|
#if defined(_MSC_VER) // Windows + Visual Studio
|
|
// New-style thread name setting
|
|
// Only supported from Win 10 version 1607/Server 2016 onwards, hence the need for dynamic linking
|
|
|
|
typedef HRESULT(WINAPI* SetThreadDescriptionFunc)(HANDLE hThread, PCWSTR lpThreadDescription);
|
|
|
|
SetThreadDescriptionFunc set_thread_description = (SetThreadDescriptionFunc)::GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetThreadDescription");
|
|
if (set_thread_description)
|
|
{
|
|
ImVector<ImWchar> buf;
|
|
const int description_wsize = ImTextCountCharsFromUtf8(description, NULL) + 1;
|
|
buf.resize(description_wsize);
|
|
ImTextStrFromUtf8(&buf[0], description_wsize, description, NULL);
|
|
set_thread_description(::GetCurrentThread(), (wchar_t*)&buf[0]);
|
|
}
|
|
|
|
// Also do the old-style method too even if the new-style one worked, as the two work in slightly different sets of circumstances
|
|
ImThreadSetCurrentThreadDescriptionWin32OldStyle(description);
|
|
#elif defined(__linux) || defined(__linux__) || defined(__MINGW32__) // Linux or MingW
|
|
pthread_setname_np(pthread_self(), description);
|
|
#elif defined(__MACH__) || defined(__MSL__) // OSX
|
|
pthread_setname_np(description);
|
|
#else
|
|
// This is a nice-to-have rather than critical functionality, so fail silently if we don't support this platform
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Build info helpers
|
|
//-----------------------------------------------------------------------------
|
|
// - ImBuildGetCompilationInfo()
|
|
// - ImBuildGetGitBranchName()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Turn __DATE__ "Jan 10 2019" into "2019-01-10"
|
|
static void ImBuildParseDateFromCompilerIntoYMD(const char* in_date, char* out_buf, size_t out_buf_size)
|
|
{
|
|
char month_str[5];
|
|
int year, month, day;
|
|
sscanf(in_date, "%3s %d %d", month_str, &day, &year);
|
|
const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
|
const char* p = strstr(month_names, month_str);
|
|
month = p ? (int)(1 + (p - month_names) / 3) : 0;
|
|
ImFormatString(out_buf, out_buf_size, "%04d-%02d-%02d", year, month, day);
|
|
}
|
|
|
|
// Those strings are used to output easily identifiable markers in compare logs. We only need to support what we use for testing.
|
|
// We can probably grab info in eaplatform.h/eacompiler.h etc. in EASTL
|
|
const ImBuildInfo* ImBuildGetCompilationInfo()
|
|
{
|
|
static ImBuildInfo build_info;
|
|
|
|
if (build_info.Type[0] == '\0')
|
|
{
|
|
// Build Type
|
|
#if defined(DEBUG) || defined(_DEBUG)
|
|
build_info.Type = "Debug";
|
|
#else
|
|
build_info.Type = "Release";
|
|
#endif
|
|
|
|
// CPU
|
|
#if defined(_M_X86) || defined(_M_IX86) || defined(__i386) || defined(__i386__) || defined(_X86_) || defined(_M_AMD64) || defined(_AMD64_) || defined(__x86_64__)
|
|
build_info.Cpu = (sizeof(size_t) == 4) ? "X86" : "X64";
|
|
#elif defined(__aarch64__) || (defined(_M_ARM64) && defined(_WIN64))
|
|
build_info.Cpu = "ARM64";
|
|
#elif defined(__EMSCRIPTEN__)
|
|
build_info.Cpu = "WebAsm";
|
|
#else
|
|
build_info.Cpu = (sizeof(size_t) == 4) ? "Unknown32" : "Unknown64";
|
|
#endif
|
|
|
|
// Platform/OS
|
|
#if defined(_WIN32)
|
|
build_info.OS = "Windows";
|
|
#elif defined(__linux) || defined(__linux__)
|
|
build_info.OS = "Linux";
|
|
#elif defined(__MACH__) || defined(__MSL__)
|
|
build_info.OS = "OSX";
|
|
#elif defined(__ORBIS__)
|
|
build_info.OS = "PS4";
|
|
#elif defined(_DURANGO)
|
|
build_info.OS = "XboxOne";
|
|
#else
|
|
build_info.OS = "Unknown";
|
|
#endif
|
|
|
|
// Compiler
|
|
#if defined(_MSC_VER)
|
|
build_info.Compiler = "MSVC";
|
|
#elif defined(__clang__)
|
|
build_info.Compiler = "Clang";
|
|
#elif defined(__GNUC__)
|
|
build_info.Compiler = "GCC";
|
|
#else
|
|
build_info.Compiler = "Unknown";
|
|
#endif
|
|
|
|
// Date/Time
|
|
ImBuildParseDateFromCompilerIntoYMD(__DATE__, build_info.Date, IM_ARRAYSIZE(build_info.Date));
|
|
build_info.Time = __TIME__;
|
|
}
|
|
|
|
return &build_info;
|
|
}
|
|
|
|
bool ImBuildFindGitBranchName(const char* git_repo_path, Str* branch_name)
|
|
{
|
|
IM_ASSERT(git_repo_path != NULL);
|
|
IM_ASSERT(branch_name != NULL);
|
|
Str256f head_path("%s/.git/HEAD", git_repo_path);
|
|
size_t head_size = 0;
|
|
bool result = false;
|
|
if (char* git_head = (char*)ImFileLoadToMemory(head_path.c_str(), "r", &head_size, 1))
|
|
{
|
|
const char prefix[] = "ref: refs/heads/"; // Branch name is prefixed with this in HEAD file.
|
|
const int prefix_length = IM_ARRAYSIZE(prefix) - 1;
|
|
strtok(git_head, "\r\n"); // Trim new line
|
|
if (head_size > prefix_length && strncmp(git_head, prefix, prefix_length) == 0)
|
|
{
|
|
// "ref: refs/heads/master" -> "master"
|
|
branch_name->set(git_head + prefix_length);
|
|
}
|
|
else
|
|
{
|
|
// Should be git hash, keep first 8 characters (see #42)
|
|
branch_name->setf("%.8s", git_head);
|
|
}
|
|
result = true;
|
|
IM_FREE(git_head);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Operating System Helpers
|
|
//-----------------------------------------------------------------------------
|
|
// - ImOsCreateProcess()
|
|
// - ImOsPOpen()
|
|
// - ImOsPClose()
|
|
// - ImOsOpenInShell()
|
|
// - ImOsConsoleSetTextColor()
|
|
// - ImOsIsDebuggerPresent()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool ImOsCreateProcess(const char* cmd_line)
|
|
{
|
|
#ifdef _WIN32
|
|
STARTUPINFOA siStartInfo;
|
|
PROCESS_INFORMATION piProcInfo;
|
|
ZeroMemory(&siStartInfo, sizeof(STARTUPINFOA));
|
|
char* cmd_line_copy = ImStrdup(cmd_line);
|
|
BOOL ret = ::CreateProcessA(NULL, cmd_line_copy, NULL, NULL, FALSE, 0, NULL, NULL, &siStartInfo, &piProcInfo);
|
|
free(cmd_line_copy);
|
|
::CloseHandle(siStartInfo.hStdInput);
|
|
::CloseHandle(siStartInfo.hStdOutput);
|
|
::CloseHandle(siStartInfo.hStdError);
|
|
::CloseHandle(piProcInfo.hProcess);
|
|
::CloseHandle(piProcInfo.hThread);
|
|
return ret != 0;
|
|
#else
|
|
IM_UNUSED(cmd_line);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
FILE* ImOsPOpen(const char* cmd_line, const char* mode)
|
|
{
|
|
IM_ASSERT(cmd_line != NULL && *cmd_line);
|
|
IM_ASSERT(mode != NULL && *mode);
|
|
#if _WIN32
|
|
ImVector<wchar_t> w_cmd_line;
|
|
ImVector<wchar_t> w_mode;
|
|
ImUtf8ToWideChar(cmd_line, &w_cmd_line);
|
|
ImUtf8ToWideChar(mode, &w_mode);
|
|
w_mode.resize(w_mode.Size + 1);
|
|
wcscat(w_mode.Data, L"b"); // Windows requires 'b' mode while unixes do not support it and default to binary.
|
|
return _wpopen(w_cmd_line.Data, w_mode.Data);
|
|
#else
|
|
return popen(cmd_line, mode);
|
|
#endif
|
|
}
|
|
|
|
void ImOsPClose(FILE* fp)
|
|
{
|
|
IM_ASSERT(fp != NULL);
|
|
#if _WIN32
|
|
_pclose(fp);
|
|
#else
|
|
pclose(fp);
|
|
#endif
|
|
}
|
|
|
|
void ImOsOpenInShell(const char* path)
|
|
{
|
|
Str256 command(path);
|
|
#ifdef _WIN32
|
|
ImPathFixSeparatorsForCurrentOS(command.c_str());
|
|
::ShellExecuteA(NULL, "open", command.c_str(), NULL, NULL, SW_SHOWDEFAULT);
|
|
#else
|
|
#if __APPLE__
|
|
const char* open_executable = "open";
|
|
#else
|
|
const char* open_executable = "xdg-open";
|
|
#endif
|
|
command.setf("%s \"%s\"", open_executable, path);
|
|
ImPathFixSeparatorsForCurrentOS(command.c_str());
|
|
system(command.c_str());
|
|
#endif
|
|
}
|
|
|
|
void ImOsConsoleSetTextColor(ImOsConsoleStream stream, ImOsConsoleTextColor color)
|
|
{
|
|
#ifdef _WIN32
|
|
HANDLE hConsole = 0;
|
|
switch (stream)
|
|
{
|
|
case ImOsConsoleStream_StandardOutput: hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE); break;
|
|
case ImOsConsoleStream_StandardError: hConsole = ::GetStdHandle(STD_ERROR_HANDLE); break;
|
|
}
|
|
WORD wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
|
switch (color)
|
|
{
|
|
case ImOsConsoleTextColor_Black: wAttributes = 0x00; break;
|
|
case ImOsConsoleTextColor_White: wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
|
|
case ImOsConsoleTextColor_BrightWhite: wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break;
|
|
case ImOsConsoleTextColor_BrightRed: wAttributes = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
|
|
case ImOsConsoleTextColor_BrightGreen: wAttributes = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
|
|
case ImOsConsoleTextColor_BrightBlue: wAttributes = FOREGROUND_BLUE | FOREGROUND_INTENSITY; break;
|
|
case ImOsConsoleTextColor_BrightYellow: wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
|
|
default: IM_ASSERT(0);
|
|
}
|
|
::SetConsoleTextAttribute(hConsole, wAttributes);
|
|
#elif defined(__linux) || defined(__linux__) || defined(__MACH__) || defined(__MSL__)
|
|
// FIXME: check system capabilities (with environment variable TERM)
|
|
FILE* handle = 0;
|
|
switch (stream)
|
|
{
|
|
case ImOsConsoleStream_StandardOutput: handle = stdout; break;
|
|
case ImOsConsoleStream_StandardError: handle = stderr; break;
|
|
}
|
|
|
|
const char* modifier = "";
|
|
switch (color)
|
|
{
|
|
case ImOsConsoleTextColor_Black: modifier = "\033[30m"; break;
|
|
case ImOsConsoleTextColor_White: modifier = "\033[0m"; break;
|
|
case ImOsConsoleTextColor_BrightWhite: modifier = "\033[1;37m"; break;
|
|
case ImOsConsoleTextColor_BrightRed: modifier = "\033[1;31m"; break;
|
|
case ImOsConsoleTextColor_BrightGreen: modifier = "\033[1;32m"; break;
|
|
case ImOsConsoleTextColor_BrightBlue: modifier = "\033[1;34m"; break;
|
|
case ImOsConsoleTextColor_BrightYellow: modifier = "\033[1;33m"; break;
|
|
default: IM_ASSERT(0);
|
|
}
|
|
|
|
fprintf(handle, "%s", modifier);
|
|
#endif
|
|
}
|
|
|
|
bool ImOsIsDebuggerPresent()
|
|
{
|
|
#ifdef _WIN32
|
|
return ::IsDebuggerPresent() != 0;
|
|
#elif defined(__linux__)
|
|
int debugger_pid = 0;
|
|
char buf[2048]; // TracerPid is located near the start of the file. If end of the buffer gets cut off thats fine.
|
|
FILE* fp = fopen("/proc/self/status", "rb"); // Can not use ImFileLoadToMemory because size detection of /proc/self/status would fail.
|
|
if (fp == NULL)
|
|
return false;
|
|
fread(buf, 1, IM_ARRAYSIZE(buf), fp);
|
|
fclose(fp);
|
|
buf[IM_ARRAYSIZE(buf) - 1] = 0;
|
|
if (char* tracer_pid = strstr(buf, "TracerPid:"))
|
|
{
|
|
tracer_pid += 10; // Skip label
|
|
while (isspace(*tracer_pid))
|
|
tracer_pid++;
|
|
debugger_pid = atoi(tracer_pid);
|
|
}
|
|
return debugger_pid != 0;
|
|
#elif defined(__APPLE__)
|
|
// https://stackoverflow.com/questions/2200277/detecting-debugger-on-mac-os-x
|
|
int junk;
|
|
int mib[4];
|
|
struct kinfo_proc info;
|
|
size_t size;
|
|
|
|
// Initialize mib, which tells sysctl the info we want, in this case
|
|
// we're looking for information about a specific process ID.
|
|
info.kp_proc.p_flag = 0;
|
|
mib[0] = CTL_KERN;
|
|
mib[1] = KERN_PROC;
|
|
mib[2] = KERN_PROC_PID;
|
|
mib[3] = getpid();
|
|
|
|
size = sizeof(info);
|
|
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
|
|
IM_ASSERT(junk == 0);
|
|
|
|
// We're being debugged if the P_TRACED flag is set.
|
|
return (info.kp_proc.p_flag & P_TRACED) != 0;
|
|
#else
|
|
// FIXME
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void ImOsOutputDebugString(const char* message)
|
|
{
|
|
#ifdef _WIN32
|
|
OutputDebugStringA(message);
|
|
#else
|
|
IM_UNUSED(message);
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Str.h + InputText bindings
|
|
//-----------------------------------------------------------------------------
|
|
|
|
struct InputTextCallbackStr_UserData
|
|
{
|
|
Str* StrObj;
|
|
ImGuiInputTextCallback ChainCallback;
|
|
void* ChainCallbackUserData;
|
|
};
|
|
|
|
static int InputTextCallbackStr(ImGuiInputTextCallbackData* data)
|
|
{
|
|
InputTextCallbackStr_UserData* user_data = (InputTextCallbackStr_UserData*)data->UserData;
|
|
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
|
|
{
|
|
// Resize string callback
|
|
// If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want.
|
|
Str* str = user_data->StrObj;
|
|
IM_ASSERT(data->Buf == str->c_str());
|
|
str->reserve(data->BufTextLen + 1);
|
|
data->Buf = (char*)str->c_str();
|
|
}
|
|
else if (user_data->ChainCallback)
|
|
{
|
|
// Forward to user callback, if any
|
|
data->UserData = user_data->ChainCallbackUserData;
|
|
return user_data->ChainCallback(data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Draw an extra colored frame over the previous item
|
|
// Similar to DebugDrawItemRect() but use Max(1.0f, FrameBorderSize)
|
|
void ImGui::ItemErrorFrame(ImU32 col)
|
|
{
|
|
ImGuiContext& g = *GetCurrentContext();
|
|
ImDrawList* drawlist = GetWindowDrawList();
|
|
ImGuiStyle& style = GetStyle();
|
|
// FIXME: GetItemRectMin() / GetItemRectMax() will include label. NavRect is not probably defined :(
|
|
drawlist->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, GetColorU32(col), style.FrameRounding, ImDrawFlags_None, ImMax(1.0f, style.FrameBorderSize));
|
|
}
|
|
|
|
bool ImGui::InputText(const char* label, Str* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
|
{
|
|
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
|
flags |= ImGuiInputTextFlags_CallbackResize;
|
|
|
|
InputTextCallbackStr_UserData cb_user_data;
|
|
cb_user_data.StrObj = str;
|
|
cb_user_data.ChainCallback = callback;
|
|
cb_user_data.ChainCallbackUserData = user_data;
|
|
return InputText(label, (char*)str->c_str(), (size_t)str->capacity() + 1, flags, InputTextCallbackStr, &cb_user_data);
|
|
}
|
|
|
|
bool ImGui::InputTextWithHint(const char* label, const char* hint, Str* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
|
{
|
|
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
|
flags |= ImGuiInputTextFlags_CallbackResize;
|
|
|
|
InputTextCallbackStr_UserData cb_user_data;
|
|
cb_user_data.StrObj = str;
|
|
cb_user_data.ChainCallback = callback;
|
|
cb_user_data.ChainCallbackUserData = user_data;
|
|
return InputTextWithHint(label, hint, (char*)str->c_str(), (size_t)str->capacity() + 1, flags, InputTextCallbackStr, &cb_user_data);
|
|
}
|
|
|
|
bool ImGui::InputTextMultiline(const char* label, Str* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
|
{
|
|
IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
|
|
flags |= ImGuiInputTextFlags_CallbackResize;
|
|
|
|
InputTextCallbackStr_UserData cb_user_data;
|
|
cb_user_data.StrObj = str;
|
|
cb_user_data.ChainCallback = callback;
|
|
cb_user_data.ChainCallbackUserData = user_data;
|
|
return InputTextMultiline(label, (char*)str->c_str(), (size_t)str->capacity() + 1, size, flags, InputTextCallbackStr, &cb_user_data);
|
|
}
|
|
|
|
// anchor parameter indicates which split would retain it's constant size.
|
|
// anchor = 0 - both splits resize when parent container size changes. Both value_1 and value_2 should be persistent.
|
|
// anchor = -1 - top/left split would have a constant size. bottom/right split would resize when parent container size changes. value_1 should be persistent, value_2 will always be recalculated from value_1.
|
|
// anchor = +1 - bottom/right split would have a constant size. top/left split would resize when parent container size changes. value_2 should be persistent, value_1 will always be recalculated from value_2.
|
|
bool ImGui::Splitter(const char* id, float* value_1, float* value_2, int axis, int anchor, float min_size_0, float min_size_1)
|
|
{
|
|
// FIXME-DOGFOODING: This needs further refining.
|
|
// FIXME-SCROLL: When resizing either we'd like to keep scroll focus on something (e.g. last clicked item for list, bottom for log)
|
|
// See https://github.com/ocornut/imgui/issues/319
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiStyle& style = g.Style;
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
if (min_size_0 < 0)
|
|
min_size_0 = ImGui::GetFrameHeight();
|
|
if (min_size_1)
|
|
min_size_1 = ImGui::GetFrameHeight();
|
|
|
|
IM_ASSERT(axis == ImGuiAxis_X || axis == ImGuiAxis_Y);
|
|
|
|
float& v_1 = *value_1;
|
|
float& v_2 = *value_2;
|
|
ImRect splitter_bb;
|
|
const float avail = axis == ImGuiAxis_X ? ImGui::GetContentRegionAvail().x - style.ItemSpacing.x : ImGui::GetContentRegionAvail().y - style.ItemSpacing.y;
|
|
if (anchor < 0)
|
|
{
|
|
v_2 = ImMax(avail - v_1, min_size_1); // First split is constant size.
|
|
}
|
|
else if (anchor > 0)
|
|
{
|
|
v_1 = ImMax(avail - v_2, min_size_0); // Second split is constant size.
|
|
}
|
|
else
|
|
{
|
|
float r = v_1 / (v_1 + v_2); // Both splits maintain same relative size to parent.
|
|
v_1 = IM_ROUND(avail * r) - 1;
|
|
v_2 = IM_ROUND(avail * (1.0f - r)) - 1;
|
|
}
|
|
if (axis == ImGuiAxis_X)
|
|
{
|
|
float x = window->DC.CursorPos.x + v_1 + IM_ROUND(style.ItemSpacing.x * 0.5f);
|
|
splitter_bb = ImRect(x - 1, window->WorkRect.Min.y, x + 1, window->WorkRect.Max.y);
|
|
}
|
|
else if (axis == ImGuiAxis_Y)
|
|
{
|
|
float y = window->DC.CursorPos.y + v_1 + IM_ROUND(style.ItemSpacing.y * 0.5f);
|
|
splitter_bb = ImRect(window->WorkRect.Min.x, y - 1, window->WorkRect.Max.x, y + 1);
|
|
}
|
|
return ImGui::SplitterBehavior(splitter_bb, ImGui::GetID(id), (ImGuiAxis)axis, &v_1, &v_2, min_size_0, min_size_1, 3.0f);
|
|
}
|
|
|
|
// FIXME-TESTS: Should eventually remove.
|
|
ImFont* ImGui::FindFontByPrefix(const char* prefix)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
for (ImFont* font : g.IO.Fonts->Fonts)
|
|
if (strncmp(font->ConfigData->Name, prefix, strlen(prefix)) == 0)
|
|
return font;
|
|
return NULL;
|
|
}
|
|
|
|
// Legacy version support
|
|
#if IMGUI_VERSION_NUM < 18924
|
|
const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
|
|
{
|
|
return tab_bar->GetTabName(tab);
|
|
}
|
|
#endif
|
|
|
|
#if IMGUI_VERSION_NUM < 18927
|
|
ImGuiID ImGui::TableGetInstanceID(ImGuiTable* table, int instance_no)
|
|
{
|
|
// Changed in #6140
|
|
return table->ID + instance_no;
|
|
}
|
|
#endif
|
|
|
|
ImGuiID TableGetHeaderID(ImGuiTable* table, const char* column, int instance_no)
|
|
{
|
|
IM_ASSERT(table != NULL);
|
|
int column_n = -1;
|
|
for (int n = 0; n < table->Columns.size() && column_n < 0; n++)
|
|
if (strcmp(ImGui::TableGetColumnName(table, n), column) == 0)
|
|
column_n = n;
|
|
IM_ASSERT(column_n != -1);
|
|
return TableGetHeaderID(table, column_n, instance_no);
|
|
}
|
|
|
|
ImGuiID TableGetHeaderID(ImGuiTable* table, int column_n, int instance_no)
|
|
{
|
|
IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);
|
|
const ImGuiID table_instance_id = ImGui::TableGetInstanceID(table, instance_no);
|
|
const char* column_name = ImGui::TableGetColumnName(table, column_n);
|
|
#if IMGUI_VERSION_NUM >= 18927
|
|
const int column_id_differencier = column_n;
|
|
#else
|
|
const int column_id_differencier = instance_no * table->ColumnsCount + column_n;
|
|
#endif
|
|
const int column_id = ImHashData(&column_id_differencier, sizeof(column_id_differencier), table_instance_id);
|
|
return ImHashData(column_name, strlen(column_name), column_id);
|
|
}
|
|
|
|
// FIXME: Could be moved to core as an internal function?
|
|
void TableDiscardInstanceAndSettings(ImGuiID table_id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.CurrentTable == NULL);
|
|
if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(table_id))
|
|
settings->ID = 0;
|
|
|
|
if (ImGuiTable* table = ImGui::TableFindByID(table_id))
|
|
ImGui::TableRemove(table);
|
|
// FIXME-TABLE: We should be able to use TableResetSettings() instead of TableRemove()! Maybe less of a clean slate but would be good to check that it does the job
|
|
//ImGui::TableResetSettings(table);
|
|
}
|
|
|
|
// Helper to verify ImDrawData integrity of buffer count (broke before e.g. #6716)
|
|
void DrawDataVerifyMatchingBufferCount(ImDrawData* draw_data)
|
|
{
|
|
#if IMGUI_VERSION_NUM >= 18973
|
|
int total_vtx_count = 0;
|
|
int total_idx_count = 0;
|
|
for (ImDrawList* draw_list : draw_data->CmdLists)
|
|
{
|
|
total_vtx_count += draw_list->VtxBuffer.Size;
|
|
total_idx_count += draw_list->IdxBuffer.Size;
|
|
}
|
|
IM_UNUSED(total_vtx_count);
|
|
IM_UNUSED(total_idx_count);
|
|
IM_ASSERT(total_vtx_count == draw_data->TotalVtxCount);
|
|
IM_ASSERT(total_idx_count == draw_data->TotalIdxCount);
|
|
#else
|
|
IM_UNUSED(draw_data);
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Simple CSV parser
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImGuiCsvParser::Clear()
|
|
{
|
|
Rows = Columns = 0;
|
|
if (_Data != NULL)
|
|
IM_FREE(_Data);
|
|
_Data = NULL;
|
|
_Index.clear();
|
|
}
|
|
|
|
bool ImGuiCsvParser::Load(const char* filename)
|
|
{
|
|
size_t len = 0;
|
|
_Data = (char*)ImFileLoadToMemory(filename, "rb", &len, 1);
|
|
if (_Data == NULL)
|
|
return false;
|
|
|
|
int columns = 1;
|
|
if (Columns > 0)
|
|
{
|
|
columns = Columns; // User-provided expected column count.
|
|
}
|
|
else
|
|
{
|
|
for (const char* c = _Data; *c != '\n' && *c != '\0'; c++) // Count columns. Quoted columns with commas are not supported.
|
|
if (*c == ',')
|
|
columns++;
|
|
}
|
|
|
|
// Count rows. Extra new lines anywhere in the file are ignored.
|
|
int max_rows = 0;
|
|
for (const char* c = _Data, *end = c + len; c < end; c++)
|
|
if ((*c == '\n' && c[1] != '\r' && c[1] != '\n') || *c == '\0')
|
|
max_rows++;
|
|
|
|
if (columns == 0 || max_rows == 0)
|
|
return false;
|
|
|
|
// Create index
|
|
_Index.resize(columns * max_rows);
|
|
|
|
int col = 0;
|
|
char* col_data = _Data;
|
|
for (char* c = _Data; *c != '\0'; c++)
|
|
{
|
|
const bool is_comma = (*c == ',');
|
|
const bool is_eol = (*c == '\n' || *c == '\r');
|
|
const bool is_eof = (*c == '\0');
|
|
if (is_comma || is_eol || is_eof)
|
|
{
|
|
_Index[Rows * columns + col] = col_data;
|
|
col_data = c + 1;
|
|
if (is_comma)
|
|
{
|
|
col++;
|
|
}
|
|
else
|
|
{
|
|
if (col + 1 == columns)
|
|
Rows++;
|
|
else
|
|
fprintf(stderr, "%s: Unexpected number of columns on line %d, ignoring.\n", filename, Rows + 1); // FIXME
|
|
col = 0;
|
|
}
|
|
*c = 0;
|
|
if (is_eol)
|
|
while (c[1] == '\r' || c[1] == '\n')
|
|
c++;
|
|
}
|
|
}
|
|
|
|
Columns = columns;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|